diff --git a/frontend/manifest.json b/frontend/manifest.json index 52903b1..c62c3d1 100644 --- a/frontend/manifest.json +++ b/frontend/manifest.json @@ -2,8 +2,8 @@ "name" : "TopFans", "appid" : "__UNI__F199FF4", "description" : "", - "versionName" : "1.0.3", - "versionCode" : 100, + "versionName" : "1.0.4", + "versionCode" : 101, "transformPx" : false, /* 5+App特有相关 */ "app-plus" : { @@ -23,7 +23,8 @@ "modules" : { "VideoPlayer" : {}, "Camera" : {}, - "Speech" : {} + "Speech" : {}, + "Push" : {} }, /* 应用发布信息 */ "distribute" : { @@ -70,12 +71,7 @@ "secretkey" : "1i5Aj8FwL3i11LYPeXMRwRWycictWq2X" } }, - "push" : { - "unipush" : { - "version" : "2", - "offline" : false - } - } + "push" : {} }, "icons" : { "android" : { diff --git a/frontend/pages/square/components/WaterfallGrid.vue b/frontend/pages/square/components/WaterfallGrid.vue index c656249..bdb5737 100644 --- a/frontend/pages/square/components/WaterfallGrid.vue +++ b/frontend/pages/square/components/WaterfallGrid.vue @@ -78,8 +78,8 @@ const GAP = rpx2px(16) const BORDER_W = rpx2px(2) const SCALE = 0.9 const ROWS = 4 -const AUTO_SCROLL_SPEED = 0.1 -const AUTO_RESUME_DELAY = 1000 +const AUTO_SCROLL_SPEED = 0.3 +const AUTO_RESUME_DELAY = 1500 const PRELOAD_THRESHOLD = rpx2px(300) // ========== 状态 ========== @@ -112,62 +112,34 @@ let rafId = null let userInteracting = false let resumeTimer = null let appendTimer = null // 防抖定时器 +let autoScrollPos = 0 // 自动滚动目标位置 +let momentumTimer = null // iOS 惯性滚动检测定时器 +let scrollUpdateTimer = null // 定期同步 scrollLeft 的定时器 const startAutoScroll = () => { - if (!isComponentMounted) { - return - } - // 确保没有重复的 RAF 在运行 + if (!isComponentMounted) return if (rafId) { cafFn(rafId) rafId = null } - // 重置滚动状态 userInteracting = false + autoScrollPos = currentScrollLeft - let lastScrollLeft = 0 // 用于检测手动滚动 + // 使用定时器定期更新 scrollLeft + clearInterval(scrollUpdateTimer) + scrollUpdateTimer = setInterval(() => { + if (!isComponentMounted || userInteracting || isLoadingMore) return + autoScrollPos += AUTO_SCROLL_SPEED + scrollLeft.value = autoScrollPos - const step = () => { - if (!isComponentMounted) { - rafId = null - return - } - // 每次 RAF 循环开始时也检查一次 - if (!isComponentMounted) { - rafId = null - return - } - - if (!userInteracting && !isLoadingMore) { - // 检测是否被手动滚动过(如果实际滚动位置和我们的不一致,说明用户手动滚了) - const actualScroll = scrollLeft.value - if (Math.abs(actualScroll - lastScrollLeft) > 5) { - // 用户手动滚了,同步位置 - currentScrollLeft = actualScroll - } else { - // 正常累加 - currentScrollLeft += AUTO_SCROLL_SPEED - scrollLeft.value = currentScrollLeft - lastScrollLeft = currentScrollLeft + // 预加载 + const remainingScroll = totalWidth.value - autoScrollPos - props.screenWidth + if (remainingScroll < Math.max(totalWidth.value / 2, props.screenWidth)) { + if (!isLoadingMore && !appendFailed && !isInitialLoading && props.useMockData) { + appendMore() } - - // 预加载:剩余可滚动距离小于一半屏幕宽度时,触发追加 - const remainingScroll = totalWidth.value - currentScrollLeft - props.screenWidth - if (remainingScroll < Math.max(totalWidth.value / 2, props.screenWidth)) { - // 直接调用 appendMore(不需要防抖) - // 注意:真实接口模式下不应该自动追加数据,由 has_more 控制 - if (!isLoadingMore && !appendFailed && !isInitialLoading && props.useMockData) { - appendMore() - } - } - } else { - // 用户交互中,记录当前滚动位置 - lastScrollLeft = scrollLeft.value - currentScrollLeft = lastScrollLeft } - rafId = rafFn(step) - } - rafId = rafFn(step) + }, 16) } const stopAutoScroll = () => { @@ -175,21 +147,41 @@ const stopAutoScroll = () => { cafFn(rafId) rafId = null } + clearInterval(scrollUpdateTimer) + scrollUpdateTimer = null clearTimeout(appendTimer) appendTimer = null clearTimeout(resumeTimer) resumeTimer = null + clearTimeout(momentumTimer) + momentumTimer = null userInteracting = false isLoadingMore = false } -const pauseForUser = () => { - userInteracting = true - clearTimeout(resumeTimer) - resumeTimer = setTimeout(() => { userInteracting = false }, AUTO_RESUME_DELAY) +// 检测 iOS 惯性滚动是否结束 +let lastTouchEndPos = 0 // touchend 时的位置快照 + +const detectMomentumEnd = () => { + clearTimeout(momentumTimer) + lastTouchEndPos = currentScrollLeft + const tick = () => { + momentumTimer = setTimeout(() => { + // 比较的是 lastTouchEndPos(touchend 时的位置),不受后续惯性影响 + if (Math.abs(currentScrollLeft - lastTouchEndPos) < 3) { + autoScrollPos = currentScrollLeft + startAutoScroll() + } else { + // 惯性中,更新快照 + lastTouchEndPos = currentScrollLeft + tick() + } + }, 60) + } + tick() } -// 防抖:延迟 500ms 后再执行追加,避免频繁调用 +// 防抖:延迟追加 const scheduleAppend = () => { if (!isComponentMounted || appendFailed) return if (appendTimer) clearTimeout(appendTimer) @@ -202,16 +194,23 @@ const scheduleAppend = () => { } // ========== 触摸 / 滚动事件 ========== -const onTouchStart = () => pauseForUser() -const onTouchEnd = () => {} +const onTouchStart = () => { + userInteracting = true + clearTimeout(momentumTimer) + clearInterval(scrollUpdateTimer) +} + +const onTouchEnd = () => { + // 等待 iOS 惯性滚动自然结束,再决定是否恢复自动滚动 + detectMomentumEnd() +} const onScroll = (e) => { if (!isComponentMounted) return currentScrollLeft = e.detail.scrollLeft - // 预加载:剩余可滚动距离小于一半屏幕宽度时,触发追加 + const remainingScroll = totalWidth.value - currentScrollLeft - props.screenWidth if (remainingScroll < Math.max(totalWidth.value / 2, props.screenWidth)) { - // 注意:真实接口模式下不应该自动追加数据,由 has_more 控制 if (!isLoadingMore && !appendFailed && !isInitialLoading && props.useMockData) { scheduleAppend() } @@ -681,12 +680,16 @@ onMounted(() => { const handleVisibilityChange = () => { if (!isComponentMounted) return if (document.hidden) { - userInteracting = true + stopAutoScroll() } else { - userInteracting = false - if (!rafId) { - startAutoScroll() - } + // 页面重新可见时,延迟启动自动滚动,等待视图稳定 + stopAutoScroll() + setTimeout(() => { + if (isComponentMounted) { + autoScrollPos = currentScrollLeft + startAutoScroll() + } + }, 150) } } @@ -698,12 +701,15 @@ if (typeof document !== 'undefined') { watch(() => props.isActive, (active) => { if (!isComponentMounted) return if (active) { - userInteracting = false - if (!rafId) { - startAutoScroll() - } + stopAutoScroll() + setTimeout(() => { + if (isComponentMounted) { + autoScrollPos = currentScrollLeft + startAutoScroll() + } + }, 150) } else { - userInteracting = true + stopAutoScroll() } }, { immediate: false }) @@ -712,6 +718,7 @@ onUnmounted(() => { stopAutoScroll() clearTimeout(resumeTimer) clearTimeout(appendTimer) + clearTimeout(momentumTimer) if (typeof document !== 'undefined') { document.removeEventListener('visibilitychange', handleVisibilityChange) }