Vue实现简单Tab标签页组件

Posted xiongxiaolong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue实现简单Tab标签页组件相关的知识,希望对你有一定的参考价值。

Tab 标签页组件

基础用法

默认情况下启用第一个标签,可以通过v-model绑定当前激活的标签索引

<tabs v-model="active">
  <tab title="标签 1">内容 1</tab>
  <tab title="标签 2">内容 2</tab>
  <tab title="标签 3">内容 3</tab>
</van-tabs>
export default {
  data() {
    return {
      active: 2
    };
  }
}

点击事件

可以在tabs上绑定click事件和change事件,事件传参为标签对应的索引和标题

<tabs @click="handleClick"></tabs>
<tabs @change="handleChange"></tabs>

Tabs API

属性名 类型 默认值 说明
v-model String Number 0 当前标签的索引
color String red 标签和底部条颜色
duration Number 0.3 动画时间,单位秒
line-width Number tab.offsetWidth / 2 底部条宽度,单位 px
line-height Number 3 底部条高度,单位 px

Tab API

属性名 类型 默认值 说明
title String - 当前标签的索引

example

技术分享图片

技术分享图片

code

tab

<template>
  <div 
    v-show="isSelected"
    class="tab__pane"
    >
    <slot />
  </div>
</template>

<script>
export default {
  name: 'tab',
  props: {
    title: String,
  },
  data() {
    return {
      parent: null,
    };
  },
  computed: {
    index() {
      return this.parent.tabs.indexOf(this);
    },
    isSelected() {
      return this.index === this.parent.curActive;
    },
  },
  watch: {
    title() {
      this.parent.setLine();
    }
  },
  methods: {
    findParent(name) {
      let parent = this.$parent;
      while (parent) {
        if (parent.$options.name === name) {
          this.parent = parent;
          break;
        }
        parent = parent.$parent; // 多层嵌套
      }
    },
  },
  created() {
    this.findParent('tabs');
  },
  mounted() {
    const { tabs } = this.parent;
    const index = this.parent.$slots.default.indexOf(this.$vnode);
    tabs.splice(index === -1 ? tabs.length : index, 0, this);
  },
  beforeDestroy() {
    this.parent.tabs.splice(this.index, 1);
  },
};
</script>

<style>
.tab__pane {
}
</style>

tabs

<template>
  <div class="tabs">
    <div class="tabs__nav">
      <div
       class="tabs__line"
       :style="lineStyle"
      ></div>
      <div 
        v-for="(tab, index) in tabs" 
        :key="index" 
        ref="tabs"
        @click="onClick(index)"
        class="tab"
        :style="getTabStyle(tab, index)"
        :class="{'tab--active': index === curActive,}"
      >
        <span>{{tab.title}}</span>
      </div>
    </div>
    <slot/>
  </div>
</template>

<script>
export default {
  name: 'tabs',
  model: {
    prop: 'active',
  },
  props: {
    color: String,
    lineWidth: {
      type: Number,
      default: null,
    },
    lineHeight: {
      type: Number,
      default: 3,
    },
    duration: {
      type: Number,
      default: 0.3,
    },
    active: {
      type: [Number, String],
      default: 0,
    },
  },
  data() {
    return {
      tabs: [],
      curActive: null,
      lineStyle: {
        backgroundColor: this.color,
      },
    };
  },
  computed: {},
  watch: {
    active(val) {
      if (val !== this.curActive) {
        this.correctActive(val);
      }
    },
    color() {
      this.setLine();
    },
    tabs() {
      this.correctActive(this.curActive || this.active);
      this.setLine();
    },
    curActive() {
      this.setLine();
    },
  },
  mounted() {
    this.correctActive(this.active);
    this.isFirstLoaded = true;
    this.$nextTick(() => {
      this.isFirstLoaded = false;
    });
  },
  methods: {
    onClick(index) {
      const { title } = this.tabs[index];
      this.setCurActive(index);
      this.$emit('click', index, title);
    },
    setActive() {},
    correctActive(active) {
      active = +active;
      // console.log(active, 'active');
      const exist = this.tabs.some(tab => tab.index === active);
      const defaultActive = (this.tabs[0] || {}).index || 0;
      this.setCurActive(exist ? active : defaultActive);
    },
    setCurActive(active) {
      if (active !== this.curActive) {
        console.log(this.curActive);
        if (this.curActive !== null) {
          this.$emit('change', active, this.tabs[active].title);
        }
        this.curActive = active;
      }
    },
    setLine() {
      const animation = this.isFirstLoaded;
      this.$nextTick(() => {
        const { tabs } = this.$refs;
        if (!tabs || !tabs[this.curActive]) {
          return;
        }
        const tab = tabs[this.curActive];
        const { lineWidth, lineHeight } = this;
        /* eslint-disable no-unneeded-ternary */
        const width = lineWidth ? lineWidth : (tab.offsetWidth / 2);
        /* eslint-disable no-unneeded-ternary */
        const left = tab.offsetLeft + ((tab.offsetWidth - width) / 2);
        const lineStyle = {
          width: `${width}px`,
          backgroundColor: this.color,
          transform: `translateX(${left}px)`,
        };
        if (!animation) {
          lineStyle.transitionDuration = `${this.duration}s`;
        }
        let height = '';
        if (lineHeight) {
          height = `${lineHeight}px`;
        } else {
          height = '3px';
        }
        lineStyle.height = height;
        lineStyle.borderRadius = height;
        this.lineStyle = lineStyle;
      });
    },
    getTabStyle(tab, index) {
      if (index === this.curActive) {
        return {
          color: this.color,
          fontWeight: 'bold',
        };
      }
    },
  },
};
</script>
<style>
.tab {
  flex: 1;
  cursor: pointer;
  min-width: 0;
  padding: 0 5px;
  font-size: 14px;
  position: relative;
  color: #000;
  line-height: 50px;
  text-align: center;
  box-sizing: border-box;
  background-color: #fff;
  span {
    display: block;
  }
  &--active {
    font-weight: 500!important;
  }
}
.tabs {
  position: relative;
  &__nav {
    display: flex;
    user-select: none;
    position: relative;
    background-color: #fff;
    height: 100%;
    box-sizing: content-box;
  }
  &__line {
    z-index: 1;
    left: 0;
    bottom: 0px;
    height: 3px;
    position: absolute;
    border-radius: 3px;
    background-color: red;
  }
}
</style>

END

以上是关于Vue实现简单Tab标签页组件的主要内容,如果未能解决你的问题,请参考以下文章

实现同一个组件页面通过不同的tab标签页打开

vue中tab标签页keep-alive二级路由+删除指定缓存页面

VUE项目实战44渲染静态属性和动态参数的Tab

vue-tab切换

vue element-ui Tabs 标签页实现更多功能

vue tab标签