重构: MessageList 拆分(提取 MessageItem+useMessageScroll, 1552->1386行)
- 提取 useMessageScroll.ts(滚动跟随/锁存/回底按钮) - 提取 MessageItem.vue(按 role 渲染单条消息) - 清理 MessageList.vue 中已移至子组件/ composable 的死代码
This commit is contained in:
89
src/composables/ai/useMessageScroll.ts
Normal file
89
src/composables/ai/useMessageScroll.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
//! 消息列表滚动控制 — 跟随/锁存/回底按钮
|
||||
//!
|
||||
//! 职责:悬浮底部按钮显隐、跟随意图锁存(isFollowingBottom)、
|
||||
//! scrollToBottom 回底操作、onContentChange 内容变化滚动策略。
|
||||
//!
|
||||
//! 不涉及工具卡片折叠逻辑(父组件注入 onNearBottom 回调解耦)。
|
||||
|
||||
import { ref, nextTick, type Ref } from 'vue'
|
||||
|
||||
/** 底部阈值:距底部 <80px 视为"在底部" */
|
||||
const NEAR_BOTTOM_PX = 80
|
||||
/** 内容溢出阈值:scrollHeight - clientHeight >100px 认为内容溢出容器 */
|
||||
const OVERFLOW_PX = 100
|
||||
|
||||
export function useMessageScroll(containerRef: Ref<HTMLDivElement | undefined>) {
|
||||
// 回到底部按钮:内容溢出且不在底部时显示
|
||||
const showBackToBottom = ref(false)
|
||||
|
||||
// B-260618-24: 跟随意图锁存。点回到底部/滚到底/发新消息→true;用户上滑→false。
|
||||
const isFollowingBottom = ref(true)
|
||||
// 边沿检测:上一帧是否在底部(用于触发 onNearBottom 回调)
|
||||
let wasNearBottom = true
|
||||
|
||||
/** 用户是否在底部附近(上滑查看历史时不强制拉回) */
|
||||
function isNearBottom(): boolean {
|
||||
const el = containerRef.value
|
||||
if (!el) return true
|
||||
return el.scrollHeight - el.scrollTop - el.clientHeight < NEAR_BOTTOM_PX
|
||||
}
|
||||
|
||||
/** 刷新回到底部按钮状态 */
|
||||
function refreshBackToBottom(): void {
|
||||
const el = containerRef.value
|
||||
if (!el) { showBackToBottom.value = false; return }
|
||||
const overflow = el.scrollHeight - el.clientHeight > OVERFLOW_PX
|
||||
showBackToBottom.value = overflow && !isNearBottom()
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动事件处理:刷新回底按钮 + 底部边沿检测触发回调。
|
||||
* @param onNearBottom 滚近底部时的回调(父组件传入,如收起工具卡片)
|
||||
*/
|
||||
function onMessagesScroll(onNearBottom?: () => void): void {
|
||||
refreshBackToBottom()
|
||||
const near = isNearBottom()
|
||||
if (near && !wasNearBottom) {
|
||||
onNearBottom?.()
|
||||
}
|
||||
wasNearBottom = near
|
||||
isFollowingBottom.value = near
|
||||
}
|
||||
|
||||
/** 滚动到容器底部 */
|
||||
function scrollToBottom(smooth = false): void {
|
||||
const el = containerRef.value
|
||||
if (!el) return
|
||||
isFollowingBottom.value = true // 显式回底 = 跟随意图
|
||||
el.scrollTo({
|
||||
top: el.scrollHeight,
|
||||
behavior: smooth ? 'smooth' : 'instant',
|
||||
})
|
||||
showBackToBottom.value = false
|
||||
}
|
||||
|
||||
/** 消息/流式内容变化时:跟随意图则自动滚,否则只刷新回底按钮 */
|
||||
function onContentChange(): void {
|
||||
if (isFollowingBottom.value) {
|
||||
nextTick(scrollToBottom)
|
||||
} else {
|
||||
refreshBackToBottom()
|
||||
}
|
||||
}
|
||||
|
||||
/** 显式标记跟随意图(发新消息/切换对话时调用) */
|
||||
function setFollowing(v: boolean): void {
|
||||
isFollowingBottom.value = v
|
||||
}
|
||||
|
||||
return {
|
||||
showBackToBottom,
|
||||
isFollowingBottom,
|
||||
isNearBottom,
|
||||
refreshBackToBottom,
|
||||
onMessagesScroll,
|
||||
scrollToBottom,
|
||||
onContentChange,
|
||||
setFollowing,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user