diff --git a/src/api/im/message/private/index.ts b/src/api/im/message/private/index.ts index bfbdbbcff..698e55d43 100644 --- a/src/api/im/message/private/index.ts +++ b/src/api/im/message/private/index.ts @@ -38,13 +38,17 @@ export const pullPrivateMessages = (params: { minId: number | string; size: numb } // 查询私聊历史消息 +// TODO @AI:历史消息,是不是通过这个接口? export const getPrivateMessageList = (params: ImPrivateMessageListReqVO) => { return request.get({ url: '/im/message/private/list', params }) } // 标记私聊消息已读 -export const readPrivateMessages = (receiverId: number | string) => { - return request.put({ url: '/im/message/private/read', params: { receiverId } }) +export const readPrivateMessages = (receiverId: number | string, messageId: number | string) => { + return request.put({ + url: '/im/message/private/read', + params: { receiverId, messageId } + }) } // 撤回私聊消息 diff --git a/src/views/im/home/store/conversationStore.ts b/src/views/im/home/store/conversationStore.ts index 2d172e292..f810386c0 100644 --- a/src/views/im/home/store/conversationStore.ts +++ b/src/views/im/home/store/conversationStore.ts @@ -512,8 +512,9 @@ export const useConversationStore = defineStore('imConversationStore', { applyReadReceipt(options: { conversationType: number targetId: number - // 私聊:把和该好友的「自己发送的」消息标为已读 - markPrivateRead?: boolean + // 私聊:把和该好友的「自己发送的、id <= privateReadMaxId 的」消息标为已读 + // 必须卡 maxId 边界:回执在路上时新发的消息不能被误标为已读 + privateReadMaxId?: number // 群聊:针对单条消息的回执刷新 groupMessageId?: number readCount?: number @@ -523,9 +524,15 @@ export const useConversationStore = defineStore('imConversationStore', { if (!conversation) { return } - if (options.conversationType === ImConversationType.PRIVATE && options.markPrivateRead) { + if (options.conversationType === ImConversationType.PRIVATE && options.privateReadMaxId) { + const maxReadId = options.privateReadMaxId conversation.messages.forEach((message) => { - if (message.selfSend && message.status !== ImMessageStatus.RECALL) { + if ( + message.selfSend && + message.id && + message.id <= maxReadId && + message.status !== ImMessageStatus.RECALL + ) { message.status = ImMessageStatus.READ } }) diff --git a/src/views/im/home/store/websocketStore.ts b/src/views/im/home/store/websocketStore.ts index 1e6c4bc06..e30ad386e 100644 --- a/src/views/im/home/store/websocketStore.ts +++ b/src/views/im/home/store/websocketStore.ts @@ -318,8 +318,9 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', { conversationStore.activeConversation?.targetId === peerId if (isActive) { // 聊天窗口打开 = 实际看到了:本端清未读 + 上报后端,让对方 UI 立刻切到"已读" + // 已读位置直接用刚到的消息 id(这条就是当前会话最大 id) conversationStore.markActiveAsRead() - apiReadPrivateMessages(peerId).catch((e) => { + apiReadPrivateMessages(peerId, websocketMessage.id).catch((e) => { console.warn('[IM WS] 自动已读上报失败', e) }) } else if (!conversation?.muted) { @@ -342,13 +343,20 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', { conversationStore.saveConversations() }, - /** 私聊 RECEIPT 事件:对方读了我的消息,把和对方会话里自己发的消息标为已读 */ + /** + * 私聊 RECEIPT 事件:对方读了我的消息,把和对方会话里自己发的消息标为已读 + * 后端将 maxReadId 编码在 DTO 的 id 字段(见 ImPrivateMessageDTO.ofReceipt), + * 这里据此卡边界,避免把"回执在路上时刚发的消息"误标为已读。 + */ handlePrivateReceipt(websocketMessage: ImPrivateMessageDTO) { + if (!websocketMessage.id) { + return + } const conversationStore = useConversationStore() conversationStore.applyReadReceipt({ conversationType: ImConversationType.PRIVATE, targetId: websocketMessage.senderId, - markPrivateRead: true + privateReadMaxId: websocketMessage.id }) },