From 8d87068ba76849bc569389e3c753a0774261b295 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Tue, 9 Jun 2026 10:47:22 +0800 Subject: [PATCH] =?UTF-8?q?fix(chat):=20=E4=BF=AE=E5=A4=8D=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=A4=A7=E5=B9=85=E5=A2=9E=E9=95=BF=E6=97=B6=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8=E9=94=81=E5=AE=9A=E4=B8=8D=E5=8F=8A=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整 vue-stick-to-bottom resize spring 参数,加快动画响应 (damping 0.68→0.5, stiffness 0.11→0.28, mass 1.05→0.75) - 在 scrollListener 中添加高度突变检测,当 scrollHeight 大幅增长 (超过 200px 或 viewport 35%)且用户未脱离锁定时,用 instant 瞬间追底,绕过 spring 动画延迟和去重机制 修复场景:思考块展开、Markdown 表格渲染等造成大幅度垂直距离增加时 滚动锁定失效或锁住不及时的问题 --- static/src/components/chat/ChatArea.vue | 33 ++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/static/src/components/chat/ChatArea.vue b/static/src/components/chat/ChatArea.vue index 4c5fd13..39a0994 100644 --- a/static/src/components/chat/ChatArea.vue +++ b/static/src/components/chat/ChatArea.vue @@ -790,9 +790,9 @@ const { stopScroll } = useStickToBottom({ resize: { - damping: 0.68, - stiffness: 0.11, - mass: 1.05 + damping: 0.5, + stiffness: 0.28, + mass: 0.75 }, initial: 'instant' }); @@ -809,6 +809,7 @@ let lastUserWheelUpTs = 0; let lastProgrammaticHintTs = 0; let lastProgrammaticHintSource = ''; let suppressUserIntentUntil = 0; +let isHandlingLargeGrowth = false; let scrollListener: ((event: Event) => void) | null = null; let wheelListener: ((event: WheelEvent) => void) | null = null; let traceAttachLogged = false; @@ -924,6 +925,32 @@ function attachBounceListener() { const heightDelta = height - lastObservedHeight; lastObservedTop = top; lastObservedHeight = height; + + // 大幅高度增长检测:当 scrollHeight 突增(内容大幅展开/渲染), + // 且用户没有主动脱离锁定(escapedFromLock=false)时, + // 用 instant 瞬间追底,绕过 spring 动画的延迟。 + // 这解决了思考块展开、Markdown 表格渲染等场景下滚动跟不上内容增长的问题。 + const largeGrowthThreshold = Math.max(200, target.clientHeight * 0.35 || 200); + if ( + !isHandlingLargeGrowth && + heightDelta > largeGrowthThreshold && + !escapedFromLock.value + ) { + isHandlingLargeGrowth = true; + markProgrammaticHint('ChatArea.largeGrowth'); + suppressUserIntentUntil = Date.now() + 900; + scrollToBottom({ animation: 'instant', preserveScrollPosition: false }); + bounceTraceLog( + 'large-growth:instant-scroll', + { heightDelta, threshold: largeGrowthThreshold, ...getScrollMetrics(target) }, + 'large-growth:instant-scroll', + 120 + ); + setTimeout(() => { + isHandlingLargeGrowth = false; + }, 400); + } + const now = Date.now(); const trusted = (event as any).isTrusted === true; const closeToProgrammaticHint = now - lastProgrammaticHintTs <= 140;