| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 |
- 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<typeof ChatMessages> | 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 };
- };
|