diff --git a/components/ai-chat-message/ai-chat-message.vue b/components/ai-chat-message/ai-chat-message.vue index c63b8fa..528f729 100644 --- a/components/ai-chat-message/ai-chat-message.vue +++ b/components/ai-chat-message/ai-chat-message.vue @@ -5,8 +5,8 @@ class="chat-messages" scroll-y :scroll-top="scrollTop" - @scrolltoupper="loadMoreHistory" - :scroll-with-animation="true"> + @scroll="onScroll" + :scroll-with-animation="false"> @@ -16,7 +16,7 @@ @@ -402,7 +402,7 @@ export default { default: 20 } }, - data() { + data(){ return { messages: [], inputText: '', @@ -437,7 +437,11 @@ export default { isAIServiceAvailable: true, aiServiceError: null, isLoadingHistory: false, // 新增:标记历史加载状态 - shouldScrollToBottom: false + shouldScrollToBottom: false, + scrollTimer: null, + lastScrollTop: 0, + historyOffset: 0, + isFetchingHistory: false } }, onShow() { @@ -449,10 +453,17 @@ export default { } else { this.currentConversationId = aiService.getConversationId(); } - this.loadChatHistoryIfExist(); - - - }, + // 加载初始历史后,同步historyOffset + this.loadChatHistoryIfExist().then(() => { + // 初始历史条数作为偏移量 + this.historyOffset = this.messages.length; + }); + // 移除:this.historyOffset = 0;(避免覆盖初始历史的偏移量) + }, + // 新增:组件销毁时清除防抖定时器 + beforeDestroy() { + if (this.scrollTimer) clearTimeout(this.scrollTimer); + }, created() { this.messages = [...this.initialMessages] this.messageId = this.messages.length @@ -505,7 +516,18 @@ export default { } // #endif }, - + onScroll(e) { + const currentScrollTop = e.detail.scrollTop; + this.lastScrollTop = currentScrollTop; + // 防抖处理:50ms内只执行一次,避免频繁触发 + if (this.scrollTimer) clearTimeout(this.scrollTimer); + this.scrollTimer = setTimeout(() => { + // 滚动到顶部附近(<20px)、不在加载中、显示加载更多时,触发加载 + if (currentScrollTop < 20 && !this.isLoadingHistory && this.showLoadMore) { + this.loadMoreHistory(); + } + }, 50); + }, // 初始化用户头像(支持缓存持久化) initUserAvatarData() { // 1. 优先读取本地缓存的头像 @@ -927,68 +949,76 @@ export default { }, // 加载更多历史消息 - async loadMoreHistory() { - if (!this.currentConversationId) { - this.currentConversationId = this.getConversationIdFromLocal(); - aiService.setConversationId(this.currentConversationId); - } - if (!this.showLoadMore || !this.currentConversationId || this.isLoadingHistory) { - return - } - - this.isLoadingHistory = true - this.loadingText = '加载历史消息中...' - this.hasMoreHistory = true - - try { - const response = await aiService.getConversationHistory({ - conversation_id: this.currentConversationId, - limit: 20, - offset: this.messages.length - }) - - if (response.success && Array.isArray(response.messages)) { - const historyMessages = response.messages.map(msg => ({ - id: msg.id, - role: msg.role === 'user' ? 'user' : 'ai', - type: 'text', - content: msg.content, - timestamp: msg.create_time * 1000 - })) - - if (historyMessages.length > 0) { - this.messages = [...historyMessages, ...this.messages] - - // 👇 注意:这里加了 async - this.$nextTick(async () => { - const query = uni.createSelectorQuery().in(this) - query.select('.chat-messages').boundingClientRect() - query.select('.chat-messages').scrollOffset() - - const res = await new Promise((resolve) => { - query.exec(resolve) - }) - - const currentScrollTop = res[1]?.scrollTop || 0 - const avgMessageHeight = 80 - const addedHeight = historyMessages.length * avgMessageHeight - - this.scrollTop = currentScrollTop - addedHeight - }) - } - } else { - this.showLoadMore = false - } - } catch (error) { - console.error('加载历史失败:', error) - uni.showToast({ title: '加载历史失败', icon: 'none' }) - this.showLoadMore = false - } finally { - this.isLoadingHistory = false - this.hasMoreHistory = false - this.loadingText = '加载更多历史消息' - } - }, + async loadMoreHistory() { + if (!this.currentConversationId) { + this.currentConversationId = this.getConversationIdFromLocal(); + aiService.setConversationId(this.currentConversationId); + } + // 防止重复加载 + if (!this.showLoadMore || !this.currentConversationId || this.isLoadingHistory) { + return; + } + this.isFetchingHistory = true; + this.isLoadingHistory = true; + this.loadingText = '加载历史消息中...'; + this.hasMoreHistory = true; + + try { + // 关键修改:用独立的historyOffset作为偏移量,不再依赖messages.length + const response = await aiService.getConversationHistory({ + conversation_id: this.currentConversationId, + limit: 20, + offset: this.historyOffset // 改用独立偏移量 + }); + + if (response.success && Array.isArray(response.messages) && response.messages.length > 0) { + const historyMessages = response.messages.map(msg => ({ + id: msg.id || `history-${Date.now()}-${Math.random().toString(36).slice(2)}`, + role: msg.role === 'user' ? 'user' : 'ai', + type: 'text', + content: msg.content, + timestamp: msg.create_time * 1000 + })); + + // 1. 记录新增消息数量 + const newMsgCount = historyMessages.length; + // 2. 添加到消息列表开头 + this.messages = [...historyMessages, ...this.messages]; + // 3. 更新历史偏移量(累加,不是覆盖) + this.historyOffset += newMsgCount; + + // 4. 等待DOM渲染后,精准计算滚动位置 + await this.$nextTick(); + // 获取新增消息的实际DOM高度 + const query = uni.createSelectorQuery().in(this); + const heightPromises = []; + for (let i = 0; i < newMsgCount; i++) { + heightPromises.push(new Promise(resolve => { + query.select(`.message-item:nth-child(${i + 1})`).boundingClientRect(rect => { + resolve(rect ? rect.height + 32 : 0); // 加上消息间距(32rpx) + }).exec(); + })); + } + // 计算总高度 + const heights = await Promise.all(heightPromises); + const totalNewHeight = heights.reduce((sum, h) => sum + h, 0); + // 5. 设置scrollTop,保持滚动条在顶部附近 + this.scrollTop = totalNewHeight; + } else { + // 无更多历史 + this.showLoadMore = false; + } + } catch (error) { + console.error('加载历史失败:', error); + uni.showToast({ title: '加载历史失败', icon: 'none' }); + this.showLoadMore = false; + } finally { + this.isLoadingHistory = false; + this.isFetchingHistory = false; + this.hasMoreHistory = false; + this.loadingText = '加载更多历史消息'; + } + }, // 显示更多工具 showMoreTools() { this.showToolsPanel = true