16、springboot3 vue3开发平台-前端-自定义tabs

1. 为什么自定义tabs

使用el-tabs时因为内容是放到el-tab-pane中, 当标签多时会出现加载慢,卡问题,还有就是集成一些第三方封装的组件,如视频组件时会出项html样式冲突失控问题,所以使用自定义组件,将路由和tabs分开,减少影响
在这里插入图片描述
支持超出时出现滑动按钮:
在这里插入图片描述

2. 组件创建

src\components\tabs\index.vue

<template>
    <div class="tags-list" ref="totalLists">
        <div class="arrow-icon" @click="arrowBack" v-if="showButton">
            <el-icon class="icon arrow-style"><ArrowLeft /></el-icon>
        </div>
        <div class="tag-style" ref="tagBox">
            <div class="scrollWrapper" ref="scrollWrapper" id="nav" :style="{ marginLeft: tabScroll }">
                <el-tag v-for="(tag, index) in tagsList" :key="tag.name" closable 
                    @close="handleTagsClose(tag, index)" @click="highTags(tag, index)"
                    :class="tag.path == activePath ? 'highStyle' : 'normalStyle'" type="info" @contextmenu.prevent.native="openContextMenu($event, tag)">
                    {{ tag.title }}
                </el-tag>
            </div>
        </div>
        <div class="arrow-icon" @click="arrowForward" v-if="showButton">
            <el-icon  class="icon arrow-style"><ArrowRight /></el-icon>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import {ArrowLeft, ArrowRight  } from '@element-plus/icons-vue'

const props = defineProps<{
    tagsList?: Array<any>,
    activePath:  String
}>()
// console.log(props.tagsList)

// 定义要触发的事件  
const emit = defineEmits(['childEvent'])

const totalLists = ref<HTMLElement | null>(null)
const tagBox = ref<HTMLElement | null>(null)
const scrollWrapper = ref<HTMLElement | null>(null)

// const currentTag = ref({
//     name: "工作台",
//     path: "workbench1",
// })


const tabScroll = ref("0px") // 移动的距离


// const test = () =>{
//     for (let i = 0; i < 10; i++) {
//         let tabInfo = {
//             name: "工作台" + i,
//             path: "workbench" + i,
//         }
//         props.tagsList!.push(tabInfo)
//     }
// }
// test()


const showButton = ref(false) // 标签左右两侧箭头是否显示
const swiperScrollWidth = ref(0) // 盒子的宽度
const swiperScrollContentWidth = ref(0) // 内容的宽度

onMounted(() => {
    window.addEventListener("resize", checkButtonStatus)
})

// 标签向左切换
const arrowBack = () => {
  let tabBoxWidth = tagBox.value!.clientWidth                       //盒子宽度
  let offsetLeft = Math.abs(scrollWrapper.value!.offsetLeft)        //移动距离
  if (offsetLeft > tabBoxWidth) {
    tabScroll.value = offsetLeft + tabBoxWidth + "px"               //移动距离大于父盒子宽度,向前移动一整个父盒子宽度
  }else {
    tabScroll.value = "0px"                                         // 移动到开始位置
  }
}

// 标签向右切换
const arrowForward = () => {
  let tabBoxWidth = tagBox.value!.clientWidth                         //盒子宽度
  let scrollWidth = scrollWrapper.value!.scrollWidth                  //内容宽度
  
  // 必须要在循环的父级添加 定位样式, offsetLeft 获取元素相对带有定位父元素左边框的偏移
  let offsetLeft = Math.abs(scrollWrapper.value!.offsetLeft)          //移动距离
  let diffWidth = scrollWidth - tabBoxWidth                           //计算内容宽度与盒子宽度的差值
  if (diffWidth - offsetLeft > tabBoxWidth) {
    tabScroll.value = -(offsetLeft + tabBoxWidth) + "px"              //判断差值减去移动距离是否大于盒子宽度 大于则滚动已移动距离+盒子宽度
  }else {
    tabScroll.value = -diffWidth + "px"                                //小于则移动差值距离
  }
}

const checkButtonStatus = () => {
  if (!scrollWrapper.value) return
  let containerSize = tagBox.value!.clientWidth      // 盒子的宽度
  let navSize = scrollWrapper.value.scrollWidth      // 内容的宽度
  if (containerSize > navSize || containerSize == navSize) {
    showButton.value = false
  }else {
    showButton.value = true
  }
  console.log("containerSize:" + containerSize +" ,navSize: " + navSize + ", showButton: " + showButton.value)
}

// 监听整个数组长度的变化
watch(
    () => props.tagsList?.length, 
    (newlength, oldlength) => {
        // console.log(newlength, oldlength)
      if (newlength != oldlength) {
        checkButtonStatus()
      }
    }
)


const handleTagsClose = (tag: any, index: number) =>{
    emit('childEvent', { type: 'close', path: tag.path })
}

const highTags = (tag: any, index: number) => {
    emit('childEvent', { type: 'active', path: tag.path })
}

const openContextMenu = (event: any, tag: any) => {
  // console.log("openContextMenu================================")
  let left = event.clientX
  let top = event.clientY + 10
  emit('childEvent', { type: 'rightClick', path: tag.path, left: left, top: top})
}


</script>

<style lang="scss" scoped>
.tags-list {
  margin: 4px 0;
  display: flex;
  align-items: center;
    .tag-style {
      display: flex;
      align-items: center;
      overflow: hidden;
      pointer-events: all;
      cursor: pointer;
      position: relative; // 必须添加该定位,offsetLeft 获取元素相对带有定位父元素左边框的偏移
      .scrollWrapper {
        display: flex;
        align-items: center;
        overflow-x: auto;
        transition:all 500ms linear
      }
      .scrollWrapper::-webkit-scrollbar {
        height: 0;
      }
      .el-tag {
        height: 28px;
        margin-right: 2px;
        cursor: pointer;
      }
      .el-tag.el-tag--info {
        background: #fff;
        // color: $text-color;
      }
      .highStyle {
        color: #1890FF !important;
        font-size: 14px;
        font-weight: 400;
        padding: 0 10px;
      }
      .normalStyle {
        font-size: 14px;
        font-weight: 400;
        padding: 0 10px;
      }
    }
  .arrow-icon {
    width: 32px;
    height: 32px;
    pointer-events: all;
    cursor: pointer;
      .arrow-style {
        font-size: 16px;
        padding: 0 8px;
        position: relative;
        top: 8px;
      }
   }
}
</style>

说明:

  • 包含点击,关闭,右键事件,右键主要是显示关闭选择框
    在这里插入图片描述

3. 使用示例(非完整,完整参考后续布局部分)

<tabs :tagsList="tabList" :activePath="activeTab" @childEvent="handChildEvent"></tabs>

// 子组件传值
const handChildEvent = (data: any) => {
    console.log("handChildEvent,", data)
    if (data.type == 'close') {
        menuStore.delTabList(data.path)
    } 
    if (data.type == 'active') {
        menuStore.setActiveTab(data.path)
        router.push(data.path)
    }
    if (data.type == 'rightClick') {
        currrentPath.value = data.path
        contextMenuVisible.value = true
        left.value = data.left
        top.value = data.top
    }
}

组件布局引用自: https://blog.csdn.net/cdd9527/article/details/130366785

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不知所云,

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值