前言
自从前两天发了我人生之中第一篇文章,底下的评论以及赞与收藏让我倍感荣幸与惶恐,思索许久还是决定把前段时间写的一个非常规侧边按钮组组件分享一下,当然了,也希望各位看官老爷轻喷。好咯,话不多说,直接上代码;
截图
效果展示
目录结构
使用
代码部分
YSideBtnsContent>index.vue
vue2
<template>
<!-- <transition name="el-fade-in-linear"> -->
<div
class="y-btn-content-item"
:style="{ height: `calc( 100vh - ${$parent.top} - 20px)`, width }"
v-show="isActiveToShowContent"
>
<slot></slot>
</div>
<!-- </transition> -->
</template>
<script>
export default {
name: "YSideBtnsContent",
props: {
name: String, // 每个下方内容区都有自己的name名字
isShow: {
default: true,
},
disabled: {
// 是否禁用这一项
type: Boolean,
default: false, // 默认不禁用
},
width: {
type: String,
default: () => "100px",
},
},
computed: {
// 控制根据高亮的tab显示对应标签页内容
isActiveToShowContent() {
let activeName = this.$parent.value; // 比如当前高亮的是 sunwukong
let currentName = this.name; // this.name的值有很多个,有:sunwukong、zhubajie、shaheshang...
// 谁等于,就显示谁
return this.isShow && activeName === currentName;
},
},
};
</script>
<style lang="scss" scoped>
.y-btn-content-item {
// padding: 12px;
height: 100%;
background-color: #48484c;
[data-theme="theme1"] & {
background: #e8effa;
}
}
</style>
YSideBtns>index.vue
vue2
<script>
import BtnNav from "./components/BtnNav";
export default {
name: "YSideBtns",
components: { BtnNav },
props: {
value: null, // v-model接参
beforeLeave: {
// 切换标签之前的钩子,若返回 false 或者返回 Promise 且被 reject,则阻止切换
type: Function,
default: () => {
return true; // 默认为true,始终允许切换tab
},
},
top: {
type: String,
default: () => "0",
},
width: {
type: String,
default: () => "76px",
},
},
data() {
return {
btnItemArr: [],
activeName: this.value,
};
},
watch: {
value(n) {
this.setCurrentName(n);
},
},
mounted() {
/**
* 计算收集btns页内容信息,将需要用到的信息存在btnItemArr数组中
* 并传递给sideBtn组件,sideBtn组件根据btnItemArr信息去v-for渲染有哪些
* */
this.calcSideBtnItemInstances();
/**
* 将组件挂载到BODY上
*/
// this.$nextTick(() => {
// const body = document.querySelector('body')
// const YSideBtns = document.querySelector('body>.SideBtns-Box')
// YSideBtns
// ? () => {
// if (body.append) {
// body.append(this.$el)
// } else {
// body.appendChild(this.$el)
// }
// }
// : null
// })
},
methods: {
/**
* @description 重点方法,获取使用的地方的Y-SideBtns标签中间的内容并存储
*/
calcSideBtnItemInstances() {
if (this.$slots.default) {
// 收集Y-SideBtns标签中间的插槽内容数组
let slotBtnItemArr = this.$slots.default.filter((v) => v.tag); // console.log("slotBtnItemArr", slotBtnItemArr);
// 然后把这些数据交给sideBtn动态渲染
this.btnItemArr = slotBtnItemArr.map((item) => {
return item.componentInstance; // 只保留componentInstance组件实例即可,可以理解为组件的this
});
} else {
this.btnItemArr = []; // 没传递就置为空,当然需要规范使用组件,规范传递相关参数
}
},
/**
* @description 点击btn的回调
*/
handleBtnClick(btnItem) {
// this.$emit("btnClick", btnItem, btnItem.name); // 通知父元素点击的是谁,是哪个SideBtn
let newBtnName = btnItem.name; // 获取传出来的最新的name名字
this.setCurrentName(newBtnName); // 执行更新方法
},
async setCurrentName(newBtnName) {
let oldBtnName = this.activeName; // 要更新了,所以当下的就变成旧的了
let res = await this.beforeLeave(newBtnName, oldBtnName);
if (res) {
this.$emit("input", newBtnName); // 更新父组件的v-model绑定的值
this.$emit("btnClick", newBtnName); // 通知父元素点击的是谁,是哪个SideBtn
this.activeName = newBtnName; // 自身也更新一下
} else {
this.$message.warning("您没有权限!");
}
},
},
render() {
// 准备参数,以便把参数传递给btn-nav组件
const btnsData = {
props: {
btnItemArr: this.btnItemArr, // 内容区相关信息数组
activeName: this.activeName, // 当前高亮的是哪一项
onBtnClick: this.handleBtnClick, // 点击某一tab项的回调
},
};
// <transition name="el-zoom-in-center"> v-show={this.activeName}
// </transition>
return (
<div class="SideBtns-Box" style={{ top: this.top, width: this.width }}>
<BtnNav {...btnsData}></BtnNav>
<div class="my-btn-content-item-box">{this.$slots.default}</div>
</div>
);
},
};
</script>
<style lang="scss" scoped>
.SideBtns-Box {
display: flex;
justify-content: space-around;
position: fixed;
right: 0;
z-index: 999;
transition: width 0.2s;
}
</style>
YSideBtns>components>BtnNav.vue
vue2
<script>
export default {
name: "BtnNav",
props: {
// 源自于内容区的数组数据,非常重要
btnItemArr: {
type: Array,
default: () => [],
},
// 当前激活的名字
activeName: {
type: String,
default: "",
},
// 接收点击选项卡函数,在点击tab选项卡的时候,通过此函数传递出去
onBtnClick: {
type: Function,
},
},
methods: {
/**
* @description 点击按钮的回调
*/
changeActiveName(btnItem) {
// 自己点自己就不让执行了
if (btnItem.name === this.activeName) {
return;
}
// 如果包含禁用项disabled属性(即处于禁用状态),也不让执行
if (btnItem.disabled) {
return;
}
this.onBtnClick(btnItem);
},
},
render() {
let topHtmlTemplate = [];
let activeHtmlTemplate = [];
let btmHtmlTemplate = [];
let isTop = true;
const getBtnHtml = (btn, idx) => {
return (
<div
onClick={() => {
this.changeActiveName(btn);
}}
class="my-tab-nav-item-box-btn flex_c_m "
key={idx}
>
{this.activeName === btn.name ? (
// <img src={btn.$attrs.activeImg} />
<span class={" iconfont " + btn.$attrs.activeIcon}></span>
) : (
// <img src={btn.$attrs.deActiveImg} />
<span class={" iconfont " + btn.$attrs.deActiveIcon}></span>
)}
</div>
);
};
this.btnItemArr.forEach((btn, idx) => {
if (isTop) {
if (btn.name === this.activeName) {
isTop = false;
activeHtmlTemplate.push(getBtnHtml(btn, idx));
} else {
topHtmlTemplate.push(getBtnHtml(btn, idx));
}
} else {
btmHtmlTemplate.push(getBtnHtml(btn, idx));
}
});
return (
<div class={"my-tab-nav-item-box " + (this.activeName ? "isOpen" : "")}>
{topHtmlTemplate.length != 0 ? (
<div class="my-tab-nav-item-box-deactive-container ">
{topHtmlTemplate.map((html) => {
return html;
})}
</div>
) : null}
{activeHtmlTemplate.length != 0 ? (
<div class="my-tab-nav-item-box-active-container flex_c_m ">
{activeHtmlTemplate.map((html) => {
return html;
})}
</div>
) : null}
{btmHtmlTemplate.length != 0 ? (
<div class="my-tab-nav-item-box-deactive-container ">
{btmHtmlTemplate.map((html) => {
return html;
})}
</div>
) : null}
</div>
);
},
};
</script>
<style lang="scss" scoped>
.my-tab-nav-item-box {
text-align: right;
font-size: 0;
span {
@include font_level1_color();
font-size: 22px;
cursor: pointer;
}
.my-tab-nav-item-box-deactive-container {
overflow: hidden;
display: inline-block;
border-top-left-radius: 1rem;
border-bottom-left-radius: 1rem;
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
background: rgba(41, 42, 45, 0.9);
[data-theme="theme1"] & {
background: rgba(0, 113, 255, 0.05);
}
.my-tab-nav-item-box-btn {
&:hover {
background: $theme-highlight-tab-color;
span {
@include font_color();
}
}
}
}
.my-tab-nav-item-box-active-container {
height: 110px;
width: 92px;
overflow: hidden;
// padding: 1rem 1rem 1rem 1.5rem;
border-top-left-radius: 1rem;
border-bottom-left-radius: 1rem;
background-color: #48484c;
[data-theme="theme1"] & {
background: #e8effa;
}
span {
@include font_color();
}
}
.my-tab-nav-item-box-btn {
// padding: 1rem 0;
height: 92px;
width: 76px;
border-bottom: 1px solid transparent;
position: relative;
&:not(:last-of-type) {
&::after {
content: "";
position: absolute;
width: 30px;
height: 1px;
// background: $text-input-color2;
@include bg_sub_color6();
bottom: 0;
left: calc(50% - 15px);
}
}
}
}
.my-tab-nav-item-box.isOpen {
span {
color: $text-level1-color1;
}
.my-tab-nav-item-box-deactive-container {
background: rgba(41, 42, 45, 0.9);
[data-theme="theme1"] & {
background: rgba(0, 0, 0, 0.3);
}
}
}
</style>