2025-12-03 18:06:10 +08:00

189 lines
4.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="tab-container">
<el-icon class="scroll-btn left" @click="scrollAndSelect('left')" :disabled="!hasLeftScroll">
<ArrowLeft />
</el-icon>
<div class="tab-wrapper">
<div class="tab-list" ref="tabListRef" @scroll="handleScroll">
<div :class="['text', activeTab === item.value ? 'active' : '']" v-for="item in tabList" :key="item.value"
@click="handleTabClick(item.value)" ref="tabItems">
{{ item.label }}
</div>
</div>
</div>
<el-icon class="scroll-btn right" @click="scrollAndSelect('right')" :disabled="!hasRightScroll">
<ArrowRight />
</el-icon>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
// 定义 tabList 元素的类型
interface TabItem {
label: string;
value: string | number;
}
const props = defineProps<{
tabList: TabItem[];
activeTab: string | number;
}>();
const emit = defineEmits(['update:activeTab']);
const tabListRef = ref();
const tabItems = ref<HTMLDivElement[]>([]);
const hasLeftScroll = ref(false);
const hasRightScroll = ref(false);
const handleScroll = () => {
if (tabListRef.value) {
const { scrollLeft, scrollWidth, clientWidth } = tabListRef.value;
hasLeftScroll.value = scrollLeft > 5;
hasRightScroll.value = scrollLeft < scrollWidth - clientWidth - 5;
}
};
const handleTabClick = (value: any) => {
emit('update:activeTab', value);
scrollToActiveTab();
};
// 滚动到当前激活的Tab
const scrollToActiveTab = () => {
if (!tabListRef.value) return;
const activeIndex = props.tabList.findIndex((item: any) => item.value === props.activeTab);
if (activeIndex === -1) return;
const activeTabElement = tabItems.value[activeIndex];
if (activeTabElement) {
const scrollContainer = tabListRef.value;
const containerRect = scrollContainer.getBoundingClientRect();
const tabRect = activeTabElement.getBoundingClientRect();
// 如果Tab不在可见区域内,则滚动到可见区域
if (tabRect.left < containerRect.left || tabRect.right > containerRect.right) {
scrollContainer.scrollTo({
left: tabRect.left - containerRect.left + scrollContainer.scrollLeft - 200,
behavior: 'smooth'
});
}
}
};
// 滚动并选中
const scrollAndSelect = (direction: 'left' | 'right') => {
if (!tabListRef.value || tabItems.value.length === 0) return;
const currentIndex = props.tabList.findIndex((item: any) => item.value === props.activeTab);
let newIndex;
if (direction === 'left') {
// 向左滚动并选中前一个Tab
newIndex = currentIndex > 0 ? currentIndex - 1 : 0;
} else {
// 向右滚动并选中后一个Tab
newIndex = currentIndex < props.tabList.length - 1 ? currentIndex + 1 : props.tabList.length - 1;
}
emit('update:activeTab', props.tabList[newIndex].value);
};
// 监听activeTab变化,确保滚动到可视区域
watch(() => props.activeTab, scrollToActiveTab);
// 初始化时检查滚动状态
setTimeout(() => {
handleScroll();
}, 0);
</script>
<style scoped lang="scss">
.tab-container {
position: relative;
display: flex;
align-items: center;
width: 100%;
}
.scroll-btn {
width: 1.875rem;
height: 2.9375rem;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
cursor: pointer;
z-index: 10;
box-shadow: 0 0 .25rem rgba(0, 0, 0, 0.1);
transition: opacity 0.3s;
&.left {
position: absolute;
left: 0;
top: 0;
border-radius: .75rem 0 0 .75rem;
border: 1px solid #fff;
}
&.right {
position: absolute;
right: 0;
top: 0;
border-radius: 0 .75rem .75rem 0;
border: 1px solid #fff;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
.tab-wrapper {
flex: 1;
overflow: hidden;
padding: 0 1.875rem; // 给箭头留出空间
}
.tab-list {
display: flex;
gap: 1px;
white-space: nowrap;
margin-bottom: 1px;
background-color: #fff;
cursor: pointer;
overflow-x: auto;
scrollbar-width: none; // 隐藏滚动条
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none; // 隐藏滚动条
}
}
.text {
font-weight: 400;
font-size: 1rem;
color: #1F6DD3;
width: 5.625rem;
height: 2.9375rem;
line-height: 2.9375rem;
text-align: center;
flex-shrink: 0;
cursor: pointer;
}
.active {
background: #e9f1fb;
color: #1F6DD3;
font-weight: 600;
font-family: SourceHanSansCN, SourceHanSansCN;
}
</style>