diff --git a/src/pages/index/components/module/AnswerComponent/index.vue b/src/pages/index/components/module/AnswerComponent/index.vue index d3fbac8..8518096 100644 --- a/src/pages/index/components/module/AnswerComponent/index.vue +++ b/src/pages/index/components/module/AnswerComponent/index.vue @@ -124,7 +124,8 @@ const lookDetailAction = () => { } catch (e) {} }); - uni.navigateTo({ url: `/pages/long-answer/index?streamId=${encodeURIComponent(streamId)}` }); + // 传递 finished 参数,完成状态下不自动滚到底部 + uni.navigateTo({ url: `/pages/long-answer/index?streamId=${encodeURIComponent(streamId)}&finished=${props.finish ? '1' : '0'}` }); } diff --git a/src/pages/long-answer/index.vue b/src/pages/long-answer/index.vue index 2347da2..38c2f45 100644 --- a/src/pages/long-answer/index.vue +++ b/src/pages/long-answer/index.vue @@ -49,14 +49,23 @@ const SCROLL_THRESHOLD = 150; // px const userInteracting = ref(false); let interactionTimer = null; +/** 是否已完成(从 URL 参数判断),完成状态下不初始初自动滚到底部 */ +let isFinishedOnInit = false; + /** ✅ 防抖 */ let scrollTimer = null; const measureScrollViewHeight = () => { try { - const sys = uni.getSystemInfoSync() || {}; - // 使用窗口高度作为 scroll-view 可视高度的近似,兼容小程序环境 - scrollViewHeight.value = sys.windowHeight || 0; + // 使用 uni.createSelectorQuery 获取 scroll-view 的准确高度 + uni.createSelectorQuery() + .select(".chat-scroll") + .boundingClientRect((rect) => { + if (rect && rect.height) { + scrollViewHeight.value = rect.height; + } + }) + .exec(); } catch (e) { } } @@ -69,31 +78,36 @@ const computeTitle = (text = "") => { const onScroll = (e) => { try { const { scrollTop = 0, scrollHeight = 0 } = e.detail || {}; - // 标记为用户主动滚动,短时内不触发自动滚动 - userInteracting.value = true; - clearTimeout(interactionTimer); - interactionTimer = setTimeout(() => (userInteracting.value = false), 1200); - - // 更新距离底部的判断(使用 windowHeight 作为近似) - const distanceToBottom = scrollHeight - (scrollTop + (scrollViewHeight.value || uni.getSystemInfoSync().windowHeight || 0)); - isNearBottom.value = distanceToBottom <= SCROLL_THRESHOLD; + + // 计算距离底部的距离(使用准确的 scroll-view 高度) + const viewHeight = scrollViewHeight.value; + const distanceToBottom = scrollHeight - scrollTop - viewHeight; + + // 判断是否在底部附近(允许 SCROLL_THRESHOLD 的误差范围) + // 注意:只更新 isNearBottom,不在滚动时强制改变 userInteracting + const atBottom = distanceToBottom <= SCROLL_THRESHOLD; + isNearBottom.value = atBottom; } catch (e) { } } const onTouchStart = () => { + // 触摸开始时,立即标记为用户交互状态 userInteracting.value = true; clearTimeout(interactionTimer); } const onTouchEnd = () => { + // 触摸结束后延迟一段时间再取消交互状态 + // 这样即使用户快速滚动,也不会被中途打断 clearTimeout(interactionTimer); interactionTimer = setTimeout(() => { userInteracting.value = false; - }, 800); + }, 600); } const scrollToBottom = () => { if (scrollTimer) return; + if (isFinishedOnInit) return; scrollTimer = setTimeout(() => { // ❗关键:强制触发滚动(小程序必须这样) @@ -118,7 +132,17 @@ const scrollToBottom = () => { }, 100); } -onLoad(({ message = "", streamId = "" }) => { +onLoad(({ message = "", streamId = "", finished = "0" }) => { + // 记录初始完成状态 + isFinishedOnInit = finished === "1"; + + console.log("LongAnswer onLoad with params:", { message, streamId, finished }); + + // 初次测量 scroll-view 高度 + nextTick(() => { + measureScrollViewHeight(); + }); + if (streamId) { // ✅ 流式数据 unsubscribe = StreamManager.subscribe( @@ -128,10 +152,15 @@ onLoad(({ message = "", streamId = "" }) => { title.value = computeTitle(answerText.value); nextTick(() => { - // 仅在用户处于接近底部时自动滚动,避免每次流式更新都打断用户阅读 + // 每次接收数据都重新测量高度(content size 可能变化,比如加载图) + measureScrollViewHeight(); + + // 流式完成时强制滚动到底部 if (finished) { scrollToBottom(); - } else if (isNearBottom.value) { + } + // 流式中的数据更新:只有在用户未交互且接近底部时才自动滚动 + else if (!userInteracting.value && isNearBottom.value) { scrollToBottom(); } }); @@ -143,13 +172,10 @@ onLoad(({ message = "", streamId = "" }) => { title.value = computeTitle(answerText.value); nextTick(() => { - // 初始非流式情况仍保持自动滚动 + // 只有在初始化为非完成状态时才自动滚到底部 scrollToBottom(); }); } - - // 初次测量 scroll-view 高度 - nextTick(() => measureScrollViewHeight()); }); onUnload(() => {