1. 开方线上功能基本完成

2. 修复了一些抽屉组件的滑动失效问题
This commit is contained in:
李琦
2026-05-14 14:29:51 +08:00
parent e9966af14d
commit eacca1bf7b
40 changed files with 6176 additions and 5335 deletions

View File

@@ -4,7 +4,32 @@
*/
import { chatConfig } from '@/config/chat.js';
import { sendWebSocketMessage, isWebSocketConnected } from '@/utils/ws/initWebSocket.js';
import { getChatRegisterInfoApi, getMessagesByRoomIdApi } from '@/request/api/im.js';
import { getChatRegisterInfoApi, getMessagesByRoomIdApi } from '@/api/chat.js';
import { getMessagePreview } from '@/utils/chat/messagePreview.js';
function unwrapMessagesPayload(res) {
if (res == null) return null;
if (res.result !== undefined && res.result !== null) return res.result;
if (res.data && res.data.result !== undefined && res.data.result !== null) return res.data.result;
if (res.data && typeof res.data === 'object') return res.data;
return res;
}
/** 房间 ID 归一化,便于 WS 与列表接口字段比较 */
export function normalizeRoomId(id) {
return String(id == null ? '' : id).trim();
}
/** 仅在工作台 / 在线接诊列表为栈顶页时派发列表增量,减少后台页无意义合并 */
function shouldEmitDoctorImListUpdate() {
const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
const cur = pages && pages.length ? pages[pages.length - 1] : null;
const route = cur && cur.route ? cur.route : '';
return (
route === 'pages/workbench/index' ||
route === 'subPackages/sub_online_reception/pages/list'
);
}
/**
* 聊天室管理器类
@@ -33,16 +58,17 @@ class ChatRoomManager {
* 职责:设置当前房间,重置未读数,保存到本地存储
*/
enterRoom(roomId) {
if (!roomId) {
const id = normalizeRoomId(roomId);
if (!id) {
console.warn('房间ID不能为空');
return;
}
this.currentRoomId = roomId;
uni.setStorageSync('currentRoomId', roomId);
this.resetUnreadCount(roomId);
console.log('进入聊天室:', roomId);
this.currentRoomId = id;
uni.setStorageSync('currentRoomId', id);
this.resetUnreadCount(id);
console.log('进入聊天室:', id);
}
/**
@@ -69,14 +95,14 @@ class ChatRoomManager {
* 职责:如果房间不是当前房间,则增加未读数并通知
*/
increaseUnreadCount(roomId) {
if (!roomId) return;
// 如果是当前房间,不增加未读数
if (this.getCurrentRoomId() === roomId) {
const key = normalizeRoomId(roomId);
if (!key) return;
if (normalizeRoomId(this.getCurrentRoomId()) === key) {
return;
}
this.unreadCounts[roomId] = (this.unreadCounts[roomId] || 0) + 1;
this.unreadCounts[key] = (this.unreadCounts[key] || 0) + 1;
this.notifyUnreadChange();
}
@@ -86,8 +112,9 @@ class ChatRoomManager {
* 职责将指定房间的未读数重置为0并通知
*/
resetUnreadCount(roomId) {
if (this.unreadCounts[roomId]) {
this.unreadCounts[roomId] = 0;
const key = normalizeRoomId(roomId);
if (this.unreadCounts[key]) {
this.unreadCounts[key] = 0;
this.notifyUnreadChange();
}
}
@@ -98,7 +125,8 @@ class ChatRoomManager {
* @returns {number} 未读数
*/
getUnreadCount(roomId) {
return this.unreadCounts[roomId] || 0;
const key = normalizeRoomId(roomId);
return this.unreadCounts[key] || 0;
}
/**
@@ -137,18 +165,19 @@ class ChatRoomManager {
* 职责:将消息添加到指定房间的消息列表
*/
addMessageToRoom(roomId, message) {
if (!roomId || !message) return;
if (!this.roomMessages[roomId]) {
this.roomMessages[roomId] = [];
const key = normalizeRoomId(roomId);
if (!key || !message) return;
if (!this.roomMessages[key]) {
this.roomMessages[key] = [];
}
// 检查消息是否已存在(避免重复)
const existingIndex = this.roomMessages[roomId].findIndex(m => m.id === message.id);
const existingIndex = this.roomMessages[key].findIndex(m => m.id === message.id);
if (existingIndex !== -1) {
this.roomMessages[roomId][existingIndex] = message;
this.roomMessages[key][existingIndex] = message;
} else {
this.roomMessages[roomId].push(message);
this.roomMessages[key].push(message);
}
}
@@ -158,7 +187,8 @@ class ChatRoomManager {
* @returns {Array} 消息列表
*/
getRoomMessages(roomId) {
return this.roomMessages[roomId] || [];
const key = normalizeRoomId(roomId);
return this.roomMessages[key] || [];
}
/**
@@ -168,28 +198,32 @@ class ChatRoomManager {
* @returns {Promise<Array>} 消息列表
*/
async loadRoomMessages(roomId, lastMessageId = 0) {
if (!roomId) {
const key = normalizeRoomId(roomId);
if (!key) {
console.warn('房间ID不能为空');
return [];
}
try {
const res = await getMessagesByRoomIdApi({
room_id: roomId,
room_id: key,
last_message_id: lastMessageId || 0
});
if (res && res.data && res.data.result) {
const messages = this.processMessages(res.data.result.list || []);
this.lastMessageId = res.data.result.last_message_id || 0;
const payload = unwrapMessagesPayload(res);
if (payload && typeof payload === 'object') {
const rawList = payload.list;
const list = Array.isArray(rawList) ? rawList : rawList != null ? [rawList] : [];
const messages = this.processMessages(list);
this.lastMessageId = payload.last_message_id || 0;
// 如果是首次加载,替换消息列表;否则追加到前面
if (lastMessageId === 0) {
this.roomMessages[roomId] = messages;
this.roomMessages[key] = messages;
} else {
this.roomMessages[roomId] = [...messages, ...(this.roomMessages[roomId] || [])];
this.roomMessages[key] = [...messages, ...(this.roomMessages[key] || [])];
}
uni.$emit('load-room-message', lastMessageId === 0 ? 1 : 0);
return messages;
}
@@ -255,13 +289,16 @@ class ChatRoomManager {
}
/**
* 格式化时间
* @param {number} timestamp - 时间戳(秒)
* @returns {string} 格式化后的时间HH:mm
* 格式化时间HH:mm时间戳支持秒或毫秒
* @param {number} timestamp
* @returns {string}
*/
formatTime(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp * 1000);
if (timestamp === undefined || timestamp === null || timestamp === '') return '';
const n = Number(timestamp);
if (Number.isNaN(n)) return '';
const ms = n > 1e12 ? n : n * 1000;
const date = new Date(ms);
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
@@ -280,8 +317,9 @@ class ChatRoomManager {
id: parsedContent.id,
message_id: message.id || ''
}).then((res) => {
if (res && res.data && res.data.result) {
const resultWithFlag = { ...res.data.result, _loaded: true };
const info = res && (res.result != null ? res.result : res.data && res.data.result);
if (res && info) {
const resultWithFlag = { ...info, _loaded: true };
const roomId = message.room_id;
const roomMessages = this.roomMessages[roomId];
@@ -312,7 +350,9 @@ class ChatRoomManager {
}
/**
* 发送消息
* 通过 WebSocket 发送消息request_type: send_message
* 在线接诊聊天页sub_online_reception/pages/chat.vue主路径已改为 Go IM `POST /api/send-to-user`(与 websocket 配置同基址),
* 此处保留供其它仍走 WS 直发的场景;新页面发聊天消息请优先使用 HTTP API。
* @param {Object} messageData - 消息数据
* @param {string} messageData.room_id - 房间ID
* @param {string} messageData.sender_user_id - 发送者ID
@@ -382,21 +422,26 @@ class ChatRoomManager {
if (data.request_type !== 'receive_message' && !data.room_id) {
return;
}
const roomId = data.room_id;
const roomId = normalizeRoomId(data.room_id);
if (!roomId) return;
const dataNorm = { ...data, room_id: roomId };
// 处理消息
const message = this.processSingleMessage(data);
const message = this.processSingleMessage(dataNorm);
// 添加到房间消息列表
this.addMessageToRoom(roomId, message);
const currentNorm = normalizeRoomId(this.getCurrentRoomId());
const incrementUnread = currentNorm !== roomId;
// 如果不是当前房间,增加未读数
if (this.getCurrentRoomId() !== roomId) {
if (incrementUnread) {
this.increaseUnreadCount(roomId);
}
// 调用所有注册的处理器
this.messageHandlers.forEach(handler => {
try {
@@ -405,6 +450,27 @@ class ChatRoomManager {
console.error('消息处理器执行失败:', error);
}
});
const lastMessageTime =
dataNorm.send_time != null && dataNorm.send_time !== ''
? dataNorm.send_time
: dataNorm.created_at != null && dataNorm.created_at !== ''
? dataNorm.created_at
: message.timestamp != null && message.timestamp !== ''
? message.timestamp
: message.created_at;
const unreadForRoom = this.getUnreadCount(roomId);
if (shouldEmitDoctorImListUpdate()) {
uni.$emit('doctor-im-room-update', {
roomId,
last_message_preview: getMessagePreview(dataNorm),
last_message_time: lastMessageTime,
incrementUnread,
unreadForRoom
});
}
}
/**
@@ -413,7 +479,8 @@ class ChatRoomManager {
*/
clearRoomCache(roomId = null) {
if (roomId) {
delete this.roomMessages[roomId];
const key = normalizeRoomId(roomId);
if (key) delete this.roomMessages[key];
} else {
this.roomMessages = {};
}

View File

@@ -0,0 +1,54 @@
/**
* 会话列表「最后一条消息」预览文案(与患者端 ChatManager.getMessagePreview 对齐)
* @param {Object} message
* @returns {string}
*/
export function getMessagePreview(message) {
if (!message || message.message_type === undefined) {
return '您有一条新消息';
}
const messageType = message.message_type;
const content = message.message_content;
switch (messageType) {
case 0:
return content != null ? String(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 = typeof content === 'object' ? content : JSON.parse(content);
if (parsed.type === 'price_update') {
return parsed.message || '[价格更新]';
}
if (parsed.type === 'price_adjust') {
return parsed.message || '[价格调整]';
}
return '[系统消息]';
} catch (e) {
return '[系统消息]';
}
case 10:
return '[挂号信息]';
case 11:
return '[就诊信息]';
case 12:
return '[商品推荐]';
case 13:
return '[问诊已结束]';
default:
return '您有一条新消息';
}
}

View File

@@ -0,0 +1,25 @@
/**
* 会话列表卡片右上角相对时间(工作台「正在接诊」、在线接诊列表等共用)
* @param {Object} item
* @returns {string}
*/
export function formatSessionRelativeTime(item) {
if (!item) return '';
const t =
item.last_message_time ||
item.updated_at ||
item.created_at ||
item.register_time;
if (t == null || t === '') return '';
const ms = typeof t === 'number' ? (t > 1e12 ? t : t * 1000) : new Date(t).getTime();
if (Number.isNaN(ms)) return '';
const d = new Date(ms);
const now = Date.now();
const diff = Math.floor((now - ms) / 60000);
if (diff < 1) return '刚刚';
if (diff < 60) return `${diff}分钟前`;
const h = Math.floor(diff / 60);
if (h < 24) return `${h}小时前`;
const pad = (n) => (n < 10 ? '0' + n : '' + n);
return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}