From 98a5d85244dbf4239e90dfbfd6faa78872a40f41 Mon Sep 17 00:00:00 2001 From: bob <312777916@qq.com> Date: Mon, 30 Dec 2024 23:22:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B6=88=E6=81=AF=E4=B8=9A=E5=8A=A1=E5=B1=82?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E9=87=8D=E5=8F=91=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/utils/common.js | 2 + src/js/websocket/constructor.js | 12 +- src/js/websocket/wsConnect.js | 21 +- src/proto/msg.js | 152 ++++---------- src/proto/msg.proto | 68 +++--- src/stores/message.js | 101 ++++++--- .../user/components/ContactListUserItem.vue | 7 +- src/views/contactList/user/sub/SubAll.vue | 16 +- src/views/message/MessageLayout.vue | 195 +++++++++++------- src/views/message/components/MessageItem.vue | 158 +++++++++----- src/views/message/components/SessionItem.vue | 10 +- 11 files changed, 407 insertions(+), 335 deletions(-) diff --git a/src/js/utils/common.js b/src/js/utils/common.js index e861c00..4802344 100644 --- a/src/js/utils/common.js +++ b/src/js/utils/common.js @@ -199,3 +199,5 @@ export const jsonParseSafe = (str) => { return '' } } + +export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) diff --git a/src/js/websocket/constructor.js b/src/js/websocket/constructor.js index b63a610..d7de477 100644 --- a/src/js/websocket/constructor.js +++ b/src/js/websocket/constructor.js @@ -3,7 +3,7 @@ import { proto } from '@/const/msgConst' import { userStore } from '@/stores' import { v4 as uuidv4 } from 'uuid' -export const chatConstructor = (sessionId, toId, content, tempMsgId) => { +export const chatConstructor = (sessionId, toId, content, seq) => { const header = Header.create({ magic: proto.magic, version: proto.version, @@ -18,7 +18,7 @@ export const chatConstructor = (sessionId, toId, content, tempMsgId) => { toId: toId, sessionId: sessionId, content: content, - tempMsgId: tempMsgId + seq: seq }) const chatMsg = Msg.create({ header: header, body: body }) const payload = Msg.encode(chatMsg).finish() @@ -27,7 +27,7 @@ export const chatConstructor = (sessionId, toId, content, tempMsgId) => { return data } -export const groupChatConstructor = (sessionId, groupId, content, tempMsgId) => { +export const groupChatConstructor = (sessionId, groupId, content, seq) => { const header = Header.create({ magic: proto.magic, version: proto.version, @@ -42,7 +42,7 @@ export const groupChatConstructor = (sessionId, groupId, content, tempMsgId) => sessionId: sessionId, groupId: groupId, content: content, - tempMsgId: tempMsgId + seq: seq }) const msg = Msg.create({ header: header, body: body }) const payload = Msg.encode(msg).finish() @@ -94,7 +94,7 @@ export const chatReadConstructor = (sessionId, toId, content) => { toId: toId, sessionId: sessionId, content: content, - tempMsgId: uuidv4() + seq: uuidv4() }) const chatMsg = Msg.create({ header: header, body: body }) const payload = Msg.encode(chatMsg).finish() @@ -118,7 +118,7 @@ export const groupChatReadConstructor = (sessionId, groupId, content) => { toId: groupId, sessionId: sessionId, content: content, - tempMsgId: uuidv4() + seq: uuidv4() }) const chatMsg = Msg.create({ header: header, body: body }) const payload = Msg.encode(chatMsg).finish() diff --git a/src/js/websocket/wsConnect.js b/src/js/websocket/wsConnect.js index 0104d11..02b286e 100644 --- a/src/js/websocket/wsConnect.js +++ b/src/js/websocket/wsConnect.js @@ -91,7 +91,7 @@ class WsConnect { } /** - * 消息发送时携带的是tempMsgId,服务端回复DELIVERED消息时返回了msgId,此时要回填msgId + * 消息发送时携带的是seq,没有msgId,服务端回复DELIVERED消息时返回了msgId,此时要回填msgId */ msgIdRefillCallback = {} @@ -104,8 +104,8 @@ class WsConnect { this.isConnect = true }, [MsgType.DELIVERED]: (deliveredMsg) => { - this.msgIdRefillCallback[deliveredMsg.body.tempMsgId](deliveredMsg.body.msgId) - delete this.msgIdRefillCallback[deliveredMsg.body.tempMsgId] + this.msgIdRefillCallback[deliveredMsg.body.seq](deliveredMsg.body.msgId) + delete this.msgIdRefillCallback[deliveredMsg.body.seq] }, [MsgType.HEART_BEAT]: () => { if (this.heartBeat.healthPoint > 0) this.heartBeat.healthPoint-- @@ -328,17 +328,20 @@ class WsConnect { * @param {*} remoteId 对方id或者群id * @param {*} msgType * @param {*} content - * @param {*} callback + * @param {*} seq + * @param {*} callbackBefore 发送前的处理,用于展示发送前状态 + * @param {*} callbackAfter 发送后(接收MsgType.DELIVERED时)的处理,用于展示发送后状态 */ - sendMsg(sessionId, remoteId, msgType, content, callback) { - const tempMsgId = uuidv4() - const data = this.dataConstructor[msgType](sessionId, remoteId, content, tempMsgId) - this.msgIdRefillCallback[tempMsgId] = callback + sendMsg(sessionId, remoteId, msgType, content, seq, callbackBefore, callbackAfter) { + const sequence = seq || uuidv4() + const data = this.dataConstructor[msgType](sessionId, remoteId, content, sequence) + callbackBefore(sequence, data) + this.msgIdRefillCallback[sequence] = callbackAfter this.sendAgent(data) } /** - * 发送代理,封装了重发机制 + * 发送代理,封装了重发机制(ws网络问题) */ sendAgent(data) { if (this.isConnect) { diff --git a/src/proto/msg.js b/src/proto/msg.js index eb10fea..b103e40 100644 --- a/src/proto/msg.js +++ b/src/proto/msg.js @@ -797,10 +797,8 @@ export const Body = ($root.Body = (() => { * @property {string|null} [toClient] Body toClient * @property {string|null} [groupId] Body groupId * @property {number|Long|null} [msgId] Body msgId - * @property {number|null} [seq] Body seq - * @property {number|null} [ack] Body ack * @property {string|null} [content] Body content - * @property {string|null} [tempMsgId] Body tempMsgId + * @property {string|null} [seq] Body seq * @property {string|null} [sessionId] Body sessionId */ @@ -808,34 +806,30 @@ export const Body = ($root.Body = (() => { * Constructs a new Body. * @exports Body * @classdesc 每种消息需要携带的字段规定:M必须,o非必须,-不带 - * NO filed HELLO HEART_BEAT CHAT GROUP_CHAT CHAT_READ GROUP_CHAT_READ DELIVERED CLOSE_BY_READ_IDLE CLOSE_BY_ERROR_MAGIC - * +----+--------------+------+-----------+-----+-----------+----------+----------------+----------+-------------------+---------------------+ - * | 1 | fromId | - | - | M | M | M | M | - | todo | todo | - * | 2 | fromClient | - | - | M | M | M | M | - | todo | todo | - * | 3 | toId | - | - | M | O | M | O | - | todo | todo | - * | 4 | toClient | - | - | O | O | O | O | - | todo | todo | - * | 5 | groupId | - | - | - | M | - | M | - | todo | todo | - * | 6 | msgId | - | - | O | O | O | O | M | todo | todo | - * | 7 | seq(todo) | - | - | - | - | - | - | - | todo | todo | - * | 8 | ack(todo) | - | - | - | - | - | - | - | todo | todo | - * | 9 | content | - | - | M | M | M | M | - | todo | todo | - * | 10 | tempMsgId | - | - | O | O | O | O | M | todo | todo | - * | 11 | sessionId | - | - | M | M | M | M | M | todo | todo | - * +----+--------------+------+-----------+-----+-----------+----------+----------------+----------+-------------------+---------------------+ - * NO filed STATUS_REQ STATUS_RES STATUS_SYNC SYS_GROUP_XXX - * +----+--------------+------------+------------+-------------+------------+ - * | 1 | fromId | M | M | M | - | - * | 2 | fromClient | M | M | M | - | - * | 3 | toId | - | - | - | - | - * | 4 | toClient | - | - | - | - | - * | 5 | groupId | - | - | - | M | - * | 6 | msgId | - | - | - | M | - * | 7 | seq(todo) | - | - | - | - | - * | 8 | ack(todo) | - | - | - | - | - * | 9 | content | M | M | M | M | - * | 10 | tempMsgId | - | - | - | - | - * | 11 | sessionId | - | - | - | M | - * +----+--------------+------------+------------+-------------+------------+ + * NO filed HELLO HEART_BEAT CHAT(up) CHAT(down) GROUP_CHAT(up) GROUP_CHAT(down) CHAT_READ GROUP_CHAT_READ DELIVERED CLOSE_BY_READ_IDLE CLOSE_BY_ERROR_MAGIC + * +---+--------------+------+-----------+---------|-----------+---------------+-----------------+----------+----------------+----------+-------------------+---------------------+ + * | 1 | fromId | - | - | M | M | M | M | M | M | - | todo | todo | + * | 2 | fromClient | - | - | M | M | M | M | M | M | - | todo | todo | + * | 3 | toId | - | - | M | M | - | M | M | O | - | todo | todo | + * | 4 | toClient | - | - | - | M | - | M | O | O | - | todo | todo | + * | 5 | groupId | - | - | - | - | M | M | - | M | - | todo | todo | + * | 6 | msgId | - | - | - | M | - | M | O | O | M | todo | todo | + * | 7 | content | - | - | M | M | M | M | M | M | - | todo | todo | + * | 8 | seq | - | - | M | M | M | M | O | O | M | todo | todo | + * | 9 | sessionId | - | - | M | M | M | M | M | M | M | todo | todo | + * +---+--------------+------+-----------+---------|-----------+---------------+-----------------+----------+----------------+----------+-------------------+---------------------+ + * NO filed STATUS_REQ STATUS_RES STATUS_SYNC SYS_GROUP_XXX + * +---+--------------+------------+------------+-------------+------------+ + * | 1 | fromId | M | M | M | - | + * | 2 | fromClient | M | M | M | - | + * | 3 | toId | - | - | - | - | + * | 4 | toClient | - | - | - | - | + * | 5 | groupId | - | - | - | M | + * | 6 | msgId | - | - | - | M | + * | 7 | content | M | M | M | M | + * | 8 | seq | - | - | - | - | + * | 9 | sessionId | - | - | - | M | + * +---+--------------+------------+------------+-------------+------------+ * @implements IBody * @constructor * @param {IBody=} [properties] Properties to set @@ -894,22 +888,6 @@ export const Body = ($root.Body = (() => { */ Body.prototype.msgId = null - /** - * Body seq. - * @member {number|null|undefined} seq - * @memberof Body - * @instance - */ - Body.prototype.seq = null - - /** - * Body ack. - * @member {number|null|undefined} ack - * @memberof Body - * @instance - */ - Body.prototype.ack = null - /** * Body content. * @member {string|null|undefined} content @@ -919,12 +897,12 @@ export const Body = ($root.Body = (() => { Body.prototype.content = null /** - * Body tempMsgId. - * @member {string|null|undefined} tempMsgId + * Body seq. + * @member {string|null|undefined} seq * @memberof Body * @instance */ - Body.prototype.tempMsgId = null + Body.prototype.seq = null /** * Body sessionId. @@ -973,18 +951,6 @@ export const Body = ($root.Body = (() => { set: $util.oneOfSetter($oneOfFields) }) - // Virtual OneOf for proto3 optional field - Object.defineProperty(Body.prototype, '_seq', { - get: $util.oneOfGetter(($oneOfFields = ['seq'])), - set: $util.oneOfSetter($oneOfFields) - }) - - // Virtual OneOf for proto3 optional field - Object.defineProperty(Body.prototype, '_ack', { - get: $util.oneOfGetter(($oneOfFields = ['ack'])), - set: $util.oneOfSetter($oneOfFields) - }) - // Virtual OneOf for proto3 optional field Object.defineProperty(Body.prototype, '_content', { get: $util.oneOfGetter(($oneOfFields = ['content'])), @@ -992,8 +958,8 @@ export const Body = ($root.Body = (() => { }) // Virtual OneOf for proto3 optional field - Object.defineProperty(Body.prototype, '_tempMsgId', { - get: $util.oneOfGetter(($oneOfFields = ['tempMsgId'])), + Object.defineProperty(Body.prototype, '_seq', { + get: $util.oneOfGetter(($oneOfFields = ['seq'])), set: $util.oneOfSetter($oneOfFields) }) @@ -1038,16 +1004,12 @@ export const Body = ($root.Body = (() => { writer.uint32(/* id 5, wireType 2 =*/ 42).string(message.groupId) if (message.msgId != null && Object.hasOwnProperty.call(message, 'msgId')) writer.uint32(/* id 6, wireType 0 =*/ 48).int64(message.msgId) - if (message.seq != null && Object.hasOwnProperty.call(message, 'seq')) - writer.uint32(/* id 7, wireType 0 =*/ 56).int32(message.seq) - if (message.ack != null && Object.hasOwnProperty.call(message, 'ack')) - writer.uint32(/* id 8, wireType 0 =*/ 64).int32(message.ack) if (message.content != null && Object.hasOwnProperty.call(message, 'content')) - writer.uint32(/* id 9, wireType 2 =*/ 74).string(message.content) - if (message.tempMsgId != null && Object.hasOwnProperty.call(message, 'tempMsgId')) - writer.uint32(/* id 10, wireType 2 =*/ 82).string(message.tempMsgId) + writer.uint32(/* id 7, wireType 2 =*/ 58).string(message.content) + if (message.seq != null && Object.hasOwnProperty.call(message, 'seq')) + writer.uint32(/* id 8, wireType 2 =*/ 66).string(message.seq) if (message.sessionId != null && Object.hasOwnProperty.call(message, 'sessionId')) - writer.uint32(/* id 11, wireType 2 =*/ 90).string(message.sessionId) + writer.uint32(/* id 9, wireType 2 =*/ 74).string(message.sessionId) return writer } @@ -1107,22 +1069,14 @@ export const Body = ($root.Body = (() => { break } case 7: { - message.seq = reader.int32() - break - } - case 8: { - message.ack = reader.int32() - break - } - case 9: { message.content = reader.string() break } - case 10: { - message.tempMsgId = reader.string() + case 8: { + message.seq = reader.string() break } - case 11: { + case 9: { message.sessionId = reader.string() break } @@ -1192,21 +1146,13 @@ export const Body = ($root.Body = (() => { ) return 'msgId: integer|Long expected' } - if (message.seq != null && message.hasOwnProperty('seq')) { - properties._seq = 1 - if (!$util.isInteger(message.seq)) return 'seq: integer expected' - } - if (message.ack != null && message.hasOwnProperty('ack')) { - properties._ack = 1 - if (!$util.isInteger(message.ack)) return 'ack: integer expected' - } if (message.content != null && message.hasOwnProperty('content')) { properties._content = 1 if (!$util.isString(message.content)) return 'content: string expected' } - if (message.tempMsgId != null && message.hasOwnProperty('tempMsgId')) { - properties._tempMsgId = 1 - if (!$util.isString(message.tempMsgId)) return 'tempMsgId: string expected' + if (message.seq != null && message.hasOwnProperty('seq')) { + properties._seq = 1 + if (!$util.isString(message.seq)) return 'seq: string expected' } if (message.sessionId != null && message.hasOwnProperty('sessionId')) { properties._sessionId = 1 @@ -1240,10 +1186,8 @@ export const Body = ($root.Body = (() => { object.msgId.low >>> 0, object.msgId.high >>> 0 ).toNumber() - if (object.seq != null) message.seq = object.seq | 0 - if (object.ack != null) message.ack = object.ack | 0 if (object.content != null) message.content = String(object.content) - if (object.tempMsgId != null) message.tempMsgId = String(object.tempMsgId) + if (object.seq != null) message.seq = String(object.seq) if (object.sessionId != null) message.sessionId = String(object.sessionId) return message } @@ -1292,21 +1236,13 @@ export const Body = ($root.Body = (() => { : message.msgId if (options.oneofs) object._msgId = 'msgId' } - if (message.seq != null && message.hasOwnProperty('seq')) { - object.seq = message.seq - if (options.oneofs) object._seq = 'seq' - } - if (message.ack != null && message.hasOwnProperty('ack')) { - object.ack = message.ack - if (options.oneofs) object._ack = 'ack' - } if (message.content != null && message.hasOwnProperty('content')) { object.content = message.content if (options.oneofs) object._content = 'content' } - if (message.tempMsgId != null && message.hasOwnProperty('tempMsgId')) { - object.tempMsgId = message.tempMsgId - if (options.oneofs) object._tempMsgId = 'tempMsgId' + if (message.seq != null && message.hasOwnProperty('seq')) { + object.seq = message.seq + if (options.oneofs) object._seq = 'seq' } if (message.sessionId != null && message.hasOwnProperty('sessionId')) { object.sessionId = message.sessionId diff --git a/src/proto/msg.proto b/src/proto/msg.proto index 5b6e91d..2cfa850 100644 --- a/src/proto/msg.proto +++ b/src/proto/msg.proto @@ -22,12 +22,12 @@ enum MsgType { SYS_GROUP_CREATE = 21; //系统消息之创建群组 SYS_GROUP_ADD_MEMBER = 22; //系统消息之添加群组成员 SYS_GROUP_DEL_MEMBER = 23; //系统消息之移除群组成员 - SYS_GROUP_SET_ADMIN = 24; //设为管理员角色 + SYS_GROUP_SET_ADMIN = 24; //设置为管理员角色 SYS_GROUP_CANCEL_ADMIN = 25; //取消管理员角色 SYS_GROUP_SET_ALL_MUTED = 26; //设置全员禁言 SYS_GROUP_CANCEL_ALL_MUTED = 27; //取消全员禁言 SYS_GROUP_SET_JOIN_APPROVAL = 28; //开启入群验证 - SYS_GROUP_CANCEL_JOIN_APPROVAL = 29; //取消入群验证 + SYS_GROUP_CANCEL_JOIN_APPROVAL = 29; //关闭入群验证 SYS_GROUP_SET_HISTORY_BROWSE = 30; // 开启新成员浏览历史记录 SYS_GROUP_CANCEL_HISTORY_BROWSE = 31; // 开启新成员浏览历史记录 SYS_GROUP_OWNER_TRANSFER = 32; // 群主转移 @@ -36,7 +36,7 @@ enum MsgType { SYS_GROUP_DROP = 35; //解散群组 SYS_GROUP_UPDATE_ANNOUNCEMENT = 36; //更新了群公告 SYS_GROUP_UPDATE_NAME = 37; //更新了群名称 - SYS_GROUP_UPDATE_AVATAR = 38; //更新了群名称 + SYS_GROUP_UPDATE_AVATAR = 38; //更新了群头像 CLOSE_BY_READ_IDLE = 50; //超时关闭 CLOSE_BY_ERROR_MAGIC = 51; //magic不对关闭 @@ -53,34 +53,30 @@ message Header { /** 每种消息需要携带的字段规定:M必须,o非必须,-不带 - NO filed HELLO HEART_BEAT CHAT GROUP_CHAT CHAT_READ GROUP_CHAT_READ DELIVERED CLOSE_BY_READ_IDLE CLOSE_BY_ERROR_MAGIC -+----+--------------+------+-----------+-----+-----------+----------+----------------+----------+-------------------+---------------------+ -| 1 | fromId | - | - | M | M | M | M | - | todo | todo | -| 2 | fromClient | - | - | M | M | M | M | - | todo | todo | -| 3 | toId | - | - | M | O | M | O | - | todo | todo | -| 4 | toClient | - | - | O | O | O | O | - | todo | todo | -| 5 | groupId | - | - | - | M | - | M | - | todo | todo | -| 6 | msgId | - | - | O | O | O | O | M | todo | todo | -| 7 | seq(todo) | - | - | - | - | - | - | - | todo | todo | -| 8 | ack(todo) | - | - | - | - | - | - | - | todo | todo | -| 9 | content | - | - | M | M | M | M | - | todo | todo | -| 10 | tempMsgId | - | - | O | O | O | O | M | todo | todo | -| 11 | sessionId | - | - | M | M | M | M | M | todo | todo | -+----+--------------+------+-----------+-----+-----------+----------+----------------+----------+-------------------+---------------------+ - NO filed STATUS_REQ STATUS_RES STATUS_SYNC SYS_GROUP_XXX -+----+--------------+------------+------------+-------------+------------+ -| 1 | fromId | M | M | M | - | -| 2 | fromClient | M | M | M | - | -| 3 | toId | - | - | - | - | -| 4 | toClient | - | - | - | - | -| 5 | groupId | - | - | - | M | -| 6 | msgId | - | - | - | M | -| 7 | seq(todo) | - | - | - | - | -| 8 | ack(todo) | - | - | - | - | -| 9 | content | M | M | M | M | -| 10 | tempMsgId | - | - | - | - | -| 11 | sessionId | - | - | - | M | -+----+--------------+------------+------------+-------------+------------+ + NO filed HELLO HEART_BEAT CHAT(up) CHAT(down) GROUP_CHAT(up) GROUP_CHAT(down) CHAT_READ GROUP_CHAT_READ DELIVERED CLOSE_BY_READ_IDLE CLOSE_BY_ERROR_MAGIC ++---+--------------+------+-----------+---------|-----------+---------------+-----------------+----------+----------------+----------+-------------------+---------------------+ +| 1 | fromId | - | - | M | M | M | M | M | M | - | todo | todo | +| 2 | fromClient | - | - | M | M | M | M | M | M | - | todo | todo | +| 3 | toId | - | - | M | M | - | M | M | O | - | todo | todo | +| 4 | toClient | - | - | - | M | - | M | O | O | - | todo | todo | +| 5 | groupId | - | - | - | - | M | M | - | M | - | todo | todo | +| 6 | msgId | - | - | - | M | - | M | O | O | M | todo | todo | +| 7 | content | - | - | M | M | M | M | M | M | - | todo | todo | +| 8 | seq | - | - | M | M | M | M | O | O | M | todo | todo | +| 9 | sessionId | - | - | M | M | M | M | M | M | M | todo | todo | ++---+--------------+------+-----------+---------|-----------+---------------+-----------------+----------+----------------+----------+-------------------+---------------------+ + NO filed STATUS_REQ STATUS_RES STATUS_SYNC SYS_GROUP_XXX ++---+--------------+------------+------------+-------------+------------+ +| 1 | fromId | M | M | M | - | +| 2 | fromClient | M | M | M | - | +| 3 | toId | - | - | - | - | +| 4 | toClient | - | - | - | - | +| 5 | groupId | - | - | - | M | +| 6 | msgId | - | - | - | M | +| 7 | content | M | M | M | M | +| 8 | seq | - | - | - | - | +| 9 | sessionId | - | - | - | M | ++---+--------------+------------+------------+-------------+------------+ */ message Body { optional string fromId = 1; @@ -88,12 +84,10 @@ message Body { optional string toId = 3; optional string toClient = 4; optional string groupId = 5; - optional int64 msgId = 6; - optional int32 seq = 7; - optional int32 ack = 8; - optional string content = 9; - optional string tempMsgId = 10; //客户端生成的临时msgId,不能用于消息排序,所以必须照服务端换正式的msgId - optional string sessionId = 11; //MsgType=SENDER_SYNC需带上该字段,因为此时fromId和toId都是发送端的账号,无法识别是哪个session + optional int64 msgId = 6; //服务端生成的消息ID,会话内单调递增,可用于消息排序 + optional string content = 7; + optional string seq = 8; //客户端生成的序列号ID,会话内唯一,可用于消息去重 + optional string sessionId = 9; //MsgType=SENDER_SYNC需带上该字段,因为此时fromId和toId都是发送端的账号,无法识别是哪个session } message Extension { diff --git a/src/stores/message.js b/src/stores/message.js index e37147d..e116639 100644 --- a/src/stores/message.js +++ b/src/stores/message.js @@ -25,12 +25,40 @@ export const messageStore = defineStore('anyim-message', () => { const sessionList = ref({}) /** - * 会话消息 - * 格式:{sessionId_1: msgRecord_1, sessionId_2: msgRecord_2, ...} - * 其中msgRecord_x是数组 + * 会话消息,双层key-value结构,方便随机查找 + * 格式: + * { + * sessionId_1: { + * msgId_1: {msgId: msgId_1, fromId: xxx,...}, + * msgId_2: {msgId: msgId_2, fromId: xxx,...}, + * ... + * } + * sessionId_2: { + * msgId_a: {msgId: msgId_a, fromId: xxx,...}, + * msgId_b: {msgId: msgId_b, fromId: xxx,...}, + * ... + * } + * ... + * } */ const msgRecordsList = ref({}) + /** + * 会话消息ID排序后的数组,只存msgId,方便顺序查找 + * 格式: + * { + * sessionId_1: [msgId_1, msgId_2...], + * sessionId_2: [msgId_a, msgId_b...] + * ... + * } + */ + const msgIdSortArray = ref({}) + + /** + * 用于消息唯一性校验,key是seq,value是msgId + */ + const msgUniqueSeq = ref({}) + const addSession = (session) => { sessionList.value[session.sessionId] = session } @@ -93,42 +121,50 @@ export const messageStore = defineStore('anyim-message', () => { } /** - * 对话列表中加入新的消息数组,加入后要进行去重和排序 + * 对话列表中加入新的消息数组 * @param {*} sessionId 会话id * @param {*} msgRecords 新的消息数组 */ const addMsgRecords = (sessionId, msgRecords) => { if (!msgRecords?.length) return - if (!msgRecordsList.value[sessionId]) { - // 去重 - let uniqueSet = new Set() - const uniqueRecords = msgRecords.filter((item) => { - if (!uniqueSet.has(item.msgId)) { - uniqueSet.add(item.msgId) - return true - } else { - return false + msgRecords.forEach((item) => { + if (!msgRecordsList.value[sessionId]) { + msgRecordsList.value[sessionId] = {} + } + + // seq为undefined或者seq没有被缓存,才能add这条消息 + if (item.seq === undefined || !(item.seq in msgUniqueSeq.value)) { + msgRecordsList.value[sessionId][item.msgId] = item + if (item.seq !== undefined) { + // seq不为空则添加至缓存 + msgUniqueSeq.value[item.seq] = item.msgId } - }) - // 排序 - msgRecordsList.value[sessionId] = uniqueRecords.sort((a, b) => a.msgId - b.msgId) - } else { - // 合并 - const combinedRecords = [...msgRecordsList.value[sessionId], ...msgRecords] - // 去重 - let uniqueSet = new Set() - const uniqueRecords = combinedRecords.filter((item) => { - if (!uniqueSet.has(item.msgId)) { - uniqueSet.add(item.msgId) - return true - } else { - return false - } - }) - // 排序 - msgRecordsList.value[sessionId] = uniqueRecords.sort((a, b) => a.msgId - b.msgId) + } + }) + // 更新排序 + msgIdSortArray.value[sessionId] = Object.keys(msgRecordsList.value[sessionId]).sort( + (a, b) => a - b + ) + } + + /** + * 移除某个消息:消息已发出后,用正式消息替换temp消息场景 + * @param {*} sessionId 会话id + * @param {*} msgId 消息id + */ + const removeMsgRecord = (sessionId, msgId) => { + const msg = msgRecordsList.value[sessionId][msgId] + if (msg === undefined) return + if (msg.seq in msgUniqueSeq.value) delete msgUniqueSeq.value[msg.seq] + if (msgId in msgRecordsList.value[sessionId]) delete msgRecordsList.value[sessionId][msgId] + } + + const getMsg = (sessionId, msgId) => { + if (!msgRecordsList.value[sessionId] || !msgRecordsList.value[sessionId][msgId]) { + return {} } + return msgRecordsList.value[sessionId][msgId] } const totalUnReadCount = computed(() => { @@ -212,7 +248,10 @@ export const messageStore = defineStore('anyim-message', () => { loadSessionList, msgRecordsList, + msgIdSortArray, addMsgRecords, + removeMsgRecord, + getMsg, partitions, setPartitions, diff --git a/src/views/contactList/user/components/ContactListUserItem.vue b/src/views/contactList/user/components/ContactListUserItem.vue index 0347f63..30e421c 100644 --- a/src/views/contactList/user/components/ContactListUserItem.vue +++ b/src/views/contactList/user/components/ContactListUserItem.vue @@ -28,12 +28,11 @@ const partitioEditing = ref(false) const newPartitionId = ref(props.session.partitionId) const lastMsg = computed(() => { - const msgRecords = messageData.msgRecordsList[props.session.sessionId] - if (!msgRecords?.length) { + const msgIds = messageData.msgIdSortArray[props.session.sessionId] + if (!msgIds?.length) { return {} } - const len = msgRecords.length - return msgRecords[len - 1] + return messageData.getMsg(props.session.sessionId, msgIds[msgIds.length - 1]) }) const onShowCard = () => { diff --git a/src/views/contactList/user/sub/SubAll.vue b/src/views/contactList/user/sub/SubAll.vue index 5408e67..7b1717b 100644 --- a/src/views/contactList/user/sub/SubAll.vue +++ b/src/views/contactList/user/sub/SubAll.vue @@ -44,15 +44,15 @@ const allData = computed(() => { return data } else { return data.sort((a, b) => { - const a_msgRecord = messageData.msgRecordsList[a.sessionId] - const a_msgRecord_len = a_msgRecord?.length - if (!a_msgRecord_len) return 1 - const a_lastMsg = a_msgRecord[a_msgRecord_len - 1] + const a_msgIds = messageData.msgIdSortArray[a.sessionId] + const a_msgIds_len = a_msgIds?.length + if (!a_msgIds_len) return 1 + const a_lastMsg = messageData.getMsg(a.sessionId, a_msgIds[a_msgIds_len - 1]) - const b_msgRecord = messageData.msgRecordsList[b.sessionId] - const b_msgRecord_len = b_msgRecord?.length - if (!b_msgRecord_len) return -1 - const b_lastMsg = b_msgRecord[b_msgRecord_len - 1] + const b_msgIds = messageData.msgIdSortArray[b.sessionId] + const b_msgIds_len = b_msgIds?.length + if (!b_msgIds_len) return -1 + const b_lastMsg = messageData.getMsg(b.sessionId, b_msgIds[b_msgIds_len - 1]) const bTime = new Date(b_lastMsg.msgTime).getTime() const aTIme = new Date(a_lastMsg.msgTime).getTime() diff --git a/src/views/message/MessageLayout.vue b/src/views/message/MessageLayout.vue index 387377e..4c375da 100644 --- a/src/views/message/MessageLayout.vue +++ b/src/views/message/MessageLayout.vue @@ -91,26 +91,28 @@ const selectedSessionId = computed(() => { const pullMsgDone = computed(() => { return selectedSession.value.pullMsgDone || false }) -// msgRecordsList是否为空,注意和hasNoMoreMsg的区别,前者为空可以再拉取 -const noMsgRecords = computed(() => { - return ( - !messageData.msgRecordsList[selectedSessionId.value] || - messageData.msgRecordsList[selectedSessionId.value].length === 0 - ) + +const msgIdSortArray = computed(() => { + return messageData.msgIdSortArray[selectedSessionId.value] }) -// msgRecordsList的第一条消息ID + +// 缓存的消息列表是否为空,注意和hasNoMoreMsg的区别,前者为空可以再拉取 +const noMsgRecords = computed(() => { + return msgIdSortArray.value.length === 0 +}) +// 当前session的第一条消息ID const firstMsgId = computed(() => { if (!noMsgRecords.value) { - return messageData.msgRecordsList[selectedSessionId.value][0].msgId + return msgIdSortArray.value[0] } else { return 0 } }) -// msgRecordsList的最后一条消息ID +// 当前session的最后一条消息ID const lastMsgId = computed(() => { if (!noMsgRecords.value) { - const len = messageData.msgRecordsList[selectedSessionId.value].length - return messageData.msgRecordsList[selectedSessionId.value][len - 1].msgId + const len = msgIdSortArray.value.length + return msgIdSortArray.value[len - 1] } else { return 0 } @@ -157,7 +159,7 @@ const capacity = ref(15) //TODO 现在是调试值 const step = 15 //TODO 现在是调试值 const startIndex = computed(() => { if (selectedSessionId.value) { - const len = messageData.msgRecordsList[selectedSessionId.value]?.length + const len = msgIdSortArray.value?.length return len > capacity.value ? len - capacity.value : 0 } else { return 0 @@ -193,28 +195,32 @@ const locateSession = (sessionId) => { }) } -const msgRecords = computed(() => { - const records = messageData.msgRecordsList[selectedSessionId.value]?.slice(startIndex.value) - if (!records) return [] - - for (let index = 0; index < records.length; index++) { - const element = records[index] +const msgIdsShow = computed(() => { + const ids = msgIdSortArray.value?.slice(startIndex.value) + if (!ids) return [] + return ids +}) +const msgExtend = computed(() => { + const data = [] + for (let index = 0; index < msgIdsShow.value.length; index++) { + const msg = messageData.getMsg(selectedSessionId.value, msgIdsShow.value[index]) + const ext = {} // 判断是否是打开session后的第一条未读消息 - if (index > 0 && records[index - 1].msgId == lastReadMsgId.value) { - element['isFirstNew'] = true + if (index > 0 && msg.msgId == lastReadMsgId.value) { + ext['isFirstNew'] = true } else { - element['isFirstNew'] = false + ext['isFirstNew'] = false } - // 上一条消息的时间,相邻的时间只出一条tips if (index > 0) { - element['preMsgTime'] = records[index - 1].msgTime + ext['preMsgTime'] = msg.msgTime } else { - element['preMsgTime'] = null + ext['preMsgTime'] = null } + data.push(ext) } - return records + return data }) const selectedSession = computed(() => { @@ -292,15 +298,15 @@ const sessionListSorted = computed(() => { return 1 } else { // 排序第三优先级:最后一条消息的时间 - const a_msgRecord = messageData.msgRecordsList[a.sessionId] - const a_msgRecord_len = a_msgRecord?.length - if (!a_msgRecord_len) return 1 - const a_lastMsg = a_msgRecord[a_msgRecord_len - 1] + const a_msgIds = messageData.msgIdSortArray[a.sessionId] + const a_msgIds_len = a_msgIds?.length + if (!a_msgIds_len) return 1 + const a_lastMsg = messageData.getMsg(a.sessionId, a_msgIds[a_msgIds_len - 1]) - const b_msgRecord = messageData.msgRecordsList[b.sessionId] - const b_msgRecord_len = b_msgRecord?.length - if (!b_msgRecord_len) return -1 - const b_lastMsg = b_msgRecord[b_msgRecord_len - 1] + const b_msgIds = messageData.msgIdSortArray[b.sessionId] + const b_msgIds_len = b_msgIds?.length + if (!b_msgIds_len) return -1 + const b_lastMsg = messageData.getMsg(b.sessionId, b_msgIds[b_msgIds_len - 1]) const bTime = new Date(b_lastMsg.msgTime).getTime() const aTIme = new Date(a_lastMsg.msgTime).getTime() @@ -332,12 +338,13 @@ const showId = computed(() => { return selectedSession.value.remoteId }) -const getMsgSenderObj = (item) => { +const getMsgSenderObj = (msgId) => { + const msg = messageData.getMsg(selectedSessionId.value, msgId) if (selectedSession.value.sessionType === MsgType.GROUP_CHAT) { // 如果此时memberList还没有加载完成,先return account给MessageItem子组件 - return allMembers.value ? allMembers.value[item.fromId] : { account: item.fromId } + return allMembers.value ? allMembers.value[msg.fromId] : { account: msg.fromId } } else { - if (myAccount.value === item.fromId) { + if (myAccount.value === msg.fromId) { return userData.user } else { return selectedSession.value.objectInfo @@ -451,7 +458,7 @@ const handleRead = () => { selectedSession.value.sessionType === MsgType.CHAT ? MsgType.CHAT_READ : MsgType.GROUP_CHAT_READ - wsConnect.sendMsg(selectedSessionId.value, showId.value, msgType, content + '', () => {}) + wsConnect.sendMsg(selectedSessionId.value, showId.value, msgType, content + '', '', () => {}) // 更新本地缓存的已读位置 messageData.updateSession({ sessionId: selectedSessionId.value, @@ -462,7 +469,7 @@ const handleRead = () => { } } -const handleSendMessage = (content) => { +const handleSendMessage = (content, resendSeq = '') => { if (isNotInGroup.value) { ElMessage.warning('您已离开该群或群已被解散') return @@ -472,45 +479,91 @@ const handleSendMessage = (content) => { return } - // TODO 这里还要考虑失败情况:1)消息发不出去;2)消息发出去了,服务器不发“已发送” + const msg = { + sessionId: selectedSessionId.value, + fromId: myAccount.value, + msgType: selectedSession.value.sessionType, + content: content, + status: 'pending', + msgTime: new Date() + } + + const resendInterval = 2000 //2秒 + const callbackBefore = (seq, data) => { + // 当2s内status如果还是pending中,则重发3次。如果最后还是pending,则把status置为failed + setTimeout(() => { + if (msg.status === 'pending') { + wsConnect.sendAgent(data) + setTimeout(() => { + if (msg.status === 'pending') { + wsConnect.sendAgent(data) + setTimeout(() => { + if (msg.status === 'pending') { + wsConnect.sendAgent(data) + setTimeout(() => { + if (msg.status === 'pending') { + messageData.removeMsgRecord(selectedSessionId.value, msg.msgId) + // 这里需要在nextTick执行add操作,否则computed没有触发更新 + nextTick(() => { + msg.status = 'failed' + messageData.addMsgRecords(selectedSessionId.value, [msg]) + ElMessage.error('消息发送失败') + }) + } + }, resendInterval) + } + }, resendInterval) + } + }, resendInterval) + } + }, resendInterval) + + messageData.updateSession({ + sessionId: selectedSessionId.value, + unreadCount: 0, // 最后一条消息是自己发的,因此未读是0 + draft: '' //草稿意味着要清空 + }) + msg.seq = seq + msg.msgId = seq //服务器没有回复DELIVERED消息之前,都用seq暂代msgId + messageData.removeMsgRecord(selectedSessionId.value, msg.msgId) + messageData.addMsgRecords(selectedSessionId.value, [msg]) + msgListReachBottom(false) // 发送消息之后,msgList要触底 + } + + const callbackAfter = (msgId) => { + messageData.updateSession({ + sessionId: selectedSessionId.value, + readMsgId: msgId, // 最后一条消息是自己发的,因此已读更新到刚发的这条消息的msgId + readTime: new Date() + }) + messageData.removeMsgRecord(selectedSessionId.value, msg.msgId) //移除seq为key的msg + msg.msgId = msgId + msg.status = 'ok' + messageData.addMsgRecords(selectedSessionId.value, [msg]) //添加服务端返回msgId为key的msg + } + wsConnect.sendMsg( selectedSessionId.value, showId.value, selectedSession.value.sessionType, content, - (msgId) => { - const now = new Date() - messageData.updateSession({ - sessionId: selectedSessionId.value, - readMsgId: msgId, // 最后一条消息是自己发的,因此已读更新到刚发的这条消息的msgId - readTime: now, - unreadCount: 0, // 最后一条消息是自己发的,因此未读是0 - draft: '' //草稿意味着要清空 - }) - - messageData.addMsgRecords(selectedSessionId.value, [ - { - sessionId: selectedSessionId.value, - msgId: msgId, - fromId: myAccount.value, - msgType: selectedSession.value.sessionType, - content: content, - msgTime: now - } - ]) - - msgListReachBottom(false) // 发送消息之后,msgList要触底 - } + resendSeq, + callbackBefore, + callbackAfter ) } +const handleResendMessage = ({ content, seq }) => { + handleSendMessage(content, seq) +} + const onLoadMore = async () => { const scrollHeight = msgListDiv.value.scrollHeight const scrollTop = msgListDiv.value.scrollTop - if (messageData.msgRecordsList[selectedSessionId.value]?.length <= capacity.value) { - await pullMsg(msgRecords.value[0].msgId) + if (msgIdSortArray.value?.length <= capacity.value) { + await pullMsg(msgIdsShow.value[0]) } - const len = messageData.msgRecordsList[selectedSessionId.value]?.length + const len = msgIdSortArray.value?.length if (len > capacity.value) { if (len - capacity.value > step) { capacity.value += step @@ -685,10 +738,10 @@ const onOpenSession = async ({ msgType, objectInfo }) => { } /** - * 监视msgRecords的数据变化,给出新消息的tips提示 + * 监视msgIdsShow的数据变化,给出新消息的tips提示 */ watch( - () => msgRecords.value, + () => msgIdsShow.value, (newValue) => { if (!newValue || selectedSession.value.unreadCount === 0) return nextTick(() => { @@ -990,10 +1043,11 @@ const onConfirmSelect = async (selected) => { @wheel="handleMsgListWheel" > { @loadMore="onLoadMore" @showUserCard="onShowUserCard" @showGroupCard="onShowGroupCard" + @resendMsg="handleResendMessage" > import { computed } from 'vue' +import { WarningFilled } from '@element-plus/icons-vue' import { MsgType } from '@/proto/msg' import { userStore, messageStore, groupStore, groupCardStore } from '@/stores' import { messageSysShowTime, messageBoxShowTime, jsonParseSafe } from '@/js/utils/common' @@ -7,7 +8,8 @@ import UserAvatarIcon from '@/components/common/UserAvatarIcon.vue' const props = defineProps([ 'sessionId', - 'msg', + 'msgId', + 'extend', 'obj', 'readMsgId', 'remoteRead', @@ -15,33 +17,41 @@ const props = defineProps([ 'hasNoMoreMsg', 'isLoadMoreLoading' ]) -const emit = defineEmits(['loadMore', 'showUserCard', 'showGroupCard']) +const emit = defineEmits(['loadMore', 'showUserCard', 'showGroupCard', 'resendMsg']) const userData = userStore() const messageData = messageStore() const groupData = groupStore() const groupCardData = groupCardStore() +const msg = computed(() => { + return messageData.getMsg(props.sessionId, props.msgId) +}) + +const msgStatus = computed(() => { + return msg.value.status || 'ok' +}) + const isSystemMsg = computed(() => { if ( - props.msg.msgType === MsgType.SYS_GROUP_CREATE || - props.msg.msgType === MsgType.SYS_GROUP_ADD_MEMBER || - props.msg.msgType === MsgType.SYS_GROUP_DEL_MEMBER || - props.msg.msgType === MsgType.SYS_GROUP_SET_ADMIN || - props.msg.msgType === MsgType.SYS_GROUP_CANCEL_ADMIN || - props.msg.msgType === MsgType.SYS_GROUP_SET_ALL_MUTED || - props.msg.msgType === MsgType.SYS_GROUP_CANCEL_ALL_MUTED || - props.msg.msgType === MsgType.SYS_GROUP_SET_JOIN_APPROVAL || - props.msg.msgType === MsgType.SYS_GROUP_CANCEL_JOIN_APPROVAL || - props.msg.msgType === MsgType.SYS_GROUP_SET_HISTORY_BROWSE || - props.msg.msgType === MsgType.SYS_GROUP_CANCEL_HISTORY_BROWSE || - props.msg.msgType === MsgType.SYS_GROUP_OWNER_TRANSFER || - props.msg.msgType === MsgType.SYS_GROUP_UPDATE_MEMBER_MUTED || - props.msg.msgType === MsgType.SYS_GROUP_LEAVE || - props.msg.msgType === MsgType.SYS_GROUP_DROP || - props.msg.msgType === MsgType.SYS_GROUP_UPDATE_ANNOUNCEMENT || - props.msg.msgType === MsgType.SYS_GROUP_UPDATE_NAME || - props.msg.msgType === MsgType.SYS_GROUP_UPDATE_AVATAR + msg.value.msgType === MsgType.SYS_GROUP_CREATE || + msg.value.msgType === MsgType.SYS_GROUP_ADD_MEMBER || + msg.value.msgType === MsgType.SYS_GROUP_DEL_MEMBER || + msg.value.msgType === MsgType.SYS_GROUP_SET_ADMIN || + msg.value.msgType === MsgType.SYS_GROUP_CANCEL_ADMIN || + msg.value.msgType === MsgType.SYS_GROUP_SET_ALL_MUTED || + msg.value.msgType === MsgType.SYS_GROUP_CANCEL_ALL_MUTED || + msg.value.msgType === MsgType.SYS_GROUP_SET_JOIN_APPROVAL || + msg.value.msgType === MsgType.SYS_GROUP_CANCEL_JOIN_APPROVAL || + msg.value.msgType === MsgType.SYS_GROUP_SET_HISTORY_BROWSE || + msg.value.msgType === MsgType.SYS_GROUP_CANCEL_HISTORY_BROWSE || + msg.value.msgType === MsgType.SYS_GROUP_OWNER_TRANSFER || + msg.value.msgType === MsgType.SYS_GROUP_UPDATE_MEMBER_MUTED || + msg.value.msgType === MsgType.SYS_GROUP_LEAVE || + msg.value.msgType === MsgType.SYS_GROUP_DROP || + msg.value.msgType === MsgType.SYS_GROUP_UPDATE_ANNOUNCEMENT || + msg.value.msgType === MsgType.SYS_GROUP_UPDATE_NAME || + msg.value.msgType === MsgType.SYS_GROUP_UPDATE_AVATAR ) { return true } else { @@ -244,8 +254,8 @@ const getSysGroupUpdateAvatar = (content) => { } const systemMsgContent = computed(() => { - const content = jsonParseSafe(props.msg.content) - switch (props.msg.msgType) { + const content = jsonParseSafe(msg.value.content) + switch (msg.value.msgType) { case MsgType.SYS_GROUP_CREATE: return `
${getSysCreateGroupMsgTips(content)}
` case MsgType.SYS_GROUP_ADD_MEMBER: @@ -260,16 +270,16 @@ const systemMsgContent = computed(() => { return `
${getSysGroupUpdateAvatar(content)}
` case MsgType.SYS_GROUP_SET_ADMIN: case MsgType.SYS_GROUP_CANCEL_ADMIN: - return `
${getSysGroupChangeRoleMsgTips(props.msg.msgType, content)}
` + return `
${getSysGroupChangeRoleMsgTips(msg.value.msgType, content)}
` case MsgType.SYS_GROUP_SET_ALL_MUTED: case MsgType.SYS_GROUP_CANCEL_ALL_MUTED: - return `
${getSysGroupUpdateAllMuted(props.msg.msgType, content)}
` + return `
${getSysGroupUpdateAllMuted(msg.value.msgType, content)}
` case MsgType.SYS_GROUP_SET_JOIN_APPROVAL: case MsgType.SYS_GROUP_CANCEL_JOIN_APPROVAL: - return `
${getSysGroupUpdateJoinApproval(props.msg.msgType, content)}
` + return `
${getSysGroupUpdateJoinApproval(msg.value.msgType, content)}
` case MsgType.SYS_GROUP_SET_HISTORY_BROWSE: case MsgType.SYS_GROUP_CANCEL_HISTORY_BROWSE: - return `
${getSysGroupUpdateHistoryBrowse(props.msg.msgType, content)}
` + return `
${getSysGroupUpdateHistoryBrowse(msg.value.msgType, content)}
` case MsgType.SYS_GROUP_OWNER_TRANSFER: return `
${getSysGroupOwnerTransfer(content)}
` case MsgType.SYS_GROUP_UPDATE_MEMBER_MUTED: @@ -284,11 +294,11 @@ const systemMsgContent = computed(() => { }) const isChatMsgType = computed(() => { - return props.msg.msgType === MsgType.CHAT + return msg.value.msgType === MsgType.CHAT }) const isGroupChatMsgType = computed(() => { - return props.msg.msgType === MsgType.GROUP_CHAT + return msg.value.msgType === MsgType.GROUP_CHAT }) const loadMoreTips = computed(() => { @@ -296,7 +306,7 @@ const loadMoreTips = computed(() => { }) const isUnreadMsg = computed(() => { - if (!isSystemMsg.value && props.readMsgId < props.msg.msgId && !isSelf.value) { + if (!isSystemMsg.value && props.readMsgId < msg.value.msgId && !isSelf.value) { return true } else { return false @@ -304,18 +314,18 @@ const isUnreadMsg = computed(() => { }) const myMsgIsRead = computed(() => { - return isSelf.value && props.msg.msgId <= props.remoteRead + return isSelf.value && msg.value.msgId <= props.remoteRead }) const isShowLoadMore = computed(() => { - if (props.msg.msgId === props.firstMsgId && !props.hasNoMoreMsg) { + if (msg.value.msgId === props.firstMsgId && !props.hasNoMoreMsg) { return true } else { return false } }) const isShowNoMoreMsg = computed(() => { - if (props.msg.msgId === props.firstMsgId && props.hasNoMoreMsg) { + if (msg.value.msgId === props.firstMsgId && props.hasNoMoreMsg) { return true } else { return false @@ -326,7 +336,7 @@ const loadMoreCursor = computed(() => { }) const isSelf = computed(() => { - return userData.user.account === props.msg.fromId + return userData.user.account === msg.value.fromId }) const account = computed(() => { @@ -342,16 +352,16 @@ const avatarThumb = computed(() => { }) const sysShowTime = computed(() => { - return messageSysShowTime(new Date(props.msg.msgTime)) + return messageSysShowTime(new Date(msg.value.msgTime)) }) // 判断是否是连续的会话,与上个会话时间差小于5分钟 const isContinuousSession = computed(() => { - if (!props.msg.preMsgTime) { + if (!props.extend.preMsgTime) { return false } - const diff = new Date(props.msg.msgTime).getTime() - new Date(props.msg.preMsgTime).getTime() + const diff = new Date(msg.value.msgTime).getTime() - new Date(props.extend.preMsgTime).getTime() if (diff < 5 * 60 * 1000) { return true } else { @@ -360,7 +370,7 @@ const isContinuousSession = computed(() => { }) const msgTime = computed(() => { - return messageBoxShowTime(props.msg.msgTime) + return messageBoxShowTime(msg.value.msgTime) }) const onLoadMore = () => { @@ -384,6 +394,10 @@ const onClickSystemMsg = (e) => { emit('showGroupCard', { groupId: messageData.sessionList[props.sessionId].remoteId }) } } + +const onResendMsg = () => { + emit('resendMsg', msg.value) +}