使用vue开发自定义tabs标签页组件
共涉及基础组件文件 tabs.vue pane.vue 和样式文件 tabs.scss 和案例页面文件 index.vue 四个文件
完整代码可到gitee下载
话不多说直接上代码:
tabs.vue 内容如下:
1 <template> 2 <div class="tabs" :class="{'tabs-border': showBorder}"> 3 <div :class="'tabs-' + getTabsType() + '-bar'"> 4 <div v-for="(item,index) in navList" :class="tabCls(item)" @click="handleChange(index)" :style="getTabFontStyle()"> 5 {{ item.label }} 6 <span v-if="ifShowClose(item)" class="close icon" @click.stop="closeTab(index)" /> 7 </div> 8 </div> 9 <div class="tabs-content"> 10 <slot /> 11 </div> 12 </div> 13 </template> 14 15 <script> 16 export default { 17 name: 'Tabs', 18 props: { 19 value: { 20 type: [String, Number] 21 }, 22 border: { 23 type: Boolean, 24 default: false 25 }, 26 tabsType: { 27 type: String, 28 default: 'tab' 29 }, 30 fontSize: { 31 type: [String, Number], 32 default: 16 33 }, 34 fontColor: { 35 type: String, 36 default: '#000000' 37 } 38 }, 39 data() { 40 return { 41 defType: ['tab', 'card', 'card2', 'brief'], 42 currentValue: this.value, 43 showBorder: this.border, 44 navList: [] 45 } 46 }, 47 watch: { 48 value: function(val) { 49 this.currentValue = val 50 }, 51 currentValue: function() { 52 this.updateStatus() 53 } 54 }, 55 methods: { 56 tabCls: function(item) { 57 var active = '' 58 var tabsType = this.getTabsType() 59 var tabsBaseClass = 'tabs-' + tabsType 60 61 if (item.name === this.currentValue) { 62 active = 'tabs-' + tabsType + '-active' 63 } 64 65 return [ 66 tabsBaseClass, 67 active 68 ] 69 }, 70 getTabs() { 71 // 通过遍历子组件,得到所有的pane组件 72 return this.$children.filter(function(item) { 73 return item.$options.name === 'Pane' || item.$options.name === 'pane' 74 }) 75 }, 76 updateNav() { 77 this.navList = [] 78 var _this = this 79 this.getTabs().forEach(function(pane, index) { 80 _this.navList.push({ 81 label: pane.label, 82 name: pane.name || index, 83 closable: pane.closable 84 }) 85 if (!pane.name) { 86 pane.name = index 87 } 88 if (index == 0) { 89 if (!_this.currentValue) { 90 _this.currentValue = pane.name || index 91 } 92 } 93 }) 94 this.updateStatus() 95 }, 96 updateStatus() { 97 var _this = this 98 // 显示当前选中的tab对应的pane组件 99 _this.getTabs().forEach(function(tab) { 100 tab.show = tab.name === _this.currentValue 101 // return 102 }) 103 }, 104 handleChange: function(index) { 105 var nav = this.navList[index] 106 var name = nav.name 107 // 更新当前选择的tab 108 this.currentValue = name 109 // 更新value 110 this.$emit('input', name) 111 }, 112 ifShowClose(item) { 113 // 是否显示关闭标签按钮 114 return item.closable 115 }, 116 // 点击关闭按钮触发的事件 117 closeTab(index) { 118 // console.log(this.navList[index].name, this.currentValue); 119 // 如果关闭的是当前选择的tab,则将currentValue转到前一个tab 120 if (this.navList[index].name == this.currentValue) { 121 let toIndex = index - 1 122 toIndex = toIndex >= 0 ? toIndex : this.navList.length + toIndex 123 console.log(toIndex) 124 this.currentValue = this.navList[toIndex].name 125 } 126 // 关闭当前标签页 127 this.navList.splice(index, 1) 128 }, 129 // 根据组件属性tabsType 设置tabs头部样式 130 getTabsType() { 131 if (this.$data.defType.indexOf(this.tabsType) === -1) { 132 return 'tab' 133 } 134 return this.tabsType 135 }, 136 // 根据组件属性fontSize 设置tab 字体大小和颜色 137 getTabFontStyle() { 138 if (!/^#[0-9a-fA-F]{6}$/.test(this.fontColor)) { 139 this.fontColor = '#000000' 140 } 141 if (!/^[1-3][0-9]$/.test(this.fontSize) || this.fontSize < 12) { 142 this.fontSize = 16 143 } 144 return 'font-size: ' + this.fontSize + 'px; color: ' + this.fontColor 145 } 146 } 147 } 148 </script> 149 <style scoped> 150 </style>
pane.vue 内容如下:
1 <template> 2 <div :class="getCls()"> 3 <slot /> 4 </div> 5 </template> 6 7 <script> 8 export default { 9 name: 'Pane', 10 props: { 11 name: { 12 type: String 13 }, 14 label: { 15 type: String, 16 default: '' 17 }, 18 closable: { 19 type: Boolean, 20 default: false 21 } 22 }, 23 data: function() { 24 return { 25 show: true 26 } 27 }, 28 watch: { 29 label() { 30 this.updateNav() 31 } 32 }, 33 mounted() { 34 this.updateNav() 35 }, 36 methods: { 37 updateNav() { 38 this.$parent.updateNav() 39 }, 40 // 决定pane是否显示 41 getCls() { 42 return [ 43 'pane', 44 { 45 'pane-active': this.show 46 } 47 ] 48 } 49 } 50 } 51 </script> 52 53 <style scoped> 54 </style>
tabs.scss 内容如下:
1 [v-cloak] { 2 display: none; 3 } 4 .tabs { 5 margin: 3px; 6 font-size: 14px; 7 color: #657180; 8 } 9 .tabs-border { 10 border-width: 1px; 11 border-radius: 2px; 12 border-style: solid; 13 border-color: #e6e6e6; 14 box-shadow: 0 2px 5px 0 rgb(0 0 0 / 10%) 15 } 16 .tabs-tab-bar:after { 17 content: ''; 18 width: 100%; 19 height: 1px; 20 display: block; 21 margin-top: -1px; 22 background: #d7dde4; 23 } 24 .tabs-tab { 25 cursor: pointer; 26 padding: 6px 16px; 27 margin-right: 6px; 28 background: #fff; 29 position: relative; 30 display: inline-block; 31 border: 1px solid #d7dde4; 32 } 33 .tabs-tab-active { 34 color: #3399ff; 35 border-top: 1px solid #3399ff; 36 border-bottom: 1px solid #fff; 37 } 38 .tabs-tab-active:before { 39 content: ''; 40 top: 0; 41 left: 0; 42 right: 0; 43 height: 1px; 44 display: block; 45 position: absolute; 46 background: #3399ff; 47 } 48 /*brief 风格的tabs 样式 start*/ 49 .tabs-brief-bar:after { 50 content: ''; 51 width: 100%; 52 height: 1px; 53 display: block; 54 margin-top: -1px; 55 background: #d7dde4; 56 } 57 .tabs-brief { 58 cursor: pointer; 59 padding: 8px 16px; 60 background: #fff; 61 position: relative; 62 display: inline-block; 63 } 64 .tabs-brief-active { 65 border-top: none; 66 font-weight: 800; 67 color: #3399ff !important; 68 border-bottom: 1px solid #d7dde4; 69 } 70 .tabs-brief-active:after { 71 content: ''; 72 left: 0; 73 right: 0; 74 height: 2px; 75 bottom: -3px; 76 display: block; 77 position: absolute; 78 background: #3399ff; 79 } 80 /*brief 风格的tabs 样式 end*/ 81 82 /*card 风格的tabs 样式 start*/ 83 .tabs-card-bar { 84 background: #F1F1F1; 85 } 86 .tabs-card { 87 cursor: pointer; 88 padding: 8px 16px; 89 position: relative; 90 display: inline-block; 91 } 92 .tabs-card-active { 93 font-weight: 800; 94 color: #3399ff !important; 95 } 96 /*card 风格的tabs 样式 end*/ 97 98 /*card2 风格的tabs 样式 start*/ 99 .tabs-card2-bar { 100 background: #F1F1F1; 101 } 102 .tabs-card2 { 103 cursor: pointer; 104 padding: 8px 16px; 105 position: relative; 106 display: inline-block; 107 } 108 .tabs-card2-active { 109 font-weight: 800; 110 background: #ffffff; 111 } 112 /*card 风格的tabs 样式 end*/ 113 114 .tabs-content { 115 min-height: 100px; 116 padding: 6px; 117 } 118 .pane { 119 height: 0; 120 width: 100%; 121 visibility: hidden; 122 transition: all .5s ease-in; 123 transform: translateX(-100%); 124 } 125 .pane-active { 126 visibility: visible; 127 transform: translateX(0); 128 } 129 130 /* 关闭按钮样式 start*/ 131 .close.icon { 132 width: 11px; 133 height: 11px; 134 color: #000; 135 margin-left: 5px; 136 border-radius: 50%; 137 position: relative; 138 display: inline-block; 139 } 140 .close.icon:hover { 141 background: #eee; 142 } 143 .close.icon:before { 144 content: ''; 145 top: 5px; 146 width: 11px; 147 height: 1px; 148 position: absolute; 149 background-color: currentColor; 150 -webkit-transform: rotate(-45deg); 151 transform: rotate(-45deg); 152 } 153 .close.icon:after { 154 content: ''; 155 top: 5px; 156 width: 11px; 157 height: 1px; 158 position: absolute; 159 background-color: currentColor; 160 -webkit-transform: rotate(45deg); 161 transform: rotate(45deg); 162 } 163 /* 关闭按钮样式 end*/
index.vue 内容如下:
<template> <div class="app-main"> <tabs v-model="activePane" tabs-type="card" // 标签页类型 非必填 可选 tab/brief/card/card2 默认tab :font-size="16" // 标签头部字体大小 数值型 非必填 默认16 font-color="#888888" // 标签头部字体颜色 非必填 默认#000000 :border="true" // 是否显示外边框 非必填 默认false > <pane label="标签一" // 标签页名称 必填 name="1" //标签页定位符 非必填 为空时取标签页的索引值进行定位 :closable="false" // 标签页是否可以关闭 非必填 默认false > <tabs v-model="activePane1" tabs-type="card" :font-size="16" :border="true"> <pane label="内标签一" name="11"> 标签一 内部标签页内容一 </pane> <pane label="内标签二" name="12"> 标签一 内部标签页内容二 </pane> <pane label="内标签三" name="13"> 标签一 内部标签页内容三 </pane> </tabs> </pane> <pane label="标签二" name="2" :closable="false"> <tabs v-model="activePane2" tabs-type="card2" :font-size="16" :border="true"> <pane label="内标签一" name="21"> 标签二 内部标签页内容一 </pane> <pane label="内标签二" name="22"> 标签二 内部标签页内容二 </pane> <pane label="内标签三" name="23"> 标签二 内部标签页内容三 </pane> </tabs> </pane> <pane label="标签三" name="3"> <tabs v-model="activePane3"> <pane label="内标签一" name="31"> 标签三 内部标签页内容一 </pane> <pane label="内标签二" name="32"> <tabs v-model="activePane32" tabs-type="brief"> <pane label="内标签一" name="32"> 标签三 内部标签二的内部标签页内容一 </pane> <pane label="内标签二" name="322"> 标签三 内部标签二的内部标签页内容二 </pane> <pane label="内标签三" name="323"> 标签三 内部标签二的内部标签页内容三 </pane> </tabs> </pane> <pane label="内标签三" name="33"> <tabs v-model="activePane33" tabs-type="brief" :border="true"> <pane label="内标签一" name="331"> 标签三 内部标签三的内部标签页内容一 </pane> <pane label="内标签二" name="332"> 标签三 内部标签三的内部标签页内容二 </pane> <pane label="内标签三" name="333"> 标签三 内部标签三的内部标签页内容三 </pane> </tabs> </pane> </tabs> </pane> </tabs> </div> </template> <script> // 引入相应组件 import Tabs from '@/components/tabs/tabs.vue' import Pane from '@/components/tabs/pane.vue' export default { name: 'Test2', components: { Tabs, Pane }, data() { return { activePane: '3', activePane1: '11', activePane2: '22', activePane3: '33', activePane32: '322', activePane33: '333' } } } </script> // 引入标签页样式文件 <style> @import '../../styles/tabs.scss';// 根据实际应用填写此路径 </style>
tabs-type 不写 或 tabs-type 为空 或 tabs-type="tab"时:
tabs-type="brief"时:
tabs-type="brief" 且 :border="true" 时:
tabs-type="card" 且 :border="true" 时:
tabs-type="card2"且 :border="true"时:
完整代码文件已上传gitee,传送门
转发自 https://juejin.cn/post/6844903881965568007 以上为在原作基础上略作改动
第一次编写发布博客 如有不足之处还望多多指教