想自己编写标签页组件,点击标签切换显示对应的内容,使用方式如下
<mytabs>
<mytab-pane label="Notice">
<notice></notice>
</mytab-pane>
<mytab-pane label="Emergency Contact">
<contact></contact>
</mytab-pane>
</mytabs>
实现原理
父组件嵌套子组件,子组件内部通过prop属性指定标签名称,通过slot自定义标签要显示的内容
子组件
this.$slots.default 获取slot插入的所有组件vnode
通过vnode.componentOptions.propsData 获取组件内部的props属性
组件内的内容通过v-if绑定data属性控制显示
mytabs.vue
<template>
<div class="tabs">
<ul>
<li
v-for="(item, index) in labels"
@click="clickTab(item, index)"
:key="index"
:class="[item == currentName ? 'active' : '']"
>
{{ item }}
</li>
</ul>
<slot></slot>
</div>
</template>
<script>
export default {
name: "mytabs",
data() {
return {
currentName: null,
panes: [],
labels: [],
};
},
methods: {
clickTab(name, index) {
// debugger;
//标签名显示选中
this.currentName = name;
},
calcPaneInstances() {
if (this.$slots.default) {
debugger;
const paneSlots = this.$slots.default.filter(
(vnode) =>
vnode.tag &&
vnode.componentOptions &&
vnode.componentOptions.Ctor.options.name === "mytabPane"
);
const panes = paneSlots.map(
({ componentInstance }) => componentInstance
);
//标签切换 currentName set也会触发updated,所以需要判断slots是不是真的变化了
const panesChanged = !(
panes.length === this.panes.length &&
panes.every((pane, index) => pane === this.panes[index])
);
if (panesChanged) {
this.panes = panes;
}
} else if (this.panes.length !== 0) {
debugger;
this.panes = [];
}
debugger;
},
},
//收集子组件mytabPane的label,不能写在created
mounted() {
debugger;
this.calcPaneInstances();
},
updated() {
// 初次加载的时候
//页面刚开始的时候还没有获取到用户权限信息,用户权限信息加载完后slot会变化
debugger;
//currentName set也会触发updated
this.calcPaneInstances();
},
watch: {
//页面刚开始的时候还没有获取到用户权限信息,用户权限信息加载完后slot会变化
panes(newVal, oldVal) {
debugger;
this.labels = newVal.map((t) => t.label);
if (this.labels.length > 0) {
const contained = this.labels.some((t) => t === this.currentName);
if (!contained) {
this.currentName = this.labels[0];
}
} else {
this.currentName = null;
}
},
},
};
</script>
<style lang="scss" scoped>
.tabs {
ul {
margin-bottom: 10px;
li {
display: inline-block;
margin: 0 10px;
padding: 5px 0;
color: rgb(182, 182, 182);
cursor: pointer;
}
.active {
border-bottom: 2px solid #931c7f;
color: rgb(10, 1, 1);
}
}
}
</style>
mytabPan.vue
<template>
<div v-show="active">
<slot></slot>
</div>
</template>
<script>
export default {
name: "mytabPane",
data() {
return {
flag: false,
};
},
props: {
label: {
type: String,
default: null,
},
value: {
type: String,
default: null,
},
},
computed: {
active() {
const active = this.$parent.currentName === this.label;
return active;
},
},
};
</script>
<style scoped>
</style>
使用