练习1 : 给pane 组件新增一个prop: closable 的布尔值, 来支持是否可以关闭这个pane , 如
果开启, 在tabs 的标签标题上会有一个关闭的按钮。
提示:初始化pane 时我们是在 mounted里通知的,关闭时,你会用到 beforedestroy
练习2 : 尝试在切换pane 的显示与隐藏时,使用滑动的动画。提示: 可以使用css 3 的transform:
translateX 。
IDE:VScode
项目由 main.js, app.vue, tabs.vue, pane.vue 4个文件构成。
main.js(略)
app.vue:
<template> <div id="app"> <tabs v-model="activeKey" @on-click="handleOnclick" @on-close="handleOnclose"> <pane v-for="item in pans" :key="item.mes" :label="item.label" :name="item.name" :closable="item.closable" > {{item.mes}} </pane> </tabs> </div> </template> <script> import tabs from './components/tabs'; import pane from './components/pane'; export default { components:{ tabs, pane }, data(){ return{ activeKey:'1', pans:[ { label:'标签一', name:'1', closable:true, mes:'标签一的内容' }, { label:'标签二', name:'2', closable:true, mes:'标签二的内容' }, { label:'标签三', name:'3', closable:true, mes:'标签三的内容' }, ] } }, methods:{ handleOnclick(name){ }, handleOnclose(name){ var index=0; for(var i=0;i<this.pans.length;i++) { if(this.pans[i].name===name){ index=i; break; } } this.pans.splice(index,1); } }, beforeDestroy(){ this.$off('on-click',this.handleOnclick); this.$off('on-close',this.handleOnclose); } } </script>
tabs.vue(不含style)
<template> <div class="tabs"> <div class="tabs-bar"> <div :class="tabCls(item)" v-for="(item, index) in navList" :key="item.name" @click.self="handleChange(index)"> {{ item.label }} <template v-if="item.closable"> <a href="#" class="del" v-show="item.name===currentValue" @click="handleClose(item,index)" >x</a> </template> </div> </div> <div class="tabs-content"> <slot></slot> </div> </div> </template> <script> export default { props:{ value:{ type:[String, Number] }, }, data(){ return{ currentValue:this.value, navList:[] } }, methods:{ handleClose(item,index){ this.navList.splice(index,1); //console.log(this.navList.length); var name = item.name; //console.log(name) this.handleChange(0) this.$emit('on-close',name); }, tabCls(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, 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; tabs.forEach(function(tab){ return tab.show = tab.name === _this.currentValue; }) }, handleChange(index){ if(this.navList.length){ var nav = this.navList[index]; var name = nav.name; //console.log(name) this.currentValue = name; this.$emit('input',name); this.$emit('on-click',name); } } }, watch:{ value(val){ this.currentValue = val; }, currentValue(){ this.updateStatus(); } }, } </script>
pane.vue
<template> <transition name="fade" mode="out-in"> <div class="pane" v-show="show"> <slot></slot> </div> </transition> </template> <script> export default { name:'pane', data(){ return{ show:true } }, props:{ name:{ type:String }, label:{ type:String, default:'' }, closable:{ type:Boolean, default:true } }, methods:{ updateNav(){ this.$parent.updateNav(); } }, watch:{ label(){ this.updateNav(); }, }, mounted(){ this.updateNav(); }, beforeDestroy(){ } } </script> <style> .pane{ display: inline-block; } .fade-enter-active,.fade-leave-active{ position: absolute; transition: all .8s ease; } .fade-enter, .fade-leave-to{ transform: translateX(100px); opacity: 0; } </style>