feat: 修复页面滚动问题

This commit is contained in:
2026-08-12 10:32:33 +08:00
parent ef4525fbcd
commit 1984f3ed97
2 changed files with 65 additions and 2 deletions

View File

@@ -90,6 +90,7 @@
:scroll-into-view="scrollIntoViewId" :scroll-into-view="scrollIntoViewId"
scroll-with-animation scroll-with-animation
@scroll="handleProductScroll" @scroll="handleProductScroll"
@touchstart="enableProductScrollSync"
> >
<view class="px-[12px] pb-[24px]"> <view class="px-[12px] pb-[24px]">
<view v-if="!groups.length" class="pt-[80px] text-center"> <view v-if="!groups.length" class="pt-[80px] text-center">
@@ -142,6 +143,23 @@
import { computed, getCurrentInstance, nextTick, onMounted, ref, watch } from 'vue' import { computed, getCurrentInstance, nextTick, onMounted, ref, watch } from 'vue'
import TopNavBar from "@/components/TopNavBar/index.vue" import TopNavBar from "@/components/TopNavBar/index.vue"
const createCategoryScrollSelection = () => {
let shouldSyncFromScroll = true
return {
select(groupId) {
shouldSyncFromScroll = false
return groupId
},
resumeScrollSync() {
shouldSyncFromScroll = true
},
resolve(currentGroupId, scrollGroupId) {
return shouldSyncFromScroll ? scrollGroupId : currentGroupId
},
}
}
const props = defineProps({ const props = defineProps({
homeData: { type: Object, required: true }, homeData: { type: Object, required: true },
}) })
@@ -164,6 +182,7 @@ const scrollIntoViewId = ref('')
const leftScrollIntoViewId = computed(() => `ticket-nav-${activeGroupId.value}`) const leftScrollIntoViewId = computed(() => `ticket-nav-${activeGroupId.value}`)
const groupOffsets = ref([]) const groupOffsets = ref([])
const rightScrollTop = ref(0) const rightScrollTop = ref(0)
const categoryScrollSelection = createCategoryScrollSelection()
const instance = getCurrentInstance() const instance = getCurrentInstance()
const scenicMetaText = computed(() => const scenicMetaText = computed(() =>
[props.homeData.levelText, props.homeData.subtitle].filter(Boolean).join('') [props.homeData.levelText, props.homeData.subtitle].filter(Boolean).join('')
@@ -178,8 +197,13 @@ const isActiveGroup = (groupId) =>
const productsForGroup = (group) => const productsForGroup = (group) =>
Array.isArray(group.products) ? group.products : [] Array.isArray(group.products) ? group.products : []
const enableProductScrollSync = () => {
categoryScrollSelection.resumeScrollSync()
scrollIntoViewId.value = ''
}
const selectGroup = (groupId) => { const selectGroup = (groupId) => {
activeGroupId.value = groupId activeGroupId.value = categoryScrollSelection.select(groupId)
scrollIntoViewId.value = '' scrollIntoViewId.value = ''
nextTick(() => { nextTick(() => {
scrollIntoViewId.value = `ticket-group-${groupId}` scrollIntoViewId.value = `ticket-group-${groupId}`
@@ -225,7 +249,12 @@ const handleProductScroll = (event) => {
(current, group) => (group.top <= activationLine ? group : current), (current, group) => (group.top <= activationLine ? group : current),
groupOffsets.value[0] groupOffsets.value[0]
) )
if (activeGroup?.id) activeGroupId.value = activeGroup.id if (activeGroup?.id) {
activeGroupId.value = categoryScrollSelection.resolve(
activeGroupId.value,
activeGroup.id
)
}
} }
onMounted(measureGroupOffsets) onMounted(measureGroupOffsets)

View File

@@ -63,6 +63,40 @@ test("storefront synchronizes the left category from right-column scrolling", as
assert.match(source, /TopNavBar/); assert.match(source, /TopNavBar/);
}); });
test("left category selection wins over programmatic product scrolling", async () => {
const source = await readTicketFile("components/TicketHome.vue");
const factorySource = source.match(
/const createCategoryScrollSelection = \(\) => \{[\s\S]*?^\}\n/m
)?.[0];
assert.match(source, /@touchstart="enableProductScrollSync"/);
assert.doesNotMatch(source, /category-scroll-selection\.mjs/);
assert.ok(factorySource, "inline category scroll selection factory missing");
assert.match(source, /const categoryScrollSelection =\s*createCategoryScrollSelection\(\)/);
assert.match(
source,
/const selectGroup = \(groupId\) => \{\s*activeGroupId\.value = categoryScrollSelection\.select\(groupId\)/
);
assert.match(
source,
/const enableProductScrollSync = \(\) => \{\s*categoryScrollSelection\.resumeScrollSync\(\)\s*scrollIntoViewId\.value = ''\s*\}/
);
assert.match(
source,
/const handleProductScroll = \(event\) => \{[\s\S]*rightScrollTop\.value = Number[\s\S]*measureGroupOffsets\(\)[\s\S]*activeGroupId\.value = categoryScrollSelection\.resolve\(\s*activeGroupId\.value,\s*activeGroup\.id\s*\)/
);
const createCategoryScrollSelection = Function(
`${factorySource}\nreturn createCategoryScrollSelection`
)();
const selection = createCategoryScrollSelection();
const clickedGroupId = selection.select("second");
assert.equal(selection.resolve(clickedGroupId, "first"), "second");
selection.resumeScrollSync();
assert.equal(selection.resolve(clickedGroupId, "third"), "third");
});
test("ticket storefront loads API home data without a static adapter", async () => { test("ticket storefront loads API home data without a static adapter", async () => {
const source = await readTicketFile("index.vue"); const source = await readTicketFile("index.vue");