From 62fc8c7caf1b793336360edf0d42cf4673a8f30c Mon Sep 17 00:00:00 2001 From: "[yxf]" <[1524240689@qq.com]> Date: Tue, 21 Apr 2026 16:44:58 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=8A=9F=E8=83=BD=E5=8F=AF=E4=BB=A5=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E8=B4=A6=E5=8F=B7=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=92=A4?= =?UTF-8?q?=E5=9B=9E=E6=B6=88=E6=81=AF=E8=AF=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../implatform/controller/UserController.java | 37 +++- im-uniapp/App.vue | 170 +++++++++--------- im-uniapp/pages/chat/chat-box.vue | 18 ++ im-uniapp/static/i18n/ara.json | 6 +- im-uniapp/static/i18n/de.json | 6 +- im-uniapp/static/i18n/en.json | 7 +- im-uniapp/static/i18n/fra.json | 6 +- im-uniapp/static/i18n/jp.json | 34 ++-- im-uniapp/static/i18n/kor.json | 34 ++-- im-uniapp/static/i18n/pt.json | 6 +- im-uniapp/static/i18n/ru.json | 22 ++- im-uniapp/static/i18n/vie.json | 14 +- im-uniapp/static/i18n/zh.json | 9 +- im-uniapp/store/chatStore.js | 147 +++++---------- .../components/account/AccountSwitchMenu.vue | 128 ++++++------- 15 files changed, 334 insertions(+), 310 deletions(-) diff --git a/im-platform/src/main/java/com/bx/implatform/controller/UserController.java b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java index e6827c2..963f0d9 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/UserController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java @@ -304,5 +304,40 @@ public class UserController { return ResultUtils.success(vo); } -} + @PostMapping("/deleteSwitchAccount") + @Operation(summary = "删除可切换账号", description = "从当前客服的可切换列表中移除指定账号") + public Result deleteSwitchAccount(@RequestBody JSONObject jsonObject) { + Long targetUserId = jsonObject.getLong("targetUserId"); + if (ObjectUtil.isNull(targetUserId)) { + return ResultUtils.error(XSS_PARAM_ERROR, "参数错误"); + } + + UserSession session = SessionContext.getSession(); + Long currentUserId = session.getUserId(); + User currentUser = userService.getById(currentUserId); + if (currentUser == null) { + return ResultUtils.error(XSS_PARAM_ERROR); + } + + // 取出可切换账号并移除目标ID + String switchableIdsStr = currentUser.getSwitchableAccountIds(); + if (StrUtil.isBlank(switchableIdsStr)) { + return ResultUtils.success("移除成功"); + } + + List idList = Arrays.stream(switchableIdsStr.split(",")) + .filter(StrUtil::isNotBlank) + .map(Long::parseLong) + .filter(id -> !id.equals(targetUserId)) + .collect(Collectors.toList()); + + String newIds = idList.stream().map(String::valueOf).collect(Collectors.joining(",")); + + // 更新到数据库 + currentUser.setSwitchableAccountIds(newIds); + userService.updateById(currentUser); + + return ResultUtils.success("移除成功"); + } +} \ No newline at end of file diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue index f6cfd3b..3443174 100644 --- a/im-uniapp/App.vue +++ b/im-uniapp/App.vue @@ -99,26 +99,37 @@ export default { }); }, // 处理客服变更 + // 处理客服变更【终极修复版】 handleCustomerChanged(msgInfo) { - console.log('客服已变更,刷新聊天记录:', msgInfo); - - // 刷新好友列表(会获取最新的客服信息) + console.log('【客服转接】后台下发变更通知', msgInfo); + // msgInfo 里自带:oldKfId 旧客服id、newKfId 新客服id + const oldKfId = msgInfo.oldKfId; + const newKfId = msgInfo.newKfId; + + // ========== 强制合并:旧客服所有消息 -> 新客服 ========== + if (oldKfId && newKfId && oldKfId != newKfId) { + const oldChat = this.chatStore.findChatByFriend(oldKfId); + const newChat = this.chatStore.findChatByFriend(newKfId); + + if (oldChat && oldChat.messages.length > 0) { + console.log('【开始强制合并】旧客服', oldKfId, '→ 新客服', newKfId); + // 执行合并 + this.chatStore.mergeOldCustomerToNew(oldKfId, newKfId); + } + } + + // 原有刷新逻辑保留 this.friendStore.loadFriend().then(() => { - // 刷新聊天列表 this.chatStore.refreshChats(); - - // 如果当前正在聊天页面,重新加载消息 + const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; - if (currentPage.route === 'pages/chat/chat-box') { - // 重新加载当前会话的消息 const targetId = currentPage.options.targetId; this.reloadChatMessages(targetId); } }); }, - // 重新加载聊天消息 reloadChatMessages(targetId) { // 找到对应的会话 @@ -201,77 +212,76 @@ export default { }) }, handlePrivateMessage(msg) { - // 标记这条消息是不是自己发的 - msg.selfSend = msg.sendId == this.userStore.userInfo.id; - // 好友id - let friendId = msg.selfSend ? msg.recvId : msg.sendId; - // 检查是否为未知用户(只检查收到的消息) - let existingFriend = this.friendStore.findFriend(friendId); - if (!existingFriend && !msg.selfSend) { - console.log("收到未知用户消息,刷新应用:", friendId); - - // 重新加载数据 - this.loadStore().then(() => { - // 刷新整个应用 - // #ifdef H5 - window.location.reload(); - // #endif - - // #ifdef APP-PLUS - plus.runtime.restart(); - // #endif - - // #ifdef MP-WEIXIN - // 小程序重新启动 - uni.reLaunch({ - url: '/pages/chat/chat' - }); - // #endif - }); - - return; - } - // 会话信息 - let chatInfo = { - type: 'PRIVATE', - targetId: friendId - } - // 消息已读处理,清空已读数量 - if (msg.type == enums.MESSAGE_TYPE.READED) { - this.chatStore.resetUnreadCount(chatInfo); - return; - } - // 消息回执处理,改消息状态为已读 - if (msg.type == enums.MESSAGE_TYPE.RECEIPT) { - this.chatStore.readedMessage({ - friendId: msg.sendId - }) - return; - } - // 消息撤回 - if (msg.type == enums.MESSAGE_TYPE.RECALL) { - this.chatStore.recallMessage(msg, chatInfo); - return; - } - // 新增好友 - if (msg.type == enums.MESSAGE_TYPE.FRIEND_NEW) { - this.friendStore.addFriend(JSON.parse(msg.content)); - return; - } - // 删除好友 - if (msg.type == enums.MESSAGE_TYPE.FRIEND_DEL) { - this.friendStore.removeFriend(friendId); - return; - } - // 对好友设置免打扰 - if (msg.type == enums.MESSAGE_TYPE.FRIEND_DND) { - this.friendStore.setDnd(friendId, JSON.parse(msg.content)); - this.chatStore.setDnd(chatInfo, JSON.parse(msg.content)); - return; - } - // 消息插入 - let friend = this.loadFriendInfo(friendId); - this.insertPrivateMessage(friend, msg); + // 标记这条消息是不是自己发的 + msg.selfSend = msg.sendId == this.userStore.userInfo.id; + // 好友id + let friendId = msg.selfSend ? msg.recvId : msg.sendId; + + + // 检查是否为未知用户(只检查收到的消息) + let existingFriend = this.friendStore.findFriend(friendId); + if (!existingFriend && !msg.selfSend) { + console.log("收到未知用户消息,刷新应用:", friendId); + + this.loadStore().then(() => { + // #ifdef H5 + window.location.reload(); + // #endif + + // #ifdef APP-PLUS + plus.runtime.restart(); + // #endif + + // #ifdef MP-WEIXIN + uni.reLaunch({ + url: '/pages/chat/chat' + }); + // #endif + }); + + return; + } + // 会话信息 + let chatInfo = { + type: 'PRIVATE', + targetId: friendId + } + // 消息已读处理,清空已读数量 + if (msg.type == enums.MESSAGE_TYPE.READED) { + this.chatStore.resetUnreadCount(chatInfo); + return; + } + // 消息回执处理,改消息状态为已读 + if (msg.type == enums.MESSAGE_TYPE.RECEIPT) { + this.chatStore.readedMessage({ + friendId: msg.sendId + }) + return; + } + // 消息撤回 + if (msg.type == enums.MESSAGE_TYPE.RECALL) { + this.chatStore.recallMessage(msg, chatInfo); + return; + } + // 新增好友 + if (msg.type == enums.MESSAGE_TYPE.FRIEND_NEW) { + this.friendStore.addFriend(JSON.parse(msg.content)); + return; + } + // 删除好友 + if (msg.type == enums.MESSAGE_TYPE.FRIEND_DEL) { + this.friendStore.removeFriend(friendId); + return; + } + // 对好友设置免打扰 + if (msg.type == enums.MESSAGE_TYPE.FRIEND_DND) { + this.friendStore.setDnd(friendId, JSON.parse(msg.content)); + this.chatStore.setDnd(chatInfo, JSON.parse(msg.content)); + return; + } + // 插入消息 + let friend = this.loadFriendInfo(friendId); + this.insertPrivateMessage(friend, msg); }, insertPrivateMessage(friend, msg) { // 单人视频信令 diff --git a/im-uniapp/pages/chat/chat-box.vue b/im-uniapp/pages/chat/chat-box.vue index 2de99fe..e1345f8 100644 --- a/im-uniapp/pages/chat/chat-box.vue +++ b/im-uniapp/pages/chat/chat-box.vue @@ -1592,6 +1592,7 @@ export default { }, }, async onLoad(options) { + try { this.currentLang = uni.getStorageSync("app_language") || "zh"; uni.showLoading({ title: this.$t('common.loading'), mask: true }); @@ -1658,6 +1659,23 @@ export default { await this.getSetting(); this.$nextTick(() => this.scrollToBottom()); + this.$socket.on('customer_transfer', (data) => { + /** + * data后端返回格式: + * { oldKfId: 旧客服ID, newKfId: 新客服ID } + */ + const { oldKfId, newKfId } = data; + if (!oldKfId || !newKfId) return; + + // 直接调用chatStore转接合并方法 + this.chatStore.mergeOldCustomerToNew(oldKfId, newKfId); + console.log(data); + // 转接完成后刷新当前聊天、滚动到底部 + this.currentTargetId = newKfId; + this.$nextTick(() => { + this.scrollToBottom(); + }); + }) } catch (err) { console.error("错误:", err); } finally { diff --git a/im-uniapp/static/i18n/ara.json b/im-uniapp/static/i18n/ara.json index b48cbf2..3b9a720 100644 --- a/im-uniapp/static/i18n/ara.json +++ b/im-uniapp/static/i18n/ara.json @@ -33,7 +33,11 @@ "album": "ألبوم", "camera": "كاميرا", "noFriends": "لا أصدقاء", - "title": "دردشة" + "title": "دردشة", + "you": "أنت", + "other": "الطرف الآخر", + "recalledMessage": "قام بتراجع رسالة", + "quoteRecalled": "تم تراجع المحتوى المقتبس" }, "common": { "confirm": "موافق", diff --git a/im-uniapp/static/i18n/de.json b/im-uniapp/static/i18n/de.json index 681ff31..e73d6d8 100644 --- a/im-uniapp/static/i18n/de.json +++ b/im-uniapp/static/i18n/de.json @@ -33,7 +33,11 @@ "album": "Album", "camera": "Kamera", "noFriends": "Keine Freunde", - "title": "Chat" + "title": "Chat", + "you": "Du", + "other": "Gegenüber", + "recalledMessage": "hat eine Nachricht zurückgenommen", + "quoteRecalled": "Zitat zurückgenommen" }, "common": { "confirm": "OK", diff --git a/im-uniapp/static/i18n/en.json b/im-uniapp/static/i18n/en.json index 27bf543..1578e03 100644 --- a/im-uniapp/static/i18n/en.json +++ b/im-uniapp/static/i18n/en.json @@ -7,7 +7,6 @@ "read": "Read", "unread": "Unread", "readedCount": "{count} read", - "guessWantAsk": "You may want to ask", "typing": "typing...", "autoReplyFailed": "Auto reply failed", @@ -34,7 +33,11 @@ "album": "Album", "camera": "Camera", "noFriends": "No friends", - "title": "Chat" + "title": "Chat", + "you": "You", + "other": "Other", + "recalledMessage": "recalled a message", + "quoteRecalled": "Quote recalled" }, "common": { "confirm": "OK", diff --git a/im-uniapp/static/i18n/fra.json b/im-uniapp/static/i18n/fra.json index 628ebea..73f94bf 100644 --- a/im-uniapp/static/i18n/fra.json +++ b/im-uniapp/static/i18n/fra.json @@ -33,7 +33,11 @@ "album": "Album", "camera": "Caméra", "noFriends": "Pas d'amis", - "title": "Discussion" + "title": "Discussion", + "you": "Vous", + "other": "Interlocuteur", + "recalledMessage": "a rappelé un message", + "quoteRecalled": "Citation rappelée" }, "common": { "confirm": "OK", diff --git a/im-uniapp/static/i18n/jp.json b/im-uniapp/static/i18n/jp.json index ca82e0b..e0db9bc 100644 --- a/im-uniapp/static/i18n/jp.json +++ b/im-uniapp/static/i18n/jp.json @@ -9,31 +9,35 @@ "readedCount": "{count}人が既読", "guessWantAsk": "質問したいこと", "typing": "入力中...", - "autoReplyFailed": "自動返信失敗", + "autoReplyFailed": "自動返信に失敗しました", "receiptMessage": "【既読確認】", "inputPlaceholder": "メッセージを入力", "send": "送信", "newMessages": "{count}件の新着メッセージ", - "cannotSendBlank": "空白メッセージは送信できません", - "copySuccess": "コピー成功", - "copyFailed": "コピー失敗", - "downloadFailed": "ダウンロード失敗", - "deleteMessage": "メッセージ削除", + "cannotSendBlank": "空メッセージは送信できません", + "copySuccess": "コピーしました", + "copyFailed": "コピーに失敗", + "downloadFailed": "ダウンロードに失敗", + "deleteMessage": "メッセージを削除", "confirmDelete": "このメッセージを削除しますか?", - "recallMessage": "メッセージ取り消し", + "recallMessage": "メッセージを取り消す", "confirmRecall": "このメッセージを取り消しますか?", - "deleteSuccess": "削除成功", - "resendNotSupported": "再送信に対応していません", - "sendFailed": "送信失敗", - "uploadFailed": "アップロード失敗", - "userBanned": "発言禁止されています: {reason}", - "groupBanned": "グループで発言禁止: {reason}", + "deleteSuccess": "削除しました", + "resendNotSupported": "このメッセージの再送信に対応していません", + "sendFailed": "送信に失敗", + "uploadFailed": "アップロードに失敗", + "userBanned": "発言を禁止されています:{reason}", + "groupBanned": "グループで発言を禁止されています:{reason}", "atAll": "全員", "file": "ファイル", "album": "アルバム", "camera": "カメラ", - "noFriends": "友達なし", - "title": "チャット" + "noFriends": "友達がいません", + "title": "チャット", + "you": "あなた", + "other": "相手", + "recalledMessage": "メッセージを取り消しました", + "quoteRecalled": "引用メッセージは取り消されました" }, "common": { "confirm": "確定", diff --git a/im-uniapp/static/i18n/kor.json b/im-uniapp/static/i18n/kor.json index 95dd003..56cc169 100644 --- a/im-uniapp/static/i18n/kor.json +++ b/im-uniapp/static/i18n/kor.json @@ -1,39 +1,43 @@ { "chat": { "copy": "복사", - "recall": "취소", + "recall": "회수", "delete": "삭제", - "download": "다운로드 및 열기", + "download": "다운로드 후 열기", "read": "읽음", - "unread": "안읽음", + "unread": "읽지 않음", "readedCount": "{count}명 읽음", - "guessWantAsk": "물어보고 싶은 것", + "guessWantAsk": "물어볼 내용", "typing": "입력 중...", "autoReplyFailed": "자동 회신 실패", "receiptMessage": "【읽음 확인】", - "inputPlaceholder": "메시지 입력", + "inputPlaceholder": "메시지를 입력하세요", "send": "전송", "newMessages": "{count}개의 새 메시지", "cannotSendBlank": "빈 메시지는 보낼 수 없습니다", - "copySuccess": "복사 성공", + "copySuccess": "복사 완료", "copyFailed": "복사 실패", "downloadFailed": "다운로드 실패", "deleteMessage": "메시지 삭제", - "confirmDelete": "이 메시지를 삭제할까요?", - "recallMessage": "메시지 취소", - "confirmRecall": "이 메시지를 취소할까요?", - "deleteSuccess": "삭제 성공", - "resendNotSupported": "재전송 지원 안됨", + "confirmDelete": "메시지를 삭제할까요?", + "recallMessage": "메시지 회수", + "confirmRecall": "메시지를 회수할까요?", + "deleteSuccess": "삭제 완료", + "resendNotSupported": "해당 메시지는 재전송할 수 없습니다", "sendFailed": "전송 실패", "uploadFailed": "업로드 실패", - "userBanned": "채팅 금지: {reason}", - "groupBanned": "그룹 채팅 금지: {reason}", + "userBanned": "채팅이 제한되었습니다: {reason}", + "groupBanned": "그룹 채팅이 제한되었습니다: {reason}", "atAll": "모두", "file": "파일", "album": "앨범", "camera": "카메라", - "noFriends": "친구 없음", - "title": "채팅" + "noFriends": "친구가 없습니다", + "title": "채팅", + "you": "당신", + "other": "상대방", + "recalledMessage": "메시지를 회수했습니다", + "quoteRecalled": "인용 내용이 회수되었습니다" }, "common": { "confirm": "확인", diff --git a/im-uniapp/static/i18n/pt.json b/im-uniapp/static/i18n/pt.json index 6dcd65a..69a394e 100644 --- a/im-uniapp/static/i18n/pt.json +++ b/im-uniapp/static/i18n/pt.json @@ -33,7 +33,11 @@ "album": "Álbum", "camera": "Câmera", "noFriends": "Sem amigos", - "title": "Chat" + "title": "Chat", + "you": "Você", + "other": "Outro", + "recalledMessage": "retirou uma mensagem", + "quoteRecalled": "Citação retirada" }, "common": { "confirm": "OK", diff --git a/im-uniapp/static/i18n/ru.json b/im-uniapp/static/i18n/ru.json index 7b58f6c..7130f9d 100644 --- a/im-uniapp/static/i18n/ru.json +++ b/im-uniapp/static/i18n/ru.json @@ -6,41 +6,45 @@ "download": "Скачать и открыть", "read": "Прочитано", "unread": "Не прочитано", - "readedCount": "{count} человек прочитало", + "readedCount": "{count} человек прочитали", "guessWantAsk": "Возможно, вы хотите спросить", "typing": "Печатает...", - "autoReplyFailed": "Автоответ не удался", + "autoReplyFailed": "Ошибка автоответа", "receiptMessage": "【Прочитано】", "inputPlaceholder": "Введите сообщение", "send": "Отправить", "newMessages": "{count} новых сообщений", - "cannotSendBlank": "Нельзя отправить пустое", + "cannotSendBlank": "Нельзя отправить пустое сообщение", "copySuccess": "Скопировано", "copyFailed": "Ошибка копирования", "downloadFailed": "Ошибка загрузки", "deleteMessage": "Удалить сообщение", - "confirmDelete": "Удалить это сообщение?", + "confirmDelete": "Удалить сообщение?", "recallMessage": "Отозвать сообщение", - "confirmRecall": "Отозвать это сообщение?", + "confirmRecall": "Отозвать сообщение?", "deleteSuccess": "Удалено", - "resendNotSupported": "Не поддерживается", + "resendNotSupported": "Повторная отправка не поддерживается", "sendFailed": "Ошибка отправки", "uploadFailed": "Ошибка загрузки", "userBanned": "Вы заблокированы: {reason}", - "groupBanned": "В группе блокировка: {reason}", + "groupBanned": "Заблокировано в группе: {reason}", "atAll": "Все", "file": "Файл", "album": "Альбом", "camera": "Камера", "noFriends": "Нет друзей", - "title": "Чат" + "title": "Чат", + "you": "Вы", + "other": "Собеседник", + "recalledMessage": "отозвал сообщение", + "quoteRecalled": "Цитата отозвана" }, "common": { "confirm": "ОК", "cancel": "Отмена", "loading": "Загрузка...", "justNow": "Только что", - "minutesAgo": "мин назад", + "minutesAgo": "мин. назад", "yesterday": "Вчера" } } \ No newline at end of file diff --git a/im-uniapp/static/i18n/vie.json b/im-uniapp/static/i18n/vie.json index b930954..bae969f 100644 --- a/im-uniapp/static/i18n/vie.json +++ b/im-uniapp/static/i18n/vie.json @@ -19,21 +19,25 @@ "copyFailed": "Sao chép thất bại", "downloadFailed": "Tải thất bại", "deleteMessage": "Xóa tin nhắn", - "confirmDelete": "Xóa tin này?", + "confirmDelete": "Xóa tin nhắn này?", "recallMessage": "Thu hồi tin nhắn", - "confirmRecall": "Thu hồi tin này?", - "deleteSuccess": "Xóa thành công", + "confirmRecall": "Thu hồi tin nhắn này?", + "deleteSuccess": "Đã xóa", "resendNotSupported": "Không hỗ trợ gửi lại", "sendFailed": "Gửi thất bại", "uploadFailed": "Tải lên thất bại", - "userBanned": "Bạn bị cấm nói: {reason}", + "userBanned": "Bạn bị cấm trò chuyện: {reason}", "groupBanned": "Bị cấm trong nhóm: {reason}", "atAll": "Tất cả", "file": "Tệp", "album": "Thư viện", "camera": "Máy ảnh", "noFriends": "Chưa có bạn bè", - "title": "Trò chuyện" + "title": "Trò chuyện", + "you": "Bạn", + "other": "Người kia", + "recalledMessage": "đã thu hồi một tin nhắn", + "quoteRecalled": "Nội dung trích dẫn đã được thu hồi" }, "common": { "confirm": "Xác nhận", diff --git a/im-uniapp/static/i18n/zh.json b/im-uniapp/static/i18n/zh.json index e73316c..3a0e782 100644 --- a/im-uniapp/static/i18n/zh.json +++ b/im-uniapp/static/i18n/zh.json @@ -7,7 +7,6 @@ "read": "已读", "unread": "未读", "readedCount": "{count}人已读", - "guessWantAsk": "你可能想问", "typing": "正在输入...", "autoReplyFailed": "自动回复失败", @@ -24,7 +23,7 @@ "recallMessage": "撤回消息", "confirmRecall": "确定撤回此消息?", "deleteSuccess": "删除成功", - "resendNotSupported": "暂不支持重发该类型消息", + "resendNotSupported": "暂不支持重发该消息", "sendFailed": "发送失败", "uploadFailed": "上传失败", "userBanned": "你已被禁言,原因:{reason}", @@ -34,7 +33,11 @@ "album": "相册", "camera": "相机", "noFriends": "暂无好友", - "title": "聊天" + "title": "聊天", + "you": "你", + "other": "对方", + "recalledMessage": "撤回了一条消息", + "quoteRecalled": "引用内容已撤回" }, "common": { "confirm": "确定", diff --git a/im-uniapp/store/chatStore.js b/im-uniapp/store/chatStore.js index b7c73ae..c74c234 100644 --- a/im-uniapp/store/chatStore.js +++ b/im-uniapp/store/chatStore.js @@ -3,6 +3,7 @@ import { MESSAGE_TYPE, MESSAGE_STATUS } from '@/common/enums.js'; import useFriendStore from './friendStore.js'; import useGroupStore from './groupStore.js'; import useUserStore from './userStore'; +import { i18n } from '@/main.js' let cacheChats = []; export default defineStore('chatStore', { @@ -20,9 +21,7 @@ export default defineStore('chatStore', { 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); } @@ -31,28 +30,35 @@ export default defineStore('chatStore', { this.groupMsgMaxId = chatsData.groupMsgMaxId || 0; }, - // 客服转接:合并旧客服会话到新客服(你要的功能) mergeOldCustomerToNew(oldKfId, newKfId) { - const oldChat = this.findChatByFriend(oldKfId); - const newChat = this.findChatByFriend(newKfId); + const oldChat = this.findChatByFriend(oldKfId); + const newChat = this.findChatByFriend(newKfId); + if (!oldChat || !newChat) { + console.warn('合并失败:旧/新会话不存在', oldKfId, newKfId); + return; + } - if (!oldChat || !newChat) return; - - newChat.messages = [...oldChat.messages, ...newChat.messages]; + newChat.messages = [...oldChat.messages, ...newChat.messages]; - newChat.unreadCount += oldChat.unreadCount; - newChat.lastContent = oldChat.lastContent || newChat.lastContent; - newChat.lastSendTime = Math.max(oldChat.lastSendTime || 0, newChat.lastSendTime || 0); + newChat.unreadCount += oldChat.unreadCount; + newChat.lastContent = oldChat.lastContent || newChat.lastContent; + newChat.lastSendTime = Math.max(oldChat.lastSendTime || 0, newChat.lastSendTime || 0); + newChat.atMe = newChat.atMe || oldChat.atMe; + newChat.atAll = newChat.atAll || oldChat.atAll; - newChat.stored = false; - oldChat.stored = false; + const keepHot = 300; + const totalMsg = newChat.messages.length; + newChat.hotMinIdx = totalMsg <= keepHot ? 0 : Math.max(0, totalMsg - keepHot); + newChat.readedMessageIdx = Math.min(oldChat.readedMessageIdx || 0, newChat.readedMessageIdx || 0); - this.removePrivateChat(oldKfId); + this.clearCustomerCache(oldKfId); + this.removePrivateChat(oldKfId); - this.saveToStorage(true); + newChat.stored = false; + this.saveToStorage(true); + console.log('【合并完成】旧客服消息全部转入新客服', newChat.messages.length); }, - // 强制清理某个用户的本地缓存(清理旧客服) clearCustomerCache(userId) { const userStore = useUserStore(); const currentUserId = userStore.userInfo.id; @@ -68,12 +74,10 @@ export default defineStore('chatStore', { if (chats[idx].type == chatInfo.type && chats[idx].targetId === chatInfo.targetId) { chat = chats[idx]; - // 放置头部 this.moveTop(idx) break; } } - // 创建会话 if (chat == null) { chat = { targetId: chatInfo.targetId, @@ -121,7 +125,6 @@ export default defineStore('chatStore', { 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) { m.status = MESSAGE_STATUS.READED chat.readedMessageIdx = idx; @@ -183,63 +186,54 @@ export default defineStore('chatStore', { } }, insertMessage(msgInfo, chatInfo) { - // 获取对方id或群id + const t = i18n.global.t; 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); let message = this.findMessage(chat, msgInfo); if (message) { - console.log("message:", message) - - // ✅ 方式1:使用 Vue.set 或 $set 确保响应式更新 - // 先删除旧消息 - const msgIndex = chat.messages.findIndex(m => - (m.id && m.id === msgInfo.id) || - (m.tmpId && m.tmpId === msgInfo.tmpId) - ); - - if (msgIndex !== -1) { - // 创建新对象而不是修改原对象 - const updatedMessage = { ...message, ...msgInfo }; - // 使用 splice 替换,确保响应式 - chat.messages.splice(msgIndex, 1, updatedMessage); - } - - chat.stored = false; - this.saveToStorage(); - return; - } - // 会话列表内容 + const msgIndex = chat.messages.findIndex(m => + (m.id && m.id === msgInfo.id) || + (m.tmpId && m.tmpId === msgInfo.tmpId) + ); + if (msgIndex !== -1) { + const updatedMessage = { ...message, ...msgInfo }; + chat.messages.splice(msgIndex, 1, updatedMessage); + } + chat.stored = false; + this.saveToStorage(); + return; + } + + // ====================== 多语言消息类型 ====================== if (msgInfo.type == MESSAGE_TYPE.IMAGE) { - chat.lastContent = "[图片]"; + chat.lastContent = t('chat.image'); } else if (msgInfo.type == MESSAGE_TYPE.FILE) { - chat.lastContent = "[文件]"; + chat.lastContent = t('chat.file'); } else if (msgInfo.type == MESSAGE_TYPE.AUDIO) { - chat.lastContent = "[语音]"; + chat.lastContent = t('chat.voice'); } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) { - chat.lastContent = "[语音通话]"; + chat.lastContent = t('chat.voiceCall'); } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) { - chat.lastContent = "[视频通话]"; + chat.lastContent = t('chat.videoCall'); } 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; - // 未读加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) { const userStore = useUserStore(); @@ -251,7 +245,6 @@ export default defineStore('chatStore', { chat.atAll = true; } } - // 间隔大于10分钟插入时间显示 if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) { chat.messages.push({ sendTime: msgInfo.sendTime, @@ -259,17 +252,14 @@ export default defineStore('chatStore', { }); chat.lastTimeTip = msgInfo.sendTime; } - // 插入消息 chat.messages.push(msgInfo); chat.stored = false; this.saveToStorage(); }, updateMessage(msgInfo, chatInfo) { - // 获取对方id或群id let chat = this.findChat(chatInfo); let message = this.findMessage(chat, msgInfo); if (message) { - // 属性拷贝 Object.assign(message, msgInfo); chat.stored = false; this.saveToStorage(); @@ -280,12 +270,10 @@ export default defineStore('chatStore', { let chat = this.findChat(chatInfo); let delIdx = -1; for (let idx in chat.messages) { - // 已经发送成功的,根据id删除 if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) { delIdx = idx; break; } - // 正在发送中的消息可能没有id,只有临时id if (chat.messages[idx].tmpId && chat.messages[idx].tmpId == msgInfo.tmpId) { delIdx = idx; break; @@ -304,21 +292,20 @@ export default defineStore('chatStore', { this.saveToStorage(isColdMessage); } }, + recallMessage(msgInfo, chatInfo) { + const t = i18n.global.t; 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; + let name = msgInfo.selfSend ? t('chat.you') : (chat.type == 'PRIVATE' ? t('chat.other') : msgInfo.sendNickName); 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.content = `${name}${t('chat.recalledMessage')}`; m.type = MESSAGE_TYPE.TIP_TEXT - // 会话列表 chat.lastContent = m.content; chat.lastSendTime = msgInfo.sendTime; chat.sendNickName = ''; @@ -327,9 +314,8 @@ export default defineStore('chatStore', { } isColdMessage = idx < chat.hotMinIdx; } - // 被引用的消息也要撤回 if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) { - m.quoteMessage.content = "引用内容已撤回"; + m.quoteMessage.content = t('chat.quoteRecalled'); m.quoteMessage.status = MESSAGE_STATUS.RECALL; m.quoteMessage.type = MESSAGE_TYPE.TIP_TEXT } @@ -341,7 +327,6 @@ export default defineStore('chatStore', { 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; @@ -350,7 +335,6 @@ export default defineStore('chatStore', { }, updateChatFromUser(user) { let chat = this.findChatByFriend(user.id); - // 更新会话中的昵称和头像 if (chat && (chat.headImage != user.headImageThumb || chat.showName != user.nickName)) { chat.headImage = user.headImageThumb; @@ -363,7 +347,6 @@ export default defineStore('chatStore', { 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; @@ -381,7 +364,6 @@ export default defineStore('chatStore', { }, refreshChats() { let chats = cacheChats || this.chats; - // 更新会话免打扰状态 const friendStore = useFriendStore(); const groupStore = useGroupStore(); chats.forEach(chat => { @@ -397,36 +379,23 @@ export default defineStore('chatStore', { } } }) - // 排序 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) { @@ -435,12 +404,9 @@ export default defineStore('chatStore', { } remainTotalSize += chat.messages.length; }) - // 保证消息总数不超过maxTotalSize条,否则继续清理 if (remainTotalSize > maxTotalSize) { this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2); } - console.log("消息留存总数量:", remainTotalSize) - console.log("单会话消息数量:", maxPerChatSize) }, cleanOtherUserCache() { const userStore = useUserStore(); @@ -448,15 +414,12 @@ 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; } @@ -464,20 +427,17 @@ export default defineStore('chatStore', { let userId = userStore.userInfo.id; let key = "chats-app-" + userId; let chatKeys = []; - // 按会话为单位存储,只存储有改动的会话 this.chats.forEach((chat) => { let chatKey = `${key}-${chat.type}-${chat.targetId}` if (!chat.stored) { if (chat.delete) { uni.removeStorageSync(chatKey); } else { - // 存储冷数据 if (withColdMessage) { let coldChat = Object.assign({}, chat); coldChat.messages = chat.messages.slice(0, chat.hotMinIdx); uni.setStorageSync(chatKey, coldChat) } - // 存储热消息 let hotKey = chatKey + '-hot'; let hotChat = Object.assign({}, chat); hotChat.messages = chat.messages.slice(chat.hotMinIdx) @@ -489,14 +449,12 @@ export default defineStore('chatStore', { 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) }, clear(state) { @@ -521,18 +479,15 @@ export default defineStore('chatStore', { 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); }) @@ -577,20 +532,17 @@ export default defineStore('chatStore', { if (!chat) { return null; } - // 通过id判断 if (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) { for (let idx = chat.messages.length - 1; idx >= 0; idx--) { let m = chat.messages[idx]; @@ -600,7 +552,6 @@ export default defineStore('chatStore', { if (msgInfo.tmpId == m.tmpId) { return m; } - // 如果id比要查询的消息小,说明没有这条消息 if (m.tmpId && m.tmpId < msgInfo.tmpId) { break; } diff --git a/im-web/src/components/account/AccountSwitchMenu.vue b/im-web/src/components/account/AccountSwitchMenu.vue index ecd1ffb..c46230e 100644 --- a/im-web/src/components/account/AccountSwitchMenu.vue +++ b/im-web/src/components/account/AccountSwitchMenu.vue @@ -65,11 +65,13 @@ @click.stop="handleSwitch(account)"> 切换 - - + + 删除 + @@ -97,7 +99,6 @@ - -