import { defineStore } from 'pinia'; 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', { state: () => { return { chats: [], privateMsgMaxId: 0, groupMsgMaxId: 0, loading: false } }, actions: { initChats(chatsData) { cacheChats = []; this.chats = []; for (let chat of chatsData.chats) { chat.stored = false; cacheChats.push(JSON.parse(JSON.stringify(chat))); if (this.chats.length < 15) { this.chats.push(chat); } } this.privateMsgMaxId = chatsData.privateMsgMaxId || 0; this.groupMsgMaxId = chatsData.groupMsgMaxId || 0; }, // 未知客服触发:全量合并所有旧客服到新客服 forceMergeAllOldCustomerToNew(newKfId) { const userStore = useUserStore(); const uid = userStore.userInfo.id; const prefix = `chats-app-${uid}-PRIVATE-`; const rootKey = `chats-app-${uid}`; const allKeys = uni.getStorageInfoSync().keys; const oldKeys = allKeys.filter(k => k.startsWith(prefix) && !k.endsWith('-hot') && !k.includes(`-${newKfId}`) ); if (oldKeys.length === 0) return; let newChat = this.findChatByFriend(newKfId); if (!newChat) { newChat = { targetId: newKfId, type: "PRIVATE", messages: [], unreadCount: 0, lastContent: "", lastSendTime: 0, hotMinIdx: 0, readedMessageIdx: 0, atMe: false, atAll: false, stored: false }; this.chats.unshift(newChat); } // 读取每一个旧客服 【冷+热完整消息】 let allOldMessages = []; oldKeys.forEach(oldKey => { const oldCold = uni.getStorageSync(oldKey); const oldHot = uni.getStorageSync(oldKey + "-hot"); // 旧客服完整消息:冷历史 + 热新消息 const oldFullMsg = [...(oldCold?.messages || []), ...(oldHot?.messages || [])]; allOldMessages = [...oldFullMsg, ...allOldMessages]; // 合并完成后才删除旧缓存,绝不提前删! uni.removeStorageSync(oldKey); uni.removeStorageSync(oldKey + "-hot"); }); newChat.messages = [...allOldMessages, ...newChat.messages]; // 根缓存只保留最新客服 const root = uni.getStorageSync(rootKey); if (root) { root.chatKeys = [`${prefix}${newKfId}`]; uni.setStorageSync(rootKey, root); } newChat.stored = false; // 按照原生冷热规则拆分保存 this.saveToStorage(true); }, openChat(chatInfo) { let chats = this.curChats; let chat = null; for (let idx in chats) { 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, type: chatInfo.type, showName: chatInfo.showName, headImage: chatInfo.headImage, isDnd: chatInfo.isDnd, lastContent: "", lastSendTime: new Date().getTime(), unreadCount: 0, hotMinIdx: 0, readedMessageIdx: 0, messages: [], atMe: false, atAll: false, stored: false }; chats.unshift(chat); this.saveToStorage(); } }, activeChat(idx) { let chats = this.curChats; if (idx >= 0) { chats[idx].unreadCount = 0; } }, resetUnreadCount(chatInfo) { let chats = this.curChats; for (let idx in chats) { if (chats[idx].type == chatInfo.type && chats[idx].targetId == chatInfo.targetId) { chats[idx].unreadCount = 0; chats[idx].atMe = false; chats[idx].atAll = false; chats[idx].stored = false; this.saveToStorage(); } } }, readedMessage(pos) { let chat = this.findChatByFriend(pos.friendId); if (!chat) return; 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) { if (!pos.maxId || m.id <= pos.maxId) { m.status = MESSAGE_STATUS.READED chat.readedMessageIdx = idx; chat.stored = false; } } } if (!chat.stored) { this.saveToStorage(); } }, cleanMessage(idx) { let chat = this.curChats[idx]; chat.lastContent = ''; chat.hotMinIdx = 0; chat.unreadCount = 0; chat.atMe = false; chat.atAll = false; chat.stored = false chat.messages = []; this.saveToStorage(true); }, removeChat(idx) { let chats = this.curChats; chats[idx].delete = true; chats[idx].stored = false; this.saveToStorage(); }, removePrivateChat(userId) { let chats = this.curChats; for (let idx in chats) { if (chats[idx].type == 'PRIVATE' && chats[idx].targetId == userId) { this.removeChat(idx); } } }, removeGroupChat(groupId) { let chats = this.curChats; for (let idx in chats) { if (chats[idx].type == 'GROUP' && chats[idx].targetId == groupId) { this.removeChat(idx); } } }, moveTop(idx) { if (this.loading) { return; } let chats = this.curChats; if (idx > 0) { let chat = chats[idx]; chats.splice(idx, 1); chats.unshift(chat); chat.lastSendTime = new Date().getTime(); chat.stored = false; this.saveToStorage(); } }, insertMessage(msgInfo, chatInfo) { const t = i18n.global.t; let type = chatInfo.type; 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) { 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 = t('chat.image'); } else if (msgInfo.type == MESSAGE_TYPE.FILE) { chat.lastContent = t('chat.file'); } else if (msgInfo.type == MESSAGE_TYPE.AUDIO) { chat.lastContent = t('chat.voice'); } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) { chat.lastContent = t('chat.voiceCall'); } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) { 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; 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(); let userId = userStore.userInfo.id; if (msgInfo.atUserIds.indexOf(userId) >= 0) { chat.atMe = true; } if (msgInfo.atUserIds.indexOf(-1) >= 0) { chat.atAll = true; } } if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) { chat.messages.push({ sendTime: msgInfo.sendTime, type: MESSAGE_TYPE.TIP_TIME, }); chat.lastTimeTip = msgInfo.sendTime; } chat.messages.push(msgInfo); chat.stored = false; this.saveToStorage(); }, updateMessage(msgInfo, chatInfo) { let chat = this.findChat(chatInfo); let message = this.findMessage(chat, msgInfo); if (message) { Object.assign(message, msgInfo); chat.stored = false; this.saveToStorage(); } }, deleteMessage(msgInfo, chatInfo) { let isColdMessage = false; let chat = this.findChat(chatInfo); let delIdx = -1; for (let idx in chat.messages) { if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) { delIdx = idx; break; } if (chat.messages[idx].tmpId && chat.messages[idx].tmpId == msgInfo.tmpId) { delIdx = idx; break; } } if (delIdx >= 0) { chat.messages.splice(delIdx, 1); if (delIdx < chat.hotMinIdx) { isColdMessage = true; chat.hotMinIdx--; } if (delIdx < chat.readedMessageIdx) { chat.readedMessageIdx--; } chat.stored = false; this.saveToStorage(isColdMessage); } }, recallMessage(msgInfo, chatInfo) { const t = i18n.global.t; let chat = this.findChat(chatInfo); if (!chat) return; let isColdMessage = false; let id = msgInfo.content; 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}${t('chat.recalledMessage')}`; m.type = MESSAGE_TYPE.TIP_TEXT chat.lastContent = m.content; chat.lastSendTime = msgInfo.sendTime; chat.sendNickName = ''; if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) { chat.unreadCount++; } isColdMessage = idx < chat.hotMinIdx; } if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) { m.quoteMessage.content = t('chat.quoteRecalled'); m.quoteMessage.status = MESSAGE_STATUS.RECALL; m.quoteMessage.type = MESSAGE_TYPE.TIP_TEXT } } 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; this.saveToStorage(); } }, updateChatFromUser(user) { let chat = this.findChatByFriend(user.id); if (chat && (chat.headImage != user.headImageThumb || chat.showName != user.nickName)) { chat.headImage = user.headImageThumb; chat.showName = user.nickName; chat.stored = false; this.saveToStorage(); } }, updateChatFromGroup(group) { let chat = this.findChatByGroup(group.id); if (chat && (chat.headImage != group.headImageThumb || chat.showName != group.showGroupName)) { chat.headImage = group.headImageThumb; chat.showName = group.showGroupName; chat.stored = false; this.saveToStorage(); } }, setLoading(loading) { this.loading = loading; }, setDnd(chatInfo, isDnd) { let chat = this.findChat(chatInfo); if (chat) { chat.isDnd = isDnd; } }, refreshChats() { let chats = cacheChats || this.chats; const friendStore = useFriendStore(); const groupStore = useGroupStore(); chats.forEach(chat => { if (chat.type == 'PRIVATE') { let friend = friendStore.findFriend(chat.targetId); if (friend) { chat.isDnd = friend.isDnd } } else if (chat.type == 'GROUP') { let group = groupStore.findGroup(chat.targetId); if (group) { chat.isDnd = group.isDnd } } }) chats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); const storageInfo = uni.getStorageInfoSync(); if (storageInfo && storageInfo.currentSize > 3000) { this.cleanOtherUserCache(); } this.fliterMessage(chats, 3000, 500); chats.forEach(chat => { // 原生规则:所有消息默认都放入热缓存,hotMinIdx归0 chat.hotMinIdx = 0; chat.stored = false; }); this.chats = chats; cacheChats = null; this.saveToStorage(true); }, fliterMessage(chats, maxTotalSize, maxPerChatSize) { let remainTotalSize = 0; chats.forEach(chat => { if (chat.messages.length > maxPerChatSize) { let idx = chat.messages.length - maxPerChatSize; chat.messages = chat.messages.slice(idx); } remainTotalSize += chat.messages.length; }) if (remainTotalSize > maxTotalSize) { this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2); } }, cleanOtherUserCache() { const userStore = useUserStore(); const userId = userStore.userInfo.id; const prefix = "chats-app-" + userId; const res = uni.getStorageInfoSync(); res.keys.forEach(key => { if (key.startsWith("chats-app") && !key.startsWith(prefix)) { uni.removeStorageSync(key); } }) }, saveToStorage(withColdMessage) { if (this.loading) { return; } const userStore = useUserStore(); let userId = userStore.userInfo.id; let rootKey = "chats-app-" + userId; let chatKeys = []; const keepHotCount = 300; // 原生热缓存保留条数 this.chats.forEach((chat) => { let chatKey = `${rootKey}-${chat.type}-${chat.targetId}` if (!chat.stored) { if (chat.delete) { uni.removeStorageSync(chatKey); uni.removeStorageSync(chatKey + '-hot'); } else { // 原生标准切割:hotMinIdx = 总消息长度 - 最新300条 const totalMsgLen = chat.messages.length; chat.hotMinIdx = Math.max(0, totalMsgLen - keepHotCount); // cold:前面所有久远历史消息 const coldMsg = chat.messages.slice(0, chat.hotMinIdx); // hot:后面最新的300条消息(所有新发消息全在这里!) const hotMsg = chat.messages.slice(chat.hotMinIdx); // 写入冷缓存 if (withColdMessage) { let coldChat = { ...chat, messages: coldMsg }; uni.setStorageSync(chatKey, coldChat); } // 写入热缓存(新发消息100%保存) let hotChat = { ...chat, messages: hotMsg }; uni.setStorageSync(chatKey + '-hot', hotChat); } chat.stored = true; } if (!chat.delete) { chatKeys.push(chatKey); } }) // 更新根会话列表 let chatsData = { privateMsgMaxId: this.privateMsgMaxId, groupMsgMaxId: this.groupMsgMaxId, chatKeys: chatKeys } uni.setStorageSync(rootKey, chatsData) this.chats = this.chats.filter(chat => !chat.delete) }, clear(state) { cacheChats = []; this.chats = []; this.privateMsgMaxId = 0; this.groupMsgMaxId = 0; this.loadingPrivateMsg = false; this.loadingGroupMsg = false; }, loadChat() { return new Promise((resolve, reject) => { let userStore = useUserStore(); let userId = userStore.userInfo.id; const rootCacheKey = `chats-app-${userId}`; let chatsData = uni.getStorageSync(rootCacheKey); // 安全处理:没有数据直接返回 if (!chatsData) { chatsData = { privateMsgMaxId: 0, groupMsgMaxId: 0, chatKeys: [] }; } // 安全处理:chatKeys 必须是数组 if (!chatsData.chatKeys || !Array.isArray(chatsData.chatKeys)) { chatsData.chatKeys = []; } uni.removeStorageSync("currentActiveCustomerId"); let finalChats = []; chatsData.chatKeys.forEach(key => { try { const coldChat = uni.getStorageSync(key) || {}; const hotChat = uni.getStorageSync(key + "-hot") || {}; // 修复发送中状态 if (hotChat.messages && Array.isArray(hotChat.messages)) { hotChat.messages.forEach(msg => { if (msg.status === MESSAGE_STATUS.SENDING) { msg.status = MESSAGE_STATUS.FAILED; } }); } // 消息合并(绝对安全) const coldMsg = Array.isArray(coldChat.messages) ? coldChat.messages : []; const hotMsg = Array.isArray(hotChat.messages) ? hotChat.messages : []; const fullMsg = [...coldMsg, ...hotMsg]; let chat = { ...coldChat, ...hotChat }; chat.messages = fullMsg; chat.hotMinIdx = coldMsg.length; chat.readedMessageIdx = chat.readedMessageIdx || 0; finalChats.push(chat); } catch (e) {} }); chatsData.chats = finalChats; this.initChats(chatsData); resolve(); }); }, }, getters: { curChats: (state) => { if (cacheChats && state.loading) { return cacheChats; } return state.chats; }, findChatIdx: (state) => (chat) => { 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) => { let chats = state.curChats; let idx = state.findChatIdx(chat); return chats[idx]; }, findChatByFriend: (state) => (fid) => { return state.curChats.find(chat => chat.type == 'PRIVATE' && chat.targetId == fid) }, findChatByGroup: (state) => (gid) => { return state.curChats.find(chat => chat.type == 'GROUP' && chat.targetId == gid) }, findMessage: (state) => (chat, msgInfo) => { if (!chat) { return null; } 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; } if (m.id && m.id < msgInfo.id) { break; } } } if (msgInfo.selfSend && 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; } if (m.tmpId && m.tmpId < msgInfo.tmpId) { break; } } } return null; } } })