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