1.优化AI聊天页面效果
This commit is contained in:
@@ -168,6 +168,13 @@ export default {
|
||||
// 组件销毁标志
|
||||
isDestroyed: false,
|
||||
|
||||
// 性能优化相关
|
||||
scrollThrottleTimer: null, // 滚动节流定时器
|
||||
contentUpdateTimer: null, // 内容更新节流定时器
|
||||
isUserAtBottom: true, // 用户是否在底部
|
||||
lastContentLength: 0, // 上次内容长度
|
||||
scrollPending: false, // 滚动待处理标志
|
||||
|
||||
// 性能优化和错误边界
|
||||
requestQueue: [],
|
||||
maxRetries: 2,
|
||||
@@ -283,6 +290,14 @@ export default {
|
||||
clearTimeout(this.loadDebounceTimer)
|
||||
this.loadDebounceTimer = null
|
||||
}
|
||||
if (this.scrollThrottleTimer) {
|
||||
clearTimeout(this.scrollThrottleTimer)
|
||||
this.scrollThrottleTimer = null
|
||||
}
|
||||
if (this.contentUpdateTimer) {
|
||||
clearTimeout(this.contentUpdateTimer)
|
||||
this.contentUpdateTimer = null
|
||||
}
|
||||
|
||||
// 取消正在进行的请求
|
||||
if (this.currentCancel) {
|
||||
@@ -809,8 +824,8 @@ export default {
|
||||
if (data.event === 'message' && data.answer) {
|
||||
const currentContent = aiMsg.content
|
||||
const newContent = (currentContent === '正在思考...' ? '' : currentContent) + data.answer
|
||||
aiMsg.content = newContent
|
||||
this.scrollToBottom(true) // 流式响应时使用平滑滚动
|
||||
// 使用节流更新方法,减少频繁的DOM更新和滚动
|
||||
this.throttledContentUpdate(aiMsg, newContent)
|
||||
}
|
||||
|
||||
// 处理结束消息
|
||||
@@ -825,12 +840,19 @@ export default {
|
||||
userMsg.messageId = data.message_id
|
||||
aiMsg.messageId = 'ai-' + data.message_id
|
||||
}
|
||||
if (data.retriever_resources) {
|
||||
aiMsg.retrieverResources = data.retriever_resources
|
||||
aiMsg.groupedReferences = this.getGroupedReferences(data.retriever_resources) // 预计算分组引用
|
||||
if (data.metadata && data.metadata.retriever_resources) {
|
||||
// 使用Vue的响应式方法确保引用信息正确更新
|
||||
this.$set(aiMsg, 'retrieverResources', data.metadata.retriever_resources)
|
||||
this.$set(aiMsg, 'groupedReferences', this.getGroupedReferences(data.metadata.retriever_resources))
|
||||
}
|
||||
// 标记流输出完成
|
||||
// 标记流输出完成并触发最终滚动
|
||||
aiMsg.streamCompleted = true
|
||||
// 重置内容长度计数器
|
||||
this.lastContentLength = 0
|
||||
// 流式响应结束后强制滚动到底部
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom(true, true) // 使用平滑滚动并强制执行
|
||||
})
|
||||
}
|
||||
|
||||
// 处理错误事件
|
||||
@@ -933,45 +955,48 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* 滚动到底部
|
||||
* 滚动到底部(优化版本)
|
||||
*/
|
||||
scrollToBottom(smooth = false) {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.messageList) {
|
||||
const targetTop = this.$refs.messageList.scrollHeight
|
||||
const messageList = this.$refs.messageList
|
||||
scrollToBottom(smooth = false, force = false) {
|
||||
// 如果用户不在底部且不是强制滚动,则跳过
|
||||
if (!force && !this.isUserAtBottom) {
|
||||
return
|
||||
}
|
||||
|
||||
if (smooth && messageList.scrollTo && !this.isLoadingHistory) {
|
||||
// 只在非加载状态时使用平滑滚动
|
||||
messageList.scrollTo({
|
||||
top: messageList.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
} else {
|
||||
// 强制立即滚动到底部
|
||||
const forceScrollToBottom = () => {
|
||||
messageList.scrollTop = messageList.scrollHeight
|
||||
// 节流处理,避免频繁滚动
|
||||
if (this.scrollThrottleTimer) {
|
||||
clearTimeout(this.scrollThrottleTimer)
|
||||
}
|
||||
|
||||
this.scrollThrottleTimer = setTimeout(() => {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.messageList && !this.isDestroyed) {
|
||||
const messageList = this.$refs.messageList
|
||||
const scrollHeight = messageList.scrollHeight
|
||||
|
||||
if (smooth && messageList.scrollTo && !this.isLoadingHistory) {
|
||||
// 只在非加载状态时使用平滑滚动
|
||||
messageList.scrollTo({
|
||||
top: scrollHeight,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
} else {
|
||||
// 立即滚动到底部
|
||||
messageList.scrollTop = scrollHeight
|
||||
}
|
||||
|
||||
// 立即执行一次
|
||||
forceScrollToBottom()
|
||||
// 更新用户位置状态
|
||||
this.isUserAtBottom = true
|
||||
|
||||
// 再次确保滚动到底部
|
||||
this.$nextTick(() => {
|
||||
forceScrollToBottom()
|
||||
// 第三次确保
|
||||
setTimeout(() => {
|
||||
forceScrollToBottom()
|
||||
}, 10)
|
||||
})
|
||||
// 延迟更新lastScrollTop
|
||||
setTimeout(() => {
|
||||
if (!this.isDestroyed) {
|
||||
this.lastScrollTop = scrollHeight
|
||||
}
|
||||
}, smooth ? 300 : 50)
|
||||
}
|
||||
|
||||
// 延迟更新lastScrollTop,避免干扰滚动检测
|
||||
setTimeout(() => {
|
||||
this.lastScrollTop = this.$refs.messageList.scrollHeight
|
||||
}, smooth ? 300 : 50) // 非平滑滚动时更快更新
|
||||
}
|
||||
})
|
||||
})
|
||||
}, smooth ? 0 : 16) // 非平滑滚动时使用16ms节流(约60fps)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -979,6 +1004,11 @@ export default {
|
||||
*/
|
||||
onScroll(e) {
|
||||
const scrollTop = e.target.scrollTop
|
||||
const scrollHeight = e.target.scrollHeight
|
||||
const clientHeight = e.target.clientHeight
|
||||
|
||||
// 检测用户是否在底部(允许10px的误差)
|
||||
this.isUserAtBottom = (scrollTop + clientHeight >= scrollHeight - 10)
|
||||
|
||||
// 检查是否需要加载历史记录
|
||||
// 当滚动到距离顶部阈值范围内且向上滚动时触发加载
|
||||
@@ -1036,6 +1066,37 @@ export default {
|
||||
return sanitizedHtml
|
||||
},
|
||||
|
||||
/**
|
||||
* 节流更新内容
|
||||
*/
|
||||
throttledContentUpdate(aiMsg, newContent) {
|
||||
// 检查内容是否真的有变化
|
||||
if (aiMsg.content === newContent) {
|
||||
return
|
||||
}
|
||||
|
||||
// 立即更新内容(保证响应性)
|
||||
aiMsg.content = newContent
|
||||
|
||||
// 只有当内容长度显著增加时才滚动
|
||||
const contentLengthDiff = newContent.length - this.lastContentLength
|
||||
if (contentLengthDiff > 20) { // 内容增加超过20个字符才滚动
|
||||
this.lastContentLength = newContent.length
|
||||
|
||||
// 清除之前的定时器
|
||||
if (this.contentUpdateTimer) {
|
||||
clearTimeout(this.contentUpdateTimer)
|
||||
}
|
||||
|
||||
// 节流滚动更新
|
||||
this.contentUpdateTimer = setTimeout(() => {
|
||||
if (this.isUserAtBottom && !this.isDestroyed) {
|
||||
this.scrollToBottom(false)
|
||||
}
|
||||
}, 100) // 100ms节流,减少滚动频率
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理点赞
|
||||
*/
|
||||
|
Reference in New Issue
Block a user