|
|
|
|
|
import { MESSAGE_TYPE, MESSAGE_STATUS } from "@/common/enums.js"
|
|
|
|
|
|
// import localForage from 'localforage'
|
|
|
|
|
|
import {myCache} from "../utils/utils";
|
|
|
|
|
|
|
|
|
|
|
|
// 全局缓存变量(用于延迟渲染)
|
|
|
|
|
|
let cacheChats = []
|
|
|
|
|
|
|
|
|
|
|
|
// =================== Vuex 模块 ===================
|
|
|
|
|
|
const chatModule = {
|
|
|
|
|
|
namespaced: true,
|
|
|
|
|
|
|
|
|
|
|
|
state: () => ({
|
|
|
|
|
|
activeChat: null,
|
|
|
|
|
|
privateMsgMaxId: 0,
|
|
|
|
|
|
groupMsgMaxId: 0,
|
|
|
|
|
|
loadingPrivateMsg: false,
|
|
|
|
|
|
loadingGroupMsg: false,
|
|
|
|
|
|
chats: [] // 实际渲染的会话列表(仅前 N 个)
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
|
|
getters: {
|
|
|
|
|
|
// 是否正在加载离线消息
|
|
|
|
|
|
isLoading: (state) => () => {
|
|
|
|
|
|
return state.loadingPrivateMsg || state.loadingGroupMsg
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 返回当前应显示的会话列表(加载中返回缓存列表,否则返回真实列表)
|
|
|
|
|
|
curChats: (state, getters) => () => {
|
|
|
|
|
|
if (cacheChats && getters.isLoading()) {
|
|
|
|
|
|
return cacheChats
|
|
|
|
|
|
}
|
|
|
|
|
|
return state.chats
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 查找会话索引
|
|
|
|
|
|
findChatIdx: (state, getters) => (chat) => {
|
|
|
|
|
|
const chats = getters.curChats()
|
|
|
|
|
|
for (let idx in chats) {
|
|
|
|
|
|
if (chats[idx].type === chat.type && chats[idx].targetId === chat.targetId) {
|
|
|
|
|
|
return parseInt(idx)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return -1
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 查找会话对象
|
|
|
|
|
|
findChat: (state, getters) => (chat) => {
|
|
|
|
|
|
const idx = getters.findChatIdx(chat)
|
|
|
|
|
|
return idx >= 0 ? getters.curChats()[idx] : null
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
findChatByFriend: (state, getters) => (fid) => {
|
|
|
|
|
|
const chats = getters.curChats()
|
|
|
|
|
|
return chats.find(c => c.type === 'PRIVATE' && c.targetId === fid)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
findChatByGroup: (state, getters) => (gid) => {
|
|
|
|
|
|
const chats = getters.curChats()
|
|
|
|
|
|
return chats.find(c => c.type === 'GROUP' && c.targetId === gid)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 在指定 chat 中查找消息
|
|
|
|
|
|
findMessage: (state, getters) => (chat, msgInfo) => {
|
|
|
|
|
|
if (!chat) return null
|
|
|
|
|
|
for (let idx in chat.messages) {
|
|
|
|
|
|
const m = chat.messages[idx]
|
|
|
|
|
|
if (msgInfo.id && m.id === msgInfo.id) return m
|
|
|
|
|
|
if (msgInfo.tmpId && m.tmpId && m.tmpId === msgInfo.tmpId) return m
|
|
|
|
|
|
}
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
mutations: {
|
|
|
|
|
|
// ---------- 基础 mutations ----------
|
|
|
|
|
|
SET_CHATS(state, chats) {
|
|
|
|
|
|
state.chats = chats
|
|
|
|
|
|
},
|
|
|
|
|
|
SET_PRIVATE_MSG_MAX_ID(state, id) {
|
|
|
|
|
|
state.privateMsgMaxId = id
|
|
|
|
|
|
},
|
|
|
|
|
|
SET_GROUP_MSG_MAX_ID(state, id) {
|
|
|
|
|
|
state.groupMsgMaxId = id
|
|
|
|
|
|
},
|
|
|
|
|
|
SET_LOADING_PRIVATE(state, loading) {
|
|
|
|
|
|
state.loadingPrivateMsg = loading
|
|
|
|
|
|
},
|
|
|
|
|
|
SET_LOADING_GROUP(state, loading) {
|
|
|
|
|
|
state.loadingGroupMsg = loading
|
|
|
|
|
|
},
|
|
|
|
|
|
SET_ACTIVE_CHAT(state, chat) {
|
|
|
|
|
|
state.activeChat = chat
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 会话操作 ----------
|
|
|
|
|
|
ADD_CHAT(state, newChat) {
|
|
|
|
|
|
// 插入到列表头部
|
|
|
|
|
|
state.chats.unshift(newChat)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
REMOVE_CHAT(state, idx) {
|
|
|
|
|
|
if (idx >= 0 && idx < state.chats.length) {
|
|
|
|
|
|
state.chats.splice(idx, 1)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
MOVE_CHAT_TO_TOP(state, idx) {
|
|
|
|
|
|
if (idx > 0 && idx < state.chats.length) {
|
|
|
|
|
|
const chat = state.chats[idx]
|
|
|
|
|
|
state.chats.splice(idx, 1)
|
|
|
|
|
|
state.chats.unshift(chat)
|
|
|
|
|
|
chat.lastSendTime = new Date().getTime()
|
|
|
|
|
|
chat.stored = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
UPDATE_CHAT(state, { idx, chat }) {
|
|
|
|
|
|
if (idx >= 0 && idx < state.chats.length) {
|
|
|
|
|
|
state.chats[idx] = chat
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
RESET_UNREAD_COUNT(state, { type, targetId }) {
|
|
|
|
|
|
for (let c of state.chats) {
|
|
|
|
|
|
if (c.type === type && c.targetId === targetId) {
|
|
|
|
|
|
c.unreadCount = 0
|
|
|
|
|
|
c.atMe = false
|
|
|
|
|
|
c.atAll = false
|
|
|
|
|
|
c.stored = false
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 消息操作 ----------
|
|
|
|
|
|
UPDATE_MESSAGE(state, { chatIdx, msgInfo }) {
|
|
|
|
|
|
const chat = state.chats[chatIdx]
|
|
|
|
|
|
if (!chat) return
|
|
|
|
|
|
const msg = chat.messages.find(m => (m.id && m.id === msgInfo.id) || (m.tmpId && m.tmpId === msgInfo.tmpId))
|
|
|
|
|
|
if (msg) {
|
|
|
|
|
|
Object.assign(msg, msgInfo)
|
|
|
|
|
|
chat.stored = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
DELETE_MESSAGE(state, { chatIdx, msgInfo }) {
|
|
|
|
|
|
const chat = state.chats[chatIdx]
|
|
|
|
|
|
if (!chat) return
|
|
|
|
|
|
const idx = chat.messages.findIndex(m => (m.id && m.id === msgInfo.id) || (m.tmpId && m.tmpId === msgInfo.tmpId))
|
|
|
|
|
|
if (idx !== -1) {
|
|
|
|
|
|
chat.messages.splice(idx, 1)
|
|
|
|
|
|
chat.stored = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 更新会话的头像/名称
|
|
|
|
|
|
UPDATE_CHAT_INFO(state, { targetId, type, headImage, showName }) {
|
|
|
|
|
|
for (let c of state.chats) {
|
|
|
|
|
|
if (c.type === type && c.targetId === targetId) {
|
|
|
|
|
|
if (headImage) c.headImage = headImage
|
|
|
|
|
|
if (showName) c.showName = showName
|
|
|
|
|
|
c.stored = false
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// 更新 chat 的基本信息(lastContent, lastSendTime, sendNickName, unreadCount, atMe, atAll)
|
|
|
|
|
|
UPDATE_CHAT_SUMMARY(state, { idx, lastContent, lastSendTime, sendNickName, unreadCount, atMe, atAll }) {
|
|
|
|
|
|
const chat = state.chats[idx];
|
|
|
|
|
|
if (!chat) return;
|
|
|
|
|
|
if (lastContent !== undefined) chat.lastContent = lastContent;
|
|
|
|
|
|
if (lastSendTime !== undefined) chat.lastSendTime = lastSendTime;
|
|
|
|
|
|
if (sendNickName !== undefined) chat.sendNickName = sendNickName;
|
|
|
|
|
|
if (unreadCount !== undefined) chat.unreadCount = unreadCount;
|
|
|
|
|
|
if (atMe !== undefined) chat.atMe = atMe;
|
|
|
|
|
|
if (atAll !== undefined) chat.atAll = atAll;
|
|
|
|
|
|
chat.stored = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 插入时间戳消息
|
|
|
|
|
|
PUSH_TIME_TIP(state, { idx, sendTime }) {
|
|
|
|
|
|
const chat = state.chats[idx];
|
|
|
|
|
|
if (!chat) return;
|
|
|
|
|
|
chat.messages.push({ sendTime, type: MESSAGE_TYPE.TIP_TIME });
|
|
|
|
|
|
chat.lastTimeTip = sendTime;
|
|
|
|
|
|
chat.stored = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 插入消息(支持指定位置)
|
|
|
|
|
|
INSERT_MESSAGE(state, { idx, msg, insertPos }) {
|
|
|
|
|
|
const chat = state.chats[idx];
|
|
|
|
|
|
if (!chat) return;
|
|
|
|
|
|
if (insertPos === undefined || insertPos === chat.messages.length) {
|
|
|
|
|
|
chat.messages.push(msg);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
chat.messages.splice(insertPos, 0, msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
chat.stored = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 更新已有的消息(合并属性)
|
|
|
|
|
|
UPDATE_EXISTING_MESSAGE(state, { idx, msgInfo }) {
|
|
|
|
|
|
const chat = state.chats[idx];
|
|
|
|
|
|
if (!chat) return;
|
|
|
|
|
|
const msg = chat.messages.find(m => (m.id && m.id === msgInfo.id) || (m.tmpId && m.tmpId === msgInfo.tmpId));
|
|
|
|
|
|
if (msg) Object.assign(msg, msgInfo);
|
|
|
|
|
|
chat.stored = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 删除消息
|
|
|
|
|
|
DELETE_MESSAGE_BY_ID(state, { idx, msgInfo }) {
|
|
|
|
|
|
const chat = state.chats[idx];
|
|
|
|
|
|
if (!chat) return;
|
|
|
|
|
|
const pos = chat.messages.findIndex(m => (m.id && m.id === msgInfo.id) || (m.tmpId && m.tmpId === msgInfo.tmpId));
|
|
|
|
|
|
if (pos !== -1) {
|
|
|
|
|
|
chat.messages.splice(pos, 1);
|
|
|
|
|
|
chat.stored = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 撤回消息
|
|
|
|
|
|
RECALL_MESSAGE(state, { idx, targetId, recallContent, sendTime, selfSend }) {
|
|
|
|
|
|
const chat = state.chats[idx];
|
|
|
|
|
|
if (!chat) return;
|
|
|
|
|
|
for (let m of chat.messages) {
|
|
|
|
|
|
if (m.id === targetId) {
|
|
|
|
|
|
m.status = MESSAGE_STATUS.RECALL;
|
|
|
|
|
|
m.content = recallContent;
|
|
|
|
|
|
m.type = MESSAGE_TYPE.TIP_TEXT;
|
|
|
|
|
|
// 更新会话最后一条(如果撤回的是最后一条)
|
|
|
|
|
|
if (chat.messages[chat.messages.length - 1] === m) {
|
|
|
|
|
|
chat.lastContent = recallContent;
|
|
|
|
|
|
chat.lastSendTime = sendTime;
|
|
|
|
|
|
chat.sendNickName = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 处理引用消息
|
|
|
|
|
|
for (let m of chat.messages) {
|
|
|
|
|
|
if (m.quoteMessage && m.quoteMessage.id === targetId) {
|
|
|
|
|
|
m.quoteMessage.content = "引用内容已撤回";
|
|
|
|
|
|
m.quoteMessage.status = MESSAGE_STATUS.RECALL;
|
|
|
|
|
|
m.quoteMessage.type = MESSAGE_TYPE.TIP_TEXT;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
chat.stored = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 更新会话头像和名称
|
|
|
|
|
|
UPDATE_CHAT_AVATAR_NAME(state, { type, targetId, headImage, showName }) {
|
|
|
|
|
|
for (let chat of state.chats) {
|
|
|
|
|
|
if (chat.type === type && chat.targetId === targetId) {
|
|
|
|
|
|
if (headImage !== undefined) chat.headImage = headImage;
|
|
|
|
|
|
if (showName !== undefined) chat.showName = showName;
|
|
|
|
|
|
chat.stored = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// 一次性替换所有会话(用于 refreshChats)
|
|
|
|
|
|
REPLACE_CHATS(state, chats) {
|
|
|
|
|
|
state.chats = chats;
|
|
|
|
|
|
// 清空缓存(全局变量)
|
|
|
|
|
|
cacheChats = null;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 更新单个 chat 的 hotMinIdx 和 stored 等
|
|
|
|
|
|
UPDATE_CHAT_META(state, { idx, hotMinIdx, stored }) {
|
|
|
|
|
|
const chat = state.chats[idx];
|
|
|
|
|
|
if (!chat) return;
|
|
|
|
|
|
if (hotMinIdx !== undefined) chat.hotMinIdx = hotMinIdx;
|
|
|
|
|
|
if (stored !== undefined) chat.stored = stored;
|
|
|
|
|
|
},
|
|
|
|
|
|
CLEAR_UNREAD_COUNT(state, idx) {
|
|
|
|
|
|
const chats = state.chats; // 或者通过 getter 获取当前列表
|
|
|
|
|
|
if (idx >= 0 && idx < chats.length) {
|
|
|
|
|
|
const chat = chats[idx];
|
|
|
|
|
|
if (chat) {
|
|
|
|
|
|
chat.unreadCount = 0;
|
|
|
|
|
|
chat.atMe = false;
|
|
|
|
|
|
chat.atAll = false;
|
|
|
|
|
|
chat.stored = false; // 标记需持久化
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
actions: {
|
|
|
|
|
|
// ========== 初始化 ==========
|
|
|
|
|
|
initChats({ commit, state }, chatsData) {
|
|
|
|
|
|
cacheChats = []
|
|
|
|
|
|
commit('SET_CHATS', [])
|
|
|
|
|
|
commit('SET_PRIVATE_MSG_MAX_ID', chatsData.privateMsgMaxId || 0)
|
|
|
|
|
|
commit('SET_GROUP_MSG_MAX_ID', chatsData.groupMsgMaxId || 0)
|
|
|
|
|
|
|
|
|
|
|
|
const allChats = chatsData.chats || []
|
|
|
|
|
|
for (let chat of allChats) {
|
|
|
|
|
|
chat.stored = false
|
|
|
|
|
|
// 深拷贝存入缓存,用于延迟渲染
|
|
|
|
|
|
cacheChats.push(JSON.parse(JSON.stringify(chat)))
|
|
|
|
|
|
// 仅渲染前 15 个会话(可调)
|
|
|
|
|
|
if (state.chats.length < 15) {
|
|
|
|
|
|
// 直接 push 新对象,避免引用缓存
|
|
|
|
|
|
state.chats.push({ ...chat })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 防止图片一直处在加载中状态
|
|
|
|
|
|
cacheChats.forEach((chat) => {
|
|
|
|
|
|
chat.messages.forEach((msg) => {
|
|
|
|
|
|
if (msg.loadStatus == "loading") {
|
|
|
|
|
|
msg.loadStatus = "fail"
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 打开会话 ==========
|
|
|
|
|
|
openChat({ state, getters, commit, dispatch }, chatInfo) {
|
|
|
|
|
|
// 加载中直接返回(原逻辑)
|
|
|
|
|
|
// if (getters.isLoading()) return
|
|
|
|
|
|
const chats = getters.curChats()
|
|
|
|
|
|
let existingIdx = -1
|
|
|
|
|
|
for (let idx in chats) {
|
|
|
|
|
|
if (chats[idx].type === chatInfo.type && chats[idx].targetId === chatInfo.targetId) {
|
|
|
|
|
|
existingIdx = parseInt(idx)
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (existingIdx !== -1) {
|
|
|
|
|
|
// 已存在 → 移到顶部
|
|
|
|
|
|
dispatch('moveTop', existingIdx)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 新建会话
|
|
|
|
|
|
const newChat = {
|
|
|
|
|
|
targetId: chatInfo.targetId,
|
|
|
|
|
|
type: chatInfo.type,
|
|
|
|
|
|
showName: chatInfo.showName,
|
|
|
|
|
|
headImage: chatInfo.headImage,
|
|
|
|
|
|
lastContent: "",
|
|
|
|
|
|
lastSendTime: new Date().getTime(),
|
|
|
|
|
|
unreadCount: 0,
|
|
|
|
|
|
hotMinIdx: 0,
|
|
|
|
|
|
messages: [],
|
|
|
|
|
|
atMe: false,
|
|
|
|
|
|
atAll: false,
|
|
|
|
|
|
stored: false,
|
|
|
|
|
|
delete: false
|
|
|
|
|
|
}
|
|
|
|
|
|
commit('ADD_CHAT', newChat)
|
|
|
|
|
|
// 原逻辑未立即保存,等待后续消息触发保存
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false })
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
activeChat({ commit, getters }, idx) {
|
|
|
|
|
|
const chats = getters.curChats(); // 使用 getter 获取当前列表(支持加载状态)
|
|
|
|
|
|
if (idx >= 0 && idx < chats.length) {
|
|
|
|
|
|
commit('CLEAR_UNREAD_COUNT', idx);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
// ========== 移动会话到顶部 ==========
|
|
|
|
|
|
moveTop({ state, getters, commit, dispatch }, idx) {
|
|
|
|
|
|
if (getters.isLoading()) return
|
|
|
|
|
|
if (idx > 0 && idx < state.chats.length) {
|
|
|
|
|
|
commit('MOVE_CHAT_TO_TOP', idx)
|
|
|
|
|
|
|
|
|
|
|
|
// 移动后保存(原逻辑在 moveTop 最后调用了 saveToStorage)
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false })
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
removeChat({ state, getters, commit, dispatch }, idx) {
|
|
|
|
|
|
|
|
|
|
|
|
const chats = getters.curChats()
|
|
|
|
|
|
chats[idx].delete = true
|
|
|
|
|
|
chats[idx].stored = false
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false })
|
|
|
|
|
|
},
|
|
|
|
|
|
resetUnreadCount({ state, getters, dispatch }, chatInfo) {
|
|
|
|
|
|
const chats = getters.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
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false })
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
readedMessage({ state, getters, dispatch }, pos) {
|
|
|
|
|
|
const chat = getters.findChatByFriend(pos.friendId)
|
|
|
|
|
|
if (!chat) return
|
|
|
|
|
|
chat.messages.forEach((m) => {
|
|
|
|
|
|
let status=Number(m.status)
|
|
|
|
|
|
if (m.id && m.selfSend && status < MESSAGE_STATUS.RECALL) {
|
|
|
|
|
|
// pos.maxId为空表示整个会话已读
|
|
|
|
|
|
if (!pos.maxId || m.id <= pos.maxId) {
|
|
|
|
|
|
m.status = MESSAGE_STATUS.READED
|
|
|
|
|
|
chat.stored = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false })
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除私聊会话
|
|
|
|
|
|
* @param {Object} context - Vuex action 上下文
|
|
|
|
|
|
* @param {Number|String} userId - 对方用户ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
removePrivateChat({ getters, dispatch }, userId) {
|
|
|
|
|
|
const chats = getters.curChats()
|
|
|
|
|
|
for (let idx in chats) {
|
|
|
|
|
|
if (chats[idx].type === 'PRIVATE' && chats[idx].targetId === userId) {
|
|
|
|
|
|
// 将索引转为数字(因为 for...in 返回字符串)
|
|
|
|
|
|
dispatch('removeChat', parseInt(idx))
|
|
|
|
|
|
break // 一个用户只有一个私聊会话,找到后退出
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除群聊会话
|
|
|
|
|
|
* @param {Object} context - Vuex action 上下文
|
|
|
|
|
|
* @param {Number|String} groupId - 群ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
removeGroupChat({ getters, dispatch }, groupId) {
|
|
|
|
|
|
const chats = getters.curChats()
|
|
|
|
|
|
for (let idx in chats) {
|
|
|
|
|
|
if (chats[idx].type === 'GROUP' && chats[idx].targetId === groupId) {
|
|
|
|
|
|
dispatch('removeChat', parseInt(idx))
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
insertMessage({ state, getters, commit, dispatch, rootState }, { msgInfo, chatInfo }) {
|
|
|
|
|
|
const type = chatInfo.type;
|
|
|
|
|
|
// 更新最大消息 ID
|
|
|
|
|
|
if (msgInfo.id && type === "PRIVATE" && msgInfo.id > state.privateMsgMaxId) {
|
|
|
|
|
|
commit('SET_PRIVATE_MSG_MAX_ID', msgInfo.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (msgInfo.id && type === "GROUP" && msgInfo.id > state.groupMsgMaxId) {
|
|
|
|
|
|
commit('SET_GROUP_MSG_MAX_ID', msgInfo.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找会话
|
|
|
|
|
|
const idx = getters.findChatIdx(chatInfo);
|
|
|
|
|
|
if (idx === -1) return;
|
|
|
|
|
|
const chat = state.chats[idx]; // 直接引用
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已存在该消息(重发或更新)
|
|
|
|
|
|
const existingMsg = getters.findMessage(chat, msgInfo);
|
|
|
|
|
|
if (existingMsg) {
|
|
|
|
|
|
commit('UPDATE_EXISTING_MESSAGE', { idx, msgInfo });
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let msgInfoType=Number(msgInfo.type)
|
|
|
|
|
|
let msgInfoStatus=Number(msgInfo.status)
|
|
|
|
|
|
// 准备要更新的字段
|
|
|
|
|
|
let lastContent = chat.lastContent;
|
|
|
|
|
|
if (msgInfoType === MESSAGE_TYPE.IMAGE) lastContent = "[图片]";
|
|
|
|
|
|
else if (msgInfoType === MESSAGE_TYPE.FILE) lastContent = "[文件]";
|
|
|
|
|
|
else if (msgInfoType === MESSAGE_TYPE.AUDIO) lastContent = "[语音]";
|
|
|
|
|
|
else if (msgInfoType === MESSAGE_TYPE.ACT_RT_VOICE) lastContent = "[语音通话]";
|
|
|
|
|
|
else if (msgInfoType === MESSAGE_TYPE.ACT_RT_VIDEO) lastContent = "[视频通话]";
|
|
|
|
|
|
else if (msgInfoType === MESSAGE_TYPE.TEXT || msgInfoType === MESSAGE_TYPE.RECALL || msgInfoType === MESSAGE_TYPE.TIP_TEXT) {
|
|
|
|
|
|
lastContent = msgInfo.content;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新最后消息摘要
|
|
|
|
|
|
let unreadCount = chat.unreadCount || 0;
|
|
|
|
|
|
let atMe = chat.atMe || false;
|
|
|
|
|
|
let atAll = chat.atAll || false;
|
|
|
|
|
|
|
|
|
|
|
|
// 未读计数
|
|
|
|
|
|
if (!msgInfo.selfSend && msgInfoStatus !== MESSAGE_STATUS.READED &&
|
|
|
|
|
|
msgInfoStatus !== MESSAGE_STATUS.RECALL && msgInfoType !== MESSAGE_TYPE.TIP_TEXT) {
|
|
|
|
|
|
unreadCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// @ 处理
|
|
|
|
|
|
if (!msgInfo.selfSend && chat.type === "GROUP" && msgInfo.atUserIds && msgInfoStatus !== MESSAGE_STATUS.READED) {
|
|
|
|
|
|
const userInfo = rootState.user?.userInfo;
|
|
|
|
|
|
if (userInfo) {
|
|
|
|
|
|
if (msgInfo.atUserIds.includes(userInfo.id)) atMe = true;
|
|
|
|
|
|
if (msgInfo.atUserIds.includes(-1)) atAll = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新会话摘要(通过 mutation)
|
|
|
|
|
|
commit('UPDATE_CHAT_SUMMARY', {
|
|
|
|
|
|
idx,
|
|
|
|
|
|
lastContent,
|
|
|
|
|
|
lastSendTime: msgInfo.sendTime,
|
|
|
|
|
|
sendNickName: msgInfo.sendNickName,
|
|
|
|
|
|
unreadCount,
|
|
|
|
|
|
atMe,
|
|
|
|
|
|
atAll
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 时间戳提示(间隔10分钟)
|
|
|
|
|
|
if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) {
|
|
|
|
|
|
commit('PUSH_TIME_TIP', { idx, sendTime: msgInfo.sendTime });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算插入位置(防止乱序)
|
|
|
|
|
|
let insertPos = chat.messages.length;
|
|
|
|
|
|
if (msgInfo.id && msgInfo.id > 0) {
|
|
|
|
|
|
for (let i = 0; i < chat.messages.length; i++) {
|
|
|
|
|
|
const m = chat.messages[i];
|
|
|
|
|
|
if (m.id && msgInfo.id < m.id) {
|
|
|
|
|
|
insertPos = i;
|
|
|
|
|
|
console.log(`消息乱序修正: ${chat.messages.length} -> ${insertPos}`);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 插入消息
|
|
|
|
|
|
commit('INSERT_MESSAGE', { idx, msg: msgInfo, insertPos });
|
|
|
|
|
|
|
|
|
|
|
|
// 标记已存储为 false(已在 mutation 中完成)
|
|
|
|
|
|
// 保存到本地
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 更新消息 ==========
|
|
|
|
|
|
updateMessage({ getters, commit, dispatch }, { msgInfo, chatInfo }) {
|
|
|
|
|
|
const idx = getters.findChatIdx(chatInfo);
|
|
|
|
|
|
if (idx === -1) return;
|
|
|
|
|
|
commit('UPDATE_EXISTING_MESSAGE', { idx, msgInfo });
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 删除消息 ==========
|
|
|
|
|
|
deleteMessage({ getters, commit, dispatch }, { msgInfo, chatInfo }) {
|
|
|
|
|
|
const idx = getters.findChatIdx(chatInfo);
|
|
|
|
|
|
if (idx === -1) return;
|
|
|
|
|
|
const chat = getters.findChat(chatInfo);
|
|
|
|
|
|
// 找到消息位置,判断是否为冷消息(仅用于记录,不影响存储)
|
|
|
|
|
|
let isCold = false;
|
|
|
|
|
|
for (let i = 0; i < chat.messages.length; i++) {
|
|
|
|
|
|
const m = chat.messages[i];
|
|
|
|
|
|
if ((m.id && m.id === msgInfo.id) || (m.tmpId && m.tmpId === msgInfo.tmpId)) {
|
|
|
|
|
|
isCold = i < chat.hotMinIdx;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
commit('DELETE_MESSAGE_BY_ID', { idx, msgInfo });
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: isCold });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 撤回消息 ==========
|
|
|
|
|
|
recallMessage({ getters, commit, dispatch, rootState }, { msgInfo, chatInfo }) {
|
|
|
|
|
|
const idx = getters.findChatIdx(chatInfo);
|
|
|
|
|
|
if (idx === -1) return;
|
|
|
|
|
|
const chat = getters.findChat(chatInfo);
|
|
|
|
|
|
const targetId = msgInfo.content; // 被撤回的消息ID
|
|
|
|
|
|
// 构建撤回提示内容
|
|
|
|
|
|
const name = msgInfo.selfSend ? '你' : (chat.type === 'PRIVATE' ? '对方' : msgInfo.sendNickName);
|
|
|
|
|
|
const recallContent = name + "撤回了一条消息";
|
|
|
|
|
|
// 判断是否为冷消息
|
|
|
|
|
|
let isCold = false;
|
|
|
|
|
|
for (let i = 0; i < chat.messages.length; i++) {
|
|
|
|
|
|
if (chat.messages[i].id === targetId) {
|
|
|
|
|
|
isCold = i < chat.hotMinIdx;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
commit('RECALL_MESSAGE', { idx, targetId, recallContent, sendTime: msgInfo.sendTime, selfSend: msgInfo.selfSend });
|
|
|
|
|
|
// 更新未读计数(如果撤回的是对方消息且未读)
|
|
|
|
|
|
if (!msgInfo.selfSend && Number(msgInfo.status) !== MESSAGE_STATUS.READED) {
|
|
|
|
|
|
// 需要在 mutation 中增加未读计数?但原逻辑是 chat.unreadCount++,我们可以在 mutation 中处理
|
|
|
|
|
|
// 但为了复用,我们在 action 中手动增加(通过 UPDATE_CHAT_SUMMARY)
|
|
|
|
|
|
const chatRef = state.chats[idx];
|
|
|
|
|
|
if (chatRef) {
|
|
|
|
|
|
commit('UPDATE_CHAT_SUMMARY', { idx, unreadCount: (chatRef.unreadCount || 0) + 1 });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: isCold });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 更新会话信息(好友) ==========
|
|
|
|
|
|
updateChatFromFriend({ commit, dispatch }, friend) {
|
|
|
|
|
|
commit('UPDATE_CHAT_AVATAR_NAME', {
|
|
|
|
|
|
type: 'PRIVATE',
|
|
|
|
|
|
targetId: friend.id,
|
|
|
|
|
|
headImage: friend.headImage,
|
|
|
|
|
|
showName: friend.nickName
|
|
|
|
|
|
});
|
|
|
|
|
|
// 存储(会影响多个会话,但只更新了头像名称,可延迟保存)
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 更新会话信息(用户自身) ==========
|
|
|
|
|
|
updateChatFromUser({ commit, dispatch }, user) {
|
|
|
|
|
|
commit('UPDATE_CHAT_AVATAR_NAME', {
|
|
|
|
|
|
type: 'PRIVATE',
|
|
|
|
|
|
targetId: user.id,
|
|
|
|
|
|
headImage: user.headImageThumb,
|
|
|
|
|
|
showName: user.nickName
|
|
|
|
|
|
});
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 更新会话信息(群组) ==========
|
|
|
|
|
|
updateChatFromGroup({ commit, dispatch }, group) {
|
|
|
|
|
|
commit('UPDATE_CHAT_AVATAR_NAME', {
|
|
|
|
|
|
type: 'GROUP',
|
|
|
|
|
|
targetId: group.id,
|
|
|
|
|
|
headImage: group.headImageThumb,
|
|
|
|
|
|
showName: group.name
|
|
|
|
|
|
});
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: false });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setLoadingPrivateMsg({ commit, dispatch, getters }, loading) {
|
|
|
|
|
|
|
|
|
|
|
|
commit('SET_LOADING_PRIVATE', loading);
|
|
|
|
|
|
if (!getters.isLoading()) { // 使用 context.getters(模块内局部 getters)
|
|
|
|
|
|
dispatch('refreshChats');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
setLoadingGroupMsg({ commit, dispatch, getters }, loading) {
|
|
|
|
|
|
commit('SET_LOADING_GROUP', loading);
|
|
|
|
|
|
if (!getters.isLoading()) { // 同上
|
|
|
|
|
|
dispatch('refreshChats');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 刷新会话列表(将缓存数据一次性渲染) ==========
|
|
|
|
|
|
refreshChats({ commit, dispatch }) {
|
|
|
|
|
|
if (!cacheChats) return;
|
|
|
|
|
|
// 排序(按最后消息时间倒序)
|
|
|
|
|
|
cacheChats.sort((a, b) => b.lastSendTime - a.lastSendTime);
|
|
|
|
|
|
|
|
|
|
|
|
// 非 App 端容量限制:每个会话最多保留1000条消息
|
|
|
|
|
|
// #ifndef APP-PLUS
|
|
|
|
|
|
cacheChats.forEach(chat => {
|
|
|
|
|
|
if (chat.messages.length > 1000) {
|
|
|
|
|
|
chat.messages = chat.messages.slice(-1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
// 记录热数据索引(所有消息都视为热数据)
|
|
|
|
|
|
cacheChats.forEach(chat => chat.hotMinIdx = chat.messages.length);
|
|
|
|
|
|
|
|
|
|
|
|
// 一次性替换 state.chats
|
|
|
|
|
|
commit('REPLACE_CHATS', cacheChats);
|
|
|
|
|
|
// 清空缓存变量(已在 mutation 中处理)
|
|
|
|
|
|
// 持久化
|
|
|
|
|
|
dispatch('saveToStorage', { withColdMessage: true });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 保存到本地 ==========
|
|
|
|
|
|
saveToStorage({ state, getters, rootState }, { withColdMessage = false } = {}) {
|
|
|
|
|
|
|
|
|
|
|
|
if (getters.isLoading()) return
|
|
|
|
|
|
const userId = myCache('user').userid;
|
|
|
|
|
|
if (!userId) return
|
|
|
|
|
|
const key = `chats-app-${userId}`
|
|
|
|
|
|
const chatKeys = []
|
|
|
|
|
|
// 按会话为单位存储,
|
|
|
|
|
|
state.chats.forEach(chat => {
|
|
|
|
|
|
// 只存储有改动的会话
|
|
|
|
|
|
const chatKey = `${key}-${chat.type}-${chat.targetId}`
|
|
|
|
|
|
if (!chat.stored) {
|
|
|
|
|
|
|
|
|
|
|
|
if (chat.delete) {
|
|
|
|
|
|
uni.removeStorageSync(chatKey);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 存储冷数据
|
|
|
|
|
|
if (withColdMessage) {
|
|
|
|
|
|
const coldChat = { ...chat, messages: chat.messages.slice(0, chat.hotMinIdx) }
|
|
|
|
|
|
uni.setStorageSync(chatKey, coldChat)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 存储热消息
|
|
|
|
|
|
const hotKey = chatKey + '-hot'
|
|
|
|
|
|
const hotChat = { ...chat, messages: chat.messages.slice(chat.hotMinIdx) }
|
|
|
|
|
|
uni.setStorageSync(hotKey, hotChat);
|
|
|
|
|
|
}
|
|
|
|
|
|
chat.stored = true
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!chat.delete) {
|
|
|
|
|
|
chatKeys.push(chatKey)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
const chatsData = {
|
|
|
|
|
|
privateMsgMaxId: state.privateMsgMaxId,
|
|
|
|
|
|
groupMsgMaxId: state.groupMsgMaxId,
|
|
|
|
|
|
chatKeys
|
|
|
|
|
|
}
|
|
|
|
|
|
uni.setStorageSync(key, chatsData)
|
|
|
|
|
|
// 移除已删除的会话
|
|
|
|
|
|
state.chats = state.chats.filter(c => !c.delete)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 加载离线消息 ==========
|
|
|
|
|
|
loadChat({ commit, dispatch, rootState }) {
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
var userId = myCache('user').userid;
|
|
|
|
|
|
if (!userId) return resolve()
|
|
|
|
|
|
const key = `chats-app-${userId}`
|
|
|
|
|
|
const chatsData = uni.getStorageSync(`chats-app-${userId}`);
|
|
|
|
|
|
if (chatsData) {
|
|
|
|
|
|
if (chatsData.chatKeys) {
|
|
|
|
|
|
chatsData.chats = [];
|
|
|
|
|
|
chatsData.chatKeys.forEach(key => {
|
|
|
|
|
|
const coldChat = uni.getStorageSync(key);
|
|
|
|
|
|
const hotChat = uni.getStorageSync(key + '-hot');
|
|
|
|
|
|
if (!coldChat && !hotChat) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 合并冷热消息
|
|
|
|
|
|
const chat = Object.assign({}, coldChat, hotChat);
|
|
|
|
|
|
if (hotChat && coldChat) {
|
|
|
|
|
|
chat.messages = coldChat.messages.concat(hotChat.messages);
|
|
|
|
|
|
}
|
|
|
|
|
|
chatsData.chats.push(chat);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// 调用 initChats action 初始化数据
|
|
|
|
|
|
dispatch('initChats', chatsData);
|
|
|
|
|
|
}
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
clear(state){
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
// ... 其他 actions 类似迁移
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default chatModule
|