仿写element-plus的el-tabs组件,主要为了将tab和内容分开,提高渲染速度
如果tab下的内容太多会导致页面加载异常缓慢
效果和官网一致
在components文件下新建tabs文件,tabs中新建index.vue
index.vue文件
<template>
<el-card shadow="Never" body-class="cardCss-table" class="mt-8">
<div class="tabs__nav-wrap">
<div class="tabs__nav-scroll">
<div class="tabs__nav">
<div class="tabs_active-bar" :style="highlightStyle"></div>
<div
v-for="(item, index) in props.tabList"
:key="item.id"
@click="changeTabs(item, index)"
class="c-p tabs__item"
ref="tabItem"
:class="{
p1: index === 0,
p2: index > 0,
p3: index === tabList.length - 1 && tabList.length > 1,
'active-tab': currentId == item.id,
}"
>
<span> {{ item.label }} </span>
<span v-if="item.num"> {{ "(" + item.num + ")" }}</span>
</div>
</div>
</div>
</div>
</el-card>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from "vue";
const props = defineProps({
tabList: {
type: Array as any,
default: () => {
return [];
},
},
});
// props.tabList
const emits = defineEmits(["changeTab"]);
const highlightStyle = ref<any>({
width: "0px", // 初始化宽度
transform: "translateX(0px)", // 默认位置
});
const currentId = ref<any>(null);// 当前的高亮id
const tabItem = ref<any>(null); // tabs的ref数组
const changeTabs = (item: any, val: number) => {
currentId.value = item.id;
if (val !== 0) {
let percentage = 0;
for (let i = 0; i < val; i++) {
percentage += leftWidth.value[i].leftwidth;
}
highlightStyle.value.transform = `translateX(${percentage + 20}px)`;
highlightStyle.value.width = leftWidth.value[val].width + "px";
} else {
highlightStyle.value.transform = `translateX(0px)`;
highlightStyle.value.width = leftWidth.value[0].width + "px";
}
emits("changeTab", item.id);
};
const leftWidth = ref<any[]>([]);// 将每个tabitem长度存储到这个数组中
const calculateWidth = () => {
const tabItems = tabItem.value;
tabItems.forEach((item, index) => {
// console.log(item.offsetWidth, index);
const offsetWidth = item.offsetWidth;
let baseWidth = index == 0 || index == tabItems.length - 1 ? offsetWidth - 20 : offsetWidth - 40;
const width = baseWidth;
leftWidth.value.push({ width: Number(width), leftwidth: Number(offsetWidth) });
});
};
onMounted(() => {
// 必须使用setTimeout
setTimeout(() => {
currentId.value = props.tabList[0].id; // 默认选中第一个
calculateWidth(); // 计算tab宽度
highlightStyle.value.width = leftWidth.value[0].width + "px"; // 设置默认宽度
}, 200);
});
</script>
<style lang="scss" scoped>
.tabs__nav-wrap {
height: 40px;
margin-bottom: -1px;
overflow: hidden;
position: relative;
font-size: 14px;
color: #303133;
}
.tabs__nav-wrap::after {
background-color: #e4e7ed;
bottom: 0;
content: "";
height: 2px;
left: 0;
position: absolute;
width: 100%;
z-index: 1;
}
.tabs__nav-scroll {
overflow: hidden;
}
.tabs__nav {
display: flex;
float: left;
position: relative;
transition: transform 0.3s;
white-space: nowrap;
z-index: 2;
}
.tabs__item {
align-items: center;
box-sizing: border-box;
display: flex;
font-weight: 500;
height: 40px;
justify-content: center;
list-style: none;
position: relative;
}
.p1 {
padding: 0 20px 0 0;
}
.p2 {
padding: 0 20px;
}
.p3 {
padding: 0 0 0 20px;
}
.tabs_active-bar {
background-color: #409eff;
bottom: 0;
height: 2px;
left: 0;
list-style: none;
position: absolute;
transition: width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
z-index: 1;
}
.active-tab {
color: #409eff;
}
</style>
在使用此组件的.vue文件中使用Tabs组件
<template>
<div>
<!-- Tabs组件-->
<Tabs :tabList="ebTabsList" @changeTab="changeTab" />
...其他内容
</div>
</template>
<script setup lang="ts">
import Tabs from "@/components/tabs/index.vue";
interface Tabs {
label: string;
id: string;
}
const tabData = ref<Tabs[]>([
{ label: "待下单", id: "1" },
{ label: "异常订单", id: "2" },
]);
</script>
适用于使用el-tabs组件但是内容太多的场景,如果想要包裹到一起再在外层包裹el-card,ui样式基本和element-plus的一样