chat.js 5.2 KB

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