chatStore.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import { defineStore } from 'pinia';
  2. import { MESSAGE_TYPE, MESSAGE_STATUS } from "../api/enums.js"
  3. import useFriendStore from './friendStore.js';
  4. import useGroupStore from './groupStore.js';
  5. import useUserStore from './userStore.js';
  6. import localForage from 'localforage';
  7. /**
  8. * 优化1(冷热消息分区):
  9. * 热消息:登录后的消息
  10. * 冷消息: 登录前的消息
  11. * 每个会话的冷热消息分别用一个key进行存储,当有新的消息时,只更新热消息key,冷消息key保持不变
  12. * 由于热消息数量不会很大,所以localForage.setItem耗时很低,可以防止消息过多时出现卡顿的情况
  13. *
  14. * 优化2(延迟渲染):
  15. * 拉取消息时,如果直接用state.chats接收,页面就开始渲染,一边渲染页面一边大量接消息会导致很严重的卡顿
  16. * 为了加速拉取离线消息效率,拉取时消息暂时存储到cacheChats,等待所有离线消息拉取完成后,再统一放至state中进行渲染
  17. *
  18. * 优化3(pinia代替vuex)
  19. * 实测pinia的远超vuex,且语法更简洁清晰
  20. *
  21. * */
  22. let cacheChats = [];
  23. export default defineStore('chatStore', {
  24. state: () => {
  25. return {
  26. activeChat: null,
  27. privateMsgMaxId: 0,
  28. groupMsgMaxId: 0,
  29. loadingPrivateMsg: false,
  30. loadingGroupMsg: false,
  31. chats: []
  32. }
  33. },
  34. actions: {
  35. initChats(chatsData) {
  36. this.chats = [];
  37. this.privateMsgMaxId = chatsData.privateMsgMaxId || 0;
  38. this.groupMsgMaxId = chatsData.groupMsgMaxId || 0;
  39. cacheChats = chatsData.chats || [];
  40. // 防止图片一直处在加载中状态
  41. cacheChats.forEach((chat) => {
  42. chat.messages.forEach((msg) => {
  43. if (msg.loadStatus == "loading") {
  44. msg.loadStatus = "fail"
  45. }
  46. })
  47. })
  48. },
  49. openChat(chatInfo) {
  50. let chats = this.findChats()
  51. let chat = null;
  52. for (let idx in chats) {
  53. if (chats[idx].type == chatInfo.type &&
  54. chats[idx].targetId === chatInfo.targetId) {
  55. chat = chats[idx];
  56. // 放置头部
  57. this.moveTop(idx)
  58. break;
  59. }
  60. }
  61. // 创建会话
  62. if (chat == null) {
  63. chat = {
  64. targetId: chatInfo.targetId,
  65. type: chatInfo.type,
  66. showName: chatInfo.showName,
  67. headImage: chatInfo.headImage,
  68. isDnd: chatInfo.isDnd,
  69. lastContent: "",
  70. lastSendTime: new Date().getTime(),
  71. unreadCount: 0,
  72. hotMinIdx: 0,
  73. messages: [],
  74. atMe: false,
  75. atAll: false,
  76. stored: false,
  77. delete: false
  78. };
  79. chats.unshift(chat);
  80. }
  81. },
  82. setActiveChat(idx) {
  83. let chats = this.findChats();
  84. this.activeChat = chats[idx];
  85. },
  86. resetUnreadCount(chatInfo) {
  87. let chats = this.findChats();
  88. for (let idx in chats) {
  89. if (chats[idx].type == chatInfo.type &&
  90. chats[idx].targetId == chatInfo.targetId) {
  91. chats[idx].unreadCount = 0;
  92. chats[idx].atMe = false;
  93. chats[idx].atAll = false;
  94. chats[idx].stored = false;
  95. this.saveToStorage();
  96. break;
  97. }
  98. }
  99. },
  100. readedMessage(pos) {
  101. let chat = this.findChatByFriend(pos.friendId);
  102. if (!chat) return;
  103. chat.messages.forEach((m) => {
  104. if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) {
  105. // pos.maxId为空表示整个会话已读
  106. if (!pos.maxId || m.id <= pos.maxId) {
  107. m.status = MESSAGE_STATUS.READED
  108. chat.stored = false;
  109. }
  110. }
  111. })
  112. this.saveToStorage();
  113. },
  114. removeChat(idx) {
  115. let chats = this.findChats();
  116. if (chats[idx] == this.activeChat) {
  117. this.activeChat = null;
  118. }
  119. chats[idx].delete = true;
  120. chats[idx].stored = false;
  121. this.saveToStorage();
  122. },
  123. removePrivateChat(friendId) {
  124. let chats = this.findChats();
  125. for (let idx in chats) {
  126. if (chats[idx].type == 'PRIVATE' &&
  127. chats[idx].targetId === friendId) {
  128. this.removeChat(idx);
  129. break;
  130. }
  131. }
  132. },
  133. removeGroupChat(groupId) {
  134. let chats = this.findChats();
  135. for (let idx in chats) {
  136. if (chats[idx].type == 'GROUP' &&
  137. chats[idx].targetId === groupId) {
  138. this.removeChat(idx);
  139. break;
  140. }
  141. }
  142. },
  143. moveTop(idx) {
  144. // 加载中不移动,很耗性能
  145. if (this.isLoading()) {
  146. return;
  147. }
  148. if (idx > 0) {
  149. let chats = this.findChats();
  150. let chat = chats[idx];
  151. chats.splice(idx, 1);
  152. chats.unshift(chat);
  153. chat.lastSendTime = new Date().getTime();
  154. chat.stored = false;
  155. this.saveToStorage();
  156. }
  157. },
  158. insertMessage(msgInfo, chatInfo) {
  159. let time = new Date().getTime()
  160. let type = chatInfo.type;
  161. // 记录消息的最大id
  162. if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) {
  163. this.privateMsgMaxId = msgInfo.id;
  164. }
  165. if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) {
  166. this.groupMsgMaxId = msgInfo.id;
  167. }
  168. // 如果是已存在消息,则覆盖旧的消息数据
  169. let chat = this.findChat(chatInfo);
  170. let message = this.findMessage(chat, msgInfo);
  171. if (message) {
  172. Object.assign(message, msgInfo);
  173. chat.stored = false;
  174. this.saveToStorage();
  175. return;
  176. }
  177. // 插入新的数据
  178. if (msgInfo.type == MESSAGE_TYPE.IMAGE) {
  179. chat.lastContent = "[图片]";
  180. } else if (msgInfo.type == MESSAGE_TYPE.FILE) {
  181. chat.lastContent = "[文件]";
  182. } else if (msgInfo.type == MESSAGE_TYPE.AUDIO) {
  183. chat.lastContent = "[语音]";
  184. } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) {
  185. chat.lastContent = "[语音通话]";
  186. } else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) {
  187. chat.lastContent = "[视频通话]";
  188. } else if (msgInfo.type == MESSAGE_TYPE.TEXT ||
  189. msgInfo.type == MESSAGE_TYPE.RECALL ||
  190. msgInfo.type == MESSAGE_TYPE.TIP_TEXT) {
  191. chat.lastContent = msgInfo.content;
  192. }
  193. chat.lastSendTime = msgInfo.sendTime;
  194. chat.sendNickName = msgInfo.sendNickName;
  195. // 未读加1
  196. if (!chat.isDnd && !msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
  197. msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
  198. chat.unreadCount++;
  199. }
  200. // 是否有人@我
  201. if (!msgInfo.selfSend && chat.type == "GROUP" && msgInfo.atUserIds &&
  202. msgInfo.status != MESSAGE_STATUS.READED) {
  203. let userStore = useUserStore();
  204. let userId = userStore.userInfo.id;
  205. if (msgInfo.atUserIds.indexOf(userId) >= 0) {
  206. chat.atMe = true;
  207. }
  208. if (msgInfo.atUserIds.indexOf(-1) >= 0) {
  209. chat.atAll = true;
  210. }
  211. }
  212. // 间隔大于10分钟插入时间显示
  213. if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) {
  214. chat.messages.push({
  215. sendTime: msgInfo.sendTime,
  216. type: MESSAGE_TYPE.TIP_TIME,
  217. });
  218. chat.lastTimeTip = msgInfo.sendTime;
  219. }
  220. // 根据id顺序插入,防止消息乱序
  221. let insertPos = chat.messages.length;
  222. // 防止 图片、文件 在发送方 显示 在顶端 因为还没存库,id=0
  223. if (msgInfo.id && msgInfo.id > 0) {
  224. for (let idx in chat.messages) {
  225. if (chat.messages[idx].id && msgInfo.id < chat.messages[idx].id) {
  226. insertPos = idx;
  227. console.log(`消息出现乱序,位置:${chat.messages.length},修正至:${insertPos}`);
  228. break;
  229. }
  230. }
  231. }
  232. chat.messages.splice(insertPos, 0, msgInfo);
  233. chat.stored = false;
  234. this.saveToStorage();
  235. console.log("耗时:", new Date().getTime() - time)
  236. },
  237. updateMessage(msgInfo, chatInfo) {
  238. // 获取对方id或群id
  239. let chat = this.findChat(chatInfo);
  240. let message = this.findMessage(chat, msgInfo);
  241. if (message) {
  242. // 属性拷贝
  243. Object.assign(message, msgInfo);
  244. chat.stored = false;
  245. this.saveToStorage();
  246. }
  247. },
  248. deleteMessage(msgInfo, chatInfo) {
  249. let chat = this.findChat(chatInfo);
  250. let isColdMessage = false;
  251. for (let idx in chat.messages) {
  252. // 已经发送成功的,根据id删除
  253. if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) {
  254. chat.messages.splice(idx, 1);
  255. isColdMessage = idx < chat.hotMinIdx;
  256. break;
  257. }
  258. // 正在发送中的消息可能没有id,只有临时id
  259. if (chat.messages[idx].tmpId && chat.messages[idx].tmpId == msgInfo.tmpId) {
  260. chat.messages.splice(idx, 1);
  261. isColdMessage = idx < chat.hotMinIdx;
  262. break;
  263. }
  264. }
  265. chat.stored = false;
  266. this.saveToStorage(isColdMessage);
  267. },
  268. recallMessage(msgInfo, chatInfo) {
  269. let chat = this.findChat(chatInfo);
  270. if (!chat) return;
  271. let isColdMessage = false;
  272. // 要撤回的消息id
  273. let id = msgInfo.content;
  274. let name = msgInfo.selfSend ? '你' : chat.type == 'PRIVATE' ? '对方' : msgInfo.sendNickName;
  275. for (let idx in chat.messages) {
  276. let m = chat.messages[idx];
  277. if (m.id && m.id == id) {
  278. // 改造成一条提示消息
  279. m.status = MESSAGE_STATUS.RECALL;
  280. m.content = name + "撤回了一条消息";
  281. m.type = MESSAGE_TYPE.TIP_TEXT
  282. // 会话列表
  283. chat.lastContent = m.content;
  284. chat.lastSendTime = msgInfo.sendTime;
  285. chat.sendNickName = '';
  286. if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) {
  287. chat.unreadCount++;
  288. }
  289. isColdMessage = idx < chat.hotMinIdx;
  290. }
  291. // 被引用的消息也要撤回
  292. if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) {
  293. m.quoteMessage.content = "引用内容已撤回";
  294. m.quoteMessage.status = MESSAGE_STATUS.RECALL;
  295. m.quoteMessage.type = MESSAGE_TYPE.TIP_TEXT
  296. }
  297. }
  298. chat.stored = false;
  299. this.saveToStorage(isColdMessage);
  300. },
  301. updateChatFromFriend(friend) {
  302. let chat = this.findChatByFriend(friend.id);
  303. // 更新会话中的群名和头像
  304. if (chat && (chat.headImage != friend.headImage ||
  305. chat.showName != friend.nickName)) {
  306. chat.headImage = friend.headImage;
  307. chat.showName = friend.nickName;
  308. chat.stored = false;
  309. this.saveToStorage()
  310. }
  311. },
  312. updateChatFromUser(user) {
  313. let chat = this.findChatByFriend(user.id);
  314. // 更新会话中的昵称和头像
  315. if (chat && (chat.headImage != user.headImageThumb ||
  316. chat.showName != user.nickName)) {
  317. chat.headImage = user.headImageThumb;
  318. chat.showName = user.nickName;
  319. chat.stored = false;
  320. this.saveToStorage();
  321. }
  322. },
  323. updateChatFromGroup(group) {
  324. let chat = this.findChatByGroup(group.id);
  325. if (chat && (chat.headImage != group.headImageThumb ||
  326. chat.showName != group.showGroupName)) {
  327. // 更新会话中的群名称和头像
  328. chat.headImage = group.headImageThumb;
  329. chat.showName = group.showGroupName;
  330. chat.stored = false;
  331. this.saveToStorage()
  332. }
  333. },
  334. setLoadingPrivateMsg(loading) {
  335. this.loadingPrivateMsg = loading;
  336. if (!this.isLoading()) {
  337. this.refreshChats();
  338. }
  339. },
  340. setLoadingGroupMsg(loading) {
  341. this.loadingGroupMsg = loading;
  342. if (!this.isLoading()) {
  343. this.refreshChats();
  344. }
  345. },
  346. setDnd(chatInfo, isDnd) {
  347. let chat = this.findChat(chatInfo);
  348. if (chat) {
  349. chat.isDnd = isDnd;
  350. chat.unreadCount = 0;
  351. }
  352. },
  353. refreshChats() {
  354. if (!cacheChats) return;
  355. // 刷新免打扰状态
  356. const friendStore = useFriendStore();
  357. const groupStore = useGroupStore();
  358. cacheChats.forEach(chat => {
  359. if (chat.type == 'PRIVATE') {
  360. let friend = friendStore.findFriend(chat.targetId);
  361. if (friend) {
  362. chat.isDnd = friend.isDnd
  363. }
  364. } else if (chat.type == 'GROUP') {
  365. let group = groupStore.findGroup(chat.targetId);
  366. if (group) {
  367. chat.isDnd = group.isDnd
  368. }
  369. }
  370. if (chat.isDnd) {
  371. chat.unreadCount = 0;
  372. }
  373. })
  374. // 排序
  375. cacheChats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime);
  376. // 记录热数据索引位置
  377. cacheChats.forEach(chat => chat.hotMinIdx = chat.messages.length);
  378. // 将消息一次性装载回来
  379. this.chats = cacheChats;
  380. // 清空缓存
  381. cacheChats = null;
  382. // 持久化消息
  383. this.saveToStorage(true);
  384. },
  385. saveToStorage(withColdMessage) {
  386. // 加载中不保存,防止卡顿
  387. if (this.isLoading()) {
  388. return;
  389. }
  390. let userStore = useUserStore();
  391. let userId = userStore.userInfo.id;
  392. let key = "chats-" + userId;
  393. let chatKeys = [];
  394. // 按会话为单位存储,
  395. this.chats.forEach((chat) => {
  396. // 只存储有改动的会话
  397. let chatKey = `${key}-${chat.type}-${chat.targetId}`
  398. if (!chat.stored) {
  399. if (chat.delete) {
  400. localForage.removeItem(chatKey);
  401. } else {
  402. // 存储冷数据
  403. if (withColdMessage) {
  404. let coldChat = Object.assign({}, chat);
  405. coldChat.messages = chat.messages.slice(0, chat.hotMinIdx);
  406. localForage.setItem(chatKey, coldChat)
  407. }
  408. // 存储热消息
  409. let hotKey = chatKey + '-hot';
  410. let hotChat = Object.assign({}, chat);
  411. hotChat.messages = chat.messages.slice(chat.hotMinIdx)
  412. localForage.setItem(hotKey, hotChat)
  413. }
  414. chat.stored = true;
  415. }
  416. if (!chat.delete) {
  417. chatKeys.push(chatKey);
  418. }
  419. })
  420. // 会话核心信息
  421. let chatsData = {
  422. privateMsgMaxId: this.privateMsgMaxId,
  423. groupMsgMaxId: this.groupMsgMaxId,
  424. chatKeys: chatKeys
  425. }
  426. localForage.setItem(key, chatsData)
  427. // 清理已删除的会话
  428. this.chats = this.chats.filter(chat => !chat.delete)
  429. },
  430. clear() {
  431. cacheChats = []
  432. this.chats = [];
  433. this.activeChat = null;
  434. },
  435. loadChat() {
  436. return new Promise((resolve, reject) => {
  437. let userStore = useUserStore();
  438. let userId = userStore.userInfo.id;
  439. let key = "chats-" + userId;
  440. localForage.getItem(key).then((chatsData) => {
  441. if (!chatsData) {
  442. resolve();
  443. } else if (chatsData.chatKeys) {
  444. const promises = [];
  445. chatsData.chatKeys.forEach(key => {
  446. promises.push(localForage.getItem(key))
  447. promises.push(localForage.getItem(key + "-hot"))
  448. })
  449. Promise.all(promises).then(chats => {
  450. chatsData.chats = [];
  451. // 偶数下标为冷消息,奇数下标为热消息
  452. for (let i = 0; i < chats.length; i += 2) {
  453. if (!chats[i] && !chats[i + 1]) {
  454. continue;
  455. }
  456. let coldChat = chats[i];
  457. let hotChat = chats[i + 1];
  458. // 冷热消息合并
  459. let chat = Object.assign({}, coldChat, hotChat);
  460. if (hotChat && coldChat) {
  461. chat.messages = coldChat.messages.concat(hotChat.messages)
  462. }
  463. chatsData.chats.push(chat);
  464. }
  465. this.initChats(chatsData);
  466. resolve();
  467. })
  468. }
  469. }).catch(e => {
  470. console.log("加载消息失败")
  471. reject(e);
  472. })
  473. })
  474. }
  475. },
  476. getters: {
  477. isLoading: (state) => () => {
  478. return state.loadingPrivateMsg || state.loadingGroupMsg
  479. },
  480. findChats: (state) => () => {
  481. if (cacheChats && state.isLoading()) {
  482. return cacheChats;
  483. }
  484. return state.chats;
  485. },
  486. findChatIdx: (state) => (chat) => {
  487. let chats = state.findChats();
  488. for (let idx in chats) {
  489. if (chats[idx].type == chat.type &&
  490. chats[idx].targetId === chat.targetId) {
  491. chat = chats[idx];
  492. return idx
  493. }
  494. }
  495. },
  496. findChat: (state) => (chat) => {
  497. let chats = state.findChats();
  498. let idx = state.findChatIdx(chat);
  499. return chats[idx];
  500. },
  501. findChatByFriend: (state) => (fid) => {
  502. let chats = state.findChats();
  503. return chats.find(chat => chat.type == 'PRIVATE' &&
  504. chat.targetId == fid)
  505. },
  506. findChatByGroup: (state) => (gid) => {
  507. let chats = state.findChats();
  508. return chats.find(chat => chat.type == 'GROUP' &&
  509. chat.targetId == gid)
  510. },
  511. findMessage: (state) => (chat, msgInfo) => {
  512. if (!chat) {
  513. return null;
  514. }
  515. for (let idx in chat.messages) {
  516. // 通过id判断
  517. if (msgInfo.id && chat.messages[idx].id == msgInfo.id) {
  518. return chat.messages[idx];
  519. }
  520. // 正在发送中的消息可能没有id,只有tmpId
  521. if (msgInfo.tmpId && chat.messages[idx].tmpId &&
  522. chat.messages[idx].tmpId == msgInfo.tmpId) {
  523. return chat.messages[idx];
  524. }
  525. }
  526. }
  527. }
  528. });