Vue3+ts 封装一个 tab标签页组件

前言:

在写项目的时候,用到了element-plus,在这个项目中需要使用tabs组件,但是使用element-plus 的tab组件来满足项目中的tab样式,有的时候会有点麻烦,且自己也想动手试试封装一个tab组件,于是,开干!!

最终效果图:

这里面的选中下横线用的是绝对定位

 <div class="indicator" :style="indicatorStyle"></div>

 .indicator {

    position: absolute;

    bottom: 0;

    height: 2px;

    background-color: @themeColor;

    transition: transform 0.3s ease;

  }

其中还需要去计算他的宽度 width 和 移动量 transform 

/**
 *这里通过computed 来计算 
 * index :标记选中的是第几个

 * tabs :获取所有 tab的信息

 * translateX:横向移动的数据

 * width:单个tab的款宽度---注意:如果需要在选中tab的后面加一个标识,比如:“当前”,则  * width和translate 计算有误,后面会出兼容版本
 */

const indicatorStyle = computed(() => {

  const panelParent = myTabsRef.value

  if (panelParent) {

    let index: number

    if (dps.isIndex) {

      index = Number(dps.active)

    } else {

      index = dps.data.findIndex((r: any) => r[getDefaultProps.value.id] === dps.active)

    }

    const tabs = panelParent.querySelectorAll('.myTab__header_item')

    let translateX = 0, width = 0

    // 移动指示器到选中的标签

    if (tabs.length && index !== -1) {

      const selectedTab = tabs[index]

      const selectedTabRect = selectedTab.getBoundingClientRect()

      width = selectedTabRect.width

      const tabsHeaderRect = selectedTab.parentNode.getBoundingClientRect()

      translateX = selectedTabRect.left - tabsHeaderRect.left

    }

    return {

      transform: `translateX(${translateX}px)`,

      width: `${width}px`

    }

  }

  return {}

})

具体代码:

<script setup lang="ts">
import { ref, computed } from 'vue'

const emits = defineEmits(['update:active'])

interface Props {
  data: any // tab的数组数据
  active: string | number// 选中标识
  isIndex?: boolean
  props?: any// 对象数组的对象里面的属性对应
  padBottom:string // 高亮横线距离tab文字的距离 
}
const dps = withDefaults(defineProps<Props>(), {
  data: [],
  active: '',
  padBottom:'14px'
})

const getDefaultProps = computed(() => {
  return dps.props || {
    id: 'elementId',
    name: 'elementName'
  }
})

const myTabsRef = ref()
const indicatorStyle = computed(() => {
  const panelParent = myTabsRef.value
  if (panelParent) {
    let index: number
    if (dps.isIndex) {
      index = Number(dps.active)
    } else {
      index = dps.data.findIndex((r: any) => r[getDefaultProps.value.id] === dps.active)
    }
    const tabs = panelParent.querySelectorAll('.myTab__header_item')
    let translateX = 0, width = 0
    // 移动指示器到选中的标签
    if (tabs.length && index !== -1) {
      const selectedTab = tabs[index]
      const selectedTabRect = selectedTab.getBoundingClientRect()
      width = selectedTabRect.width
      const tabsHeaderRect = selectedTab.parentNode.getBoundingClientRect()
      translateX = selectedTabRect.left - tabsHeaderRect.left
    }
    return {
      transform: `translateX(${translateX}px)`,
      width: `${width}px`
    }
  }
  return {}
})

const onTabClick = (item: any, index: number) => {
  emits('update:active', { ...item, index: index })
}
</script>

<template>
  <div class="myTab tabs-container" ref="myTabsRef" v-if="data.length">
    <div class="myTab__header">
      <div class="indicator" :style="indicatorStyle"></div>
      <div class="myTab__header_box">
        <div class="myTab__header_item" :style="{paddingBottom:padBottom}" :class="{ 'active': active === (isIndex ? index : item[getDefaultProps.id]) }"
          v-for="(item, index) in data" :key="index" @click="onTabClick(item, index)">
          <slot name="label" :index="index" :row="item">{{ item[getDefaultProps.name] }}</slot>
        </div>
      </div>
    </div>
    <slot name="right"></slot>
  </div>
</template>

<style lang="less" scoped>
.myTab {
  display: flex;
  justify-content: space-between;
  column-gap: 12px;
}

.myTab__header {
  position: relative;
  border-bottom: 1px solid #F6F7FA;
  flex: 1;
  overflow-x: auto;

  &:hover::-webkit-scrollbar-thumb {
    background: #dddddd;
  }

  &_box {
    flex: 1;
    white-space: nowrap;
  }

  .indicator {
    position: absolute;
    bottom: 0;
    height: 2px;
    background-color: @themeColor;
    transition: transform 0.3s ease;
  }

  &_item {
    display: inline-block;
    // padding-bottom: 14px;
    min-width: 56px;
    margin-left: 24px;
    text-align: center;
    color: #666;
    cursor: pointer;

    &:first-child {
      margin-left: 0;
    }

    &.active {
      font-weight: 600;
    }

    &.active,
    &:hover {
      color: @themeColor;
    }
  }
}
</style>
 

以上就是完整代码了

单个tab的款宽度-需注意:如果需要在选中tab的后面加一个标识,比如:“当前”,则   width和translate 计算有误,后面会出兼容版本

后续会出兼容版本,尽情期待

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值