feat: 修复页面滚动问题
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user