You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dandelionPlatformToB-uni-v3/components/custom-tabs.vue

150 lines
4.4 KiB
Vue

2 months ago
<template>
<!-- 横向滚动容器恢复scroll-into-view绑定动态变量targetScrollId移除ref -->
<scroll-view class="tabs-scroll-container" scroll-x="true" scroll-with-animation :show-scrollbar="false" :scroll-into-view="targetScrollId">
<view class="tabs-wrapper" :style="{ display: noScroll ? 'flex' : 'inline-block' }">
<!-- 遍历tab列表保留tab-${index}的id用于scroll-into-view定位 -->
<!-- 核心与scroll-into-view绑定的id -->
<view v-for="(item, index) in list" :key="index" :id="`tab-${index}`" hover-class="none" active-class="none" :style="{ flex: noScroll ? '1' : '' }" :class="['tab-item', `tab-item-${index}`, { active: activeIndex === index }]" @click="handleTabClick(index)">
<span class="tab-text" :style="getTabTextStyle(index)">{{ item.name }}</span>
<view class="tab-active-line" v-show="activeIndex === index" :style="getActiveLineStyle()"></view>
</view>
</view>
</scroll-view>
</template>
<script>
export default {
name: "ScrollableTabs",
props: {
list: { type: Array, default: () => [] },
noScroll: { type: Boolean, default: true },
current: { type: Number, default: 0 },
activeColor: { type: String, default: getApp().globalData.themeColor },
fontSize: { type: [String, Number], default: 32 },
activeFontWeight: { type: [String, Number], default: 600 },
normalColor: { type: String, default: "#333333" },
bgColor: { type: String, default: "#ffffff" },
},
data() {
return {
2 months ago
// activeIndex: this.current, // 改用computed计算属性
// targetScrollId: `tab-${this.current}`, // 核心变量控制scroll-into-view的目标id(改用computed计算属性)
2 months ago
};
},
2 months ago
computed: {
activeIndex() {
return this.current;
},
targetScrollId() {
return `tab-${this.current}`;
},
},
2 months ago
methods: {
// 点击tab切换核心通过变量赋值控制滚动
handleTabClick(index) {
if (this.activeIndex === index) return;
this.activeIndex = index;
this.$emit("change", index);
// 计算要滚动到的目标id让相邻tab显示到可视区
this.calcTargetScrollId(index);
},
/**
* 计算scroll-into-view的目标id完全基于索引无节点查询
* @param {Number} currentIndex - 点击的tab索引
*/
calcTargetScrollId(currentIndex) {
const listLen = this.list.length;
if (listLen <= 1) return;
let targetIndex = currentIndex;
// 情况1点击第一个tab → 滚动到第一个,确保第二个显示
if (currentIndex === 0) {
targetIndex = 0;
}
// 情况2点击最后一个tab → 滚动到倒数第二个,确保倒数第二个显示
else if (currentIndex === listLen - 1) {
targetIndex = listLen - 2;
}
// 情况3点击中间tab → 滚动到当前tab的前一个确保上一个和下一个都显示
else {
// 优先滚动到当前tab的前一个让当前+下一个都在可视区
targetIndex = currentIndex - 1;
// 兜底:确保目标索引不越界
targetIndex = Math.max(0, Math.min(targetIndex, listLen - 1));
}
// 变量赋值控制scroll-into-view滚动到目标tab
this.targetScrollId = `tab-${targetIndex}`;
},
// 动态生成tab文字样式
getTabTextStyle(index) {
const baseStyle = {
fontSize: `${this.fontSize}rpx`,
fontWeight: 400,
transition: "all 0.2s ease",
};
return this.activeIndex === index ? { ...baseStyle, color: this.activeColor, fontWeight: this.activeFontWeight } : { ...baseStyle, color: this.normalColor };
},
// 动态生成active底部横条样式
getActiveLineStyle() {
return {
backgroundColor: this.activeColor,
transition: "all 0.2s ease",
};
},
},
};
</script>
<style lang="scss" scoped>
/* 滚动容器:保留原有样式,仅移除无用注释 */
.tabs-scroll-container {
width: 100%;
white-space: nowrap;
height: 88rpx;
box-sizing: border-box;
background-color: v-bind(bgColor);
}
.tabs-wrapper {
width: 100%;
padding: 0;
}
.tab-item {
display: inline-block;
position: relative;
padding: 0 20rpx;
height: 88rpx;
line-height: 88rpx;
text-align: center;
box-sizing: border-box;
cursor: default;
-webkit-tap-highlight-color: transparent;
tap-highlight-color: transparent;
user-select: none;
}
.tab-text {
display: inline-block;
}
.tab-active-line {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 6rpx;
border-radius: 3rpx;
}
::-webkit-scrollbar {
display: none;
}
</style>