import { nextTick, onBeforeUnmount } from 'vue'; import type ChatMessages from '/@/views/ventAI/manageAssistent/components/chat/ChatMessages.vue'; /** * 消息区滚动控制:流式增量用 rAF 合并到每帧一次,显式调用走 async 版本(等 nextTick 再滚)。 * 滚动目标实例由调用方以 getter 传入,composable 不直接依赖组件 ref。 */ export const useChatScroll = (getChatMessages: () => InstanceType | undefined) => { let scrollRafId: number | null = null; const scrollToBottomThrottled = (force = false) => { if (scrollRafId) cancelAnimationFrame(scrollRafId); scrollRafId = requestAnimationFrame(async () => { scrollRafId = null; await nextTick(); if (!force && !getChatMessages()?.isAtBottom) return; const el = getChatMessages()?.messagesRef; if (el) { el.scrollTop = el.scrollHeight - el.clientHeight; } }); }; // Async version for explicit await calls const scrollToBottom = async (force = false) => { await nextTick(); if (!force && !getChatMessages()?.isAtBottom) return; const el = getChatMessages()?.messagesRef; if (el) { el.scrollTop = el.scrollHeight - el.clientHeight; } }; // [审计-第四轮 R4-8] 卸载时取消挂起的 rAF。 // 原行为:scrollRafId 仅在重入时 cancel 自身,组件卸载后不 cancel,残留回调会在卸载后 // 再执行一次(内部全 optional chaining,无崩溃、无累积,但无意义)。 // 现行为:onBeforeUnmount 里若有挂起 rAF 则 cancel 并复位。 // 为什么:定时器/动画帧挂摘对称,消除卸载后的空转回调。 // 注:本 composable 仅在组件 setup 内调用(AiAssistantModal),onBeforeUnmount 注册到该组件上。 // 上游重构(master)在同一位置也加了等价实现(destroyOnClose 语义),此处按「冲突以审计分支为主」取审计版本。 onBeforeUnmount(() => { if (scrollRafId !== null) { cancelAnimationFrame(scrollRafId); scrollRafId = null; } }); return { scrollToBottom, scrollToBottomThrottled }; };