chat.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. /**
  2. * 聊天全局状态:会话列表、各会话消息列表,供 composables 与页面共享
  3. */
  4. import { reactive } from 'vue'
  5. export const chatStore = reactive({
  6. // 会话列表(来自 getContacts)
  7. contacts: [],
  8. loadingContacts: false,
  9. /** 最近一次拉取会话列表是否因网络/接口失败(用于空列表时区分「请下拉刷新」与「暂无消息」) */
  10. contactsFetchFailed: false,
  11. // 按 contactId 存消息列表
  12. messages: {},
  13. // 已加载过历史的 contactId 集合,避免重复拉取
  14. loadedContactIds: {},
  15. // 当前正在查看的会话,用于前台/后台消息通知判断
  16. activeContactId: '',
  17. // 某会话是否正在加载更多
  18. loadingMore: {},
  19. /** 登出 / 清空 token 时重置,避免换账号仍显示上一用户会话与消息 */
  20. reset() {
  21. this.contacts = []
  22. this.loadingContacts = false
  23. this.contactsFetchFailed = false
  24. this.messages = {}
  25. this.loadedContactIds = {}
  26. this.activeContactId = ''
  27. this.loadingMore = {}
  28. this.setTabBarUnreadFromServer(0)
  29. },
  30. setContacts(list) {
  31. this.contacts = list || []
  32. },
  33. /** 仅更新某会话在列表中的最后一条预览与时间(WS 收到新消息时本地更新,不整表刷新) */
  34. updateContactPreview(contactId, { lastMessage, time }) {
  35. const id = String(contactId)
  36. const list = this.contacts || []
  37. const idx = list.findIndex((c) => String(c.user_id || c.id) === id)
  38. if (idx === -1) return
  39. const next = list.slice()
  40. next[idx] = { ...next[idx], lastMessage: lastMessage ?? next[idx].lastMessage, time: time ?? next[idx].time }
  41. this.contacts = next
  42. },
  43. setMessagesForContact(contactId, list, append = false) {
  44. const id = String(contactId)
  45. if (!this.messages[id]) this.messages[id] = []
  46. if (append) {
  47. this.messages[id].push(...list)
  48. } else {
  49. const current = this.messages[id] || []
  50. const tempMessages = current.filter((m) => m.tempId != null)
  51. // 合并接口历史与 store 里已有消息(含 WS 推送),避免点进会话时把实时收到的消息冲掉
  52. const existingStable = current.filter((m) => !m.tempId)
  53. const apiList = list || []
  54. const seen = new Set()
  55. const merged = []
  56. for (const m of [...apiList, ...existingStable]) {
  57. const k = String(m.id || m.tempId || '')
  58. if (seen.has(k)) continue
  59. seen.add(k)
  60. merged.push(m)
  61. }
  62. merged.sort((a, b) => {
  63. const ta = (a.createdAt && new Date(a.createdAt).getTime()) || 0
  64. const tb = (b.createdAt && new Date(b.createdAt).getTime()) || 0
  65. return ta - tb
  66. })
  67. this.messages[id] = [...merged, ...tempMessages]
  68. }
  69. },
  70. prependMessages(contactId, list) {
  71. const id = String(contactId)
  72. if (!this.messages[id]) this.messages[id] = []
  73. const merged = (list || []).concat(this.messages[id])
  74. // 按 id 去重,保留首次出现(历史在前,避免重复)
  75. const seen = new Set()
  76. this.messages[id] = merged.filter((m) => {
  77. const k = m.id || m.tempId
  78. if (seen.has(k)) return false
  79. seen.add(k)
  80. return true
  81. })
  82. },
  83. appendMessage(contactId, message) {
  84. const id = String(contactId)
  85. const prev = this.messages[id] || []
  86. this.messages[id] = [...prev, message]
  87. },
  88. replaceTempMessage(contactId, tempId, serverMessage) {
  89. const id = String(contactId)
  90. const list = this.messages[id]
  91. if (!list || !list.length) return
  92. const idx = list.findIndex((m) => m.id === tempId || m.tempId === tempId)
  93. if (idx !== -1) {
  94. this.messages[id] = [...list.slice(0, idx), serverMessage, ...list.slice(idx + 1)]
  95. }
  96. },
  97. /** 按 id / tempId 移除一条(如再次提醒发送失败时撤回临时消息) */
  98. removeMessage(contactId, messageId) {
  99. const id = String(contactId)
  100. const list = this.messages[id]
  101. if (!list || !list.length) return
  102. this.messages[id] = list.filter((m) => m.id !== messageId && m.tempId !== messageId)
  103. },
  104. /** 更新某条消息(如 status: 'sending' / 'failed') */
  105. updateMessage(contactId, tempIdOrId, updates) {
  106. const id = String(contactId)
  107. const list = this.messages[id]
  108. if (!list || !list.length) return
  109. const idx = list.findIndex((m) => m.id === tempIdOrId || m.tempId === tempIdOrId)
  110. if (idx !== -1) {
  111. const next = list.slice()
  112. next[idx] = { ...next[idx], ...updates }
  113. this.messages[id] = next
  114. }
  115. },
  116. markContactLoaded(contactId) {
  117. this.loadedContactIds[String(contactId)] = true
  118. },
  119. hasContactLoaded(contactId) {
  120. return !!this.loadedContactIds[String(contactId)]
  121. },
  122. setActiveContact(contactId) {
  123. this.activeContactId = contactId ? String(contactId) : ''
  124. },
  125. /**
  126. * 底部「消息」Tab 角标(index 0),数值来自 GET /messages/unread-count
  127. * @param {number|string} n - 未读总数
  128. */
  129. setTabBarUnreadFromServer(n) {
  130. const total = Math.max(0, Number(n) || 0)
  131. try {
  132. if (total <= 0) {
  133. uni.removeTabBarBadge({ index: 0 })
  134. } else {
  135. uni.setTabBarBadge({ index: 0, text: total > 99 ? '99+' : String(total) })
  136. }
  137. } catch (e) {
  138. // 非 Tab 页或未就绪时可能报错,忽略
  139. }
  140. }
  141. })