app.vue
<template>
<div style="width: 700px;">
<Tab :default="data[0].catagory" ref="tabsCustom" @tabs-click="handleTabs">
<TabPanel v-for="(item) of data" :key="item.badElements" :title="item.catagory" :name="item.catagory">
<div>{{ item.badElements }} </div>
</TabPanel>
</Tab>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Tab from '@/components/Tab.vue';
import TabPanel from '@/components/TabPanel.vue';
const data = ref([
{
catagory: "name1",
badElements: "12341"
},
{
catagory: "name2",
badElements: "12342"
},
{
catagory: "name3",
badElements: "12343"
},
{
catagory: "name4",
badElements: "12344"
},
{
catagory: "name5",
badElements: "12345"
},
])
</script>
<style>
</style>
Tab.vue
<template>
<div class="card text-center" :style="{ maxWidth: 650 + 'px' }">
<div class="card-header">
<ul class="nav card-header-tabs">
<li class="nav-item" v-for="(titleInfo, index) in titles" :key="index"
:class="{ 'active-style': titleInfo.title === currentTab }"
@click.prevent="selectTab(titleInfo.title, index)">
<a class="nav-link" href="#" :ref="ref => navLink[index] = ref">{{ titleInfo.title }}</a>
</li>
</ul>
<div class="underline">
<li :style="{ left: updateLeft + 'px', width: underlineWidth + 'px' }"></li>
</div>
</div>
<div class="card-body">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, provide, ref, defineProps, nextTick, useContext, onUpdated } from 'vue';
import { toRaw } from '@vue/reactivity'
// const emit = defineEmits(['tabs-click']);
const { slots } = useContext();
const props = defineProps({
default: {
type: String,
default: '',
required: false,
},
});
const updateLeft = ref(0);
// const navLink = ref<HTMLElement | null>(null);
const navLink = ref([]);
const navLinkWidth = ref(0);
const underlineWidth = ref(0);
const currentTab = ref(props.default);
const titles = ref([]);
// console.log("name", name);
console.log("currenTab", currentTab.value);
const selectTab = (name: string, titleIndex: number) => {
// console.log("navLink", navLink.value);
// console.log("currenTab", currentTab.value);
currentTab.value = name;
navLinkWidth.value = navLink.value[titleIndex].clientWidth;
underlineWidth.value = navLinkWidth.value * 0.8;
if (titleIndex === 0) {
updateLeft.value = 0;
} else {
updateLeft.value = navLink.value[titleIndex].offsetLeft;
}
};
onMounted(() => {
console.log("onMounted", navLink.value);
console.log("slots", slots.default()[0].children);
titles.value = slots.default()[0].children.map(({ props }) => {
if (props) {
const { title, name } = props;
return {
title,
name,
};
}
});
console.log("titles", titles.value);
const defaultTab = titles.value.find((child: { name: any }) => child.name === currentTab.value);
if (defaultTab) {
currentTab.value = defaultTab.name;
} else if (titles.value.length > 0) {
currentTab.value = titles.value[0].name;
}
});
onUpdated(() => {
console.log("onUpdated", navLink.value[0]);
const titleIndex = titles.value.findIndex((item: { name: any }) => item.name === currentTab.value);
const index = titles.value.findIndex((item: { name: any }) => item.name === currentTab.value);
console.log("a标签", navLink.value[0]);
navLinkWidth.value = navLink.value[index].clientWidth;
underlineWidth.value = navLinkWidth.value * 0.8;
if (titleIndex === 0) {
updateLeft.value = 0;
} else {
updateLeft.value = navLink.value[titleIndex].offsetLeft;
}
});
provide('currentTab', currentTab);
</script>
<style scoped>
.active-style {
box-shadow: -3px -3px 3px 3px rgba(209, 204, 204, 0.47);
border-radius: 10%;
};
.underline {
position: relative;
li {
transition: all 0.4s ease;
height: 3px;
background-color: rgb(126, 225, 225);
position: absolute;
a {
box-sizing: border-box;
};
};
};
</style>
TabPanel.vue
<template>
<div v-if="currentTab === name">
<slot></slot>
</div>
</template>
<script setup lang="ts">
import { inject, defineProps } from 'vue';
const props = defineProps({
title: {
type: String,
require: true,
},
name: {
type: String,
require: true,
},
});
console.log("props", props);
const currentTab = inject('currentTab');
</script>
<style scoped></style>