refactor(消息组件): 重构聊天消息加载逻辑为分页加载
将消息加载逻辑从父组件移动到MessageDetail组件内部实现 添加分页加载功能,支持下拉刷新历史消息 优化消息加载状态管理和错误处理
Showing
2 changed files
with
137 additions
and
35 deletions
| ... | @@ -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 | } | ... | ... |
-
Please register or login to post a comment