diff --git a/im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java index c0b8400..5b554ad 100644 --- a/im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java +++ b/im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java @@ -28,4 +28,7 @@ public class LoginDTO { @Schema(description = "ip地址") private String ip; + @Schema(description = "来源网址") + private String sourceUrl; + } diff --git a/im-platform/src/main/java/com/bx/implatform/entity/User.java b/im-platform/src/main/java/com/bx/implatform/entity/User.java index b34d948..76521a1 100644 --- a/im-platform/src/main/java/com/bx/implatform/entity/User.java +++ b/im-platform/src/main/java/com/bx/implatform/entity/User.java @@ -104,4 +104,10 @@ public class User { */ private String ipAddress; + /** + * 来源网址 + */ + private String sourceUrl; + + } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java index bfb5af1..7cfadce 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java @@ -93,7 +93,7 @@ public class UserServiceImpl extends ServiceImpl implements Us @Override public LoginVO login(LoginDTO dto) { - log.info("【测试】前端传的IP:{}", dto.getIp()); + log.info("【测试】前端传的IP:{}", dto.getSourceUrl()); // 生成游客唯一标识UUID String guestUuid = UUID.randomUUID().toString(); @@ -108,7 +108,7 @@ public class UserServiceImpl extends ServiceImpl implements Us guestUser.setUuid(guestUuid); // ========== 先设置IP ========== guestUser.setLastLoginIp(dto.getIp()); - + guestUser.setSourceUrl(dto.getSourceUrl()); // 保存到数据库 this.save(guestUser); diff --git a/im-uniapp/pages/chat/chat-box.vue b/im-uniapp/pages/chat/chat-box.vue index 54de557..00d0ed6 100644 --- a/im-uniapp/pages/chat/chat-box.vue +++ b/im-uniapp/pages/chat/chat-box.vue @@ -134,7 +134,9 @@ export default { scrollMsgIdx: 0, // 滚动条定位为到哪条消息 chatTabBox: 'none', currentTargetId: null, // 加这一行 - currentChatType: 'PRIVATE', + currentChatType: 'PRIVATE', + isLoading: true, // 添加加载状态 + defaultTitle: '加载中...', // 默认标题 // activeChatIdx: 0, _activeChatIdx: 0, // 添加这个私有变量 showRecord: false, @@ -293,71 +295,106 @@ export default { } }, sendTextMessage() { - this.editorCtx.getContents({ - success: (e) => { - // 清空编辑框数据 - this.editorCtx.clear(); - // 检查是否被封禁 - if (this.isBanned) { - this.showBannedTip(); - return; - } - let sendText = ""; - e.delta.ops.forEach((op) => { - if (op.insert.image) { - // emo表情 - sendText += `#${op.attributes.alt};` - } else{ - // 文字 - sendText += op.insert - } - }) - // 去除最后的换行符 - sendText = sendText.trim(); - if (!sendText && this.atUserIds.length == 0) { - return uni.showToast({ - title: "不能发送空白信息", - icon: "none" - }); - } - let receiptText = this.isReceipt ? "【回执消息】" : ""; - let atText = this.createAtText(); - let msgInfo = { - tmpId: this.generateId(), - content: receiptText + sendText + atText, - atUserIds: this.atUserIds, - receipt: this.isReceipt, - type: this.$enums.MESSAGE_TYPE.TEXT - } - // 清空@成员和回执标记 - this.atUserIds = []; - this.isReceipt = false; - // 填充对方id - this.fillTargetId(msgInfo, this.chat.targetId); - // 防止发送期间用户切换会话导致串扰 - const chat = this.chat; - // const chat = this.chatStore.chats[this.activeChatIdx]; - if (!chat) return; - // 回显消息 - let tmpMessage = this.buildTmpMessage(msgInfo); - this.chatStore.insertMessage(tmpMessage, chat); - this.moveChatToTop(); - this.sendMessageRequest(msgInfo).then((m) => { - // 更新消息 - tmpMessage = JSON.parse(JSON.stringify(tmpMessage)); - tmpMessage.id = m.id; - tmpMessage.status = m.status; - tmpMessage.content = m.content; - this.chatStore.updateMessage(tmpMessage, chat); - }).catch(() => { - // 更新消息 - tmpMessage = JSON.parse(JSON.stringify(tmpMessage)); - tmpMessage.status = this.$enums.MESSAGE_STATUS.FAILED; - this.chatStore.updateMessage(tmpMessage, chat); - }) - } - }) - + this.editorCtx.getContents({ + success: (e) => { + this.editorCtx.clear(); + if (this.isBanned) { + this.showBannedTip(); + return; + } + + let sendText = ""; + e.delta.ops.forEach((op) => { + if (op.insert.image) { + sendText += `#${op.attributes.alt};` + } else { + sendText += op.insert + } + }) + + sendText = sendText.trim(); + if (!sendText && this.atUserIds.length == 0) { + return uni.showToast({ + title: "不能发送空白信息", + icon: "none" + }); + } + + let receiptText = this.isReceipt ? "【回执消息】" : ""; + let atText = this.createAtText(); + let msgInfo = { + tmpId: this.generateId(), + content: receiptText + sendText + atText, + atUserIds: this.atUserIds, + receipt: this.isReceipt, + type: this.$enums.MESSAGE_TYPE.TEXT + } + + this.atUserIds = []; + this.isReceipt = false; + this.fillTargetId(msgInfo, this.chat.targetId); + + const chat = this.chat; + if (!chat) return; + + // 创建临时消息 + let tmpMessage = this.buildTmpMessage(msgInfo); + + // 保存临时消息的副本用于更新 + const tmpMessageCopy = JSON.parse(JSON.stringify(tmpMessage)); + + // 插入消息到 store + this.chatStore.insertMessage(tmpMessageCopy, chat); + + // 关键:使用 chat 引用来获取最新消息 + const chatIndex = this.chatStore.chats.findIndex(c => + c.targetId === chat.targetId && c.type === chat.type + ); + + if (chatIndex !== -1) { + const targetChat = this.chatStore.chats[chatIndex]; + const msgIndex = targetChat.messages.findIndex(m => m.tmpId === msgInfo.tmpId); + + if (msgIndex !== -1) { + // 滚动到底部 + this.$nextTick(() => { + this.scrollToBottom(); + }); + + // 发送请求 + this.sendMessageRequest(msgInfo).then((m) => { + // 更新消息 - 使用响应式更新 + const updatedMessage = { + ...targetChat.messages[msgIndex], + id: m.id, + status: m.status, + content: m.content + }; + + // 使用 Vue.set 或直接替换数组项 + this.$set(targetChat.messages, msgIndex, updatedMessage); + + // 触发 chat 引用更新 + this.$forceUpdate(); + + }).catch(() => { + // 更新失败状态 + const failedMessage = { + ...targetChat.messages[msgIndex], + status: this.$enums.MESSAGE_STATUS.FAILED + }; + this.$set(targetChat.messages, msgIndex, failedMessage); + this.$forceUpdate(); + + uni.showToast({ + title: "发送失败", + icon: "none" + }); + }); + } + } + } + }) }, createAtText() { let atText = ""; @@ -1012,6 +1049,21 @@ export default { } }, computed: { + title() { + // 加载中显示默认标题 + if (this.isLoading) { + return this.defaultTitle; + } + if (!this.chat) { + return ""; + } + let title = this.chat.showName; + if (this.isGroup) { + let size = this.groupMembers.filter(m => !m.quit).length; + title += `(${size})`; + } + return title; + }, chat() { if (!this.currentTargetId) return null; return this.chatStore.chats.find(c => @@ -1105,21 +1157,43 @@ export default { } }, watch: { - messageSize: function(newSize, oldSize) { - // 接收到新消息 - if (newSize > oldSize && oldSize > 0) { - let lastMessage = this.chat.messages[newSize - 1]; - if (this.$msgType.isNormal(lastMessage.type)) { - if (this.isInBottom || lastMessage.selfSend) { - // 收到消息,则滚动至底部 - this.scrollToBottom(); - } else { - // 若滚动条不在底部,说明用户正在翻历史消息,此时滚动条不能动,同时增加新消息提示 - this.newMessageSize++; - } - } - } - }, + chat: { + handler(newChat, oldChat) { + if (newChat && newChat.messages && newChat.messages.length > 0) { + // 当消息数量变化时,如果正在底部则滚动 + if (this.isInBottom) { + this.$nextTick(() => { + this.scrollToBottom(); + }); + } + } + }, + deep: true, + immediate: true + }, + + // 监听消息数组长度变化 + 'chat.messages.length': function(newLength, oldLength) { + if (newLength > oldLength && this.isInBottom) { + this.$nextTick(() => { + this.scrollToBottom(); + }); + } + }, + + // 原有的 messageSize watch + messageSize: function(newSize, oldSize) { + if (newSize > oldSize && oldSize > 0) { + let lastMessage = this.chat.messages[newSize - 1]; + if (this.$msgType.isNormal(lastMessage.type)) { + if (this.isInBottom || lastMessage.selfSend) { + this.scrollToBottom(); + } else { + this.newMessageSize++; + } + } + } + }, unreadCount: { handler(newCount, oldCount) { if (newCount > 0) { @@ -1179,8 +1253,17 @@ export default { await this.$nextTick(); - // 初始化 + // ✅ 重要:先等待 chat 初始化完成 + if (!this.chat) { + console.error('Chat 初始化失败'); + uni.hideLoading(); + return; + } + + // ✅ 然后再调用需要 chat 的方法 this.readedMessage(); + + // 加载好友信息 this.loadFriend(targetId); this.loadReaded(targetId); diff --git a/im-uniapp/pages/login/login.vue b/im-uniapp/pages/login/login.vue index f57dca6..2fec36d 100644 --- a/im-uniapp/pages/login/login.vue +++ b/im-uniapp/pages/login/login.vue @@ -18,11 +18,41 @@ export default { terminal: 1, userName: '', password: '', - ip: '' + ip: '', + sourceUrl: '' } } }, methods: { + // 获取来源网址 + getSourceUrl() { + + const pages = getCurrentPages(); + const currentPage = pages[pages.length - 1]; + const options = currentPage.options || {}; + if (options.from) { + this.dataForm.sourceUrl = options.from; + } + + // #ifdef H5 + this.dataForm.sourceUrl = window.location.origin; + // #endif + + // #ifdef APP-PLUS + this.dataForm.sourceUrl = 'app'; + // #endif + + // #ifdef MP-WEIXIN + this.dataForm.sourceUrl = 'wechat-miniprogram'; + // #endif + + // 如果没有获取到,设置默认值 + if (!this.dataForm.sourceUrl) { + this.dataForm.sourceUrl = 'unknown'; + } + + console.log("来源网址:", this.dataForm.sourceUrl); + }, async autoLogin() { if (GLOBAL_AUTO_LOGIN_LOCK) return; GLOBAL_AUTO_LOGIN_LOCK = true; @@ -45,7 +75,8 @@ export default { // } // return; // } - + // 获取来源网址 + this.getSourceUrl(); await this.getIp(); this.$http({ @@ -59,20 +90,22 @@ export default { getApp().$vm.init() getApp().$vm.unloadStore(); this.$http({ - url: "/friend/add?friendId=" + loginInfo.customerServiceId, - method: "POST" + url: "/friend/add?friendId=" + loginInfo.customerServiceId, + method: "POST" }).then((data) => { - let friend = { - id: loginInfo.user.id, - nickName: loginInfo.user.nickName, - headImage: loginInfo.user.headImageThumb, - online: loginInfo.user.online, - delete: false - } - this.friendStore.addFriend(friend); - uni.reLaunch({ - url: "/pages/chat/chat-box?chatIdx=0" - }); + let friend = { + id: loginInfo.user.id, + nickName: loginInfo.user.nickName, + headImage: loginInfo.user.headImageThumb, + online: loginInfo.user.online, + delete: false + } + this.friendStore.addFriend(friend); + + // ✅ 修改:传递客服ID和类型 + uni.reLaunch({ + url: `/pages/chat/chat-box?targetId=${loginInfo.customerServiceId}&type=PRIVATE` + }); }) }).catch(err => { diff --git a/im-uniapp/store/chatStore.js b/im-uniapp/store/chatStore.js index 1e636e7..ac47518 100644 --- a/im-uniapp/store/chatStore.js +++ b/im-uniapp/store/chatStore.js @@ -6,19 +6,23 @@ import useUserStore from './userStore'; let cacheChats = []; export default defineStore('chatStore', { - state: () => ({ - chats: [], - privateMsgMaxId: 0, - groupMsgMaxId: 0, - loading: false - }), + state: () => { + return { + chats: [], + privateMsgMaxId: 0, + groupMsgMaxId: 0, + loading: false + } + }, actions: { initChats(chatsData) { cacheChats = []; this.chats = []; for (let chat of chatsData.chats) { chat.stored = false; + // 暂存至缓冲区 cacheChats.push(JSON.parse(JSON.stringify(chat))); + // 加载期间显示只前15个会话做做样子,一切都为了加快初始化时间 if (this.chats.length < 15) { this.chats.push(chat); } @@ -27,15 +31,18 @@ export default defineStore('chatStore', { this.groupMsgMaxId = chatsData.groupMsgMaxId || 0; }, openChat(chatInfo) { - let chats = this.chats; + let chats = this.curChats; let chat = null; for (let idx in chats) { - if (chats[idx].type == chatInfo.type && chats[idx].targetId === chatInfo.targetId) { + if (chats[idx].type == chatInfo.type && + chats[idx].targetId === chatInfo.targetId) { chat = chats[idx]; - this.moveTop(idx); + // 放置头部 + this.moveTop(idx) break; } } + // 创建会话 if (chat == null) { chat = { targetId: chatInfo.targetId, @@ -58,265 +65,323 @@ export default defineStore('chatStore', { } }, activeChat(idx) { + let chats = this.curChats; if (idx >= 0) { - this.chats[idx] = { ...this.chats[idx], unreadCount: 0 }; + chats[idx].unreadCount = 0; } }, resetUnreadCount(chatInfo) { - this.chats = this.chats.map(chat => { - if (chat.type == chatInfo.type && chat.targetId == chatInfo.targetId) { - return { ...chat, unreadCount: 0, atMe: false, atAll: false, stored: false }; + let chats = this.curChats; + for (let idx in chats) { + if (chats[idx].type == chatInfo.type && + chats[idx].targetId == chatInfo.targetId) { + chats[idx].unreadCount = 0; + chats[idx].atMe = false; + chats[idx].atAll = false; + chats[idx].stored = false; + this.saveToStorage(); } - return chat; - }); - this.saveToStorage(); + } + }, readedMessage(pos) { let chat = this.findChatByFriend(pos.friendId); if (!chat) return; - let messages = [...chat.messages]; - let changed = false; - for (let idx = chat.readedMessageIdx; idx < messages.length; idx++) { - let m = messages[idx]; + for (let idx = chat.readedMessageIdx; idx < chat.messages.length; idx++) { + let m = chat.messages[idx]; if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) { + // pos.maxId为空表示整个会话已读 if (!pos.maxId || m.id <= pos.maxId) { - messages[idx] = { ...m, status: MESSAGE_STATUS.READED }; + m.status = MESSAGE_STATUS.READED chat.readedMessageIdx = idx; - changed = true; + chat.stored = false; } } } - if (changed) { - chat.messages = messages; - chat.stored = false; + if (!chat.stored) { this.saveToStorage(); } }, cleanMessage(idx) { - let chat = { ...this.chats[idx] }; + let chat = this.curChats[idx]; chat.lastContent = ''; chat.hotMinIdx = 0; chat.unreadCount = 0; chat.atMe = false; chat.atAll = false; - chat.stored = false; + chat.stored = false chat.messages = []; - this.chats[idx] = chat; this.saveToStorage(true); }, removeChat(idx) { - this.chats[idx] = { ...this.chats[idx], delete: true, stored: false }; + let chats = this.curChats; + chats[idx].delete = true; + chats[idx].stored = false; this.saveToStorage(); }, removePrivateChat(userId) { - this.chats = this.chats.map(chat => { - if (chat.type == 'PRIVATE' && chat.targetId == userId) { - return { ...chat, delete: true, stored: false }; + let chats = this.curChats; + for (let idx in chats) { + if (chats[idx].type == 'PRIVATE' && + chats[idx].targetId == userId) { + this.removeChat(idx); } - return chat; - }); - this.saveToStorage(); + } }, removeGroupChat(groupId) { - this.chats = this.chats.map(chat => { - if (chat.type == 'GROUP' && chat.targetId == groupId) { - return { ...chat, delete: true, stored: false }; + let chats = this.curChats; + for (let idx in chats) { + if (chats[idx].type == 'GROUP' && + chats[idx].targetId == groupId) { + this.removeChat(idx); } - return chat; - }); - this.saveToStorage(); + } }, moveTop(idx) { - if (this.loading) return; + if (this.loading) { + return; + } + let chats = this.curChats; if (idx > 0) { - let chats = [...this.chats]; - let chat = chats.splice(idx, 1)[0]; - chat = { ...chat, lastSendTime: new Date().getTime(), stored: false }; + let chat = chats[idx]; + chats.splice(idx, 1); chats.unshift(chat); - this.chats = chats; + chat.lastSendTime = new Date().getTime(); + chat.stored = false; this.saveToStorage(); } }, insertMessage(msgInfo, chatInfo) { + // 获取对方id或群id let type = chatInfo.type; + // 记录消息的最大id if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) { this.privateMsgMaxId = msgInfo.id; } if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) { this.groupMsgMaxId = msgInfo.id; } - + // 如果是已存在消息,则覆盖旧的消息数据 let chat = this.findChat(chatInfo); - if (!chat) return; - let message = this.findMessage(chat, msgInfo); if (message) { - chat.messages = chat.messages.map(m => - (m.id === msgInfo.id || m.tmpId === msgInfo.tmpId) ? { ...m, ...msgInfo } : m - ); + console.log("message:", message) + Object.assign(message, msgInfo); chat.stored = false; this.saveToStorage(); return; } - - chat = { ...chat }; - if (msgInfo.type == MESSAGE_TYPE.IMAGE) chat.lastContent = "[图片]"; - else if (msgInfo.type == MESSAGE_TYPE.FILE) chat.lastContent = "[文件]"; - else if (msgInfo.type == MESSAGE_TYPE.AUDIO) chat.lastContent = "[语音]"; - else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) chat.lastContent = "[语音通话]"; - else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) chat.lastContent = "[视频通话]"; - else if (msgInfo.type == MESSAGE_TYPE.TEXT || msgInfo.type == MESSAGE_TYPE.RECALL || msgInfo.type == MESSAGE_TYPE.TIP_TEXT) { + // 会话列表内容 + if (msgInfo.type == MESSAGE_TYPE.IMAGE) { + chat.lastContent = "[图片]"; + } else if (msgInfo.type == MESSAGE_TYPE.FILE) { + chat.lastContent = "[文件]"; + } else if (msgInfo.type == MESSAGE_TYPE.AUDIO) { + chat.lastContent = "[语音]"; + } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) { + chat.lastContent = "[语音通话]"; + } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) { + chat.lastContent = "[视频通话]"; + } else if (msgInfo.type == MESSAGE_TYPE.TEXT || + msgInfo.type == MESSAGE_TYPE.RECALL || + msgInfo.type == MESSAGE_TYPE.TIP_TEXT) { chat.lastContent = msgInfo.content; } chat.lastSendTime = msgInfo.sendTime; chat.sendNickName = msgInfo.sendNickName; - - if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED && msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) { + // 未读加1 + if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED && + msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) { chat.unreadCount++; } - - if (!msgInfo.selfSend && chat.type == "GROUP" && msgInfo.atUserIds && msgInfo.status != MESSAGE_STATUS.READED) { + // 是否有人@我 + if (!msgInfo.selfSend && chat.type == "GROUP" && msgInfo.atUserIds && + msgInfo.status != MESSAGE_STATUS.READED) { const userStore = useUserStore(); let userId = userStore.userInfo.id; - if (msgInfo.atUserIds.indexOf(userId) >= 0) chat.atMe = true; - if (msgInfo.atUserIds.indexOf(-1) >= 0) chat.atAll = true; + if (msgInfo.atUserIds.indexOf(userId) >= 0) { + chat.atMe = true; + } + if (msgInfo.atUserIds.indexOf(-1) >= 0) { + chat.atAll = true; + } } - - let messages = [...chat.messages]; - if (!chat.lastTimeTip || chat.lastTimeTip < msgInfo.sendTime - 600 * 1000) { - messages.push({ sendTime: msgInfo.sendTime, type: MESSAGE_TYPE.TIP_TIME }); + // 间隔大于10分钟插入时间显示 + if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) { + chat.messages.push({ + sendTime: msgInfo.sendTime, + type: MESSAGE_TYPE.TIP_TIME, + }); chat.lastTimeTip = msgInfo.sendTime; } - messages.push(msgInfo); - chat.messages = messages; + // 插入消息 + chat.messages.push(msgInfo); chat.stored = false; - - this.chats = this.chats.map(c => c.type === chat.type && c.targetId === chat.targetId ? chat : c); this.saveToStorage(); }, updateMessage(msgInfo, chatInfo) { + // 获取对方id或群id let chat = this.findChat(chatInfo); - if (!chat) return; - - this.chats = this.chats.map(c => { - if (c.type === chat.type && c.targetId === chat.targetId) { - return { - ...c, - messages: c.messages.map(m => - (m.id && m.id === msgInfo.id) || (m.tmpId && m.tmpId === msgInfo.tmpId) - ? { ...m, ...msgInfo } - : m - ), - stored: false - }; - } - return c; - }); - this.saveToStorage(); + let message = this.findMessage(chat, msgInfo); + if (message) { + // 属性拷贝 + Object.assign(message, msgInfo); + chat.stored = false; + this.saveToStorage(); + } }, deleteMessage(msgInfo, chatInfo) { + let isColdMessage = false; let chat = this.findChat(chatInfo); - if (!chat) return; - - this.chats = this.chats.map(c => { - if (c.type === chat.type && c.targetId === chat.targetId) { - let messages = c.messages.filter(m => - !(m.id && m.id === msgInfo.id) && !(m.tmpId && m.tmpId === msgInfo.tmpId) - ); - return { ...c, messages, stored: false }; + let delIdx = -1; + for (let idx in chat.messages) { + // 已经发送成功的,根据id删除 + if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) { + delIdx = idx; + break; } - return c; - }); - this.saveToStorage(); + // 正在发送中的消息可能没有id,只有临时id + if (chat.messages[idx].tmpId && chat.messages[idx].tmpId == msgInfo.tmpId) { + delIdx = idx; + break; + } + } + if (delIdx >= 0) { + chat.messages.splice(delIdx, 1); + if (delIdx < chat.hotMinIdx) { + isColdMessage = true; + chat.hotMinIdx--; + } + if (delIdx < chat.readedMessageIdx) { + chat.readedMessageIdx--; + } + chat.stored = false; + this.saveToStorage(isColdMessage); + } }, recallMessage(msgInfo, chatInfo) { let chat = this.findChat(chatInfo); if (!chat) return; + let isColdMessage = false; + // 要撤回的消息id let id = msgInfo.content; let name = msgInfo.selfSend ? '你' : chat.type == 'PRIVATE' ? '对方' : msgInfo.sendNickName; - - this.chats = this.chats.map(c => { - if (c.type === chat.type && c.targetId === chat.targetId) { - let messages = c.messages.map(m => { - if (m.id && m.id == id) { - return { - ...m, - status: MESSAGE_STATUS.RECALL, - content: name + "撤回了一条消息", - type: MESSAGE_TYPE.TIP_TEXT - }; - } - if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) { - return { - ...m, - quoteMessage: { ...m.quoteMessage, content: "引用内容已撤回", status: MESSAGE_STATUS.RECALL, type: MESSAGE_TYPE.TIP_TEXT } - }; - } - return m; - }); - return { ...c, messages, stored: false }; + for (let idx in chat.messages) { + let m = chat.messages[idx]; + if (m.id && m.id == id) { + // 改造成一条提示消息 + m.status = MESSAGE_STATUS.RECALL; + m.content = name + "撤回了一条消息"; + m.type = MESSAGE_TYPE.TIP_TEXT + // 会话列表 + chat.lastContent = m.content; + chat.lastSendTime = msgInfo.sendTime; + chat.sendNickName = ''; + if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) { + chat.unreadCount++; + } + isColdMessage = idx < chat.hotMinIdx; } - return c; - }); - this.saveToStorage(); + // 被引用的消息也要撤回 + if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) { + m.quoteMessage.content = "引用内容已撤回"; + m.quoteMessage.status = MESSAGE_STATUS.RECALL; + m.quoteMessage.type = MESSAGE_TYPE.TIP_TEXT + } + } + chat.stored = false; + this.saveToStorage(isColdMessage); }, updateChatFromFriend(friend) { - this.chats = this.chats.map(chat => { - if (chat.type === 'PRIVATE' && chat.targetId === friend.id) { - return { ...chat, headImage: friend.headImage, showName: friend.nickName, stored: false }; - } - return chat; - }); - this.saveToStorage(); + let chat = this.findChatByFriend(friend.id) + if (chat && (chat.headImage != friend.headImage || + chat.showName != friend.nickName)) { + // 更新会话中的群名和头像 + chat.headImage = friend.headImage; + chat.showName = friend.nickName; + chat.stored = false; + this.saveToStorage(); + } }, updateChatFromUser(user) { - this.chats = this.chats.map(chat => { - if (chat.type === 'PRIVATE' && chat.targetId === user.id) { - return { ...chat, headImage: user.headImageThumb, showName: user.nickName, stored: false }; - } - return chat; - }); - this.saveToStorage(); + let chat = this.findChatByFriend(user.id); + // 更新会话中的昵称和头像 + if (chat && (chat.headImage != user.headImageThumb || + chat.showName != user.nickName)) { + chat.headImage = user.headImageThumb; + chat.showName = user.nickName; + chat.stored = false; + this.saveToStorage(); + } }, updateChatFromGroup(group) { - this.chats = this.chats.map(chat => { - if (chat.type === 'GROUP' && chat.targetId === group.id) { - return { ...chat, headImage: group.headImageThumb, showName: group.showGroupName, stored: false }; - } - return chat; - }); - this.saveToStorage(); + let chat = this.findChatByGroup(group.id); + if (chat && (chat.headImage != group.headImageThumb || + chat.showName != group.showGroupName)) { + // 更新会话中的群名称和头像 + chat.headImage = group.headImageThumb; + chat.showName = group.showGroupName; + chat.stored = false; + this.saveToStorage(); + } }, setLoading(loading) { this.loading = loading; }, setDnd(chatInfo, isDnd) { - this.chats = this.chats.map(chat => { - if (chat.type === chatInfo.type && chat.targetId === chatInfo.targetId) { - return { ...chat, isDnd }; - } - return chat; - }); + let chat = this.findChat(chatInfo); + if (chat) { + chat.isDnd = isDnd; + } }, refreshChats() { let chats = cacheChats || this.chats; + // 更新会话免打扰状态 const friendStore = useFriendStore(); const groupStore = useGroupStore(); chats.forEach(chat => { if (chat.type == 'PRIVATE') { let friend = friendStore.findFriend(chat.targetId); - if (friend) chat.isDnd = friend.isDnd; + if (friend) { + chat.isDnd = friend.isDnd + } } else if (chat.type == 'GROUP') { let group = groupStore.findGroup(chat.targetId); - if (group) chat.isDnd = group.isDnd; + if (group) { + chat.isDnd = group.isDnd + } } - }); + }) + // 排序 chats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); + // #ifndef APP-PLUS + // h5和小程序的stroge一般只有5m,大约只能存储1w条消息,所以可能需要清理部分历史消息 + const storageInfo = uni.getStorageInfoSync(); + console.log(`storage缓存: ${storageInfo.currentSize} KB`) + // 空间不足(大于3mb)时,清理这个设备登录过其他账户的消息 + if (storageInfo && storageInfo.currentSize > 3000) { + console.log("storage空间不足,清理其他用户缓存..") + this.cleanOtherUserCache(); + } + // 保证消息总数量不超过3000条,每个会话不超过500条 + this.fliterMessage(chats, 3000, 500); + // #endif + // 记录热数据索引位置 + chats.forEach(chat => { + if (!chat.hotMinIdx || chat.hotMinIdx != chat.messages.length) { + chat.hotMinIdx = chat.messages.length; + chat.stored = false; + } + }); + // 将消息一次性装载回来 this.chats = chats; + // 清空缓存,不再使用 cacheChats = null; + // 消息持久化 this.saveToStorage(true); }, fliterMessage(chats, maxTotalSize, maxPerChatSize) { + // 每个会话只保留maxPerChatSize条消息 let remainTotalSize = 0; chats.forEach(chat => { if (chat.messages.length > maxPerChatSize) { @@ -324,10 +389,13 @@ export default defineStore('chatStore', { chat.messages = chat.messages.slice(idx); } remainTotalSize += chat.messages.length; - }); + }) + // 保证消息总数不超过maxTotalSize条,否则继续清理 if (remainTotalSize > maxTotalSize) { this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2); } + console.log("消息留存总数量:", remainTotalSize) + console.log("单会话消息数量:", maxPerChatSize) }, cleanOtherUserCache() { const userStore = useUserStore(); @@ -335,103 +403,165 @@ export default defineStore('chatStore', { const prefix = "chats-app-" + userId; const res = uni.getStorageInfoSync(); res.keys.forEach(key => { + // 清理其他用户的消息 if (key.startsWith("chats-app") && !key.startsWith(prefix)) { uni.removeStorageSync(key); + console.log("清理key:", key) } - }); + }) }, saveToStorage(withColdMessage) { - if (this.loading) return; + // 加载中不保存,防止卡顿 + if (this.loading) { + return; + } const userStore = useUserStore(); let userId = userStore.userInfo.id; let key = "chats-app-" + userId; let chatKeys = []; + // 按会话为单位存储,只存储有改动的会话 this.chats.forEach((chat) => { - let chatKey = `${key}-${chat.type}-${chat.targetId}`; + let chatKey = `${key}-${chat.type}-${chat.targetId}` if (!chat.stored) { if (chat.delete) { uni.removeStorageSync(chatKey); } else { + // 存储冷数据 if (withColdMessage) { - let coldChat = { ...chat }; + let coldChat = Object.assign({}, chat); coldChat.messages = chat.messages.slice(0, chat.hotMinIdx); - uni.setStorageSync(chatKey, coldChat); + uni.setStorageSync(chatKey, coldChat) } + // 存储热消息 let hotKey = chatKey + '-hot'; - let hotChat = { ...chat }; - hotChat.messages = chat.messages.slice(chat.hotMinIdx); + let hotChat = Object.assign({}, chat); + hotChat.messages = chat.messages.slice(chat.hotMinIdx) uni.setStorageSync(hotKey, hotChat); } chat.stored = true; } - if (!chat.delete) chatKeys.push(chatKey); - }); + if (!chat.delete) { + chatKeys.push(chatKey); + } + }) + // 会话核心信息 let chatsData = { privateMsgMaxId: this.privateMsgMaxId, groupMsgMaxId: this.groupMsgMaxId, chatKeys: chatKeys - }; - uni.setStorageSync(key, chatsData); - this.chats = this.chats.filter(chat => !chat.delete); + } + uni.setStorageSync(key, chatsData) + // 清理已删除的会话 + this.chats = this.chats.filter(chat => !chat.delete) }, - clear() { + clear(state) { cacheChats = []; this.chats = []; this.privateMsgMaxId = 0; this.groupMsgMaxId = 0; + this.loadingPrivateMsg = false; + this.loadingGroupMsg = false; }, loadChat() { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { let userStore = useUserStore(); let userId = userStore.userInfo.id; - let chatsData = uni.getStorageSync("chats-app-" + userId); - if (chatsData && chatsData.chatKeys) { - chatsData.chats = []; - chatsData.chatKeys.forEach(key => { - let coldChat = uni.getStorageSync(key); - let hotChat = uni.getStorageSync(key + '-hot'); - if (!coldChat && !hotChat) return; - if (hotChat) { - hotChat.messages.forEach(msg => { - if (msg.status == MESSAGE_STATUS.SENDING) msg.status = MESSAGE_STATUS.FAILED; - }); - } - let chat = { ...coldChat, ...hotChat }; - if (hotChat && coldChat) { - chat.messages = [...(coldChat.messages || []), ...(hotChat.messages || [])]; - } - chat.readedMessageIdx = chat.readedMessageIdx || 0; - chatsData.chats.push(chat); - }); + let chatsData = uni.getStorageSync("chats-app-" + userId) + if (chatsData) { + if (chatsData.chatKeys) { + chatsData.chats = []; + chatsData.chatKeys.forEach(key => { + let coldChat = uni.getStorageSync(key); + let hotChat = uni.getStorageSync(key + '-hot'); + if (!coldChat && !hotChat) { + return; + } + // 防止消息一直处在发送中状态 + hotChat && hotChat.messages.forEach(msg => { + if (msg.status == MESSAGE_STATUS.SENDING) { + msg.status = MESSAGE_STATUS.FAILED + } + }) + // 冷热消息合并 + let chat = Object.assign({}, coldChat, hotChat); + if (hotChat && coldChat) { + chat.messages = coldChat.messages.concat(hotChat.messages) + } + // 历史版本没有readedMessageIdx字段,做兼容一下 + chat.readedMessageIdx = chat.readedMessageIdx || 0; + chatsData.chats.push(chat); + }) + } this.initChats(chatsData); } - resolve(); - }); + resolve() + }) } }, getters: { - curChats: (state) => state.chats, // ✅ 修复 + curChats: (state) => { + if (cacheChats && state.loading) { + return cacheChats; + } + return state.chats; + }, findChatIdx: (state) => (chat) => { - return state.chats.findIndex(c => c.type == chat.type && c.targetId === chat.targetId); + let chats = state.curChats; + for (let idx in chats) { + if (chats[idx].type == chat.type && + chats[idx].targetId === chat.targetId) { + chat = state.chats[idx]; + return idx; + } + } }, findChat: (state) => (chat) => { - return state.chats.find(c => c.type == chat.type && c.targetId === chat.targetId); + let chats = state.curChats; + let idx = state.findChatIdx(chat); + return chats[idx]; }, findChatByFriend: (state) => (fid) => { - return state.chats.find(c => c.type == 'PRIVATE' && c.targetId == fid); + return state.curChats.find(chat => chat.type == 'PRIVATE' && + chat.targetId == fid) }, findChatByGroup: (state) => (gid) => { - return state.chats.find(c => c.type == 'GROUP' && c.targetId == gid); + return state.curChats.find(chat => chat.type == 'GROUP' && + chat.targetId == gid) }, findMessage: (state) => (chat, msgInfo) => { - if (!chat) return null; + if (!chat) { + return null; + } + // 通过id判断 if (msgInfo.id) { - return chat.messages.find(m => m.id === msgInfo.id); + for (let idx = chat.messages.length - 1; idx >= 0; idx--) { + let m = chat.messages[idx]; + if (m.id && msgInfo.id == m.id) { + return m; + } + // 如果id比要查询的消息小,说明没有这条消息 + if (m.id && m.id < msgInfo.id) { + break; + } + } } + // 正在发送中的临时消息可能没有id,只有tmpId if (msgInfo.selfSend && msgInfo.tmpId) { - return chat.messages.find(m => m.tmpId === msgInfo.tmpId); + for (let idx = chat.messages.length - 1; idx >= 0; idx--) { + let m = chat.messages[idx]; + if (!m.selfSend || !m.tmpId) { + continue; + } + if (msgInfo.tmpId == m.tmpId) { + return m; + } + // 如果id比要查询的消息小,说明没有这条消息 + if (m.tmpId && m.tmpId < msgInfo.tmpId) { + break; + } + } } return null; } } -}); +}); \ No newline at end of file