|
|
@@ -264,11 +264,12 @@ let scrollFrameId: number | null = null
|
|
|
let scrollResizeObserver: ResizeObserver | null = null
|
|
|
let scrollMutationObserver: MutationObserver | null = null
|
|
|
let scrollStabilityTimerId: number | null = null
|
|
|
+let iframeLoadController: AbortController | null = null
|
|
|
let boundScrollContainer: HTMLElement | null = null
|
|
|
let isProgrammaticScroll = false
|
|
|
const scrollTimeoutIds = new Set<number>()
|
|
|
const AUTO_SCROLL_BOTTOM_THRESHOLD = 120
|
|
|
-const SCROLL_STABILITY_MS = 3000
|
|
|
+const SCROLL_STABILITY_MS = 10000
|
|
|
|
|
|
const bubbleListItems = computed(() =>
|
|
|
props.messages.map((item) => ({
|
|
|
@@ -292,6 +293,9 @@ const clearScheduledScroll = () => {
|
|
|
}
|
|
|
scrollTimeoutIds.forEach((id) => window.clearTimeout(id))
|
|
|
scrollTimeoutIds.clear()
|
|
|
+
|
|
|
+ iframeLoadController?.abort()
|
|
|
+ iframeLoadController = null
|
|
|
}
|
|
|
|
|
|
const unbindScrollContainer = () => {
|
|
|
@@ -369,8 +373,8 @@ const keepBottomDuringLayout = () => {
|
|
|
}
|
|
|
})
|
|
|
scrollResizeObserver.observe(scrollContainer)
|
|
|
- Array.from(scrollContainer.children).forEach((child) => {
|
|
|
- scrollResizeObserver?.observe(child)
|
|
|
+ scrollContainer.querySelectorAll('.el-bubble').forEach((el) => {
|
|
|
+ scrollResizeObserver?.observe(el)
|
|
|
})
|
|
|
|
|
|
// MutationObserver: 观察整个子树的 DOM 变化(Markdown 渲染、图片加载、虚拟滚动新增项等)
|
|
|
@@ -381,17 +385,35 @@ const keepBottomDuringLayout = () => {
|
|
|
resetStabilityTimer()
|
|
|
}
|
|
|
// 动态观察新增的子元素(虚拟滚动可能动态添加)
|
|
|
- Array.from(scrollContainer.children).forEach((child) => {
|
|
|
- scrollResizeObserver?.observe(child)
|
|
|
+ scrollContainer.querySelectorAll('.el-bubble').forEach((el) => {
|
|
|
+ scrollResizeObserver?.observe(el)
|
|
|
})
|
|
|
})
|
|
|
scrollMutationObserver.observe(scrollContainer, {
|
|
|
+ attributes: true,
|
|
|
+ attributeFilter: ['style'],
|
|
|
characterData: true,
|
|
|
childList: true,
|
|
|
subtree: true
|
|
|
})
|
|
|
|
|
|
// 内容稳定后自动断开(无 DOM 变化 3s 后停止观察,避免长期性能开销)
|
|
|
+
|
|
|
+ // iframe 加载完成后重置稳定性计时器,让观察器继续等待高度测量完成
|
|
|
+ iframeLoadController?.abort()
|
|
|
+ iframeLoadController = new AbortController()
|
|
|
+ scrollContainer.addEventListener(
|
|
|
+ 'load',
|
|
|
+ (e) => {
|
|
|
+ if ((e.target as HTMLElement)?.tagName === 'IFRAME' && autoScrollEnabled.value) {
|
|
|
+ // 不立刻滚动(iframe 高度还在测量中),但重置计时器保持观察器活跃
|
|
|
+ resetStabilityTimer()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { capture: true, signal: iframeLoadController.signal }
|
|
|
+ )
|
|
|
+
|
|
|
+ // 启动稳定性计时器,内容无变化后自动断开观察器释放性能
|
|
|
resetStabilityTimer()
|
|
|
}
|
|
|
|
|
|
@@ -423,7 +445,7 @@ const scrollToBottom = async () => {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- for (const delay of [60, 180, 360]) {
|
|
|
+ for (const delay of [60, 180, 360, 1000, 3000, 6000]) {
|
|
|
const timeoutId = window.setTimeout(() => {
|
|
|
scrollTimeoutIds.delete(timeoutId)
|
|
|
if (autoScrollEnabled.value) {
|
|
|
@@ -600,6 +622,7 @@ const handleAddToKb = (message: BubbleMessage) => {
|
|
|
.item-list :deep(.el-bubble-list) {
|
|
|
height: 100%;
|
|
|
padding-right: 4px;
|
|
|
+ scroll-behavior: auto !important;
|
|
|
}
|
|
|
|
|
|
.item-list :deep(.el-bubble + .el-bubble) {
|
|
|
@@ -664,7 +687,7 @@ const handleAddToKb = (message: BubbleMessage) => {
|
|
|
}
|
|
|
|
|
|
.msg-content-text :deep(p) {
|
|
|
- margin: 0 0 10px;
|
|
|
+ margin: 10px 0;
|
|
|
white-space: pre-wrap;
|
|
|
}
|
|
|
|