hookehuyr

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

将消息加载逻辑从父组件移动到MessageDetail组件内部实现
添加分页加载功能,支持下拉刷新历史消息
优化消息加载状态管理和错误处理
......@@ -51,7 +51,15 @@
:scroll-y="true"
:scroll-top="scrollTop"
:scroll-into-view="scrollIntoView"
:refresher-enabled="true"
:refresher-triggered="isLoadingMessages"
@refresherrefresh="handleRefresh"
>
<!-- 数据加载完成提示 -->
<view v-if="!hasMoreMessages && !isInitialLoad && messages.length > 0 && hasTriedLoadMore" class="load-complete-tip">
<text class="tip-text">已加载完所有数据</text>
</view>
<view
v-for="(message, index) in messages"
:key="index"
......@@ -115,7 +123,7 @@
<script setup>
import { ref, computed, watch, nextTick } from 'vue'
import Taro from '@tarojs/taro'
import { sendChatAPI } from '@/api/chat'
import { sendChatAPI, getChatListAPI } from '@/api/chat'
import { useUserStore } from '@/stores/user'
/**
......@@ -152,28 +160,81 @@ const inputMessage = ref('')
const scrollTop = ref(0)
const scrollIntoView = ref('')
// 模拟聊天消息数据
// 聊天消息数据
const messages = ref([])
// 分页相关状态
const PAGE_SIZE = 20 // 每页加载的消息数量
const currentPage = ref(0)
const hasMoreMessages = ref(true)
const isLoadingMessages = ref(false)
const isInitialLoad = ref(true)
const hasTriedLoadMore = ref(false) // 是否已经尝试过手动上拉加载更多
/**
* 初始化聊天消息
* 加载聊天消息
* @param {boolean} isLoadMore - 是否为加载更多(下拉加载历史记录)
*/
const initChatMessages = () => {
const loadChatMessages = async (isLoadMore = false) => {
if (props.conversation?.type !== 'chat' || !props.conversation?.id) {
return
}
// 如果正在加载,则不继续加载
if (isLoadingMessages.value) {
return
}
// 对于scrolltoupper事件,如果没有更多消息则不加载
// 但对于下拉刷新,总是允许尝试加载
if (!hasMoreMessages.value && isLoadMore && !isInitialLoad.value) {
// 这里可以根据具体需求决定是否允许重新加载
}
const userStore = useUserStore()
const currentUserId = userStore.userInfo?.id
if (props.conversation?.type === 'chat') {
// 使用从API获取的真实聊天记录
if (props.conversation.chatMessages && props.conversation.chatMessages.length > 0) {
try {
isLoadingMessages.value = true
// 如果是加载更多,页码+1
const page = isLoadMore ? currentPage.value + 1 : 0
const response = await getChatListAPI({
conversation_id: props.conversation.id,
page: page,
limit: PAGE_SIZE
})
if (response.code && response.data) {
const newMessages = response.data.list || []
// 转换API数据格式为组件需要的格式
messages.value = props.conversation.chatMessages.map(msg => ({
type: msg.created_by === currentUserId ? 'sent' : 'received', // 根据创建者ID判断
const formattedMessages = newMessages.map(msg => ({
type: msg.created_by === currentUserId ? 'sent' : 'received',
content: msg.note || '',
time: msg.created_time_desc || '',
id: msg.id
id: msg.id,
created_by: msg.created_by,
create_time: msg.create_time
}))
if (isLoadMore) {
// 加载更多时,将新消息添加到列表顶部(历史消息)
messages.value = [...formattedMessages, ...messages.value]
currentPage.value = page
} else {
// 如果没有历史消息,显示欢迎消息
// 初始加载时,直接设置消息列表
messages.value = formattedMessages
currentPage.value = 0
isInitialLoad.value = false
}
// 判断是否还有更多消息
hasMoreMessages.value = newMessages.length >= PAGE_SIZE
// 如果没有任何消息,显示欢迎消息
if (messages.value.length === 0 && !isLoadMore) {
messages.value = [
{
type: 'received',
......@@ -186,6 +247,15 @@ const initChatMessages = () => {
]
}
}
} catch (error) {
console.error('加载聊天消息失败:', error)
Taro.showToast({
title: '加载消息失败',
icon: 'error'
})
} finally {
isLoadingMessages.value = false
}
}
/**
......@@ -212,7 +282,7 @@ const sendMessage = async () => {
const messageContent = inputMessage.value.trim()
try {
// 先添加到本地显示
// 先添加到本地显示(新消息添加到末尾)
const newMessage = {
type: 'sent',
content: messageContent,
......@@ -272,6 +342,20 @@ const sendMessage = async () => {
}
/**
* 处理下拉刷新
*/
const handleRefresh = async () => {
if (!isLoadingMessages.value) {
hasTriedLoadMore.value = true // 标记用户已经尝试手动加载更多
await loadChatMessages(true)
}
// 确保刷新状态被重置
setTimeout(() => {
isLoadingMessages.value = false
}, 100)
}
/**
* 滚动到底部
*/
const scrollToBottom = () => {
......@@ -305,17 +389,29 @@ const handleClose = () => {
scrollTop.value = 0
scrollIntoView.value = ''
messages.value = []
// 重置分页状态
currentPage.value = 0
hasMoreMessages.value = true
isLoadingMessages.value = false
isInitialLoad.value = true
hasTriedLoadMore.value = false // 重置手动加载标记
emit('close')
}
/**
* 监听对话变化,初始化消息
* 监听对话变化,加载消息
*/
watch(
() => props.conversation,
(newConversation) => {
async (newConversation) => {
if (newConversation && visible.value) {
initChatMessages()
// 重置状态
currentPage.value = 0
hasMoreMessages.value = true
isInitialLoad.value = true
hasTriedLoadMore.value = false // 重置手动加载标记
await loadChatMessages()
// 延迟滚动确保消息渲染完成
setTimeout(() => {
nextTick(() => {
......@@ -330,9 +426,15 @@ watch(
/**
* 监听弹框显示状态
*/
watch(visible, (newVisible) => {
watch(visible, async (newVisible) => {
if (newVisible && props.conversation) {
initChatMessages()
// 重置状态
currentPage.value = 0
hasMoreMessages.value = true
isInitialLoad.value = true
hasTriedLoadMore.value = false // 重置手动加载标记
await loadChatMessages()
// 确保弹框完全打开后再滚动到底部
setTimeout(() => {
nextTick(() => {
......@@ -408,6 +510,22 @@ watch(visible, (newVisible) => {
overflow-y: auto;
}
// 数据加载完成提示样式
.load-complete-tip {
text-align: center;
padding: 20rpx 0;
margin-bottom: 16rpx;
.tip-text {
font-size: 24rpx;
color: #999999;
background: #f5f5f5;
padding: 12rpx 24rpx;
border-radius: 20rpx;
display: inline-block;
}
}
.message-item {
margin-bottom: 24rpx;
display: flex;
......
......@@ -103,7 +103,7 @@ import MessageDetail from '@/components/MessageDetail.vue'
import { $ } from '@tarojs/extend'
import Taro from '@tarojs/taro'
// 导入接口
import { getMessagesListAPI, getMessagesDetailAPI, getChatListAPI } from '@/api/chat'
import { getMessagesListAPI, getMessagesDetailAPI } from '@/api/chat'
// 默认头像
const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
......@@ -353,24 +353,8 @@ const onConversationClick = async (conversation) => {
selectedConversation.value = conversation
}
} else if (conversation.type === 'chat') {
// 获取聊天消息列表
const response = await getChatListAPI({
conversation_id: conversation.id,
page: 0,
limit: 50
})
if (response.code && response.data) {
// 更新conversation数据,包含聊天记录和接收者信息
selectedConversation.value = {
...conversation,
chatMessages: response.data.list || [],
receiver: response.data.receiver || {},
name: response.data.receiver?.nickname || conversation.name,
avatar: response.data.receiver?.avatar || conversation.avatar
}
} else {
// 聊天类型直接传递conversation,消息加载在MessageDetail组件内部处理
selectedConversation.value = conversation
}
} else {
selectedConversation.value = conversation
}
......