Browse Source

修改转接客服功能

master
[yxf] 1 month ago
parent
commit
b64791bad8
  1. 3
      im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java
  2. 6
      im-platform/src/main/java/com/bx/implatform/entity/User.java
  3. 4
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  4. 245
      im-uniapp/pages/chat/chat-box.vue
  5. 63
      im-uniapp/pages/login/login.vue
  6. 530
      im-uniapp/store/chatStore.js

3
im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java

@ -28,4 +28,7 @@ public class LoginDTO {
@Schema(description = "ip地址") @Schema(description = "ip地址")
private String ip; private String ip;
@Schema(description = "来源网址")
private String sourceUrl;
} }

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

@ -104,4 +104,10 @@ public class User {
*/ */
private String ipAddress; private String ipAddress;
/**
* 来源网址
*/
private String sourceUrl;
} }

4
im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java

@ -93,7 +93,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Override @Override
public LoginVO login(LoginDTO dto) { public LoginVO login(LoginDTO dto) {
log.info("【测试】前端传的IP:{}", dto.getIp()); log.info("【测试】前端传的IP:{}", dto.getSourceUrl());
// 生成游客唯一标识UUID // 生成游客唯一标识UUID
String guestUuid = UUID.randomUUID().toString(); String guestUuid = UUID.randomUUID().toString();
@ -108,7 +108,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
guestUser.setUuid(guestUuid); guestUser.setUuid(guestUuid);
// ========== 先设置IP ========== // ========== 先设置IP ==========
guestUser.setLastLoginIp(dto.getIp()); guestUser.setLastLoginIp(dto.getIp());
guestUser.setSourceUrl(dto.getSourceUrl());
// 保存到数据库 // 保存到数据库
this.save(guestUser); this.save(guestUser);

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

@ -134,7 +134,9 @@ export default {
scrollMsgIdx: 0, // scrollMsgIdx: 0, //
chatTabBox: 'none', chatTabBox: 'none',
currentTargetId: null, // currentTargetId: null, //
currentChatType: 'PRIVATE', currentChatType: 'PRIVATE',
isLoading: true, //
defaultTitle: '加载中...', //
// activeChatIdx: 0, // activeChatIdx: 0,
_activeChatIdx: 0, // _activeChatIdx: 0, //
showRecord: false, showRecord: false,
@ -293,71 +295,106 @@ export default {
} }
}, },
sendTextMessage() { sendTextMessage() {
this.editorCtx.getContents({ this.editorCtx.getContents({
success: (e) => { success: (e) => {
// this.editorCtx.clear();
this.editorCtx.clear(); if (this.isBanned) {
// this.showBannedTip();
if (this.isBanned) { return;
this.showBannedTip(); }
return;
} let sendText = "";
let sendText = ""; e.delta.ops.forEach((op) => {
e.delta.ops.forEach((op) => { if (op.insert.image) {
if (op.insert.image) { sendText += `#${op.attributes.alt};`
// emo } else {
sendText += `#${op.attributes.alt};` sendText += op.insert
} else{ }
// })
sendText += op.insert
} sendText = sendText.trim();
}) if (!sendText && this.atUserIds.length == 0) {
// return uni.showToast({
sendText = sendText.trim(); title: "不能发送空白信息",
if (!sendText && this.atUserIds.length == 0) { icon: "none"
return uni.showToast({ });
title: "不能发送空白信息", }
icon: "none"
}); let receiptText = this.isReceipt ? "【回执消息】" : "";
} let atText = this.createAtText();
let receiptText = this.isReceipt ? "【回执消息】" : ""; let msgInfo = {
let atText = this.createAtText(); tmpId: this.generateId(),
let msgInfo = { content: receiptText + sendText + atText,
tmpId: this.generateId(), atUserIds: this.atUserIds,
content: receiptText + sendText + atText, receipt: this.isReceipt,
atUserIds: this.atUserIds, type: this.$enums.MESSAGE_TYPE.TEXT
receipt: this.isReceipt, }
type: this.$enums.MESSAGE_TYPE.TEXT
} this.atUserIds = [];
// @ this.isReceipt = false;
this.atUserIds = []; this.fillTargetId(msgInfo, this.chat.targetId);
this.isReceipt = false;
// id const chat = this.chat;
this.fillTargetId(msgInfo, this.chat.targetId); if (!chat) return;
//
const chat = this.chat; //
// const chat = this.chatStore.chats[this.activeChatIdx]; let tmpMessage = this.buildTmpMessage(msgInfo);
if (!chat) return;
// //
let tmpMessage = this.buildTmpMessage(msgInfo); const tmpMessageCopy = JSON.parse(JSON.stringify(tmpMessage));
this.chatStore.insertMessage(tmpMessage, chat);
this.moveChatToTop(); // store
this.sendMessageRequest(msgInfo).then((m) => { this.chatStore.insertMessage(tmpMessageCopy, chat);
//
tmpMessage = JSON.parse(JSON.stringify(tmpMessage)); // 使 chat
tmpMessage.id = m.id; const chatIndex = this.chatStore.chats.findIndex(c =>
tmpMessage.status = m.status; c.targetId === chat.targetId && c.type === chat.type
tmpMessage.content = m.content; );
this.chatStore.updateMessage(tmpMessage, chat);
}).catch(() => { if (chatIndex !== -1) {
// const targetChat = this.chatStore.chats[chatIndex];
tmpMessage = JSON.parse(JSON.stringify(tmpMessage)); const msgIndex = targetChat.messages.findIndex(m => m.tmpId === msgInfo.tmpId);
tmpMessage.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.updateMessage(tmpMessage, chat); 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() { createAtText() {
let atText = ""; let atText = "";
@ -1012,6 +1049,21 @@ export default {
} }
}, },
computed: { 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() { chat() {
if (!this.currentTargetId) return null; if (!this.currentTargetId) return null;
return this.chatStore.chats.find(c => return this.chatStore.chats.find(c =>
@ -1105,21 +1157,43 @@ export default {
} }
}, },
watch: { watch: {
messageSize: function(newSize, oldSize) { chat: {
// handler(newChat, oldChat) {
if (newSize > oldSize && oldSize > 0) { if (newChat && newChat.messages && newChat.messages.length > 0) {
let lastMessage = this.chat.messages[newSize - 1]; //
if (this.$msgType.isNormal(lastMessage.type)) { if (this.isInBottom) {
if (this.isInBottom || lastMessage.selfSend) { this.$nextTick(() => {
// , this.scrollToBottom();
this.scrollToBottom(); });
} else { }
// }
this.newMessageSize++; },
} 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: { unreadCount: {
handler(newCount, oldCount) { handler(newCount, oldCount) {
if (newCount > 0) { if (newCount > 0) {
@ -1179,8 +1253,17 @@ export default {
await this.$nextTick(); await this.$nextTick();
// // chat
if (!this.chat) {
console.error('Chat 初始化失败');
uni.hideLoading();
return;
}
// chat
this.readedMessage(); this.readedMessage();
//
this.loadFriend(targetId); this.loadFriend(targetId);
this.loadReaded(targetId); this.loadReaded(targetId);

63
im-uniapp/pages/login/login.vue

@ -18,11 +18,41 @@ export default {
terminal: 1, terminal: 1,
userName: '', userName: '',
password: '', password: '',
ip: '' ip: '',
sourceUrl: ''
} }
} }
}, },
methods: { 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() { async autoLogin() {
if (GLOBAL_AUTO_LOGIN_LOCK) return; if (GLOBAL_AUTO_LOGIN_LOCK) return;
GLOBAL_AUTO_LOGIN_LOCK = true; GLOBAL_AUTO_LOGIN_LOCK = true;
@ -45,7 +75,8 @@ export default {
// } // }
// return; // return;
// } // }
//
this.getSourceUrl();
await this.getIp(); await this.getIp();
this.$http({ this.$http({
@ -59,20 +90,22 @@ export default {
getApp().$vm.init() getApp().$vm.init()
getApp().$vm.unloadStore(); getApp().$vm.unloadStore();
this.$http({ this.$http({
url: "/friend/add?friendId=" + loginInfo.customerServiceId, url: "/friend/add?friendId=" + loginInfo.customerServiceId,
method: "POST" method: "POST"
}).then((data) => { }).then((data) => {
let friend = { let friend = {
id: loginInfo.user.id, id: loginInfo.user.id,
nickName: loginInfo.user.nickName, nickName: loginInfo.user.nickName,
headImage: loginInfo.user.headImageThumb, headImage: loginInfo.user.headImageThumb,
online: loginInfo.user.online, online: loginInfo.user.online,
delete: false delete: false
} }
this.friendStore.addFriend(friend); this.friendStore.addFriend(friend);
uni.reLaunch({
url: "/pages/chat/chat-box?chatIdx=0" // ID
}); uni.reLaunch({
url: `/pages/chat/chat-box?targetId=${loginInfo.customerServiceId}&type=PRIVATE`
});
}) })
}).catch(err => { }).catch(err => {

530
im-uniapp/store/chatStore.js

@ -6,19 +6,23 @@ import useUserStore from './userStore';
let cacheChats = []; let cacheChats = [];
export default defineStore('chatStore', { export default defineStore('chatStore', {
state: () => ({ state: () => {
chats: [], return {
privateMsgMaxId: 0, chats: [],
groupMsgMaxId: 0, privateMsgMaxId: 0,
loading: false groupMsgMaxId: 0,
}), loading: false
}
},
actions: { actions: {
initChats(chatsData) { initChats(chatsData) {
cacheChats = []; cacheChats = [];
this.chats = []; this.chats = [];
for (let chat of chatsData.chats) { for (let chat of chatsData.chats) {
chat.stored = false; chat.stored = false;
// 暂存至缓冲区
cacheChats.push(JSON.parse(JSON.stringify(chat))); cacheChats.push(JSON.parse(JSON.stringify(chat)));
// 加载期间显示只前15个会话做做样子,一切都为了加快初始化时间
if (this.chats.length < 15) { if (this.chats.length < 15) {
this.chats.push(chat); this.chats.push(chat);
} }
@ -27,15 +31,18 @@ export default defineStore('chatStore', {
this.groupMsgMaxId = chatsData.groupMsgMaxId || 0; this.groupMsgMaxId = chatsData.groupMsgMaxId || 0;
}, },
openChat(chatInfo) { openChat(chatInfo) {
let chats = this.chats; let chats = this.curChats;
let chat = null; let chat = null;
for (let idx in chats) { 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]; chat = chats[idx];
this.moveTop(idx); // 放置头部
this.moveTop(idx)
break; break;
} }
} }
// 创建会话
if (chat == null) { if (chat == null) {
chat = { chat = {
targetId: chatInfo.targetId, targetId: chatInfo.targetId,
@ -58,265 +65,323 @@ export default defineStore('chatStore', {
} }
}, },
activeChat(idx) { activeChat(idx) {
let chats = this.curChats;
if (idx >= 0) { if (idx >= 0) {
this.chats[idx] = { ...this.chats[idx], unreadCount: 0 }; chats[idx].unreadCount = 0;
} }
}, },
resetUnreadCount(chatInfo) { resetUnreadCount(chatInfo) {
this.chats = this.chats.map(chat => { let chats = this.curChats;
if (chat.type == chatInfo.type && chat.targetId == chatInfo.targetId) { for (let idx in chats) {
return { ...chat, unreadCount: 0, atMe: false, atAll: false, stored: false }; 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) { readedMessage(pos) {
let chat = this.findChatByFriend(pos.friendId); let chat = this.findChatByFriend(pos.friendId);
if (!chat) return; if (!chat) return;
let messages = [...chat.messages]; for (let idx = chat.readedMessageIdx; idx < chat.messages.length; idx++) {
let changed = false; let m = chat.messages[idx];
for (let idx = chat.readedMessageIdx; idx < messages.length; idx++) {
let m = messages[idx];
if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) { if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) {
// pos.maxId为空表示整个会话已读
if (!pos.maxId || m.id <= pos.maxId) { if (!pos.maxId || m.id <= pos.maxId) {
messages[idx] = { ...m, status: MESSAGE_STATUS.READED }; m.status = MESSAGE_STATUS.READED
chat.readedMessageIdx = idx; chat.readedMessageIdx = idx;
changed = true; chat.stored = false;
} }
} }
} }
if (changed) { if (!chat.stored) {
chat.messages = messages;
chat.stored = false;
this.saveToStorage(); this.saveToStorage();
} }
}, },
cleanMessage(idx) { cleanMessage(idx) {
let chat = { ...this.chats[idx] }; let chat = this.curChats[idx];
chat.lastContent = ''; chat.lastContent = '';
chat.hotMinIdx = 0; chat.hotMinIdx = 0;
chat.unreadCount = 0; chat.unreadCount = 0;
chat.atMe = false; chat.atMe = false;
chat.atAll = false; chat.atAll = false;
chat.stored = false; chat.stored = false
chat.messages = []; chat.messages = [];
this.chats[idx] = chat;
this.saveToStorage(true); this.saveToStorage(true);
}, },
removeChat(idx) { 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(); this.saveToStorage();
}, },
removePrivateChat(userId) { removePrivateChat(userId) {
this.chats = this.chats.map(chat => { let chats = this.curChats;
if (chat.type == 'PRIVATE' && chat.targetId == userId) { for (let idx in chats) {
return { ...chat, delete: true, stored: false }; if (chats[idx].type == 'PRIVATE' &&
chats[idx].targetId == userId) {
this.removeChat(idx);
} }
return chat; }
});
this.saveToStorage();
}, },
removeGroupChat(groupId) { removeGroupChat(groupId) {
this.chats = this.chats.map(chat => { let chats = this.curChats;
if (chat.type == 'GROUP' && chat.targetId == groupId) { for (let idx in chats) {
return { ...chat, delete: true, stored: false }; if (chats[idx].type == 'GROUP' &&
chats[idx].targetId == groupId) {
this.removeChat(idx);
} }
return chat; }
});
this.saveToStorage();
}, },
moveTop(idx) { moveTop(idx) {
if (this.loading) return; if (this.loading) {
return;
}
let chats = this.curChats;
if (idx > 0) { if (idx > 0) {
let chats = [...this.chats]; let chat = chats[idx];
let chat = chats.splice(idx, 1)[0]; chats.splice(idx, 1);
chat = { ...chat, lastSendTime: new Date().getTime(), stored: false };
chats.unshift(chat); chats.unshift(chat);
this.chats = chats; chat.lastSendTime = new Date().getTime();
chat.stored = false;
this.saveToStorage(); this.saveToStorage();
} }
}, },
insertMessage(msgInfo, chatInfo) { insertMessage(msgInfo, chatInfo) {
// 获取对方id或群id
let type = chatInfo.type; let type = chatInfo.type;
// 记录消息的最大id
if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) { if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) {
this.privateMsgMaxId = msgInfo.id; this.privateMsgMaxId = msgInfo.id;
} }
if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) { if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) {
this.groupMsgMaxId = msgInfo.id; this.groupMsgMaxId = msgInfo.id;
} }
// 如果是已存在消息,则覆盖旧的消息数据
let chat = this.findChat(chatInfo); let chat = this.findChat(chatInfo);
if (!chat) return;
let message = this.findMessage(chat, msgInfo); let message = this.findMessage(chat, msgInfo);
if (message) { if (message) {
chat.messages = chat.messages.map(m => console.log("message:", message)
(m.id === msgInfo.id || m.tmpId === msgInfo.tmpId) ? { ...m, ...msgInfo } : m Object.assign(message, msgInfo);
);
chat.stored = false; chat.stored = false;
this.saveToStorage(); this.saveToStorage();
return; return;
} }
// 会话列表内容
chat = { ...chat }; if (msgInfo.type == MESSAGE_TYPE.IMAGE) {
if (msgInfo.type == MESSAGE_TYPE.IMAGE) chat.lastContent = "[图片]"; chat.lastContent = "[图片]";
else if (msgInfo.type == MESSAGE_TYPE.FILE) chat.lastContent = "[文件]"; } else if (msgInfo.type == MESSAGE_TYPE.FILE) {
else if (msgInfo.type == MESSAGE_TYPE.AUDIO) chat.lastContent = "[语音]"; chat.lastContent = "[文件]";
else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) chat.lastContent = "[语音通话]"; } else if (msgInfo.type == MESSAGE_TYPE.AUDIO) {
else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) chat.lastContent = "[视频通话]"; chat.lastContent = "[语音]";
else if (msgInfo.type == MESSAGE_TYPE.TEXT || msgInfo.type == MESSAGE_TYPE.RECALL || msgInfo.type == MESSAGE_TYPE.TIP_TEXT) { } 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.lastContent = msgInfo.content;
} }
chat.lastSendTime = msgInfo.sendTime; chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName; chat.sendNickName = msgInfo.sendNickName;
// 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED && msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) { if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
chat.unreadCount++; 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(); const userStore = useUserStore();
let userId = userStore.userInfo.id; let userId = userStore.userInfo.id;
if (msgInfo.atUserIds.indexOf(userId) >= 0) chat.atMe = true; if (msgInfo.atUserIds.indexOf(userId) >= 0) {
if (msgInfo.atUserIds.indexOf(-1) >= 0) chat.atAll = true; chat.atMe = true;
}
if (msgInfo.atUserIds.indexOf(-1) >= 0) {
chat.atAll = true;
}
} }
// 间隔大于10分钟插入时间显示
let messages = [...chat.messages]; if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) {
if (!chat.lastTimeTip || chat.lastTimeTip < msgInfo.sendTime - 600 * 1000) { chat.messages.push({
messages.push({ sendTime: msgInfo.sendTime, type: MESSAGE_TYPE.TIP_TIME }); sendTime: msgInfo.sendTime,
type: MESSAGE_TYPE.TIP_TIME,
});
chat.lastTimeTip = msgInfo.sendTime; chat.lastTimeTip = msgInfo.sendTime;
} }
messages.push(msgInfo); // 插入消息
chat.messages = messages; chat.messages.push(msgInfo);
chat.stored = false; chat.stored = false;
this.chats = this.chats.map(c => c.type === chat.type && c.targetId === chat.targetId ? chat : c);
this.saveToStorage(); this.saveToStorage();
}, },
updateMessage(msgInfo, chatInfo) { updateMessage(msgInfo, chatInfo) {
// 获取对方id或群id
let chat = this.findChat(chatInfo); let chat = this.findChat(chatInfo);
if (!chat) return; let message = this.findMessage(chat, msgInfo);
if (message) {
this.chats = this.chats.map(c => { // 属性拷贝
if (c.type === chat.type && c.targetId === chat.targetId) { Object.assign(message, msgInfo);
return { chat.stored = false;
...c, this.saveToStorage();
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();
}, },
deleteMessage(msgInfo, chatInfo) { deleteMessage(msgInfo, chatInfo) {
let isColdMessage = false;
let chat = this.findChat(chatInfo); let chat = this.findChat(chatInfo);
if (!chat) return; let delIdx = -1;
for (let idx in chat.messages) {
this.chats = this.chats.map(c => { // 已经发送成功的,根据id删除
if (c.type === chat.type && c.targetId === chat.targetId) { if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) {
let messages = c.messages.filter(m => delIdx = idx;
!(m.id && m.id === msgInfo.id) && !(m.tmpId && m.tmpId === msgInfo.tmpId) break;
);
return { ...c, messages, stored: false };
} }
return c; // 正在发送中的消息可能没有id,只有临时id
}); if (chat.messages[idx].tmpId && chat.messages[idx].tmpId == msgInfo.tmpId) {
this.saveToStorage(); 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) { recallMessage(msgInfo, chatInfo) {
let chat = this.findChat(chatInfo); let chat = this.findChat(chatInfo);
if (!chat) return; if (!chat) return;
let isColdMessage = false;
// 要撤回的消息id
let id = msgInfo.content; let id = msgInfo.content;
let name = msgInfo.selfSend ? '你' : chat.type == 'PRIVATE' ? '对方' : msgInfo.sendNickName; let name = msgInfo.selfSend ? '你' : chat.type == 'PRIVATE' ? '对方' : msgInfo.sendNickName;
for (let idx in chat.messages) {
this.chats = this.chats.map(c => { let m = chat.messages[idx];
if (c.type === chat.type && c.targetId === chat.targetId) { if (m.id && m.id == id) {
let messages = c.messages.map(m => { // 改造成一条提示消息
if (m.id && m.id == id) { m.status = MESSAGE_STATUS.RECALL;
return { m.content = name + "撤回了一条消息";
...m, m.type = MESSAGE_TYPE.TIP_TEXT
status: MESSAGE_STATUS.RECALL, // 会话列表
content: name + "撤回了一条消息", chat.lastContent = m.content;
type: MESSAGE_TYPE.TIP_TEXT chat.lastSendTime = msgInfo.sendTime;
}; chat.sendNickName = '';
} if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) {
if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) { chat.unreadCount++;
return { }
...m, isColdMessage = idx < chat.hotMinIdx;
quoteMessage: { ...m.quoteMessage, content: "引用内容已撤回", status: MESSAGE_STATUS.RECALL, type: MESSAGE_TYPE.TIP_TEXT }
};
}
return m;
});
return { ...c, messages, stored: false };
} }
return c; // 被引用的消息也要撤回
}); if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) {
this.saveToStorage(); m.quoteMessage.content = "引用内容已撤回";
m.quoteMessage.status = MESSAGE_STATUS.RECALL;
m.quoteMessage.type = MESSAGE_TYPE.TIP_TEXT
}
}
chat.stored = false;
this.saveToStorage(isColdMessage);
}, },
updateChatFromFriend(friend) { updateChatFromFriend(friend) {
this.chats = this.chats.map(chat => { let chat = this.findChatByFriend(friend.id)
if (chat.type === 'PRIVATE' && chat.targetId === friend.id) { if (chat && (chat.headImage != friend.headImage ||
return { ...chat, headImage: friend.headImage, showName: friend.nickName, stored: false }; chat.showName != friend.nickName)) {
} // 更新会话中的群名和头像
return chat; chat.headImage = friend.headImage;
}); chat.showName = friend.nickName;
this.saveToStorage(); chat.stored = false;
this.saveToStorage();
}
}, },
updateChatFromUser(user) { updateChatFromUser(user) {
this.chats = this.chats.map(chat => { let chat = this.findChatByFriend(user.id);
if (chat.type === 'PRIVATE' && chat.targetId === user.id) { // 更新会话中的昵称和头像
return { ...chat, headImage: user.headImageThumb, showName: user.nickName, stored: false }; if (chat && (chat.headImage != user.headImageThumb ||
} chat.showName != user.nickName)) {
return chat; chat.headImage = user.headImageThumb;
}); chat.showName = user.nickName;
this.saveToStorage(); chat.stored = false;
this.saveToStorage();
}
}, },
updateChatFromGroup(group) { updateChatFromGroup(group) {
this.chats = this.chats.map(chat => { let chat = this.findChatByGroup(group.id);
if (chat.type === 'GROUP' && chat.targetId === group.id) { if (chat && (chat.headImage != group.headImageThumb ||
return { ...chat, headImage: group.headImageThumb, showName: group.showGroupName, stored: false }; chat.showName != group.showGroupName)) {
} // 更新会话中的群名称和头像
return chat; chat.headImage = group.headImageThumb;
}); chat.showName = group.showGroupName;
this.saveToStorage(); chat.stored = false;
this.saveToStorage();
}
}, },
setLoading(loading) { setLoading(loading) {
this.loading = loading; this.loading = loading;
}, },
setDnd(chatInfo, isDnd) { setDnd(chatInfo, isDnd) {
this.chats = this.chats.map(chat => { let chat = this.findChat(chatInfo);
if (chat.type === chatInfo.type && chat.targetId === chatInfo.targetId) { if (chat) {
return { ...chat, isDnd }; chat.isDnd = isDnd;
} }
return chat;
});
}, },
refreshChats() { refreshChats() {
let chats = cacheChats || this.chats; let chats = cacheChats || this.chats;
// 更新会话免打扰状态
const friendStore = useFriendStore(); const friendStore = useFriendStore();
const groupStore = useGroupStore(); const groupStore = useGroupStore();
chats.forEach(chat => { chats.forEach(chat => {
if (chat.type == 'PRIVATE') { if (chat.type == 'PRIVATE') {
let friend = friendStore.findFriend(chat.targetId); let friend = friendStore.findFriend(chat.targetId);
if (friend) chat.isDnd = friend.isDnd; if (friend) {
chat.isDnd = friend.isDnd
}
} else if (chat.type == 'GROUP') { } else if (chat.type == 'GROUP') {
let group = groupStore.findGroup(chat.targetId); 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); 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; this.chats = chats;
// 清空缓存,不再使用
cacheChats = null; cacheChats = null;
// 消息持久化
this.saveToStorage(true); this.saveToStorage(true);
}, },
fliterMessage(chats, maxTotalSize, maxPerChatSize) { fliterMessage(chats, maxTotalSize, maxPerChatSize) {
// 每个会话只保留maxPerChatSize条消息
let remainTotalSize = 0; let remainTotalSize = 0;
chats.forEach(chat => { chats.forEach(chat => {
if (chat.messages.length > maxPerChatSize) { if (chat.messages.length > maxPerChatSize) {
@ -324,10 +389,13 @@ export default defineStore('chatStore', {
chat.messages = chat.messages.slice(idx); chat.messages = chat.messages.slice(idx);
} }
remainTotalSize += chat.messages.length; remainTotalSize += chat.messages.length;
}); })
// 保证消息总数不超过maxTotalSize条,否则继续清理
if (remainTotalSize > maxTotalSize) { if (remainTotalSize > maxTotalSize) {
this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2); this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2);
} }
console.log("消息留存总数量:", remainTotalSize)
console.log("单会话消息数量:", maxPerChatSize)
}, },
cleanOtherUserCache() { cleanOtherUserCache() {
const userStore = useUserStore(); const userStore = useUserStore();
@ -335,101 +403,163 @@ export default defineStore('chatStore', {
const prefix = "chats-app-" + userId; const prefix = "chats-app-" + userId;
const res = uni.getStorageInfoSync(); const res = uni.getStorageInfoSync();
res.keys.forEach(key => { res.keys.forEach(key => {
// 清理其他用户的消息
if (key.startsWith("chats-app") && !key.startsWith(prefix)) { if (key.startsWith("chats-app") && !key.startsWith(prefix)) {
uni.removeStorageSync(key); uni.removeStorageSync(key);
console.log("清理key:", key)
} }
}); })
}, },
saveToStorage(withColdMessage) { saveToStorage(withColdMessage) {
if (this.loading) return; // 加载中不保存,防止卡顿
if (this.loading) {
return;
}
const userStore = useUserStore(); const userStore = useUserStore();
let userId = userStore.userInfo.id; let userId = userStore.userInfo.id;
let key = "chats-app-" + userId; let key = "chats-app-" + userId;
let chatKeys = []; let chatKeys = [];
// 按会话为单位存储,只存储有改动的会话
this.chats.forEach((chat) => { this.chats.forEach((chat) => {
let chatKey = `${key}-${chat.type}-${chat.targetId}`; let chatKey = `${key}-${chat.type}-${chat.targetId}`
if (!chat.stored) { if (!chat.stored) {
if (chat.delete) { if (chat.delete) {
uni.removeStorageSync(chatKey); uni.removeStorageSync(chatKey);
} else { } else {
// 存储冷数据
if (withColdMessage) { if (withColdMessage) {
let coldChat = { ...chat }; let coldChat = Object.assign({}, chat);
coldChat.messages = chat.messages.slice(0, chat.hotMinIdx); coldChat.messages = chat.messages.slice(0, chat.hotMinIdx);
uni.setStorageSync(chatKey, coldChat); uni.setStorageSync(chatKey, coldChat)
} }
// 存储热消息
let hotKey = chatKey + '-hot'; let hotKey = chatKey + '-hot';
let hotChat = { ...chat }; let hotChat = Object.assign({}, chat);
hotChat.messages = chat.messages.slice(chat.hotMinIdx); hotChat.messages = chat.messages.slice(chat.hotMinIdx)
uni.setStorageSync(hotKey, hotChat); uni.setStorageSync(hotKey, hotChat);
} }
chat.stored = true; chat.stored = true;
} }
if (!chat.delete) chatKeys.push(chatKey); if (!chat.delete) {
}); chatKeys.push(chatKey);
}
})
// 会话核心信息
let chatsData = { let chatsData = {
privateMsgMaxId: this.privateMsgMaxId, privateMsgMaxId: this.privateMsgMaxId,
groupMsgMaxId: this.groupMsgMaxId, groupMsgMaxId: this.groupMsgMaxId,
chatKeys: chatKeys chatKeys: chatKeys
}; }
uni.setStorageSync(key, chatsData); uni.setStorageSync(key, chatsData)
this.chats = this.chats.filter(chat => !chat.delete); // 清理已删除的会话
this.chats = this.chats.filter(chat => !chat.delete)
}, },
clear() { clear(state) {
cacheChats = []; cacheChats = [];
this.chats = []; this.chats = [];
this.privateMsgMaxId = 0; this.privateMsgMaxId = 0;
this.groupMsgMaxId = 0; this.groupMsgMaxId = 0;
this.loadingPrivateMsg = false;
this.loadingGroupMsg = false;
}, },
loadChat() { loadChat() {
return new Promise((resolve) => { return new Promise((resolve, reject) => {
let userStore = useUserStore(); let userStore = useUserStore();
let userId = userStore.userInfo.id; let userId = userStore.userInfo.id;
let chatsData = uni.getStorageSync("chats-app-" + userId); let chatsData = uni.getStorageSync("chats-app-" + userId)
if (chatsData && chatsData.chatKeys) { if (chatsData) {
chatsData.chats = []; if (chatsData.chatKeys) {
chatsData.chatKeys.forEach(key => { chatsData.chats = [];
let coldChat = uni.getStorageSync(key); chatsData.chatKeys.forEach(key => {
let hotChat = uni.getStorageSync(key + '-hot'); let coldChat = uni.getStorageSync(key);
if (!coldChat && !hotChat) return; let hotChat = uni.getStorageSync(key + '-hot');
if (hotChat) { if (!coldChat && !hotChat) {
hotChat.messages.forEach(msg => { return;
if (msg.status == MESSAGE_STATUS.SENDING) msg.status = MESSAGE_STATUS.FAILED; }
}); // 防止消息一直处在发送中状态
} hotChat && hotChat.messages.forEach(msg => {
let chat = { ...coldChat, ...hotChat }; if (msg.status == MESSAGE_STATUS.SENDING) {
if (hotChat && coldChat) { msg.status = MESSAGE_STATUS.FAILED
chat.messages = [...(coldChat.messages || []), ...(hotChat.messages || [])]; }
} })
chat.readedMessageIdx = chat.readedMessageIdx || 0; // 冷热消息合并
chatsData.chats.push(chat); 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); this.initChats(chatsData);
} }
resolve(); resolve()
}); })
} }
}, },
getters: { getters: {
curChats: (state) => state.chats, // ✅ 修复 curChats: (state) => {
if (cacheChats && state.loading) {
return cacheChats;
}
return state.chats;
},
findChatIdx: (state) => (chat) => { 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) => { 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) => { 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) => { 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) => { findMessage: (state) => (chat, msgInfo) => {
if (!chat) return null; if (!chat) {
return null;
}
// 通过id判断
if (msgInfo.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) { 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; return null;
} }

Loading…
Cancel
Save