一、Tab 切换组件

组件说明:
实现 tab 切换。

效果展示:
实现 tab 切换,改变激活样式,切换到对应的页面
在这里插入图片描述
以上 tab 切换功能在前端开发中司空见惯。各种现存的组件也随手拈来。在 Vue 中,有配套的 element-ui 组件,也有 vue-ant-design。

element-ui 中 el-tabs 效果如下:
在这里插入图片描述
vue-ant-design 中 a-tabs 效果如下:
在这里插入图片描述
但是使用现存组件面临的问题如下

  1. element-ui 中 el-tabs 使用问题
    tab 标签文本没有居中,整体靠左,通过复写样式也不行,因为下面的高亮下划线是通过 JS 动态控制。
  2. vue-ant-design 中 a-tabs 使用问题
    tab 标签文本间的距离太大。
  • 基于以上问题,所以打算自己封装一个 tab 组件。

二、使用案例

该组件有两种使用方式。

  1. 只有一个主页面,切换时更新数据源即可,用法如下。
<template>
    <el-tab defaultKey="1" @on-click="changeTab">
      <el-tab-panes actKey="1" label="全部"></el-tab-panes>
      <el-tab-panes actKey="2" label="推荐"></el-tab-panes>
      <el-tab-panes actKey="3" label="最新"></el-tab-panes>
    </el-tab>
    <div>只有我一个页面,更新数据源即可</div>
</template>
<script>
 export default{
    data(){
      return{
       
       }
    },
    methods:{
       changeTab(item,index){
             //调数据
        }
    }
}
</script>
  1. 有几个tab,就有几个主页面,切换时切换到不同的页面,用法如下。
<template>
    <el-tab defaultKey="1" @on-click="changeTab">
      <el-tab-panes actKey="1" label="全部">
        <div>页面1</div>
      </el-tab-panes>
      <el-tab-panes actKey="2" label="推荐">
        <div>页面2</div>
      </el-tab-panes>
      <el-tab-panes actKey="3" label="最新">
        <div>页面3</div>
      </el-tab-panes>
    </el-tab>
</template>
<script>
 export default{
    data(){
      return{
       
       }
    },
    methods:{
       changeTab(item,index){
             //调数据
        }
    }
}
</script>

三、API 使用指南

属性说明类型默认值
defaultKey默认选中的tabString1
actKey每个tab的唯一keyString
label每个tab的标题String
on-clicktab 被选中时触发点击按钮的回调函数(e: Object): void

四、源代码

实现思路
(1) tab 标签文本样式高亮;
(2) tab 下面的下划线调整到相应位置;
(3) tab 对应的内容切换。
在这里插入图片描述

  1. tabs 组件
<template>
  <div>
    <div class="tabs">
      <div  ref="line" class="tab-line"></div>
      <div :class="[activeKey == item.actKey? 'active-tab' : 'tab']"  @click="changeTab($event,item,index)" v-for="(item,index) in childList">{{item.label}}</div>
    </div>
    <slot></slot>
  </div>
</template>
<script>
  let self;
  export default {
    name: "ElTab",
    data(){
      return {
        childList:[],
        activeKey:this.defaultKey,//将初始化tab赋值给activeKey
        slideWidth:0
      }
    },
   //获取子组件传过来的激活tab
    props:{
      defaultKey:{
        type: String,
        default: "1"
      }
    },
    created(){
      self = this;
    },
    mounted(){
      //循环tab标签
      this.childList = this.$children;
      //设置滑动距离。平分设备宽度
      this.slideWidth = window.innerWidth/this.childList.length;
      //设置状态线初始化滑动位置
      this.$refs.line.style.width = this.slideWidth+"px";
    },
    methods:{
      //切换tab触发事件
      changeTab:(event,item,index)=>{
        self.activeKey = item.actKey;
        self.$refs.line.style.transform = "translateX("+self.slideWidth*index+"px)";
        self.$refs.line.style.transition = "transform .3s";
        self.$emit('on-click',item,index);//将切换tab的事件暴露给父组件
      },
      //初始化时tab状态设置与相应内容显示
      updateNav:()=>{
        self.$children.map((item,index)=>{
          if(item.actKey == self.activeKey){
            item.show = true;
            self.$nextTick(function() {
              self.$refs.line.style.transform = "translateX("+self.slideWidth*index+"px)";
              self.$refs.line.style.transition = "transform 0s";
            });
          }else {
            item.show = false;
          }
        })
      }
    },
    watch: {
      //监听当前tab,显示相应内容
      activeKey() {
        self.$children.map((item)=>{
          if(item.actKey == self.activeKey){
            item.show = true;
          }else {
            item.show = false;
          }
        })
      }
    }
  }
</script>
<style>
  .active-tab{
    color:#158ef3;
    height: 50px;
    font-weight: bold;
    line-height: 50px;
    font-size: 16px;
  }
  .tab{
    color:#333;
    height: 50px;
    line-height: 50px;
    font-size: 16px;
  }
  .tabs{
    display: flex;
    justify-content: space-around;
    align-items: center;
    height: 50px;
    border-bottom: 1px solid #f6f6f6;
  }
  .tab-line{
    position: absolute;
    left: 0;
    border-bottom: 2px solid #158ef3;
    height: 50px;
  }
</style>
  1. tab-pane 组件
<template>
  <div v-if="show">
    <slot></slot>
  </div>
</template>
<script>
  export default {
    name: "ElTabPanes",
    data(){
      return {
        show: false //初始时将所有内容隐藏
      }
    },
    props:{
      actKey:{
        type: String,
        default: "1"
      }, label:{
        type: String,
        default: ""
      },
    },
    mounted(){
      this.$parent.updateNav();
    },
  }
</script>

五、总结

最后总结一下封装一个 tabs 的核心思路和方法。

1. 设置初始化 tab 标签

在 tab-pane 子组件中将所有的内容隐藏(show 属性设置为 false),在 tabs 父组件内接收由开发者自定义的 activeKey,定义一个方法,将 activeKey与子组件的 actKey 比较,如果相同,则该 tab 为初始化时激活的 tab 标签,将相应 tab 的 show 属性设置为 true,并修改 tab 样式。该方法在 tab-pane 子组件中调用。

将 tab-pane 子组件中所有的开发者添加的内容隐藏:
在这里插入图片描述
tabs 父组件提供的方法:
在这里插入图片描述

tab-pane 子组件调用:
在这里插入图片描述

  1. tabs 组件内部循环 tab-pane 子组件的标签。接收 activeKey,点击时将 tab-pane 子组件的 actKey 赋值给 activeKey。然后每个 tab 的样式通过当前 activeKey 与 actKey 比较,判断是否是当前 tab 标签。如果是,则样式设置为激活样式。
<div :class="[activeKey == item.actKey? 'active-tab' : 'tab']"  @click="changeTab($event,item.actKey,index)" v-for="(item,index) in childList">{{item.label}}</div>

changeTab:(event,tab,index)=>{
                self.activeKey = tab;
                self.$refs.line.style.transform = "translateX("+self.slideWidth*index+"px)";
                self.$refs.line.style.transition = "transform .3s";
                self.$emit('on-click',event,tab)
            },
  1. 在 tab-pane 上方添加状态线,状态线的样式需要注意一下。
<div  ref="line" class="tab-line"></div>

    .tab-line{
        height: 2px;
        background: #409eff;
        position: absolute;
        left: 0;
        margin-top: 20px;
    }
  1. 切换 tab 时,改变相应的内容。
    这一步最关键,需要在 tabs 组件中使用 watch 监听当前状态,如果子组件中的 actKey 等于当前状态,则显示相应的内容。
     activeKey() {
                self.$children.map((item)=>{
                    if(item.actKey == self.activeKey){
                        item.show = true;
                    }else {
                        item.show = false;
                    }
                })
            }
Logo

前往低代码交流专区

更多推荐