/** * 聊天全局状态:会话列表、各会话消息列表,供 composables 与页面共享 */ import { reactive } from 'vue' export const chatStore = reactive({ // 会话列表(来自 getContacts) contacts: [], loadingContacts: false, /** 最近一次拉取会话列表是否因网络/接口失败(用于空列表时区分「请下拉刷新」与「暂无消息」) */ contactsFetchFailed: false, // 按 contactId 存消息列表 messages: {}, // 已加载过历史的 contactId 集合,避免重复拉取 loadedContactIds: {}, // 当前正在查看的会话,用于前台/后台消息通知判断 activeContactId: '', // 某会话是否正在加载更多 loadingMore: {}, /** 登出 / 清空 token 时重置,避免换账号仍显示上一用户会话与消息 */ reset() { this.contacts = [] this.loadingContacts = false this.contactsFetchFailed = false this.messages = {} this.loadedContactIds = {} this.activeContactId = '' this.loadingMore = {} this.setTabBarUnreadFromServer(0) }, setContacts(list) { this.contacts = list || [] }, /** 仅更新某会话在列表中的最后一条预览与时间(WS 收到新消息时本地更新,不整表刷新) */ updateContactPreview(contactId, { lastMessage, time }) { const id = String(contactId) const list = this.contacts || [] const idx = list.findIndex((c) => String(c.user_id || c.id) === id) if (idx === -1) return const next = list.slice() next[idx] = { ...next[idx], lastMessage: lastMessage ?? next[idx].lastMessage, time: time ?? next[idx].time } this.contacts = next }, setMessagesForContact(contactId, list, append = false) { const id = String(contactId) if (!this.messages[id]) this.messages[id] = [] if (append) { this.messages[id].push(...list) } else { const current = this.messages[id] || [] const tempMessages = current.filter((m) => m.tempId != null) // 合并接口历史与 store 里已有消息(含 WS 推送),避免点进会话时把实时收到的消息冲掉 const existingStable = current.filter((m) => !m.tempId) const apiList = list || [] const seen = new Set() const merged = [] for (const m of [...apiList, ...existingStable]) { const k = String(m.id || m.tempId || '') if (seen.has(k)) continue seen.add(k) merged.push(m) } merged.sort((a, b) => { const ta = (a.createdAt && new Date(a.createdAt).getTime()) || 0 const tb = (b.createdAt && new Date(b.createdAt).getTime()) || 0 return ta - tb }) this.messages[id] = [...merged, ...tempMessages] } }, prependMessages(contactId, list) { const id = String(contactId) if (!this.messages[id]) this.messages[id] = [] const merged = (list || []).concat(this.messages[id]) // 按 id 去重,保留首次出现(历史在前,避免重复) const seen = new Set() this.messages[id] = merged.filter((m) => { const k = m.id || m.tempId if (seen.has(k)) return false seen.add(k) return true }) }, appendMessage(contactId, message) { const id = String(contactId) const prev = this.messages[id] || [] this.messages[id] = [...prev, message] }, replaceTempMessage(contactId, tempId, serverMessage) { const id = String(contactId) const list = this.messages[id] if (!list || !list.length) return const idx = list.findIndex((m) => m.id === tempId || m.tempId === tempId) if (idx !== -1) { this.messages[id] = [...list.slice(0, idx), serverMessage, ...list.slice(idx + 1)] } }, /** 按 id / tempId 移除一条(如再次提醒发送失败时撤回临时消息) */ removeMessage(contactId, messageId) { const id = String(contactId) const list = this.messages[id] if (!list || !list.length) return this.messages[id] = list.filter((m) => m.id !== messageId && m.tempId !== messageId) }, /** 更新某条消息(如 status: 'sending' / 'failed') */ updateMessage(contactId, tempIdOrId, updates) { const id = String(contactId) const list = this.messages[id] if (!list || !list.length) return const idx = list.findIndex((m) => m.id === tempIdOrId || m.tempId === tempIdOrId) if (idx !== -1) { const next = list.slice() next[idx] = { ...next[idx], ...updates } this.messages[id] = next } }, markContactLoaded(contactId) { this.loadedContactIds[String(contactId)] = true }, hasContactLoaded(contactId) { return !!this.loadedContactIds[String(contactId)] }, setActiveContact(contactId) { this.activeContactId = contactId ? String(contactId) : '' }, /** * 底部「消息」Tab 角标(index 0),数值来自 GET /messages/unread-count * @param {number|string} n - 未读总数 */ setTabBarUnreadFromServer(n) { const total = Math.max(0, Number(n) || 0) try { if (total <= 0) { uni.removeTabBarBadge({ index: 0 }) } else { uni.setTabBarBadge({ index: 0, text: total > 99 ? '99+' : String(total) }) } } catch (e) { // 非 Tab 页或未就绪时可能报错,忽略 } } })