ykt-wxapp/pages/home/message-home.vue
2026-02-10 14:29:18 +08:00

848 lines
23 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">
<!-- 头部组件 -->
<message-header
v-model:activeTab="activeTab"
@team-change="handleTeamChange"
@add-patient="handleAddPatient"
/>
<!-- 消息列表 -->
<scroll-view
class="message-list"
scroll-y="true"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="handleRefresh"
@scrolltolower="handleLoadMore"
>
<!-- 消息列表项 -->
<view
v-for="conversation in filteredConversationList"
:key="conversation.groupID || conversation.conversationID"
class="message-item"
@click="handleClickConversation(conversation)"
>
<view class="avatar-container">
<image
class="avatar"
:src="conversation.avatar || '/static/default-patient-avatar.png'"
mode="aspectFill"
/>
<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">
<view class="name-info">
<text class="name">{{ formatPatientName(conversation) }}</text>
<text
v-if="conversation.patientSex || conversation.patientAge"
class="patient-info"
>
{{ formatPatientInfo(conversation) }}
</text>
</view>
<text class="time">{{
formatMessageTime(conversation.lastMessageTime)
}}</text>
</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-else-if="!loading && filteredConversationList.length === 0"
class="empty-container"
>
<image class="empty-image" src="/static/empty.svg" mode="aspectFit" />
<text class="empty-text">{{
activeTab === "processing" ? "暂无处理中的会话" : "暂无已结束的会话"
}}</text>
</view>
<!-- 加载更多 -->
<view
v-if="hasMore && filteredConversationList.length > 0"
class="load-more"
>
<text class="load-more-text">{{
loadingMore ? "加载中..." : "上拉加载更多"
}}</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import { storeToRefs } from "pinia";
import useAccountStore from "@/store/account.js";
import useTeamStore from "@/store/team.js";
import useInfoCheck from "@/hooks/useInfoCheck.js";
import { globalTimChatManager } from "@/utils/tim-chat.js";
import { mergeConversationWithGroupDetails } from "@/utils/conversation-merger.js";
import MessageHeader from "../home/components/message-header.vue";
// 获取登录状态
const { account, openid, isIMInitialized, doctorInfo } = storeToRefs(
useAccountStore()
);
const { initIMAfterLogin } = useAccountStore();
// 获取团队信息
const teamStore = useTeamStore();
const { getTeams } = teamStore;
// 信息完善检查
const { withInfo } = useInfoCheck();
// 团队相关状态
const currentTeamId = ref(""); // 空字符串表示"全部会话消息"
// 状态
const conversationList = ref([]);
const loading = ref(false);
const loadingMore = ref(false);
const hasMore = ref(false);
const refreshing = ref(false);
const activeTab = ref("processing");
// 根据 orderStatus 过滤会话列表
const filteredConversationList = computed(() => {
let filtered = [];
if (activeTab.value === "processing") {
// 处理中pending(待处理) 和 processing(处理中)
filtered = conversationList.value.filter(
(conv) =>
conv.orderStatus === "pending" || conv.orderStatus === "processing"
);
} else {
// 已结束cancelled(已取消)、completed(已完成)、finished(已结束)
filtered = conversationList.value.filter(
(conv) =>
conv.orderStatus === "cancelled" ||
conv.orderStatus === "completed" ||
conv.orderStatus === "finished"
);
}
// 如果选择了团队,进一步过滤
if (currentTeamId.value) {
filtered = filtered.filter((conv) => conv.teamId === currentTeamId.value);
}
return filtered;
});
// 处理团队切换
const handleTeamChange = (teamId) => {
currentTeamId.value = teamId;
console.log("切换到团队ID:", teamId);
};
// 邀请患者 - 使用 withInfo 包装,确保信息完善后才能使用
const handleAddPatient = withInfo(() => {
uni.navigateTo({
url: "/pages/work/team/invite/invite-patient",
});
});
// 立即更新未读徽章
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);
// 更新 tabBar 徽章 - 添加错误处理防止在非TabBar页面调用时出错
try {
if (totalUnreadCount > 0) {
uni.setTabBarBadge({
index: 1,
text: totalUnreadCount > 99 ? "99+" : String(totalUnreadCount),
});
console.log("已更新 tabBar 徽章:", totalUnreadCount);
} else {
uni.removeTabBarBadge({
index: 1,
});
console.log("已移除 tabBar 徽章");
}
} catch (badgeError) {
// 在非TabBar页面上调用时会出错这是正常的不需要处理
if (badgeError.errMsg && badgeError.errMsg.includes("not TabBar page")) {
console.log("当前不是TabBar页面跳过徽章更新");
} else {
console.error("更新TabBar徽章失败:", badgeError);
}
}
} catch (error) {
console.error("更新未读徽章失败:", error);
}
};
// 初始化IM
const initIM = async () => {
// 关键修复:不仅检查 isIMInitialized还要检查实际连接状态
const needsInit =
!isIMInitialized.value ||
!globalTimChatManager ||
!globalTimChatManager.isLoggedIn;
if (needsInit) {
uni.showLoading({
title: "连接中...",
});
// 如果已初始化但连接断开,先清理旧实例
if (
isIMInitialized.value &&
globalTimChatManager &&
!globalTimChatManager.isLoggedIn
) {
console.log("IM已初始化但连接已断开清理旧实例后重新初始化");
await globalTimChatManager.cleanupOldInstance();
}
const success = await initIMAfterLogin();
uni.hideLoading();
if (!success) {
handleReloginIM();
// // 显示重试提示
// uni.showModal({
// title: "IM连接失败",
// content:
// "连接失败请检查网络后重试。如果IM连接失败请重新登陆IM再连接",
// confirmText: "重新登陆",
// cancelText: "取消",
// success: (res) => {
// if (res.confirm) {
// // 重新登陆
// handleReloginIM();
// }
// },
// });
// return false;
}
}
return true;
};
// 重新登陆IM
const handleReloginIM = async () => {
try {
uni.showLoading({
title: "重新登陆中...",
});
// 清理旧的IM实例
if (globalTimChatManager) {
await globalTimChatManager.cleanupOldInstance();
}
// 重新初始化IM
const { initIMAfterLogin } = useAccountStore();
const success = await initIMAfterLogin();
uni.hideLoading();
if (success) {
await loadConversationList();
setupConversationListener();
}
} catch (error) {
uni.hideLoading();
console.error("重新登陆IM失败:", error);
}
};
// 加载会话列表
const loadConversationList = async () => {
if (loading.value) return;
loading.value = true;
const accountStore = useAccountStore();
if (!doctorInfo.value?.userid) {
console.warn("userId 未获取,跳过加载会话列表");
loading.value = false;
return;
}
try {
console.log("开始加载群聊列表");
// 确保 IM 已连接
if (!globalTimChatManager) {
throw new Error("IM管理器未初始化");
}
// 检查 TIM 实例是否存在
if (!globalTimChatManager.tim) {
console.warn("TIM实例不存在尝试重新初始化IM");
const reinitialized = await initIMAfterLogin();
if (!reinitialized) {
// throw new Error("IM重新初始化失败");
}
}
// 检查是否已登录 - 这是关键检查
if (!globalTimChatManager.isLoggedIn) {
console.warn("IM未登录尝试重新连接");
const reconnected = await globalTimChatManager.ensureIMConnection();
if (!reconnected) {
throw new Error("IM重新连接失败");
}
}
if (!globalTimChatManager.getGroupList) {
throw new Error("IM管理器方法不可用");
}
// 添加超时控制,防止永久等待
const timeoutPromise = new Promise((_, reject) => {
setTimeout(
() => reject(new Error("加载会话列表超时,请检查网络连接")),
35000
);
});
const result = await Promise.race([
globalTimChatManager.getGroupList(),
timeoutPromise,
]);
if (result && result.success && result.groupList) {
// 合并后端群组详细信息(已包含格式化和排序)
conversationList.value = await mergeConversationWithGroupDetails(
result.groupList
);
console.log("=== 会话列表加载完成 ===");
console.log("总会话数:", conversationList.value.length);
// 打印前3个会话的 orderStatus
conversationList.value.slice(0, 3).forEach((conv, index) => {
console.log(
`会话 ${index} - orderStatus: ${conv.orderStatus}, 名称: ${conv.name}`
);
});
console.log(
"群聊列表加载成功,共",
conversationList.value.length,
"个会话"
);
} else {
console.error("加载群聊列表失败:", result);
throw new Error(result?.message || "加载失败,请重试");
}
} catch (error) {
console.error("加载会话列表失败:", error);
} finally {
loading.value = false;
}
};
// 防抖更新定时器
let updateTimer = null;
// 设置会话列表监听,实时更新列表
const setupConversationListener = () => {
if (!globalTimChatManager) return;
// 监听会话列表更新事件
globalTimChatManager.setCallback("onConversationListUpdated", (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}`
);
}
}
return;
}
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 ||
existing.patientName !== conversationData.patientName ||
existing.patientSex !== conversationData.patientSex ||
existing.patientAge !== conversationData.patientAge
) {
// 只更新变化的字段,保持头像和未读数稳定
conversationList.value[existingIndex] = {
...conversationData,
// 保持原有头像,避免闪动
avatar: existing.avatar || conversationData.avatar,
// 保留较大的未读数(避免被后端数据覆盖)
unreadCount: Math.max(
existing.unreadCount || 0,
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 防抖延迟
});
// 监听消息接收事件(用于更新未读数)
globalTimChatManager.setCallback("onMessageReceived", (message) => {
console.log("消息列表页面收到新消息:", message);
// 找到对应的会话
const conversationID = message.conversationID;
const conversationIndex = conversationList.value.findIndex(
(conv) => conv.conversationID === conversationID
);
if (conversationIndex !== -1) {
const conversation = conversationList.value[conversationIndex];
// 检查当前页面栈,判断用户是否正在查看该会话的聊天详情页
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
// 获取当前页面的 groupID 参数(如果在聊天详情页)
const currentGroupID = currentPage?.options?.groupID;
const isViewingThisConversation =
currentPage?.route === "pages/message/index" &&
currentGroupID === conversation.groupID;
// 如果用户正在查看这个具体的会话,不增加未读数
if (isViewingThisConversation) {
console.log("用户正在查看该会话,不增加未读数");
return;
}
// 只在用户不在该会话的聊天页面时才增加未读数
conversation.unreadCount = (conversation.unreadCount || 0) + 1;
console.log(
"已更新会话未读数:",
conversation.name,
"unreadCount:",
conversation.unreadCount
);
}
});
};
// 清理消息文本(移除换行符)
const cleanMessageText = (text) => {
if (!text) return "";
return text.replace(/[\r\n]+/g, " ").trim();
};
// 格式化患者姓名
const formatPatientName = (conversation) => {
return conversation.patientName || "未知患者";
};
// 格式化患者信息(性别 + 年龄)
const formatPatientInfo = (conversation) => {
const parts = [];
// 性别
if (conversation.patientSex === "男") {
parts.push("男");
} else if (conversation.patientSex === "女") {
parts.push("女");
}
// 年龄
if (conversation.patientAge) {
parts.push(`${conversation.patientAge}`);
}
return parts.join(" ");
};
// 格式化消息时间
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 = (conversation) => {
console.log("点击会话:", conversation);
// 立即清空本地未读数(优化用户体验)
const conversationIndex = conversationList.value.findIndex(
(conv) => conv.conversationID === conversation.conversationID
);
if (conversationIndex !== -1) {
conversationList.value[conversationIndex].unreadCount = 0;
console.log("已清空本地未读数:", conversation.name);
}
// 跳转到聊天页面
uni.navigateTo({
url: `/pages/message/index?conversationID=${encodeURIComponent(
conversation.conversationID
)}&groupID=${encodeURIComponent(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(() => {
console.log("消息列表页面加载");
});
// 页面显示
onShow(async () => {
try {
// 加载团队列表
await getTeams();
// 初始化IM - 关键修复确保IM连接状态正确
const imReady = await initIM();
if (!imReady) {
console.error("IM初始化失败");
return;
}
// 加载会话列表
await loadConversationList();
// 设置监听器,后续通过事件更新列表
setupConversationListener();
} catch (error) {
console.error("页面初始化失败:", error);
uni.showToast({
title: "初始化失败,请重试",
icon: "none",
});
}
});
// 页面隐藏
onHide(() => {
// 清除防抖定时器
if (updateTimer) {
clearTimeout(updateTimer);
updateTimer = null;
}
// 移除消息监听
if (globalTimChatManager) {
globalTimChatManager.setCallback("onConversationListUpdated", null);
globalTimChatManager.setCallback("onMessageReceived", null);
}
});
</script>
<style scoped lang="scss">
.message-page {
width: 100%;
height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
.message-list {
width: 100%;
flex: 1;
}
.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-info {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.name {
font-size: 30rpx;
font-weight: 500;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-shrink: 1;
}
.patient-info {
font-size: 26rpx;
padding-left: 12rpx;
color: #999;
flex-shrink: 0;
}
.time {
font-size: 24rpx;
color: #999;
margin-left: 16rpx;
flex-shrink: 0;
}
.message-preview {
display: flex;
align-items: center;
}
.preview-text {
font-size: 26rpx;
color: #999;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-all;
}
.load-more {
padding: 20rpx 0;
text-align: center;
}
.load-more-text {
font-size: 24rpx;
color: #999;
}
</style>