Vue实战--标签页组件

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

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

![演示图](https://user-gold-cdn.xitu.io/2019/7/7/16bcc09348961dc9?w=435&h=235&f=gif&s=42077 "演示图")

### 组件文件结构

- index.html 入口页

- style.css 样式页

- tab.js 标签页组件 tabs

- pane.js 标签页组件 pane

![文件目录](https://user-gold-cdn.xitu.io/2019/7/7/16bcc09348bf725c?w=312&h=181&f=png&s=7738 "文件目录")

### 初始化各个文件

index.js:

```javascript

<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:

```javascript

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

```javascript

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`用来是否显示关闭标签按钮。这部分代码如下:

```javascript

props:{

    name: {

        type:String

    },

    label: {

        type:String,

        default:''

    },

    closable: {

        type: Boolean,

        default: true

    }

}

```

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

```javascript

    methods:{

        updateNav (){

            this.$parent.updateNav();

        }

    },

    watch:{

        label(){

            this.updateNav();

        }

    },

    mounted(){

        this.updateNav();

    }

```

pane.js功能基本实现了,剩余任务就是完成tabs.js组件。</BR>

首先需要把pane组件设置的标题动态谊染出来,也就是当pane触发tabs的updateNav方法时,更新标题内容。这部分的代码:

```javascript

    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当前的状态:是否选择,是否可以关闭。这部分代码如下:

```javascript

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`,其代码如下

```javascript

    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`来增加标签页内容切换动画:

```css

.pane {

    visibility: hidden;

    width: 100%;

    height: 0;

    transform: translateX(-100%);

    transition: all .5s ease-in;

}

.pane-active {

    visibility: visible;

    transform: translateX(0);

}

```

### 完整代码

完整代码已经上传到github上了,[传送门](https://github.com/libilin2018/learn-Vue.js)

### 最后

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容