使用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 以上为在原作基础上略作改动
第一次编写发布博客 如有不足之处还望多多指教

原文地址:https://www.cnblogs.com/jindao3691/p/14538041.html