|
|
|
@ -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,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; |
|
|
|
} |
|
|
|
|