# 标签页篇
# 设计细节
结构分明。为了让结构层次更明显,在
tabs-head
里面有子组件tabs-item
,在tabs-body
里面有子组件tabs-pane
,表示各自的标签与内容。但结构分明的同时也有些许缺陷,就是
tabs-item
和tabs-pane
都要加上相同的name
属性,对于喜欢“偷懒”的程序猿来说,多写一遍属性是比较麻烦的事情。头部支持自定义。
tabs-head
里有名为actions的插槽slot
,可以添加按钮或其它自定义内容。选中tab下划线滑动。使用一个div元素作为下划线容器,通过JS设置宽度和left。
# 功能细节
组件通信。因为存在爷tabs -> 父head、body -> 孙item、pane多层组件的通信以及兄弟组件的通信;为了让通信更方便,使用
provide
和inject
注入属性。事件通信。还是上面的原因,为了让多层组件的通信更简单,这里使用了事件总线eventBus,在tabs组件通过new一个vue实例,将这个实例通过
provide
传递,在各组件通过inject
注入,来实现组件之间的事件通信:
<script>
// tabs.vue
import Vue from 'vue'
export default {
name: "YvTabs",
provide() {
return {
eventBus: this.eventBus
}
},
data() {
return {
eventBus: new Vue()
}
},
// ...
}
</script>
- tab切换以及选中tab下划线滑动。使用一个div元素作为下划线容器,通过JS设置宽度和left。先看下下划线line在模板中的位置以及初始样式:
<!-- TabsHead.vue -->
<template>
<div class="yv-tabs-head">
<div class="title-wrapper" ref="titleWrapper">
<slot></slot>
<div class="line" ref='line'></div>
</div>
<div class="actions">
<slot name="actions"></slot>
</div>
</div>
</template>
<style scoped lang="scss">
$tab-item-current-color: #296aef;
.yv-tabs-head{
// ...
>.title-wrapper {
display: flex;
justify-content: flex-start;
align-items: center;
position: relative;
>.line {
position: absolute;
bottom: 0;
border-bottom: 3px solid $tab-item-current-color;
transition: all 0.3s linear;
}
}
}
</style>
然后分三步走:
tabs组件:在tabs组件中,通过遍历找出当前所选tab,通过eventBus传递出去:
<script>
export default {
// ...
mounted() {
// ...
this.$children.forEach(child => {
if (child.$options.name === 'YvTabsHead') {
child.$children.forEach(vm => {
if (vm.$options.name === 'YvTabsItem' && vm.name === this.selected) {
this.eventBus.$emit('update:selected', this.selected, vm)
}
})
}
})
}
}
</script>
tabs-item组件:tabs-item通过eventBus监听事件接收参数,设置自身样式;点击切换时通过eventBus把name属性以及自身实例当成参数传递出去:
<script>
export default {
// ...
created(){
if (this.eventBus) {
this.eventBus.$on('update:selected',(name,vm) => {
this.active = name === this.name
})
}
},
methods:{
selectTab() {
if (this.disabled) return
this.eventBus && this.eventBus.$emit('update:selected', this.name, this)
}
}
}
</script>
tabs-head组件:通过监听eventBus,得到当前选中的tab实例,计算出当前tab元素的尺寸和位置,通过JS动态设置下划线:
<script>
export default {
// ...
mounted() {
this.eventBus.$on('update:selected',(name, vm) => {
let line = this.$refs.line
let { left, right, top, height } = vm.$el.getBoundingClientRect()
let { left: wrapperLeft, top: wrapperTop, height: wrapperHeight } = this.$refs.titleWrapper.getBoundingClientRect()
line.style.width = right - left + 'px'
line.style.bottom = -4 +'px'
line.style.left = left - wrapperLeft + 'px'
})
}
}
</script>
# 人工测试
手动测试。。。已完成。
# 自动化测试
在test文件夹下增加tabs.test.js
和tabs-item.test.js
文件。
因为tabs-pane
和tabs-item
基本一致,这里就不再做自动化测试了。
tabs.test.js
文件有2个测试用例:测试tabs是否存在、接收selected;
tabs-item.test.js
文件有4个测试用例:测试tabs-item是否存在、接收name、接收disabled、点击事件。
运行命令parcel watch test/* --no-cache
和karma start
查看测试结果:
# vuepress
在docs/.vuepress/components文件夹下增加多个tabs-demo
的vue文件,内容就是我们要展示的tabs
示例,然后在docs/components文件夹下增加tabs
的md文件,内容就是放置整个tabs
组件说明。
具体内容请访问这里。