volunteer-4/src/components/tab/Tabs.vue

217 lines
4.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="tab-container">
<!-- tab标题栏 -->
<scroll-view
scroll-x
class="tab-scroll-view"
:scroll-left="scrollLeft"
scroll-with-animation
show-scrollbar="false"
:id="tabScrollId"
>
<view class="tab-items-container">
<view
v-for="(item, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: currentIndex === index }"
@click="handleTabClick(index)"
:id="`tab-item-${index}`"
>
<text class="tab-text">{{ item.title }}</text>
</view>
<!-- -->
<view
class="tab-line"
:style="{
transform: `translateX(${lineLeft}px)`,
width: `${lineWidth}rpx`,
backgroundColor: props.themeColor,
}"
></view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, watch, nextTick, onMounted } from 'vue'
// 唯一ID防止多个Tab组件冲突
const tabScrollId = `tab-scroll-${Date.now()}`
// 定义组件属性
const props = defineProps({
// tabs数据格式[{title: '标签1'}, {title: '标签2'}]
tabs: {
type: Array,
default: () => [],
},
// 默认选中的索引
modelValue: {
type: Number,
default: 0,
},
// 主题色
themeColor: {
type: String,
default: '#3C9CFD',
},
// 滑块宽度
lineWidth: {
type: [Number, String],
default: 48,
},
})
// 定义事件
const emit = defineEmits(['update:modelValue', 'change'])
// 当前激活的索引
const currentIndex = ref(props.modelValue)
// 滚动位置
const scrollLeft = ref(0)
// 滑块位置
const lineLeft = ref(0)
// 监听props变化
watch(
() => props.modelValue,
(newVal) => {
if (currentIndex.value !== newVal) {
currentIndex.value = newVal
updateTabPosition()
}
},
)
// 监听currentIndex变化
watch(
() => currentIndex.value,
(newVal, oldVal) => {
if (newVal !== oldVal) {
updateTabPosition()
// 向父组件同步更新
emit('update:modelValue', newVal)
emit('change', {
index: newVal,
item: props.tabs[newVal] || {},
})
}
},
)
// 监听tabs变化重新计算滑块位置
watch(
() => props.tabs,
() => {
nextTick(() => {
updateTabPosition()
})
},
{ deep: true },
)
// 组件挂载后初始化
onMounted(() => {
nextTick(() => {
updateTabPosition()
})
})
const instance = getCurrentInstance()
// 更新标签位置和滑块位置 - 优化版
const updateTabPosition = () => {
nextTick(() => {
// 创建查询对象
const query = uni.createSelectorQuery().in(instance)
// 获取滚动视图和当前选中标签的信息
query.select(`#${tabScrollId}`).boundingClientRect()
query.select(`#tab-item-${currentIndex.value}`).boundingClientRect()
query.exec((res) => {
if (res && res[0] && res[1]) {
const scrollView = res[0]
const currentTab = res[1]
// 1. 计算滑块位置 - 直接使用当前标签的中心位置
const tabCenter = currentTab.left + currentTab.width / 2 - scrollView.left
// 2. 计算滑块左侧应该在的位置(居中)
const lineWidthPx = uni.upx2px(Number(props.lineWidth))
lineLeft.value = tabCenter - lineWidthPx / 2
// 3. 计算滚动位置,使选中的标签居中显示在滚动视图中
const offsetLeft = currentTab.left - scrollView.left
scrollLeft.value = offsetLeft - scrollView.width / 2 + currentTab.width / 2
}
})
})
}
// 点击标签切换
const handleTabClick = (index) => {
if (currentIndex.value !== index) {
currentIndex.value = index
}
}
</script>
<style scoped>
.tab-container {
width: 100%;
}
.tab-scroll-view {
white-space: nowrap;
width: 100%;
height: 88rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f5f5f5;
position: relative;
}
.tab-items-container {
display: inline-flex;
height: 100%;
position: relative;
width: 100%;
justify-content: space-around;
}
.tab-item {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 32rpx;
position: relative;
height: 100%;
}
.tab-text {
font-size: 28rpx;
color: #333333;
transition: all 0.3s;
}
.tab-item.active .tab-text {
color: v-bind('props.themeColor');
}
.tab-line {
position: absolute;
height: 6rpx;
border-radius: 6rpx;
bottom: 0;
left: 0;
/* 平滑过渡效果 */
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
</style>