《Vue.js实战》 标签页组件

1.所需要的文件及结构

2.标签页组件的核心代码分解

       先初始化各个文件

●index.html

●tabs.js

●pane.js

       pane需要控制标签页内容的显示与隐藏,设置一个data:show,并且用v-show指令来控制元素;

       当点击到这个pane对应的标签页标题按钮时,此pane的show值设置为true,否则应该是false,这步操作是在tabs组件上完成的,我们稍后再介绍。

        既然要点击对应的标签页标题按钮。那应该有一个唯一的值来标识这个 pane, 我们可以设置个prop: name让用户来设置,但它不是必需的。如果使用者不设置,可以默认从0开始自动设置,这步操作仍然是tabs执行的,因为pane本身并不知道自己是第几个。除了name. 还需要标签页标题的prop: label, tabs 组件需要将它显示在标签页标题里。这部分代码如下:

       上面的prop: label 用户是可以动态调整的,所以在pane初始化及label更新时,都要通知父组件也更新,因为是独立组件,所以不能依赖像bus.js或vuex这样的状态管理办法,我们可以直接通过this.$parent访问tabs组件的实例来调用它的方法更新标题,该方法名暂定为updateNav。注意,在业务中尽可能不要使用$parent来操作父链,这种方法适合于标签页这样的独立用件,这部分代码如下:

扫描二维码关注公众号,回复: 4539806 查看本文章

        在生命周期mounted,也就是pane初始化时,调用一遍tabs的updateNav方法,同时监听了prop: label,在label更新时,同样调用。

        剩余任务就是完成tabs.js组件。

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

        getTabs是一个公用的方法,使用this.$children来拿到所有的pane组件实例。

        需要注意的是,在methods里使用了有function 回调的方法时(例如遍历数组的方法forfEach),在回调内的this不再执行当前的Vue实例,也就是tabs组件本身,所以要在外层设置个_this =this的局部变量来间接使用this。如果你熟悉ES2015,也可以直接使用箭头函数=>。

        遍历了每一个pane组件后,把它的label和name提取出来,构成一个Object并添加到数据navList数组里,后面我们会在template里用到它。

         设置完navList数组后,我们调用了updateStatus方法,又将pane组件遍历了一遍,不过这时是为了将当前选中的tab 对应的pane组件内容显示出来,把没有选中的隐藏掉。因为在上一步操作里,我们有可能需要设置curentValue来标识当前选中项的name (在用户没有设置value时,才会自动设置),所以必须要遍历2次才可以。

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

        在使用v-for指令循环显示tab标题时,使用v-bind:class指向了一个名为tabCls的methods来动态设置class名称。因为计算属性不能接收参数,无法知道当前tab是否是选中的,所以这里我们才用到methods,不过要知道,methods 是不缓存的。

        点击每个tab标题时,会触发handleChange方法来改变当前选中tab的索引,也就是pane组件的name.在watch选项里,我们监听了currentValue,当其发生变化时,触发updateStatus 方法来更新pane组件的显示状态。

         以上就是标签页组件的核心代码分解。总结一下该示例的技术难点:使用了组件嵌套的方式,将一系列pane组件作为tabs组件的slot;tabs 组件和pane组件通信上,使用了$parent和$children的方法访问父链和子链:定义了prop: value和data: currentValuc,使用$emit('input')来实现 v-model的用法。

3.标签页组件的完整代码

●index.html

<!--入口页-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>标签页组件</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
    <div id="app" v-cloak>
        <tabs v-model="activeKey">
            <pane label="标签一" name="1">
                标签一的内容
            </pane>
            <pane label="标签二" name="2">
                标签二的内容
            </pane>
            <pane label="标签三" name="3">
                标签三的内容
            </pane>
        </tabs>
    </div>
    <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
    <script src="pane.js"></script>
    <script src="tabs.js"></script>
    <script>
       var app=new Vue({
           el:'#app',
           data:{
               activeKey:'1'
           }
       })
    </script>

</body>
</html>

●pane.js

//标签页嵌套的组件 pane
Vue.component('pane',{
    name:'pane',
    template:'\
        <div class="pane" v-show="show">\
            <slot></slot>\
        </div>',
    props:{
        name:{
            type:String 
        },
        label:{
            type:String,
            default:''
        }
    },
    data:function(){
        return{
            show:true
        }
    },
    methods:{
        updateNav(){
            this.$parent.updateNav();
        }
    },
    watch:{
        label(){
            this.updateNav();
        }
    },
    mounted(){
        this.updateNav();
    }
})

●tabs.js

//标签页外层的组件 tabs
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}} \
	          </div> \
	      </div> \
	      <div class="tabs-content"> \
	         <slot></slot> \
	      </div> \
	  </div>',
	  props:{
	  	value:{
	  		type:[String,Number]
	  	}
	  },
	  data:function(){
	  	return{
	  		currentValue:this.value,
	  		navList:[]
	  	}
	  },
	  methods:{
	  	tabCls:function(item){
	  		return [
	  			'tabs-tab',
	  			{
	  				'tabs-tab-active':item.name===this.currentValue
	  			}
	  		]
	  	},
	  	getTabs(){
	  		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
	  			});
	  			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;
	  		tabs.forEach(function(tab){
	  			return tab.show=tab.name===_this.currentValue;
	  		})
	  	},
	  	handleChange:function(index){
	  		var nav=this.navList[index];
	  		var name=nav.name;
	  		this.currentValue=name;
	  		this.$emit('input',name);
	  		this.$emit('on-click',name)
	  	}
	  },
	  watch:{
	  		value:function(val){
	  			this.currentValue=val;
	  		},
	  		currentValue:function(){
	  			this.updateStatus();
	  		}
	  	}
})

●style.css

/*样式表*/
[v-cloak]{
	display:none;
}
.tabs{
	font-size:14px;
	color:#657180;
}
.tabs-bar:after{
	content:'';
	display:block;
	width:100%;
	height:1px;
	background:#dedde4;
	margin-top:-1px;
}
.tabs-bar{
	display:inline-block;
	padding:4px 16px;
	margin-right:6px;
	background:#fff;
	border:1px solid #d7dde4;
	cursor:pointer;
	position:relative;
}
.tabs-bar div{
	float:left;
}
.tabs-tab-active{
	color:#3399ff;
	border-top:1px solid #3399ff;
	border-bottom:1px solid #fff;
}
.tabs-tab-active:before{
	content:'';
	display:block;
	height:1px;
	background:#3399ff;
	position:absolute;
	top:0;
	left:0;
	right:0;
}
.tabs-content{
	padding:8px 0;
}

4.效果图

猜你喜欢

转载自blog.csdn.net/qq_37473645/article/details/82317332