<template>
<view class="product-detail">
<view class="product-header">...</view>
<view v-if="showStickyTab" class="sticky-tabs" :style="{ top: stickyTop + 'px' }">
<view class="tab-list">
<view
v-for="(tab, index) in tabList"
:key="index"
class="tab-item"
:class="{ 'active': activeTabIndex === index }"
@click="switchTab(index)"
>
{{ tab.name }}
</view>
</view>
</view>
<scroll-view
scroll-y
:scroll-top="scrollTop"
@scroll="onScroll"
class="content-scroll-view"
>
<view id="anchor-product" class="content-section">商品详情内容...</view>
<view id="anchor-comment" class="content-section">评价内容...</view>
<view id="anchor-detail" class="content-section">图文详情内容...</view>
<view id="anchor-recommend" class="content-section">推荐内容...</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue';
const tabList = [
{ name: '商品', anchor: 'anchor-product' },
{ name: '评价', anchor: 'anchor-comment' },
{ name: '详情', anchor: 'anchor-detail' },
{ name: '推荐', anchor: 'anchor-recommend' }
];
const activeTabIndex = ref(0);
const showStickyTab = ref(false);
const scrollTop = ref(0);
const sectionTops = ref([]);
const isScrollingByClick = ref(false);
onMounted(() => {
nextTick(() => {
setTimeout(() => {
calculateAnchorPositions();
}, 100);
});
});
const calculateAnchorPositions = () => {
const query = uni.createSelectorQuery().in(this);
sectionTops.value = [];
tabList.forEach((tab) => {
query.select(`#${tab.anchor}`).boundingClientRect((rect) => {
if (rect) {
sectionTops.value.push(Math.floor(rect.top));
}
}).exec();
});
};
const onScroll = (event) => {
if (isScrollingByClick.value) return;
const scrollTop = event.detail.scrollTop;
showStickyTab.value = scrollTop > 200;
for (let i = sectionTops.value.length - 1; i >= 0; i--) {
if (scrollTop >= sectionTops.value[i] - 80) {
activeTabIndex.value = i;
break;
}
}
};
const switchTab = (index) => {
activeTabIndex.value = index;
isScrollingByClick.value = true;
uni.pageScrollTo({
scrollTop: sectionTops.value[index],
duration: 300,
success: () => {
setTimeout(() => {
isScrollingByClick.value = false;
}, 400);
},
fail: () => {
isScrollingByClick.value = false;
}
});
};
<style scoped>
.sticky-tabs {
position: sticky;
z-index: 999;
background-color: #ffffff;
border-bottom: 1rpx solid #eeeeee;
}
.tab-list {
display: flex;
height: 80rpx;
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #666;
transition: all 0.3s;
}
.tab-item.active {
color: #e4393c;
font-weight: bold;
}
.content-section {
min-height: 100vh;
padding: 30rpx;
box-sizing: border-box;
}
</style>
</script>