2025-08-29 16:59:14 +08:00
|
|
|
|
import {getChatRegisterInfoApi, getMessagesByRoomIdApi} from "@/request/api/im";
|
2025-07-25 15:59:33 +08:00
|
|
|
|
|
2025-07-22 16:54:37 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 聊天消息管理器
|
2025-07-25 15:59:33 +08:00
|
|
|
|
* @type {{currentRoomId: null, lastMessageId: null, unreadCounts: {}, messageCache: {}, roomMessages: {}, enterRoom(*): void, leaveRoom(): void, getCurrentRoomId(): *, increaseUnreadCount(*): void, resetUnreadCount(*): void, getUnreadCount(*): *, getAllUnreadCount(): *, loadHistory(): *, getMessageType(): *, notifyUnreadChange(): void,, addMessageToRoom(*, *): void, getRoomMessages(*): *, handleIncomingMessage(*): void}}
|
2025-07-22 16:54:37 +08:00
|
|
|
|
*/
|
2025-07-19 16:04:43 +08:00
|
|
|
|
const ChatManager = {
|
|
|
|
|
|
// 当前房间ID
|
|
|
|
|
|
currentRoomId: null,
|
|
|
|
|
|
|
2025-07-25 15:59:33 +08:00
|
|
|
|
lastMessageId: 0,
|
|
|
|
|
|
|
2025-07-19 16:04:43 +08:00
|
|
|
|
// 存储未读消息数 { roomId: count }
|
|
|
|
|
|
unreadCounts: {},
|
|
|
|
|
|
|
2025-07-22 16:54:37 +08:00
|
|
|
|
// 消息缓存 { roomId: [message1, message2, ...] }
|
|
|
|
|
|
messageCache: {},
|
|
|
|
|
|
|
|
|
|
|
|
// 房间消息存储 { roomId: [message1, message2, ...] }
|
|
|
|
|
|
roomMessages: {},
|
|
|
|
|
|
|
2025-07-19 16:04:43 +08:00
|
|
|
|
// 进入聊天室
|
|
|
|
|
|
enterRoom(roomId) {
|
|
|
|
|
|
this.currentRoomId = roomId;
|
|
|
|
|
|
uni.setStorageSync('currentRoomId', roomId);
|
2025-07-22 16:54:37 +08:00
|
|
|
|
// 重置当前房间的未读消息
|
|
|
|
|
|
this.resetUnreadCount(roomId);
|
2025-07-19 16:04:43 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 离开聊天室
|
|
|
|
|
|
leaveRoom() {
|
|
|
|
|
|
this.currentRoomId = null;
|
|
|
|
|
|
uni.removeStorageSync('currentRoomId');
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前房间ID
|
|
|
|
|
|
getCurrentRoomId() {
|
|
|
|
|
|
return this.currentRoomId || uni.getStorageSync('currentRoomId');
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 增加未读消息数
|
|
|
|
|
|
increaseUnreadCount(roomId) {
|
|
|
|
|
|
// 如果当前不在该房间,才增加未读计数
|
|
|
|
|
|
if (this.getCurrentRoomId() !== roomId) {
|
|
|
|
|
|
this.unreadCounts[roomId] = (this.unreadCounts[roomId] || 0) + 1;
|
|
|
|
|
|
this.notifyUnreadChange();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 重置未读消息数
|
|
|
|
|
|
resetUnreadCount(roomId) {
|
|
|
|
|
|
if (this.unreadCounts[roomId]) {
|
|
|
|
|
|
this.unreadCounts[roomId] = 0;
|
|
|
|
|
|
this.notifyUnreadChange();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取未读消息数
|
|
|
|
|
|
getUnreadCount(roomId) {
|
|
|
|
|
|
return this.unreadCounts[roomId] || 0;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有未读消息数
|
|
|
|
|
|
getAllUnreadCount() {
|
|
|
|
|
|
return Object.values(this.unreadCounts).reduce((sum, count) => sum + count, 0);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 通知未读消息变化
|
|
|
|
|
|
notifyUnreadChange() {
|
|
|
|
|
|
uni.$emit('unread-message-update', {
|
|
|
|
|
|
total: this.getAllUnreadCount(),
|
|
|
|
|
|
byRoom: {...this.unreadCounts}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-07-22 16:54:37 +08:00
|
|
|
|
// 添加消息到房间
|
|
|
|
|
|
addMessageToRoom(roomId, message) {
|
|
|
|
|
|
if (!this.roomMessages[roomId]) {
|
|
|
|
|
|
this.roomMessages[roomId] = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
this.roomMessages[roomId].push(message);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取房间消息
|
|
|
|
|
|
getRoomMessages(roomId) {
|
2025-07-25 15:59:33 +08:00
|
|
|
|
let that = this;
|
|
|
|
|
|
this.roomMessages[roomId] = [];
|
|
|
|
|
|
getMessagesByRoomIdApi({
|
|
|
|
|
|
room_id: roomId,
|
|
|
|
|
|
last_message_id: 0,
|
|
|
|
|
|
}).then((res) => {
|
|
|
|
|
|
that.roomMessages[roomId] = that.checkMessage(res.data.result.list);
|
|
|
|
|
|
that.lastMessageId = res.data.result.last_message_id;
|
|
|
|
|
|
|
|
|
|
|
|
uni.$emit('load-room-message', 1);
|
|
|
|
|
|
return this.roomMessages[roomId] || [];
|
|
|
|
|
|
})
|
2025-07-22 16:54:37 +08:00
|
|
|
|
return this.roomMessages[roomId] || [];
|
2025-07-25 15:59:33 +08:00
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
checkMessage(message, type = 0) {
|
|
|
|
|
|
if (type === 1) {
|
2025-12-23 08:31:21 +08:00
|
|
|
|
// 直接返回数据库字段,不进行转换
|
2025-07-25 15:59:33 +08:00
|
|
|
|
return {
|
|
|
|
|
|
id: message.id,
|
2025-12-23 08:31:21 +08:00
|
|
|
|
sender_user_id: message.sender_user_id,
|
|
|
|
|
|
receiver_user_id: message.receiver_user_id,
|
|
|
|
|
|
message_type: message.message_type,
|
|
|
|
|
|
message_content: message.message_content,
|
2025-07-25 15:59:33 +08:00
|
|
|
|
duration: message.duration,
|
2025-12-23 08:31:21 +08:00
|
|
|
|
room_id: message.room_id,
|
|
|
|
|
|
created_at: message.created_at || message.send_time,
|
|
|
|
|
|
created_at_text: message.created_at_text || this.formatTime(message.send_time || message.created_at),
|
|
|
|
|
|
timestamp: message.send_time || message.created_at
|
2025-07-25 15:59:33 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-08-29 16:59:14 +08:00
|
|
|
|
return message.map((item, index) => {
|
2025-12-23 08:31:21 +08:00
|
|
|
|
// 直接返回数据库字段,不进行转换
|
|
|
|
|
|
const result = {
|
2025-07-25 15:59:33 +08:00
|
|
|
|
id: item.id,
|
2025-12-23 08:31:21 +08:00
|
|
|
|
sender_user_id: item.sender_user_id,
|
|
|
|
|
|
receiver_user_id: item.receiver_user_id,
|
|
|
|
|
|
message_type: item.message_type,
|
|
|
|
|
|
message_content: item.message_content,
|
2025-07-25 15:59:33 +08:00
|
|
|
|
duration: item.duration,
|
2025-12-23 08:31:21 +08:00
|
|
|
|
room_id: item.room_id,
|
|
|
|
|
|
created_at: item.created_at || item.send_time,
|
|
|
|
|
|
created_at_text: item.created_at_text || this.formatTime(item.send_time || item.created_at),
|
|
|
|
|
|
timestamp: item.send_time || item.created_at
|
2025-07-25 15:59:33 +08:00
|
|
|
|
};
|
2025-12-23 08:31:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 对于挂号消息,仍然需要异步获取详细信息
|
|
|
|
|
|
if (item.message_type === 10) {
|
|
|
|
|
|
this.getChatRegisterInfo(result, index)
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
2025-07-25 15:59:33 +08:00
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-12-23 08:31:21 +08:00
|
|
|
|
// 根据消息类型ID获取类型名称(辅助函数,不转换字段名)
|
|
|
|
|
|
getMessageTypeName(typeId) {
|
2025-07-25 15:59:33 +08:00
|
|
|
|
const types = {
|
|
|
|
|
|
0: 'text',
|
|
|
|
|
|
1: 'image',
|
|
|
|
|
|
2: 'audio',
|
|
|
|
|
|
3: 'video',
|
|
|
|
|
|
4: 'prescription',
|
2025-08-29 16:59:14 +08:00
|
|
|
|
5: 'file',
|
2025-12-23 08:31:21 +08:00
|
|
|
|
6: 'video-call',
|
|
|
|
|
|
7: 'audio-call',
|
|
|
|
|
|
8: 'file',
|
|
|
|
|
|
9: 'system',
|
2025-08-29 16:59:14 +08:00
|
|
|
|
10: 'register',
|
2025-12-23 08:31:21 +08:00
|
|
|
|
11: 'patient-experience',
|
|
|
|
|
|
12: 'product-card',
|
|
|
|
|
|
13: 'end-consultation',
|
2025-07-25 15:59:33 +08:00
|
|
|
|
};
|
2025-12-23 08:31:21 +08:00
|
|
|
|
return types[typeId] || 'unknown';
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 按需解析消息内容(辅助函数)
|
|
|
|
|
|
getParsedContent(messageType, messageContent) {
|
|
|
|
|
|
// 对于需要 JSON 解析的消息类型:4, 10, 11, 12, 13
|
|
|
|
|
|
if ([4, 10, 11, 12, 13].includes(messageType)) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.parse(messageContent);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('解析消息内容失败:', e);
|
|
|
|
|
|
return messageContent;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return messageContent;
|
2025-07-25 15:59:33 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
2025-08-29 16:59:14 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取挂号信息卡片
|
|
|
|
|
|
* @param item
|
|
|
|
|
|
* @param index
|
|
|
|
|
|
* @returns {*}
|
|
|
|
|
|
*/
|
|
|
|
|
|
getChatRegisterInfo(item, index) {
|
|
|
|
|
|
let that = this;
|
|
|
|
|
|
getChatRegisterInfoApi({
|
|
|
|
|
|
id: item.id
|
|
|
|
|
|
}).then((res) => {
|
|
|
|
|
|
that.roomMessages[that.currentRoomId][index].content = res.data.result;
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-07-25 15:59:33 +08:00
|
|
|
|
// 格式化时间
|
|
|
|
|
|
formatTime(timestamp) {
|
|
|
|
|
|
let date;
|
|
|
|
|
|
|
|
|
|
|
|
// 处理不同输入类型
|
|
|
|
|
|
if (typeof timestamp === 'number') {
|
|
|
|
|
|
// 数字直接作为时间戳处理
|
|
|
|
|
|
date = new Date(timestamp);
|
|
|
|
|
|
} else if (timestamp instanceof Date) {
|
|
|
|
|
|
// Date对象直接使用
|
|
|
|
|
|
date = timestamp;
|
|
|
|
|
|
} else if (typeof timestamp === 'string') {
|
|
|
|
|
|
// 字符串类型:尝试转换为时间戳
|
|
|
|
|
|
const parsed = Date.parse(timestamp);
|
|
|
|
|
|
if (isNaN(parsed)) {
|
|
|
|
|
|
// 转换失败则使用当前时间
|
|
|
|
|
|
date = new Date();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
date = new Date(parsed);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 其他类型使用当前时间
|
|
|
|
|
|
date = new Date();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const diff = now - date; // 时间差(毫秒)
|
|
|
|
|
|
const seconds = Math.floor(diff / 1000);
|
|
|
|
|
|
|
|
|
|
|
|
// 定义星期几的名称
|
|
|
|
|
|
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
|
|
|
|
|
|
const currentWeekday = date.getDay(); // 0=周日,1=周一,...,6=周六
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化时间函数 (补零)
|
|
|
|
|
|
const formatTime = (num) => num.toString().padStart(2, '0');
|
|
|
|
|
|
const formattedTime = `${formatTime(date.getHours())}:${formatTime(date.getMinutes())}`;
|
|
|
|
|
|
|
|
|
|
|
|
if (seconds < 60) {
|
|
|
|
|
|
return '刚刚';
|
|
|
|
|
|
} else if (seconds < 3600) {
|
|
|
|
|
|
return Math.floor(seconds / 60) + '分钟前';
|
|
|
|
|
|
} else if (seconds < 7200) { // 1-2小时
|
|
|
|
|
|
return Math.floor(seconds / 3600) + '小时前';
|
|
|
|
|
|
} else if (seconds < 86400) { // 2-24小时
|
|
|
|
|
|
return formattedTime;
|
|
|
|
|
|
} else if (seconds < 172800) { // 1-2天(昨天)
|
|
|
|
|
|
return `昨天 ${formattedTime}`;
|
|
|
|
|
|
} else if (seconds < 259200) { // 2-3天(前天)
|
|
|
|
|
|
return `前天 ${formattedTime}`;
|
|
|
|
|
|
} else if (seconds < 604800) { // 3-7天内(本周X)
|
|
|
|
|
|
return `周${weekdays[currentWeekday]} ${formattedTime}`;
|
|
|
|
|
|
} else if (seconds < 1209600) { // 7-14天(上周X)
|
|
|
|
|
|
return `上周${weekdays[currentWeekday]} ${formattedTime}`;
|
|
|
|
|
|
} else if (seconds < 2592000) { // 14天-1个月
|
|
|
|
|
|
return Math.floor(seconds / 86400) + '天前';
|
|
|
|
|
|
} else if (seconds < 31536000) { // 1个月-1年
|
|
|
|
|
|
return Math.floor(seconds / 2592000) + '个月前';
|
|
|
|
|
|
} else { // 超过1年
|
|
|
|
|
|
return `${date.getFullYear()}-${formatTime(date.getMonth() + 1)}-${formatTime(date.getDate())}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
loadHistory(roomId) {
|
|
|
|
|
|
let that = this;
|
|
|
|
|
|
getMessagesByRoomIdApi({
|
|
|
|
|
|
room_id: roomId,
|
|
|
|
|
|
last_message_id: that.lastMessageId,
|
|
|
|
|
|
}).then((res) => {
|
|
|
|
|
|
|
|
|
|
|
|
if (res.data.result.list?.length === 0) {
|
|
|
|
|
|
// 没有更多历史消息
|
|
|
|
|
|
uni.$emit('no-more-history');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
that.roomMessages[roomId] = [...that.checkMessage(res.data.result.list), ...that.roomMessages[roomId]];
|
|
|
|
|
|
that.lastMessageId = res.data.result.last_message_id;
|
|
|
|
|
|
|
|
|
|
|
|
uni.$emit('load-room-message');
|
|
|
|
|
|
return res.data.result.list
|
|
|
|
|
|
})
|
2025-07-22 16:54:37 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
2025-07-19 16:04:43 +08:00
|
|
|
|
// 处理收到的消息
|
|
|
|
|
|
handleIncomingMessage(message) {
|
2025-07-22 16:54:37 +08:00
|
|
|
|
const roomId = message.room_id;
|
2025-07-19 16:04:43 +08:00
|
|
|
|
const currentRoomId = this.getCurrentRoomId();
|
|
|
|
|
|
|
2025-07-22 16:54:37 +08:00
|
|
|
|
// 添加消息到房间
|
2025-07-25 15:59:33 +08:00
|
|
|
|
this.addMessageToRoom(roomId, this.checkMessage(message, 1));
|
2025-07-22 16:54:37 +08:00
|
|
|
|
|
2025-07-19 16:04:43 +08:00
|
|
|
|
// 如果当前在消息所属的房间,直接显示
|
2025-07-22 16:54:37 +08:00
|
|
|
|
if (currentRoomId === roomId) {
|
2025-07-19 16:04:43 +08:00
|
|
|
|
// 通知聊天页面显示消息
|
2025-07-25 15:59:33 +08:00
|
|
|
|
uni.$emit('new-room-message', this.checkMessage(message, 1));
|
2025-07-19 16:04:43 +08:00
|
|
|
|
} else {
|
2025-07-22 16:54:37 +08:00
|
|
|
|
// 不在当前房间,缓存消息并增加未读消息数
|
|
|
|
|
|
this.increaseUnreadCount(roomId);
|
2026-01-07 14:21:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 触发顶部通知(传递原始消息,由 App.vue 处理通知显示)
|
|
|
|
|
|
uni.$emit('show-global-notification', message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 通知聊天列表页刷新
|
|
|
|
|
|
uni.$emit('chat-list-update');
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取消息预览文本
|
|
|
|
|
|
* @param {Object} message 消息对象
|
|
|
|
|
|
* @returns {string} 预览文本
|
|
|
|
|
|
*/
|
|
|
|
|
|
getMessagePreview(message) {
|
|
|
|
|
|
const messageType = message.message_type;
|
|
|
|
|
|
const content = message.message_content;
|
|
|
|
|
|
|
|
|
|
|
|
switch (messageType) {
|
|
|
|
|
|
case 0: // 文本
|
|
|
|
|
|
return content;
|
|
|
|
|
|
case 1: // 图片
|
|
|
|
|
|
return '[图片]';
|
|
|
|
|
|
case 2: // 音频
|
|
|
|
|
|
return '[语音消息]';
|
|
|
|
|
|
case 3: // 视频
|
|
|
|
|
|
return '[视频]';
|
|
|
|
|
|
case 4: // 处方
|
|
|
|
|
|
return '[处方单]';
|
|
|
|
|
|
case 5: // 文件
|
|
|
|
|
|
return '[文件]';
|
|
|
|
|
|
case 6: // 视频通话
|
|
|
|
|
|
return '[视频通话]';
|
|
|
|
|
|
case 7: // 音频通话
|
|
|
|
|
|
return '[语音通话]';
|
|
|
|
|
|
case 9: // 系统消息
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(content);
|
|
|
|
|
|
if (parsed.type === 'price_update') {
|
|
|
|
|
|
return parsed.message || '[价格更新]';
|
|
|
|
|
|
}
|
2026-02-02 16:10:46 +08:00
|
|
|
|
if (parsed.type === 'price_adjust') {
|
|
|
|
|
|
return parsed.message || '[价格调整]';
|
|
|
|
|
|
}
|
2026-01-07 14:21:29 +08:00
|
|
|
|
return '[系统消息]';
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return '[系统消息]';
|
|
|
|
|
|
}
|
|
|
|
|
|
case 10: // 挂号
|
|
|
|
|
|
return '[挂号信息]';
|
|
|
|
|
|
case 11: // 患者就诊经历
|
|
|
|
|
|
return '[就诊信息]';
|
|
|
|
|
|
case 12: // 产品卡片
|
|
|
|
|
|
return '[商品推荐]';
|
|
|
|
|
|
case 13: // 结束问诊
|
|
|
|
|
|
return '[问诊已结束]';
|
|
|
|
|
|
default:
|
|
|
|
|
|
return '您有一条新消息';
|
2025-07-19 16:04:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export { ChatManager };
|