Vue实战--标签页组件

4,623 阅读3分钟

  好久没有写文章了,这一段时间在忙着准备期末考试和找实习,上周已经在一家公司开始实习了,公司是用Vue开发项目,Boss让我们实习生看几个项目的代码,第一次接触完整的上线项目,发现代码结构分的很细,几乎每个文件都在使用export、import导入导出,有些Vue用法是之前没有接触过的,实习一周下来看代码看的有点蒙。。。所以打算利用空余时间,重新学习Vue,至少一周写一篇Vue实践总结~

  Vue作为一个前端轻量级的MVVM框架,组件化是其一个重要的功能和特点,组件化的优点是显而易见的,一个页面的不同部分可以拆分成独立的组件,然后在不同的页面就可以共享这些组件,避免重复开发。下面是我编写的一个标签页组件,先上最终效果图~

演示图
演示图

组件文件结构

  • index.html 入口页
  • style.css 样式页
  • tab.js 标签页组件 tabs
  • pane.js 标签页组件 pane
    文件目录
    文件目录

初始化各个文件

index.js:

<html>
<head>
    <meta charset="UTF-8">
    <title>标签页组件</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="app" v-cloak>
        <tabs v-model="activeKey">
            <pane label="标签一" name="1">
                标签一的内容
            </pane>
            <pane label="标签二" name="2" :closable="false">
                标签二的内容很重要,不能关闭
            </pane>
            <pane label="标签三" name="3">
                标签三的内容
            </pane>
        </tabs>
    </div>
    <script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
    <script src="pane.js"></script>
    <script src="tabs.js"></script>
    <script type="text/javascript">
        var app = new Vue({
            el: '#app',
            data: {
                activeKey: '1'
            }
        })
    </script>
</body>
</html>

tab.js:

Vue.component('tabs',{
    template:'\
        <div class="tabs">\
            <div class="tabs-bar">\
                <!--标签页的标题,需要使用v-for-->\
                <div \
                </div>\
            </div>\
            <div class="tabs-content">\
                <!--这里的slot即是嵌套的pane组件-->\
                <slot></slot>\
            </div>\
        </div>',
})

pane.js

Vue.component('pane',{
    name:'pane',
    template: '\
        <div>\
            <slot></slot>\
        </div>',
    data: function(){
        return{
            show:true
        }
    }
})

pane需要控制标签页内容的显示与隐藏,设置一个data:show,通过这个属性来动态添加class

功能实现

在pane.js里设置prop: name唯一的值来标识这个 pane,但它不是必需的,如果使用者不设置,可以默认从0开始自动设置;设置prop: label其内容显示在标签页标题里;还有prop: closable用来是否显示关闭标签按钮。这部分代码如下:

props:{
    name: {
        type:String 
    },
    label: {
        type:String,
        default:''
    },
    closable: {
        type: Boolean,
        default: true
    }
}

由于上面的prop: label用户是可以动态调整的,所以在pane初始化及label更新时,都要通知父组件更新,更新方法定为updateNav

    methods:{
        updateNav (){
            this.$parent.updateNav();
        }
    },
    watch:{
        label(){
            this.updateNav();
        }
    },
    mounted(){
        this.updateNav();
    }

pane.js功能基本实现了,剩余任务就是完成tabs.js组件。
首先需要把pane组件设置的标题动态谊染出来,也就是当pane触发tabs的updateNav方法时,更新标题内容。这部分的代码:

    methods: {
        getTabs () {
            // 通过遍历子组件,得到所有的pane组件
            return this.$children.filter(function(item){
                return item.$options.name==='pane';
            })
        },
        updateNav () {
            this.navList=[];
            var _this=this;
            this.getTabs().forEach(function(pane,index){
                _this.navList.push({
                    label: pane.label,
                    name: pane.name||index,
                    closable: pane.closable
                });
                if(!pane.name){
                        pane.name=index;
                }
                if(index==0){
                    if(!_this.currentValue){
                        _this.currentValue=pane.name||index;
                    }
                }
            });
            this.updateStatus();
        },
        updateStatus () {
            var tabs=this.getTabs();
            var _this=this;
            // 显示当前选中的tab对应的pane组件
            tabs.forEach(function(tab){
                return tab.show = tab.name === _this.currentValue;
            })
        }
    }

拿到navList后,就需要对它用v-for指令把tab的标题渲染出来,并且判断每个tab当前的状态:是否选择,是否可以关闭。这部分代码如下:

Vue.component('tabs',{
    template:' \
	  <div class="tabs"> \
	      <div class="tabs-bar"> \
	          <div \
                :class="tabCls(item)" \
                v-for="(item,index) in navList" \
                @click="handleChange(index)">\
                    {{item.label}} \
                    <span v-if="ifShowClose(item)" class="close icon" @click.stop="closeTab(index)"></span> \
	          </div> \
	      </div> \
	      <div class="tabs-content"> \
	         <slot></slot> \
	      </div> \
      </div>'
})

上面标签绑定了三个方法:handleChange,ifShowClose,closeTab,其代码如下

    handleChange: function(index){
        var nav=this.navList[index];
        var name=nav.name;
        // 更新当前选择的tab
        this.currentValue=name;
        // 更新value
        this.$emit('input',name);
    },
    ifShowClose (item) {
        // 是否显示关闭标签按钮
        return item.closable;
    },
    // 点击关闭按钮触发的事件
    closeTab (index) {
        // console.log(this.navList[index].name, this.currentValue);
        // 如果关闭的是当前选择的tab,则将currentValue转到前一个tab
        if (this.navList[index].name == this.currentValue) {
            let toIndex = index - 1;
            toIndex = toIndex >=0 ? toIndex : this.navList.length + toIndex;
            console.log(toIndex);
            this.currentValue = this.navList[toIndex].name;
        }
        //关闭当前标签页
        this.navList.splice(index, 1);
    }

另外通过CSS3的transform: translateX来增加标签页内容切换动画:

.pane {
    visibility: hidden;
    width: 100%;
    height: 0;
    transform: translateX(-100%);
    transition: all .5s ease-in;
}
.pane-active {
    visibility: visible;
    transform: translateX(0);
}

完整代码

完整代码已经上传到github上了,传送门

最后

  以上是该组件的基本实现,这是我结合《Vue.js实战》这本书完成的,通过这个实例,巩固了Vue的一些基本用法,对Vue的组件化思想有了更清晰的认识。另外我添加的标签切换动画感觉比较生硬,掘友们如果有更好的办法欢迎提出来~