hookehuyr

refactor(消息组件): 重构聊天消息加载逻辑为分页加载

将消息加载逻辑从父组件移动到MessageDetail组件内部实现
添加分页加载功能,支持下拉刷新历史消息
优化消息加载状态管理和错误处理
...@@ -51,7 +51,15 @@ ...@@ -51,7 +51,15 @@
51 :scroll-y="true" 51 :scroll-y="true"
52 :scroll-top="scrollTop" 52 :scroll-top="scrollTop"
53 :scroll-into-view="scrollIntoView" 53 :scroll-into-view="scrollIntoView"
54 + :refresher-enabled="true"
55 + :refresher-triggered="isLoadingMessages"
56 + @refresherrefresh="handleRefresh"
54 > 57 >
58 + <!-- 数据加载完成提示 -->
59 + <view v-if="!hasMoreMessages && !isInitialLoad && messages.length > 0 && hasTriedLoadMore" class="load-complete-tip">
60 + <text class="tip-text">已加载完所有数据</text>
61 + </view>
62 +
55 <view 63 <view
56 v-for="(message, index) in messages" 64 v-for="(message, index) in messages"
57 :key="index" 65 :key="index"
...@@ -115,7 +123,7 @@ ...@@ -115,7 +123,7 @@
115 <script setup> 123 <script setup>
116 import { ref, computed, watch, nextTick } from 'vue' 124 import { ref, computed, watch, nextTick } from 'vue'
117 import Taro from '@tarojs/taro' 125 import Taro from '@tarojs/taro'
118 -import { sendChatAPI } from '@/api/chat' 126 +import { sendChatAPI, getChatListAPI } from '@/api/chat'
119 import { useUserStore } from '@/stores/user' 127 import { useUserStore } from '@/stores/user'
120 128
121 /** 129 /**
...@@ -152,28 +160,81 @@ const inputMessage = ref('') ...@@ -152,28 +160,81 @@ const inputMessage = ref('')
152 const scrollTop = ref(0) 160 const scrollTop = ref(0)
153 const scrollIntoView = ref('') 161 const scrollIntoView = ref('')
154 162
155 -// 模拟聊天消息数据 163 +// 聊天消息数据
156 const messages = ref([]) 164 const messages = ref([])
157 165
166 +// 分页相关状态
167 +const PAGE_SIZE = 20 // 每页加载的消息数量
168 +const currentPage = ref(0)
169 +const hasMoreMessages = ref(true)
170 +const isLoadingMessages = ref(false)
171 +const isInitialLoad = ref(true)
172 +const hasTriedLoadMore = ref(false) // 是否已经尝试过手动上拉加载更多
173 +
158 /** 174 /**
159 - * 初始化聊天消息 175 + * 加载聊天消息
176 + * @param {boolean} isLoadMore - 是否为加载更多(下拉加载历史记录)
160 */ 177 */
161 -const initChatMessages = () => { 178 +const loadChatMessages = async (isLoadMore = false) => {
179 + if (props.conversation?.type !== 'chat' || !props.conversation?.id) {
180 + return
181 + }
182 +
183 + // 如果正在加载,则不继续加载
184 + if (isLoadingMessages.value) {
185 + return
186 + }
187 +
188 + // 对于scrolltoupper事件,如果没有更多消息则不加载
189 + // 但对于下拉刷新,总是允许尝试加载
190 + if (!hasMoreMessages.value && isLoadMore && !isInitialLoad.value) {
191 + // 这里可以根据具体需求决定是否允许重新加载
192 + }
193 +
162 const userStore = useUserStore() 194 const userStore = useUserStore()
163 const currentUserId = userStore.userInfo?.id 195 const currentUserId = userStore.userInfo?.id
164 196
165 - if (props.conversation?.type === 'chat') { 197 + try {
166 - // 使用从API获取的真实聊天记录 198 + isLoadingMessages.value = true
167 - if (props.conversation.chatMessages && props.conversation.chatMessages.length > 0) { 199 +
200 + // 如果是加载更多,页码+1
201 + const page = isLoadMore ? currentPage.value + 1 : 0
202 +
203 + const response = await getChatListAPI({
204 + conversation_id: props.conversation.id,
205 + page: page,
206 + limit: PAGE_SIZE
207 + })
208 +
209 + if (response.code && response.data) {
210 + const newMessages = response.data.list || []
211 +
168 // 转换API数据格式为组件需要的格式 212 // 转换API数据格式为组件需要的格式
169 - messages.value = props.conversation.chatMessages.map(msg => ({ 213 + const formattedMessages = newMessages.map(msg => ({
170 - type: msg.created_by === currentUserId ? 'sent' : 'received', // 根据创建者ID判断 214 + type: msg.created_by === currentUserId ? 'sent' : 'received',
171 content: msg.note || '', 215 content: msg.note || '',
172 time: msg.created_time_desc || '', 216 time: msg.created_time_desc || '',
173 - id: msg.id 217 + id: msg.id,
218 + created_by: msg.created_by,
219 + create_time: msg.create_time
174 })) 220 }))
221 +
222 + if (isLoadMore) {
223 + // 加载更多时,将新消息添加到列表顶部(历史消息)
224 + messages.value = [...formattedMessages, ...messages.value]
225 + currentPage.value = page
175 } else { 226 } else {
176 - // 如果没有历史消息,显示欢迎消息 227 + // 初始加载时,直接设置消息列表
228 + messages.value = formattedMessages
229 + currentPage.value = 0
230 + isInitialLoad.value = false
231 + }
232 +
233 + // 判断是否还有更多消息
234 + hasMoreMessages.value = newMessages.length >= PAGE_SIZE
235 +
236 + // 如果没有任何消息,显示欢迎消息
237 + if (messages.value.length === 0 && !isLoadMore) {
177 messages.value = [ 238 messages.value = [
178 { 239 {
179 type: 'received', 240 type: 'received',
...@@ -186,6 +247,15 @@ const initChatMessages = () => { ...@@ -186,6 +247,15 @@ const initChatMessages = () => {
186 ] 247 ]
187 } 248 }
188 } 249 }
250 + } catch (error) {
251 + console.error('加载聊天消息失败:', error)
252 + Taro.showToast({
253 + title: '加载消息失败',
254 + icon: 'error'
255 + })
256 + } finally {
257 + isLoadingMessages.value = false
258 + }
189 } 259 }
190 260
191 /** 261 /**
...@@ -212,7 +282,7 @@ const sendMessage = async () => { ...@@ -212,7 +282,7 @@ const sendMessage = async () => {
212 const messageContent = inputMessage.value.trim() 282 const messageContent = inputMessage.value.trim()
213 283
214 try { 284 try {
215 - // 先添加到本地显示 285 + // 先添加到本地显示(新消息添加到末尾)
216 const newMessage = { 286 const newMessage = {
217 type: 'sent', 287 type: 'sent',
218 content: messageContent, 288 content: messageContent,
...@@ -272,6 +342,20 @@ const sendMessage = async () => { ...@@ -272,6 +342,20 @@ const sendMessage = async () => {
272 } 342 }
273 343
274 /** 344 /**
345 + * 处理下拉刷新
346 + */
347 +const handleRefresh = async () => {
348 + if (!isLoadingMessages.value) {
349 + hasTriedLoadMore.value = true // 标记用户已经尝试手动加载更多
350 + await loadChatMessages(true)
351 + }
352 + // 确保刷新状态被重置
353 + setTimeout(() => {
354 + isLoadingMessages.value = false
355 + }, 100)
356 +}
357 +
358 +/**
275 * 滚动到底部 359 * 滚动到底部
276 */ 360 */
277 const scrollToBottom = () => { 361 const scrollToBottom = () => {
...@@ -305,17 +389,29 @@ const handleClose = () => { ...@@ -305,17 +389,29 @@ const handleClose = () => {
305 scrollTop.value = 0 389 scrollTop.value = 0
306 scrollIntoView.value = '' 390 scrollIntoView.value = ''
307 messages.value = [] 391 messages.value = []
392 + // 重置分页状态
393 + currentPage.value = 0
394 + hasMoreMessages.value = true
395 + isLoadingMessages.value = false
396 + isInitialLoad.value = true
397 + hasTriedLoadMore.value = false // 重置手动加载标记
308 emit('close') 398 emit('close')
309 } 399 }
310 400
311 /** 401 /**
312 - * 监听对话变化,初始化消息 402 + * 监听对话变化,加载消息
313 */ 403 */
314 watch( 404 watch(
315 () => props.conversation, 405 () => props.conversation,
316 - (newConversation) => { 406 + async (newConversation) => {
317 if (newConversation && visible.value) { 407 if (newConversation && visible.value) {
318 - initChatMessages() 408 + // 重置状态
409 + currentPage.value = 0
410 + hasMoreMessages.value = true
411 + isInitialLoad.value = true
412 + hasTriedLoadMore.value = false // 重置手动加载标记
413 +
414 + await loadChatMessages()
319 // 延迟滚动确保消息渲染完成 415 // 延迟滚动确保消息渲染完成
320 setTimeout(() => { 416 setTimeout(() => {
321 nextTick(() => { 417 nextTick(() => {
...@@ -330,9 +426,15 @@ watch( ...@@ -330,9 +426,15 @@ watch(
330 /** 426 /**
331 * 监听弹框显示状态 427 * 监听弹框显示状态
332 */ 428 */
333 -watch(visible, (newVisible) => { 429 +watch(visible, async (newVisible) => {
334 if (newVisible && props.conversation) { 430 if (newVisible && props.conversation) {
335 - initChatMessages() 431 + // 重置状态
432 + currentPage.value = 0
433 + hasMoreMessages.value = true
434 + isInitialLoad.value = true
435 + hasTriedLoadMore.value = false // 重置手动加载标记
436 +
437 + await loadChatMessages()
336 // 确保弹框完全打开后再滚动到底部 438 // 确保弹框完全打开后再滚动到底部
337 setTimeout(() => { 439 setTimeout(() => {
338 nextTick(() => { 440 nextTick(() => {
...@@ -408,6 +510,22 @@ watch(visible, (newVisible) => { ...@@ -408,6 +510,22 @@ watch(visible, (newVisible) => {
408 overflow-y: auto; 510 overflow-y: auto;
409 } 511 }
410 512
513 +// 数据加载完成提示样式
514 +.load-complete-tip {
515 + text-align: center;
516 + padding: 20rpx 0;
517 + margin-bottom: 16rpx;
518 +
519 + .tip-text {
520 + font-size: 24rpx;
521 + color: #999999;
522 + background: #f5f5f5;
523 + padding: 12rpx 24rpx;
524 + border-radius: 20rpx;
525 + display: inline-block;
526 + }
527 +}
528 +
411 .message-item { 529 .message-item {
412 margin-bottom: 24rpx; 530 margin-bottom: 24rpx;
413 display: flex; 531 display: flex;
......
...@@ -103,7 +103,7 @@ import MessageDetail from '@/components/MessageDetail.vue' ...@@ -103,7 +103,7 @@ import MessageDetail from '@/components/MessageDetail.vue'
103 import { $ } from '@tarojs/extend' 103 import { $ } from '@tarojs/extend'
104 import Taro from '@tarojs/taro' 104 import Taro from '@tarojs/taro'
105 // 导入接口 105 // 导入接口
106 -import { getMessagesListAPI, getMessagesDetailAPI, getChatListAPI } from '@/api/chat' 106 +import { getMessagesListAPI, getMessagesDetailAPI } from '@/api/chat'
107 107
108 // 默认头像 108 // 默认头像
109 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg' 109 const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
...@@ -353,24 +353,8 @@ const onConversationClick = async (conversation) => { ...@@ -353,24 +353,8 @@ const onConversationClick = async (conversation) => {
353 selectedConversation.value = conversation 353 selectedConversation.value = conversation
354 } 354 }
355 } else if (conversation.type === 'chat') { 355 } else if (conversation.type === 'chat') {
356 - // 获取聊天消息列表 356 + // 聊天类型直接传递conversation,消息加载在MessageDetail组件内部处理
357 - const response = await getChatListAPI({
358 - conversation_id: conversation.id,
359 - page: 0,
360 - limit: 50
361 - })
362 - if (response.code && response.data) {
363 - // 更新conversation数据,包含聊天记录和接收者信息
364 - selectedConversation.value = {
365 - ...conversation,
366 - chatMessages: response.data.list || [],
367 - receiver: response.data.receiver || {},
368 - name: response.data.receiver?.nickname || conversation.name,
369 - avatar: response.data.receiver?.avatar || conversation.avatar
370 - }
371 - } else {
372 selectedConversation.value = conversation 357 selectedConversation.value = conversation
373 - }
374 } else { 358 } else {
375 selectedConversation.value = conversation 359 selectedConversation.value = conversation
376 } 360 }
......