网站首页 » 前端开发 » Vue » Vue2 组件:tab 标签页
上一篇:
下一篇:

Vue2 组件:tab 标签页

这个组件实现了:

  • 支持设置默认选中
  • 支持 tab 嵌套
  • 支持设置 tab 面签的排列方向

实现这个 tab 标签页效果我把它细分成了三个文件(Tabs.vue,TabsNav.vue,TabsPannel.vue).下面我们直接来看代码:

Tabs.vue
<template>
  <div :class="`ku-tabs ${finallyTabsPosition}`" v-if="panesSlots.length > 0">
    <div class="ku-tabs-header">
      <ku-tabs-nav ></ku-tabs-nav>
    </div>
    <div class="ku-tabs-body">
      <slot></slot>
    </div>
  </div>
</template>
<script>
import kuTabsNav from "./TabsNav.vue"
export default {
  name: "Tabs",
  components:{
    kuTabsNav
  },
  provide(){ // 这里需要注意:如果要想传递 this 那么一定要以这种形式书写,不可以用 provide:{rootTabs:this},这样写 this 为 undefined
    return {
      rootTabs:this
    }
  },
  data() {
    return {
      tabsHeader: [],
      panesSlots: [],
      nameIndex:""
    };
  },
  props: {
    tabsPosition: {
      type: String,
      default: "top"
    },
    defaultActive: {
      type: String,
      default: ""
    },
     tabsClick: {
      type: Function,
      default: null
    },
  },
  created(){
    if(this.$slots.default){
        this.panesSlots = this.$slots.default;
    }
  },
  computed:{
    finallyTabsPosition(){
      console.log(this.tabsPosition);
      if(this.tabsPosition === "left" || this.tabsPosition === "right"){
        return `is-custom-position ku-tabs-${this.tabsPosition}`;
      }
      return "top";
    }
  }
};
</script>
<style scoped>
.ku-tabs-body {
  padding: 12px;
}
.ku-tabs-left {
  display: flex;
}

.ku-tabs-right {
  display: flex;
  flex-direction: row-reverse
}
.ku-tabs-left .ku-tabs-body,
.ku-tabs-right .ku-tabs-body {
  flex: 1;
}
.ku-tabs-right .ku-tabs-body{
  padding-left: 0;
  padding-right: 0;
}
</style>
<template>
  <div :class="`ku-tabs ${finallyTabsPosition}`" v-if="panesSlots.length > 0">
    <div class="ku-tabs-header">
      <ku-tabs-nav ></ku-tabs-nav>
    </div>
    <div class="ku-tabs-body">
      <slot></slot>
    </div>
  </div>
</template>
<script>
import kuTabsNav from "./TabsNav.vue"
export default {
  name: "Tabs",
  components:{
    kuTabsNav
  },
  provide(){ // 这里需要注意:如果要想传递 this 那么一定要以这种形式书写,不可以用 provide:{rootTabs:this},这样写 this 为 undefined
    return {
      rootTabs:this
    }
  },
  data() {
    return {
      tabsHeader: [],
      panesSlots: [],
      nameIndex:""
    };
  },
  props: {
    tabsPosition: {
      type: String,
      default: "top"
    },
    defaultActive: {
      type: String,
      default: ""
    },
     tabsClick: {
      type: Function,
      default: null
    },
  },
  created(){
    if(this.$slots.default){
        this.panesSlots = this.$slots.default;
    }
  },
  computed:{
    finallyTabsPosition(){
      console.log(this.tabsPosition);
      if(this.tabsPosition === "left" || this.tabsPosition === "right"){
        return `is-custom-position ku-tabs-${this.tabsPosition}`;
      }
      return "top";
    }
  }
};
</script>
<style scoped>
.ku-tabs-body {
  padding: 12px;
}
.ku-tabs-left {
  display: flex;
}

.ku-tabs-right {
  display: flex;
  flex-direction: row-reverse
}
.ku-tabs-left .ku-tabs-body,
.ku-tabs-right .ku-tabs-body {
  flex: 1;
}
.ku-tabs-right .ku-tabs-body{
  padding-left: 0;
  padding-right: 0;
}
</style>
TabsNav.vue
<template>
  <div class="ku-tabs-menu">
    <div class="ku-tabs-active" :style="activeStyle"></div>
    <div class="ku-tabs-item" :name="item.name" :class="{'active':rootTabs.nameIndex === item.name,'is-disabled':item.disabled}" v-for="(item,index) in rootTabs.tabsHeader" :key="index" @click="tabClick(index,$event)">
      {{item.label}}
    </div>
  </div>    
</template>
<script>
export default {
  name: "TabsNav",
  data(){
    return {
      activeStyle:"",
      envInitActive:null
    }
  },
  inject:["rootTabs"], // 注入父级组件提供的 rootTabs 变量
  created() {
      // 把这个方法写在这里立即执行一遍,避免点击切换时每次都作判断(top|left|right)
      this.envInitActive = (() => {
        if (this.rootTabs.tabsPosition === "top") {
          return function(target) {
            this.setNameIndex(target.attributes.name.value);
            return `width:${target.offsetWidth}px;left:${target.offsetLeft}px`;
          };
        } else {
          return function(target) {
            this.setNameIndex(target.attributes.name.value);
            return `height:${target.offsetHeight}px;top:${target.offsetTop}px`;
          };
        }
      })();
    },
    methods: {
      tabClick(index, event) {
        const pannelInstance = this.rootTabs.panesSlots[index].componentInstance;
        if(this.rootTabs.tabsClick){
          this.rootTabs.tabsClick(pannelInstance.name,index); // 页签点击回调,有两个值可用
        }
        if (pannelInstance.disabled) {
          return;
        }
        if (pannelInstance.name === this.rootTabs.nameIndex) {
          return;
        }
        this.activeStyle = this.initActive(event.target);
      },
      initActive(target) {
        return this.envInitActive(target);
      },
      setNameIndex(value) {
        this.rootTabs.nameIndex = value;
      }
    },
    mounted() {
      this.$nextTick(() => {
        let _target = null;
        const _el = this.$el;
        if (this.rootTabs.panesSlots.length === 0) {
          // 如果没有传 TabsPannel 则不往下处理
          return;
        }

        if (this.rootTabs.defaultActive) {
          // 如果用户自定义了激活页签
          _target = _el.querySelector(`[name=${this.rootTabs.defaultActive}]`);
        } else {
          // 设置默认激活页签(排除掉 disabled)
          for (let i = 0, element; (element = this.rootTabs.panesSlots[i++]); ) {
            if (!!!element.componentInstance.disabled) {
              const name = element.componentInstance.name;
              _target = _el.querySelector(`[name=${name}]`);
              break;
            }
          }
        }
        // 如果不存在页签
        if (!!!_target) {
          return;
        }
        this.activeStyle = this.initActive(_target);
      });
    }
};
</script>
<style scoped>
.ku-tabs-menu {
  position: relative;
}
.ku-tabs-item {
  display: inline-block;
  padding: 0 12px;
  height: 40px;
  line-height: 40px;
  cursor: pointer;
  transition: color 0.3s ease 0s;
  user-select:none;
  box-sizing: border-box;
}
.ku-tabs-item:hover {
  color: #409eff;
}
.ku-tabs-menu {
  border-bottom: 1px solid #ccc;
}
.ku-tabs-active {
  background: #409eff;
  position: absolute;
  transition: width 0.5s ease 0s, left 0.5s ease 0s, height 0.5s ease 0s,top 0.5s ease 0s;
}
.ku-tabs-menu .active {
  color: #409eff;
}
.ku-tabs.ku-tabs-top .ku-tabs-menu .ku-tabs-active {
  width: 0;
  left: 0;
  bottom: -2px;
  height: 3px;
}
.ku-tabs.is-custom-position .ku-tabs-menu {
  width: 200px;
  border-bottom: none;
}
.ku-tabs.ku-tabs-left .ku-tabs-menu{
  border-right: 1px solid #ccc;
}
.ku-tabs.is-custom-position .ku-tabs-menu .ku-tabs-active {
  height: 0;
  width: 3px;
  top: 0;
}
.ku-tabs.ku-tabs-left .ku-tabs-menu .ku-tabs-active{
  left: initial;
  right: -2px;
}
.ku-tabs.is-custom-position .ku-tabs-menu .ku-tabs-item {
  height: initial;
  line-height: initial;
  width: 100%;
}
.ku-tabs.ku-tabs-left .ku-tabs-menu .ku-tabs-item {
  padding: 8px 12px 8px 0;
}
.ku-tabs.ku-tabs-right .ku-tabs-menu {
  border-left: 1px solid #ccc;
}
.ku-tabs.ku-tabs-right .ku-tabs-menu .ku-tabs-active {
  left: -2px;
}
.ku-tabs.ku-tabs-right .ku-tabs-menu .ku-tabs-item {
  padding: 8px 12px 8px 8px;
}
.ku-tabs-menu .ku-tabs-item.is-disabled {
  color: #c0c4cc;
  cursor: default;
}
</style><template>
  <div class="ku-tabs-menu">
    <div class="ku-tabs-active" :style="activeStyle"></div>
    <div class="ku-tabs-item" :name="item.name" :class="{'active':rootTabs.nameIndex === item.name,'is-disabled':item.disabled}" v-for="(item,index) in rootTabs.tabsHeader" :key="index" @click="tabClick(index,$event)">
      {{item.label}}
    </div>
  </div>    
</template>
<script>
export default {
  name: "TabsNav",
  data(){
    return {
      activeStyle:"",
      envInitActive:null
    }
  },
  inject:["rootTabs"], // 注入父级组件提供的 rootTabs 变量
  created() {
      // 把这个方法写在这里立即执行一遍,避免点击切换时每次都作判断(top|left|right)
      this.envInitActive = (() => {
        if (this.rootTabs.tabsPosition === "top") {
          return function(target) {
            this.setNameIndex(target.attributes.name.value);
            return `width:${target.offsetWidth}px;left:${target.offsetLeft}px`;
          };
        } else {
          return function(target) {
            this.setNameIndex(target.attributes.name.value);
            return `height:${target.offsetHeight}px;top:${target.offsetTop}px`;
          };
        }
      })();
    },
    methods: {
      tabClick(index, event) {
        const pannelInstance = this.rootTabs.panesSlots[index].componentInstance;
        if(this.rootTabs.tabsClick){
          this.rootTabs.tabsClick(pannelInstance.name,index); // 页签点击回调,有两个值可用
        }
        if (pannelInstance.disabled) {
          return;
        }
        if (pannelInstance.name === this.rootTabs.nameIndex) {
          return;
        }
        this.activeStyle = this.initActive(event.target);
      },
      initActive(target) {
        return this.envInitActive(target);
      },
      setNameIndex(value) {
        this.rootTabs.nameIndex = value;
      }
    },
    mounted() {
      this.$nextTick(() => {
        let _target = null;
        const _el = this.$el;
        if (this.rootTabs.panesSlots.length === 0) {
          // 如果没有传 TabsPannel 则不往下处理
          return;
        }

        if (this.rootTabs.defaultActive) {
          // 如果用户自定义了激活页签
          _target = _el.querySelector(`[name=${this.rootTabs.defaultActive}]`);
        } else {
          // 设置默认激活页签(排除掉 disabled)
          for (let i = 0, element; (element = this.rootTabs.panesSlots[i++]); ) {
            if (!!!element.componentInstance.disabled) {
              const name = element.componentInstance.name;
              _target = _el.querySelector(`[name=${name}]`);
              break;
            }
          }
        }
        // 如果不存在页签
        if (!!!_target) {
          return;
        }
        this.activeStyle = this.initActive(_target);
      });
    }
};
</script>
<style scoped>
.ku-tabs-menu {
  position: relative;
}
.ku-tabs-item {
  display: inline-block;
  padding: 0 12px;
  height: 40px;
  line-height: 40px;
  cursor: pointer;
  transition: color 0.3s ease 0s;
  user-select:none;
  box-sizing: border-box;
}
.ku-tabs-item:hover {
  color: #409eff;
}
.ku-tabs-menu {
  border-bottom: 1px solid #ccc;
}
.ku-tabs-active {
  background: #409eff;
  position: absolute;
  transition: width 0.5s ease 0s, left 0.5s ease 0s, height 0.5s ease 0s,top 0.5s ease 0s;
}
.ku-tabs-menu .active {
  color: #409eff;
}
.ku-tabs.ku-tabs-top .ku-tabs-menu .ku-tabs-active {
  width: 0;
  left: 0;
  bottom: -2px;
  height: 3px;
}
.ku-tabs.is-custom-position .ku-tabs-menu {
  width: 200px;
  border-bottom: none;
}
.ku-tabs.ku-tabs-left .ku-tabs-menu{
  border-right: 1px solid #ccc;
}
.ku-tabs.is-custom-position .ku-tabs-menu .ku-tabs-active {
  height: 0;
  width: 3px;
  top: 0;
}
.ku-tabs.ku-tabs-left .ku-tabs-menu .ku-tabs-active{
  left: initial;
  right: -2px;
}
.ku-tabs.is-custom-position .ku-tabs-menu .ku-tabs-item {
  height: initial;
  line-height: initial;
  width: 100%;
}
.ku-tabs.ku-tabs-left .ku-tabs-menu .ku-tabs-item {
  padding: 8px 12px 8px 0;
}
.ku-tabs.ku-tabs-right .ku-tabs-menu {
  border-left: 1px solid #ccc;
}
.ku-tabs.ku-tabs-right .ku-tabs-menu .ku-tabs-active {
  left: -2px;
}
.ku-tabs.ku-tabs-right .ku-tabs-menu .ku-tabs-item {
  padding: 8px 12px 8px 8px;
}
.ku-tabs-menu .ku-tabs-item.is-disabled {
  color: #c0c4cc;
  cursor: default;
}
</style>
TabsPannel.vue
<template>
  <div :class="`ku-tabs-pannel tabs-pannel-${name}`" v-if="$parent.nameIndex === name">
      <slot></slot>
  </div>
</template>
<script>
export default {
  name: "TabsPannel",
  data() {
    return {};
  },
  props: {
    label: {
      type: String,
      default: ""
    },
    name: {
      type: String,
      default: ""
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  created() {
    this.$parent.tabsHeader.push({
      name: this.name,
      label: this.label,
      disabled: this.disabled
    });
  }
};
</script>
<style scoped>
.ku-tabs-header {
  position: relative;
}
.ku-tabs-item {
  display: inline-block;
  padding: 0 12px;
  height: 40px;
  line-height: 40px;
  cursor: pointer;
  transition: color 0.3s ease 0s;
}
.ku-tabs-item:hover {
  color: #409eff;
}
.ku-tabs-header {
  border-bottom: 1px solid #ccc;
}
.ku-tabs-active {
  height: 2px;
  background: #409eff;
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0;
  transition: width 0.5s ease 0s, left 0.5s ease 0s;
}
.ku-tabs-header .active {
  color: #409eff;
}
</style>

调用方法:

用例
<template>
  <div id="app">
     <ku-tabs :tabs-click="tabsClick" default-active="myFollow">
      <ku-tabs-pannel label="我的关注" name="myFollow">
         <ku-tabs tabs-position="left" default-active="industryExpert">
          <ku-tabs-pannel label="名星" name="celebrity">这里是名星详情内容</ku-tabs-pannel>
          <ku-tabs-pannel label="网红" name="webCelebrity">这里是网络红详情内容</ku-tabs-pannel>
          <ku-tabs-pannel label="行业专家" name="industryExpert">这里是行业专家详情内容</ku-tabs-pannel>
        </ku-tabs>
      </ku-tabs-pannel>
      <ku-tabs-pannel label="我的参与" name="myPartake">我参与详情内容</ku-tabs-pannel>
      <ku-tabs-pannel label="我的天" name="mySky">
        <ku-tabs tabs-position="right" default-active="javaScript">
          <ku-tabs-pannel label="HTML5" name="html5">HTML5详情内容</ku-tabs-pannel>
          <ku-tabs-pannel label="JavaScript" name="javaScript">JavaScript详情内容</ku-tabs-pannel>
          <ku-tabs-pannel label="Node.js" name="nodejs">Node.js详情内容</ku-tabs-pannel>
        </ku-tabs>
      </ku-tabs-pannel>
    </ku-tabs>
  </div>
</template>
<script>
import kuTabs from "./components/Tabs.vue";
import kuTabsPannel from "./components/TabsPannel.vue";
export default {
  name: 'app',
  components: {
    kuTabs,
    kuTabsPannel
  },
  methods:{
    tabsClick(name,index){
      console.log(name,index);
    }
  }
}
</script>
<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

 

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/vue-components-tab/

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论 END