2026-02-12 12:04:57 +08:00

736 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="message-page">
<!-- 消息列表 -->
<scroll-view
class="message-list"
scroll-y="true"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="handleRefresh"
@scrolltolower="handleLoadMore"
>
<!-- 加载状态 -->
<view
v-if="loading && conversationList.length === 0"
class="loading-container"
>
<text class="loading-text">加载中...</text>
</view>
<!-- 消息列表项 -->
<view
v-for="conversation in conversationList"
:key="conversation.groupID || conversation.conversationID"
class="message-item"
@click="handleClickConversation(conversation)"
>
<view class="avatar-container">
<GroupAvatar
:avatarList="getAvatarList(conversation.groupID)"
:size="96"
classType="square"
/>
<view v-if="conversation.unreadCount > 0" class="unread-badge">
<text class="unread-text">{{
conversation.unreadCount > 99 ? "99+" : conversation.unreadCount
}}</text>
</view>
</view>
<view class="content">
<view class="header">
<text class="name">{{ conversation.teamName }}</text>
<text class="time">{{
formatMessageTime(conversation.lastMessageTime)
}}</text>
</view>
<view class="patient-info">
咨询人 | {{ conversation.patientName }}
</view>
<view class="message-preview">
<text class="preview-text">{{
cleanMessageText(conversation.lastMessage) || "暂无消息"
}}</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view
v-if="!loading && conversationList.length === 0"
class="empty-container"
>
<image class="empty-image" src="/static/empty.svg" mode="aspectFit" />
<text class="empty-text">暂无消息</text>
</view>
<!-- 加载更多 -->
<view v-if="hasMore && conversationList.length > 0" class="load-more">
<text class="load-more-text">{{
loadingMore ? "加载中..." : "上拉加载更多"
}}</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed, onUnmounted } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import { storeToRefs } from "pinia";
import useAccountStore from "@/store/account.js";
import { globalTimChatManager } from "@/utils/tim-chat.js";
import { mergeConversationWithGroupDetails } from "@/utils/conversation-merger.js";
import { globalUnreadListenerManager } from "@/utils/global-unread-listener.js";
import useGroupAvatars from "./hooks/use-group-avatars.js";
import GroupAvatar from "@/components/group-avatar.vue";
// 获取登录状态
const { account, openid, isIMInitialized } = storeToRefs(useAccountStore());
const { initIMAfterLogin } = useAccountStore();
// 状态
const conversationList = ref([]);
const loading = ref(false);
const loadingMore = ref(false);
const hasMore = ref(false);
const refreshing = ref(false);
// 群聊头像管理
const { loadGroupAvatars, getAvatarList } = useGroupAvatars();
// 计算总未读消息数
const totalUnreadCount = computed(() => {
return conversationList.value.reduce(
(sum, conv) => sum + (conv.unreadCount || 0),
0
);
});
// 立即更新未读徽章
const updateUnreadBadgeImmediately = async () => {
try {
if (!globalTimChatManager || !globalTimChatManager.tim) {
console.warn("TIM实例不存在无法更新徽章");
return;
}
const response = await globalTimChatManager.tim.getConversationList();
if (!response || !response.data || !response.data.conversationList) {
console.warn("获取会话列表返回数据异常");
return;
}
// 计算群聊总未读数
const totalUnreadCount = response.data.conversationList
.filter(
(conv) => conv.conversationID && conv.conversationID.startsWith("GROUP")
)
.reduce((sum, conv) => sum + (conv.unreadCount || 0), 0);
try {
if (totalUnreadCount > 0) {
uni.setTabBarBadge({
index: 1,
text: totalUnreadCount > 99 ? "99+" : String(totalUnreadCount),
});
console.log("已更新 tabBar 徽章:", totalUnreadCount);
} else {
uni.setTabBarBadge({
index: 1,
text: "",
});
console.log("已清除 tabBar 徽章");
}
} catch (badgeError) {
console.error("更新TabBar徽章失败:", badgeError);
}
} catch (error) {
console.error("更新未读徽章失败:", error);
}
};
// 初始化IM
const initIM = async () => {
console.log("=== message.vue initIM 开始 ===");
console.log("isIMInitialized:", isIMInitialized.value);
console.log("globalTimChatManager 存在:", !!globalTimChatManager);
console.log("globalTimChatManager.tim 存在:", !!globalTimChatManager?.tim);
console.log(
"globalTimChatManager.isLoggedIn:",
globalTimChatManager?.isLoggedIn
);
if (!isIMInitialized.value) {
console.log("开始调用 initIMAfterLogin");
const success = await initIMAfterLogin();
console.log("initIMAfterLogin 返回:", success);
if (!success) {
console.log("initIMAfterLogin 失败,跳过 IM 初始化");
return false;
}
console.log("initIMAfterLogin 成功后检查:");
console.log("- globalTimChatManager 存在:", !!globalTimChatManager);
console.log(
"- globalTimChatManager.tim 存在:",
!!globalTimChatManager?.tim
);
console.log(
"- globalTimChatManager.isLoggedIn:",
globalTimChatManager?.isLoggedIn
);
} else if (globalTimChatManager && !globalTimChatManager.isLoggedIn) {
console.log("IM 已初始化但未登录,尝试重连");
const reconnected = await globalTimChatManager.ensureIMConnection();
if (!reconnected) {
console.log("重连失败");
return false;
}
}
console.log("=== message.vue initIM 完成 ===");
return true;
};
// 加载会话列表
const loadConversationList = async () => {
if (loading.value) return;
// loading.value = true;
try {
console.log("开始加载群聊列表");
// 验证 IM 管理器和 TIM 实例是否存在
if (!globalTimChatManager) {
console.log("IM管理器未初始化跳过加载");
return;
}
if (!globalTimChatManager.tim) {
console.log("TIM实例不存在尝试重新初始化");
const imReady = await initIM();
if (!imReady || !globalTimChatManager.tim) {
console.log("TIM实例初始化失败跳过加载");
return;
}
}
if (!globalTimChatManager.getGroupList) {
console.log("getGroupList 方法不存在,跳过加载");
return;
}
// 直接调用getGroupList它会自动等待SDK就绪
const result = await globalTimChatManager.getGroupList();
if (result && result.success && result.groupList) {
// 合并后端群组详细信息(已包含格式化和排序)
conversationList.value = await mergeConversationWithGroupDetails(
result.groupList
);
console.log(
"群聊列表加载成功,共",
conversationList.value.length,
"个会话"
);
// 加载所有群聊的头像
await loadGroupAvatars(conversationList.value);
} else {
console.log("加载群聊列表失败或返回数据为空");
}
} catch (error) {
console.log("加载会话列表异常:", error.message);
} finally {
loading.value = false;
}
};
// 防抖更新定时器
let updateTimer = null;
// 会话列表更新处理函数
const handleConversationListUpdate = async (eventData) => {
console.log("【消息列表页】会话列表更新事件:", eventData);
// 处理单个会话更新(标记已读的情况)
if (eventData && !Array.isArray(eventData) && eventData.conversationID) {
const conversationID = eventData.conversationID;
const existingIndex = conversationList.value.findIndex(
(conv) => conv.conversationID === conversationID
);
if (existingIndex !== -1) {
// 直接更新未读数,避免触发整个对象的响应式更新
if (eventData.unreadCount !== undefined) {
conversationList.value[existingIndex].unreadCount =
eventData.unreadCount;
console.log(
`【消息列表页】已更新会话未读数: ${conversationList.value[existingIndex].name}, unreadCount: ${eventData.unreadCount}`
);
}
// 更新最后消息
if (eventData.lastMessage) {
conversationList.value[existingIndex].lastMessage = eventData.lastMessage.messageForShow || '';
conversationList.value[existingIndex].lastMessageTime = eventData.lastMessage.lastTime || Date.now();
// 重新排序
conversationList.value.sort((a, b) => b.lastMessageTime - a.lastMessageTime);
}
}
return;
}
// eventData 是一个数组,包含所有更新的会话
if (!eventData || !Array.isArray(eventData)) {
console.warn("【消息列表页】会话列表更新事件数据格式错误");
return;
}
// 防抖处理:避免频繁更新导致闪动
if (updateTimer) {
clearTimeout(updateTimer);
}
updateTimer = setTimeout(async () => {
// 过滤出群聊会话
const groupConversations = eventData.filter(
(conv) => conv.conversationID && conv.conversationID.startsWith("GROUP")
);
console.log(`【消息列表页】收到 ${groupConversations.length} 个群聊会话更新`);
// 使用 TimChatManager 的格式化方法转换为标准格式
const formattedConversations = groupConversations.map((conv) =>
globalTimChatManager.formatConversationData(conv)
);
// 合并后端群组详细信息(已包含格式化和排序)
const mergedConversations = await mergeConversationWithGroupDetails(
formattedConversations
);
if (!mergedConversations || mergedConversations.length === 0) {
console.log("【消息列表页】合并后的会话数据为空,跳过更新");
return;
}
let needSort = false;
// 更新会话列表
mergedConversations.forEach((conversationData) => {
const conversationID = conversationData.conversationID;
const existingIndex = conversationList.value.findIndex(
(conv) => conv.conversationID === conversationID
);
if (existingIndex !== -1) {
const existing = conversationList.value[existingIndex];
// 只在关键字段变化时才更新,减少不必要的渲染
if (
existing.lastMessage !== conversationData.lastMessage ||
existing.lastMessageTime !== conversationData.lastMessageTime ||
existing.unreadCount !== conversationData.unreadCount
) {
// 只更新变化的字段,保持头像稳定
conversationList.value[existingIndex] = {
...conversationData,
// 保持原有头像,避免闪动
avatar: existing.avatar || conversationData.avatar,
// 直接使用 TIM SDK 返回的未读数
unreadCount: conversationData.unreadCount || 0
};
needSort = true;
console.log(
`【消息列表页】已更新会话: ${conversationData.name}, unreadCount: ${conversationList.value[existingIndex].unreadCount}`
);
}
} else {
// 添加新会话
conversationList.value.push(conversationData);
needSort = true;
console.log(`【消息列表页】已添加新会话: ${conversationData.name}`);
}
});
// 只在需要时才排序
if (needSort) {
conversationList.value.sort(
(a, b) => b.lastMessageTime - a.lastMessageTime
);
}
}, 100); // 100ms 防抖延迟
};
// 消息接收处理函数
const handleMessageReceived = (message) => {
console.log("【消息列表页】收到新消息:", message);
// TIM SDK 会在收到新消息时自动更新会话的未读数,并触发 onConversationListUpdated 事件
// 因此这里不需要手动处理,只记录日志
};
// 设置会话列表监听,实时更新列表
const setupConversationListener = () => {
if (!globalTimChatManager) return;
console.log("【消息列表页】设置会话监听器");
// 将回调添加到全局未读监听器的回调链中
// 这样不会覆盖全局未读监听器,而是与之共存
if (globalUnreadListenerManager.isInitialized) {
// globalUnreadListenerManager.addCallback("onConversationListUpdated", handleConversationListUpdate);
// globalUnreadListenerManager.addCallback("onMessageReceived", handleMessageReceived);
console.log("【消息列表页】已添加回调到全局监听器回调链");
} else {
console.warn("【消息列表页】全局未读监听器未初始化,使用直接回调方式");
// 如果全局监听器未初始化,直接设置回调(兼容处理)
globalTimChatManager.setCallback("onConversationListUpdated", handleConversationListUpdate);
globalTimChatManager.setCallback("onMessageReceived", handleMessageReceived);
}
};
// 格式化消息时间
const formatMessageTime = (timestamp) => {
if (!timestamp) return "";
const now = Date.now();
const diff = now - timestamp;
const date = new Date(timestamp);
// 1分钟内
if (diff < 60 * 1000) {
return "刚刚";
}
// 1小时内
if (diff < 60 * 60 * 1000) {
return `${Math.floor(diff / (60 * 1000))}分钟前`;
}
// 今天
const today = new Date();
if (date.toDateString() === today.toDateString()) {
return `${String(date.getHours()).padStart(2, "0")}:${String(
date.getMinutes()
).padStart(2, "0")}`;
}
// 昨天
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
if (date.toDateString() === yesterday.toDateString()) {
return "昨天";
}
// 今年
if (date.getFullYear() === today.getFullYear()) {
return `${date.getMonth() + 1}${date.getDate()}`;
}
// 其他
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}`;
};
// 点击会话
const handleClickConversation = async (conversation) => {
console.log("点击会话:", conversation);
// 立即清除本地未读数显示
const conversationIndex = conversationList.value.findIndex(
(conv) => conv.conversationID === conversation.conversationID
);
if (conversationIndex !== -1) {
conversationList.value[conversationIndex].unreadCount = 0;
}
// 标记会话为已读
if (globalTimChatManager && globalTimChatManager.tim) {
try {
await globalTimChatManager.tim.setMessageRead({
conversationID: conversation.conversationID,
});
console.log("✓ 已标记会话为已读:", conversation.conversationID);
// 立即刷新 tabBar 徽章
// await globalUnreadListenerManager.refreshBadge();
} catch (error) {
console.error("✗ 标记会话已读失败:", error);
}
}
// 跳转到聊天页面
uni.navigateTo({
url: `/pages/message/index?conversationID=${conversation.conversationID}&groupID=${conversation.groupID}`,
});
};
// 加载更多
const handleLoadMore = () => {
if (loadingMore.value || !hasMore.value) return;
loadingMore.value = true;
// TODO: 实现分页加载
setTimeout(() => {
loadingMore.value = false;
}, 1000);
};
// 下拉刷新
const handleRefresh = async () => {
refreshing.value = true;
try {
await loadConversationList();
} finally {
refreshing.value = false;
}
};
// 页面加载
onLoad( async() => {
try {
// 初始化IM
const imReady = await initIM();
if (!imReady) {
console.log("IM初始化失败继续加载列表");
}
// 先加载初始会话列表
await loadConversationList();
// 再设置监听器,后续通过事件更新列表
setupConversationListener();
} catch (error) {
console.log("页面初始化异常:", error.message);
}
});
// 清理消息文本(移除换行符)
const cleanMessageText = (text) => {
if (!text) return "";
return text.replace(/[\r\n]+/g, " ").trim();
};
// 页面显示
onShow(async () => {
// 页面显示时刷新 tabBar 徽章
// if (globalUnreadListenerManager.isInitialized) {
// await globalUnreadListenerManager.refreshBadge();
// }
});
// 页面隐藏
onHide(() => {
console.log("【消息列表页】页面隐藏");
// 清除防抖定时器
if (updateTimer) {
clearTimeout(updateTimer);
updateTimer = null;
}
// 注意:不要清除 globalTimChatManager 的回调
// 因为全局未读监听器需要持续工作
// 回调链会在页面销毁时自动清理
});
// 页面卸载
onUnmounted(() => {
console.log("【消息列表页】页面卸载,清理回调");
// 清除防抖定时器
if (updateTimer) {
clearTimeout(updateTimer);
updateTimer = null;
}
// // 从全局未读监听器的回调链中移除本页面的回调
// if (globalUnreadListenerManager.isInitialized) {
// globalUnreadListenerManager.removeCallback("onConversationListUpdated", handleConversationListUpdate);
// globalUnreadListenerManager.removeCallback("onMessageReceived", handleMessageReceived);
// console.log("【消息列表页】已从回调链移除回调");
// }
});
</script>
<style scoped lang="scss">
.message-page {
width: 100%;
height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
.message-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
}
.header-title {
font-size: 36rpx;
font-weight: 600;
color: #000;
}
.total-unread-badge {
min-width: 40rpx;
height: 40rpx;
padding: 0 12rpx;
background-color: #ff4d4f;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
}
.badge-text {
font-size: 24rpx;
color: #fff;
font-weight: 600;
line-height: 1;
}
.message-list {
flex: 1;
width: 100%;
}
.loading-container,
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
.empty-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
.message-item {
display: flex;
align-items: center;
padding: 24rpx 32rpx;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
&:active {
background-color: #f5f5f5;
}
}
.avatar-container {
position: relative;
margin-right: 24rpx;
flex-shrink: 0;
}
.avatar {
width: 96rpx;
height: 96rpx;
border-radius: 8rpx;
}
.unread-badge {
position: absolute;
top: -8rpx;
right: -8rpx;
min-width: 32rpx;
height: 32rpx;
padding: 0 8rpx;
background-color: #ff4d4f;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.unread-text {
font-size: 20rpx;
color: #fff;
line-height: 1;
}
.content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
min-width: 0;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8rpx;
}
.name {
font-size: 32rpx;
font-weight: 500;
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
font-size: 24rpx;
color: #999;
margin-left: 16rpx;
flex-shrink: 0;
}
.message-preview {
display: flex;
align-items: center;
min-width: 0;
}
.preview-text {
font-size: 26rpx;
color: #999;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}
.load-more {
padding: 20rpx 0;
text-align: center;
}
.load-more-text {
font-size: 24rpx;
color: #999;
}
.patient-info {
font-size: 26rpx;
color: #999;
padding-bottom: 10rpx;
}
</style>