Browse Source

Merge remote-tracking branch 'origin/master'

master
La123123 4 weeks ago
parent
commit
8a35b7e05a
  1. 79
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  2. 6
      im-platform/src/main/java/com/bx/implatform/entity/User.java
  3. 4
      im-platform/src/main/java/com/bx/implatform/vo/UserVO.java
  4. 158
      im-uniapp/App.vue
  5. 56
      im-uniapp/pages/chat/chat-box.vue
  6. 6
      im-uniapp/static/i18n/ara.json
  7. 6
      im-uniapp/static/i18n/de.json
  8. 7
      im-uniapp/static/i18n/en.json
  9. 6
      im-uniapp/static/i18n/fra.json
  10. 34
      im-uniapp/static/i18n/jp.json
  11. 34
      im-uniapp/static/i18n/kor.json
  12. 6
      im-uniapp/static/i18n/pt.json
  13. 22
      im-uniapp/static/i18n/ru.json
  14. 14
      im-uniapp/static/i18n/vie.json
  15. 9
      im-uniapp/static/i18n/zh.json
  16. 145
      im-uniapp/store/chatStore.js
  17. 128
      im-web/src/components/account/AccountSwitchMenu.vue
  18. 21
      im-web/src/components/chat/ChatBox.vue

79
im-platform/src/main/java/com/bx/implatform/controller/UserController.java

@ -68,7 +68,7 @@ public class UserController {
@Operation(summary = "保存用户分组", description = "单个分组,保存到 group_ids 字段")
public Result<?> saveGroup(@RequestBody JSONObject jsonObject) {
Long userId = jsonObject.getLong("userId");
String groupId = jsonObject.getStr("groupIds"); // 前端传分组ID
String groupId = jsonObject.getStr("groupIds");
userService.saveUserGroup(userId, groupId);
return ResultUtils.success();
@ -104,6 +104,27 @@ public class UserController {
return ResultUtils.success();
}
@PostMapping("/updateLanguage")
@Operation(summary = "更新用户语言", description = "更新当前用户的语言设置:zh/en/jp/kor等")
public Result<?> updateLanguage(@RequestBody JSONObject jsonObject) {
String language = jsonObject.getStr("language");
if (StrUtil.isBlank(language)) {
return ResultUtils.error(ResultCode.XSS_PARAM_ERROR, "语言不能为空");
}
UserSession session = SessionContext.getSession();
Long userId = session.getUserId();
User user = userService.getById(userId);
if (user == null) {
return ResultUtils.error(ResultCode.XSS_PARAM_ERROR, "用户不存在");
}
user.setLanguage(language);
boolean success = userService.updateById(user);
return success ? ResultUtils.success("语言更新成功") : ResultUtils.error(ResultCode.XSS_PARAM_ERROR, "更新失败");
}
@GetMapping("/findByName")
@Operation(summary = "查找用户", description = "根据用户名或昵称查找用户")
public Result<List<UserVO>> findByName(@RequestParam String name) {
@ -113,18 +134,15 @@ public class UserController {
@PostMapping("/getEnableChangeCustomer")
@Operation(summary = "获取可转接的客服", description = "转接客服")
public Result<List<Map<String, Object>>> getEnableChangeCustomer() {
// 获取当前客服id、转接客服id、转接用户id
UserSession session = SessionContext.getSession();
Long userId = session.getUserId();
if(ObjectUtil.isNull(userId)){
return ResultUtils.error(XSS_PARAM_ERROR);
}
List<User> list = userService.getEnableChangeCustomerList(userId);
//使用Map返回id、昵称
List<Map<String, Object>> result = list.stream().map(user -> {
Map<String, Object> map = new HashMap<>();
map.put("id", user.getId());
@ -152,7 +170,6 @@ public class UserController {
return ResultUtils.error(XSS_PARAM_ERROR);
}
// 获取当前用户信息
User currentUser = userService.getById(userId);
if (currentUser == null) {
return ResultUtils.error(XSS_PARAM_ERROR);
@ -160,7 +177,6 @@ public class UserController {
Map<String, Object> result = new HashMap<>();
// 获取可切换的账号ID列表(逗号分隔的字符串,如 "13,14")
String switchableIdsStr = currentUser.getSwitchableAccountIds();
List<Map<String, Object>> switchableUsers = new ArrayList<>();
@ -173,7 +189,6 @@ public class UserController {
if (!ids.isEmpty()) {
List<User> users = userService.listByIds(ids);
// 过滤掉被封禁的账号
users = users.stream()
.filter(u -> !Boolean.TRUE.equals(u.getIsBanned()))
.collect(Collectors.toList());
@ -192,27 +207,21 @@ public class UserController {
result.put("switchableUsers", switchableUsers);
// 获取当前用户的 unique_token
String currentUserUniqueToken = currentUser.getUniqueToken();
// 构建查询条件
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
.eq(User::getIsCustomer, 2)
.ne(User::getId, userId)
.eq(User::getIsBanned, 0);
// 添加 unique_token 条件
if (StrUtil.isNotBlank(currentUserUniqueToken)) {
// 当前用户有 unique_token,只查询相同 unique_token 的客服
queryWrapper.eq(User::getUniqueToken, currentUserUniqueToken);
} else {
// 当前用户没有 unique_token,只查询也没有 unique_token 的客服
queryWrapper.isNull(User::getUniqueToken).or().eq(User::getUniqueToken, "");
}
List<User> availableUsers = userService.list(queryWrapper);
// 获取已添加的ID集合
Set<Long> existingIds = new HashSet<>();
if (StrUtil.isNotBlank(switchableIdsStr)) {
Arrays.stream(switchableIdsStr.split(","))
@ -221,14 +230,12 @@ public class UserController {
.forEach(existingIds::add);
}
// 标记是否已添加
List<Map<String, Object>> availableUsersList = availableUsers.stream().map(user -> {
Map<String, Object> map = new HashMap<>();
map.put("id", user.getId());
map.put("userName", user.getUserName());
map.put("nickName", user.getNickName());
map.put("headImage", user.getHeadImage());
// map.put("headImageThumb", user.getHeadImageThumb());
map.put("isAdded", existingIds.contains(user.getId()));
return map;
}).collect(Collectors.toList());
@ -244,7 +251,6 @@ public class UserController {
UserSession session = SessionContext.getSession();
// 获取当前客服id、转接客服id、转接用户id
Long customerId = session.getUserId();
Long targetId = jsonObject.getLong("targetId");
Long userId = jsonObject.getLong("userId");
@ -267,22 +273,18 @@ public class UserController {
Long targetUserId = jsonObject.getLong("targetUserId");
Integer terminal = jsonObject.getInt("terminal");
// 获取当前登录用户
UserSession currentSession = SessionContext.getSession();
User currentUser = userService.getById(currentSession.getUserId());
// 权限校验:只有客服才能切换账号
if (currentUser.getIsCustomer() != 2) {
return ResultUtils.error(XSS_PARAM_ERROR, "无权限切换账号");
}
// 获取目标用户信息
User targetUser = userService.getById(targetUserId);
if (ObjectUtil.isNull(targetUser)) {
return ResultUtils.error(ResultCode.XSS_PARAM_ERROR, "目标用户不存在");
}
// 生成新的token
UserSession newSession = BeanUtils.copyProperties(targetUser, UserSession.class);
newSession.setUserId(targetUser.getId());
newSession.setTerminal(terminal);
@ -299,10 +301,41 @@ public class UserController {
vo.setRefreshTokenExpiresIn(jwtProperties.getRefreshTokenExpireIn());
vo.setUser(targetUser);
// log.info("账号切换:从用户 {} 切换到用户 {}", currentSession.getUserId(), targetUserId);
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);
}
String switchableIdsStr = currentUser.getSwitchableAccountIds();
if (StrUtil.isBlank(switchableIdsStr)) {
return ResultUtils.success("移除成功");
}
List<Long> 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("移除成功");
}
}

6
im-platform/src/main/java/com/bx/implatform/entity/User.java

@ -134,6 +134,8 @@ public class User {
*/
private String switchableAccountIds;
/**
* 语言
*/
private String language;
}

4
im-platform/src/main/java/com/bx/implatform/vo/UserVO.java

@ -78,6 +78,8 @@ public class UserVO {
// 修改为完整的UserGroup列表
private List<UserLabelVO> labelList; // 完整的分组信息列表
private String platformName; // 新增:平台名称
private String platformName;
private String language;
}

158
im-uniapp/App.vue

@ -99,26 +99,37 @@ export default {
});
},
//
//
handleCustomerChanged(msgInfo) {
console.log('客服已变更,刷新聊天记录:', msgInfo);
console.log('【客服转接】后台下发变更通知', msgInfo);
// msgInfo oldKfId idnewKfId 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);
//
msg.selfSend = msg.sendId == this.userStore.userInfo.id;
// id
let friendId = msg.selfSend ? msg.recvId : msg.sendId;
//
this.loadStore().then(() => {
//
// #ifdef H5
window.location.reload();
// #endif
// #ifdef APP-PLUS
plus.runtime.restart();
// #endif
//
let existingFriend = this.friendStore.findFriend(friendId);
if (!existingFriend && !msg.selfSend) {
console.log("收到未知用户消息,刷新应用:", friendId);
// #ifdef MP-WEIXIN
//
uni.reLaunch({
url: '/pages/chat/chat'
});
// #endif
});
this.loadStore().then(() => {
// #ifdef H5
window.location.reload();
// #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);
// #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) {
//

56
im-uniapp/pages/chat/chat-box.vue

@ -268,8 +268,8 @@ export default {
isFileOpen: false,
isPasting: false,
hasPasteListener: false,
currentLang: 'zh',
showLangModal: false, //
currentLang: uni.getStorageSync("app_language") || "zh",
langList: [
{ label: "中文", value: "zh" },
{ label: "English", value: "en" },
@ -285,17 +285,25 @@ export default {
};
},
methods: {
//
selectLang(lang) {
this.currentLang = lang;
this.$i18n.locale = lang;
uni.setStorageSync("app_language", lang);
this.showLangDrop = false;
setTimeout(() => {
window.location.reload(); // H5
}, 100);
//
this.$http({
url: "/user/updateLanguage",
method: "POST",
data: { language: lang }
}).then(() => {
console.log("语言保存成功");
}).finally(() => {
this.$i18n.locale = lang;
uni.setStorageSync("app_language", lang);
setTimeout(() => {
window.location.reload();
}, 100);
});
},
loadCommonQuestions(userId) {
this.$http({
@ -1257,13 +1265,18 @@ export default {
this.chatStore.updateChatFromUser(this.userInfo);
}
},
loadFriend(friendId) {
this.$http({
url: `/user/find/${friendId}`,
method: "GET",
}).then((userInfo) => {
this.userInfo = userInfo;
this.updateFriendInfo();
async loadFriend(friendId) {
return new Promise((resolve) => {
this.$http({
url: `/user/find/${friendId}`,
method: "GET",
}).then((userInfo) => {
this.userInfo = userInfo;
this.updateFriendInfo();
resolve(); // then
}).catch(() => {
resolve();
});
});
},
rpxTopx(rpx) {
@ -1592,6 +1605,7 @@ export default {
},
},
async onLoad(options) {
try {
this.currentLang = uni.getStorageSync("app_language") || "zh";
uni.showLoading({ title: this.$t('common.loading'), mask: true });
@ -1647,7 +1661,9 @@ export default {
}
this.readedMessage();
this.loadFriend(targetId);
// this.loadFriend(targetId);
await this.loadFriend(targetId);
this.loadReaded(targetId);
this.loadCommonQuestions(targetId);
@ -1658,6 +1674,7 @@ export default {
await this.getSetting();
this.$nextTick(() => this.scrollToBottom());
} catch (err) {
console.error("错误:", err);
} finally {
@ -1678,11 +1695,10 @@ export default {
</script>
<style lang="scss">
/* ========== 右上角语言按钮 ========== */
.lang-wrap {
position: fixed;
top: var(--status-bar-height);
right: 40rpx; /* 按钮靠右距离,美观不贴边 */
right: 40rpx;
z-index: 9999 !important;
height: $im-nav-bar-height;
display: flex;
@ -1700,11 +1716,10 @@ export default {
cursor: pointer;
}
/* ========== 自定义下拉面板(向左展开!永远不裁切) ========== */
.lang-drop-panel {
position: absolute;
top: 100%;
right: 0; /* 面板右边缘和按钮右边缘对齐,**向左展开** */
right: 0;
min-width: 180rpx;
background: #fff;
border-radius: 12rpx;
@ -1714,7 +1729,6 @@ export default {
z-index: 10000;
}
/* 下拉每一个语言选项 */
.lang-drop-item {
padding: 24rpx 30rpx;
font-size: 28rpx;
@ -1733,7 +1747,6 @@ export default {
}
}
/* ========== 全局遮罩(点击空白关闭下拉) ========== */
.lang-mask {
position: fixed;
top: 0;
@ -1743,7 +1756,6 @@ export default {
z-index: 9998;
}
/* 原有标题样式不动 */
.nav-title-wrapper {
width: 100%;
text-align: center;

6
im-uniapp/static/i18n/ara.json

@ -33,7 +33,11 @@
"album": "ألبوم",
"camera": "كاميرا",
"noFriends": "لا أصدقاء",
"title": "دردشة"
"title": "دردشة",
"you": "أنت",
"other": "الطرف الآخر",
"recalledMessage": "قام بتراجع رسالة",
"quoteRecalled": "تم تراجع المحتوى المقتبس"
},
"common": {
"confirm": "موافق",

6
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",

7
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",

6
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",

34
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": "確定",

34
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": "확인",

6
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",

22
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": "Вчера"
}
}

14
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",

9
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": "确定",

145
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);
if (!oldChat || !newChat) return;
const oldChat = this.findChatByFriend(oldKfId);
const newChat = this.findChatByFriend(newKfId);
if (!oldChat || !newChat) {
console.warn('合并失败:旧/新会话不存在', oldKfId, newKfId);
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,51 @@ 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 +242,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 +249,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 +267,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 +289,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 +311,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
}
@ -337,11 +320,11 @@ export default defineStore('chatStore', {
chat.stored = false;
this.saveToStorage(isColdMessage);
},
updateChatFromFriend(friend) {
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 +333,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 +345,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 +362,6 @@ export default defineStore('chatStore', {
},
refreshChats() {
let chats = cacheChats || this.chats;
// 更新会话免打扰状态
const friendStore = useFriendStore();
const groupStore = useGroupStore();
chats.forEach(chat => {
@ -397,36 +377,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 +402,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 +412,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 +425,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 +447,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 +477,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 +530,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 +550,6 @@ export default defineStore('chatStore', {
if (msgInfo.tmpId == m.tmpId) {
return m;
}
// 如果id比要查询的消息小,说明没有这条消息
if (m.tmpId && m.tmpId < msgInfo.tmpId) {
break;
}

128
im-web/src/components/account/AccountSwitchMenu.vue

@ -65,11 +65,13 @@
@click.stop="handleSwitch(account)">
切换
</el-button>
<i
class="el-icon-close delete-icon"
@click.stop="handleRemove(account)"
title="移除可切换账号">
</i>
<el-button
type="danger"
size="small"
@click.stop="handleDeleteAccount(account)"
>
删除
</el-button>
</div>
</div>
</div>
@ -97,7 +99,6 @@
</div>
</div>
<!-- 添加账号登录对话框 -->
<el-dialog
title="添加账号"
:visible.sync="addAccountDialogVisible"
@ -138,7 +139,7 @@
</template>
<script>
const ACCOUNTS_CACHE_KEY = 'switchable_accounts_cache'; // key
const ACCOUNTS_CACHE_KEY = 'switchable_accounts_cache';
export default {
name: 'AccountSwitchMenu',
@ -228,7 +229,6 @@ export default {
//
saveCachedAccounts(accounts) {
try {
//
const cacheData = accounts.map(a => ({
id: a.id,
userName: a.userName,
@ -245,7 +245,6 @@ export default {
//
addAccountToCache(account) {
const accounts = this.getCachedAccounts();
//
const exists = accounts.some(a => a.id === account.id);
if (!exists) {
accounts.push({
@ -266,18 +265,44 @@ export default {
this.saveCachedAccounts(accounts);
},
async handleDeleteAccount(account) {
try {
await this.$confirm(
`确定要删除账号【${account.nickName}】吗?\n删除后将不再显示此账号`,
'删除账号',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning'
}
)
try {
await this.$http({
url: '/user/deleteSwitchAccount',
method: 'post',
data: { targetUserId: account.id }
})
} catch (err) {
console.log('后端删除失败,继续清理本地')
}
this.removeAccountFromCache(account.id)
await this.loadAccountList()
this.$message.success('删除账号成功')
} catch (error) {
//
}
},
async loadAccountList() {
this.loading = true;
try {
// 1.
let accounts = this.getCachedAccounts();
// 2.
accounts = accounts.filter(a => a.id !== this.currentUser.id);
console.log('从缓存加载账号列表:', accounts);
// 3.
try {
const res = await this.$http({
url: '/user/getSwitchableAccounts',
@ -287,9 +312,7 @@ export default {
const data = res.data || res || {};
const serverAccounts = data.switchableUsers || [];
//
if (serverAccounts.length > 0) {
//
const mergedAccounts = this.mergeAccounts(accounts, serverAccounts);
this.saveCachedAccounts(mergedAccounts);
accounts = mergedAccounts.filter(a => a.id !== this.currentUser.id);
@ -298,7 +321,6 @@ export default {
console.log('后端接口获取失败,使用缓存数据:', error);
}
// 4.
if (accounts.length > 0) {
accounts = await this.fetchUnreadCounts(accounts);
}
@ -307,7 +329,6 @@ export default {
} catch (error) {
console.error('加载账号列表失败:', error);
//
const cached = this.getCachedAccounts();
this.allAccounts = cached.filter(a => a.id !== this.currentUser.id);
} finally {
@ -315,17 +336,13 @@ export default {
}
},
//
mergeAccounts(cached, server) {
const map = new Map();
//
cached.forEach(a => map.set(a.id, a));
// /
server.forEach(a => map.set(a.id, a));
return Array.from(map.values());
},
//
async refreshUnreadCounts() {
if (this.allAccounts.length === 0) return;
@ -337,7 +354,6 @@ export default {
}
},
//
async fetchUnreadCounts(accounts) {
try {
const userIds = accounts.map(a => a.id);
@ -345,9 +361,7 @@ export default {
const res = await this.$http({
url: '/message/private/unreadCounts',
method: 'post',
data: {
userIds: userIds
}
data: { userIds }
});
const unreadMap = res.data || res || {};
@ -359,10 +373,7 @@ export default {
} catch (error) {
console.error('获取未读消息数失败:', error);
return accounts.map(account => ({
...account,
unreadCount: 0
}));
return accounts.map(account => ({ ...account, unreadCount: 0 }));
}
},
@ -442,7 +453,6 @@ export default {
handleClickOutside(event) {
if (!this.visible) return;
if (this.addAccountDialogVisible) return;
const menu = this.$refs.menu || this.$el;
if (menu && !menu.contains(event.target)) {
@ -454,11 +464,11 @@ export default {
}
},
// ========== ==========
handleAddAccount() {
this.loginForm = {
userName: '',
password: ''
};
this.loginForm = { userName: '', password: '' };
//
this.$emit('close');
this.addAccountDialogVisible = true;
this.$nextTick(() => {
@ -484,10 +494,8 @@ export default {
}
});
//
const accountData = res.data || res || {};
//
if (accountData.user) {
this.addAccountToCache(accountData.user);
} else if (accountData.id) {
@ -502,11 +510,7 @@ export default {
} catch (error) {
console.error('添加失败:', error);
if (error.response && error.response.data) {
this.$message.error(error.response.data.message || '添加失败');
} else {
this.$message.error('添加失败');
}
this.$message.error(error?.response?.data?.message || '添加失败');
} finally {
this.adding = false;
}
@ -514,22 +518,15 @@ export default {
},
handleSwitch(account) {
//
this.addAccountToCache(account);
//
this.addAccountToCache(this.currentUser);
this.$emit('switch', account);
},
getTerminalType() {
const userAgent = navigator.userAgent;
if (/mobile/i.test(userAgent)) {
return 2;
}
if (/tablet/i.test(userAgent)) {
return 3;
}
if (/mobile/i.test(userAgent)) return 2;
if (/tablet/i.test(userAgent)) return 3;
return 1;
},
@ -541,17 +538,13 @@ export default {
type: 'warning'
});
//
this.removeAccountFromCache(account.id);
//
try {
await this.$http({
url: '/user/removeSwitchableAccount',
method: 'post',
data: {
targetUserId: account.id
}
data: { targetUserId: account.id }
});
} catch (error) {
console.log('后端移除失败,已从本地缓存移除');
@ -572,9 +565,7 @@ export default {
};
</script>
<style lang="scss">
.add-account-dialog {
border-radius: 10px !important;
}
@ -696,10 +687,6 @@ export default {
&:hover {
background: #f5f7fa;
.delete-icon {
opacity: 1;
}
}
.chat-left {
@ -770,21 +757,6 @@ export default {
align-items: center;
gap: 8px;
flex-shrink: 0;
.delete-icon {
opacity: 0;
color: #999;
font-size: 16px;
padding: 6px;
cursor: pointer;
transition: all 0.2s;
border-radius: 50%;
&:hover {
color: #f56c6c;
background: rgba(245, 108, 108, 0.1);
}
}
}
}
}

21
im-web/src/components/chat/ChatBox.vue

@ -165,6 +165,10 @@
<div class="info-label">来源地址</div>
<div class="info-value">{{ userInfo.platformName }}</div>
</div>
<div class="info-item">
<div class="info-label">用户语言</div>
<div class="info-value">{{ getLanguageText(userInfo.language) }}</div>
</div>
<div class="info-item">
<div class="info-label">标签</div>
<div class="info-value">
@ -340,6 +344,7 @@ export default {
maxTmpId: 0,
countryCode: "en",
countryCodeList: [
{ label: "中文", value: "zh" },
{ label: "英语", value: "en" },
{ label: "日语", value: "jp" },
{ label: "韩语", value: "kor" },
@ -353,6 +358,22 @@ export default {
};
},
methods: {
getLanguageText(lang) {
if (!lang) return '未知';
const langMap = {
'zh': '中文',
'en': '英语',
'jp': '日语',
'kor': '韩语',
'vie': '越南语',
'ru': '俄语',
'de': '德语',
'fra': '法语',
'pt': '葡萄牙语',
'ara': '阿拉伯语',
};
return langMap[lang.toLowerCase()] || lang;
},
moveChatToTop() {
let chatIdx = this.chatStore.findChatIdx(this.chat);
this.chatStore.moveTop(chatIdx);

Loading…
Cancel
Save