gaoluyang
2026-06-29 27cd042df9aca0383a49f3514bc21958dd890912
src/views/im/home/store/groupRequestStore.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,242 @@
import type { ImGroupRequestApi } from '#/api/im/group/request'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { agreeGroupRequest as apiAgreeGroupRequest, getMyGroupRequest as apiGetMyGroupRequest, getUnhandledRequestList as apiGetUnhandledRequestList, pullMyGroupRequestList as apiPullMyGroupRequestList, refuseGroupRequest as apiRefuseGroupRequest } from '#/api/im/group/request'
import { getCurrentUserId } from '#/views/im/utils/auth'
import { ImGroupRequestHandleResult } from '#/views/im/utils/constants'
import { getDb, StorageKeys } from '../../utils/db'
import { runIncrementalPull } from '../../utils/pull'
type PendingRequest = { epoch: number; promise: Promise<void>; userId: number; }
/** clear() æ—¶é€’增;旧账号 in-flight çš„ pullGroupRequests ç»“æžœ resolve åŽæ¯”对一致才写 store,防跨账号红点污染(与 friendStore åŒå£å¾„) */
let storeEpoch = 0
let pendingUnhandledFetch: null | PendingRequest = null
/**
 * IM åŠ ç¾¤ç”³è¯· Store
 *
 * ä»…维护「我管理的所有群」下未处理的申请列表(unhandledList);
 * æ¨ªå¹… / Drawer éƒ½ä»Žè¿™é‡Œæ´¾ç”Ÿ count å’Œåˆ†ç»„列表,避免给 ImGroupApi.GroupRespVO æŒ‚ pendingRequestCount å­—段
 *
 * æ•°æ®ç”Ÿå‘½å‘¨æœŸï¼š
 * - è¿› IM åŽè°ƒä¸€æ¬¡ fetchUnhandledGroupRequestList æ‹‰é¦–页全量
 * - WebSocket 1503 â†’ è°ƒ addGroupRequestById(requestId) æ‹‰å•条 + push åˆ° unhandledList å¤´éƒ¨
 * - WebSocket 1505 / 1506 â†’ æŒ‰ requestId ä»Ž unhandledList ç§»é™¤
 * - WebSocket 1517 GROUP_ADMIN_ADD(自己被加为 admin)→ é‡æ–°è°ƒ fetchUnhandledGroupRequestList
 * - æœ¬ç«¯ agree / refuse å¤„理后 â†’ æœ¬åœ°æŒ‰ requestId ç§»é™¤
 */
export const useGroupRequestStore = defineStore('imGroupRequestStore', {
  state: () => ({
    /** æˆ‘管理的所有群下未处理申请列表(按 id å€’序) */
    unhandledList: [] as ImGroupRequestApi.GroupRequestRespVO[],
    /** fetchUnhandledGroupRequestList æ˜¯å¦æˆåŠŸæ‰§è¡Œè¿‡ï¼›é¿å…æ¨ªå¹…æ˜¾ç¤º 0 ç„¶åŽè·³æ•°å­—的闪烁 */
    loaded: false
  }),
  getters: {
    /**
     * å„群下未处理申请数的 Map;O(N) æ‰«ä¸€æ¬¡ç¼“存供 ConversationItem ç­‰ N å¤„复用,避免 N×M é‡å¤ filter
     */
    getUnhandledGroupRequestCountMap(state): Map<number, number> {
      const map = new Map<number, number>()
      for (const request of state.unhandledList) {
        map.set(request.groupId, (map.get(request.groupId) ?? 0) + 1)
      }
      return map
    },
    /** æŒ‡å®šç¾¤ä¸‹çš„æœªå¤„理申请数 */
    getUnhandledGroupRequestCount(): (groupId: number) => number {
      return (groupId: number) => this.getUnhandledGroupRequestCountMap.get(groupId) ?? 0
    },
    /** æŒ‡å®šç¾¤ä¸‹çš„æœªå¤„理申请列表 */
    getUnhandledGroupRequestListByGroupId:
      (state) =>
      (groupId: number): ImGroupRequestApi.GroupRequestRespVO[] =>
        state.unhandledList.filter((r) => r.groupId === groupId)
  },
  actions: {
    /** ä»Ž IndexedDB æ¢å¤åŠ ç¾¤ç”³è¯· */
    async loadGroupRequestList(): Promise<boolean> {
      try {
        const cached = await getDb().getAll<ImGroupRequestApi.GroupRequestRespVO>('groupRequests')
        if (!cached || cached.length === 0) {
          return false
        }
        this.unhandledList = cached
          .filter((request) => request.handleResult === ImGroupRequestHandleResult.UNHANDLED)
          .toSorted((requestA, requestB) => requestB.id - requestA.id)
        return true
      } catch (error) {
        console.warn('[IM groupRequestStore] æœ¬åœ°åŠ ç¾¤ç”³è¯·ç¼“å­˜è¯»å–å¤±è´¥', error)
        return false
      }
    },
    /** ä¿å­˜åŠ ç¾¤ç”³è¯·åˆ—è¡¨ */
    saveGroupRequestList(): void {
      void getDb()
        .transaction(['groupRequests'], 'readwrite', async (tx) => {
          const db = getDb()
          await db.clearStore('groupRequests', tx)
          for (const request of this.unhandledList) {
            await db.put('groupRequests', request, tx)
          }
        })
        .catch((error) => console.warn('[IM groupRequestStore] æœ¬åœ°åŠ ç¾¤ç”³è¯·ç¼“å­˜å†™å…¥å¤±è´¥', error))
    },
    /** ä¿å­˜å•条加群申请 */
    async saveGroupRequestRecord(request: ImGroupRequestApi.GroupRequestRespVO): Promise<void> {
      await getDb().put('groupRequests', request)
    },
    /** ä¿å­˜å•条加群申请 */
    saveGroupRequest(request: ImGroupRequestApi.GroupRequestRespVO): void {
      void this.saveGroupRequestRecord(request).catch((error) =>
        console.warn('[IM groupRequestStore] æœ¬åœ°åŠ ç¾¤ç”³è¯·å†™å…¥å¤±è´¥', error)
      )
    },
    /** æ‹‰å–我管理的所有群下未处理申请;进 IM åŽ / å‡çº§ admin åŽ / WS æŽ¨é€æœ‰å†²çªæ—¶è°ƒç”¨ */
    async fetchUnhandledGroupRequestList() {
      const requestEpoch = storeEpoch
      const requestUserId = getCurrentUserId()
      if (
        pendingUnhandledFetch?.epoch === requestEpoch &&
        pendingUnhandledFetch.userId === requestUserId
      ) {
        return pendingUnhandledFetch.promise
      }
      const promise = (async () => {
        const list = await apiGetUnhandledRequestList()
        if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
          return
        }
        this.unhandledList = list || []
        this.loaded = true
        this.saveGroupRequestList()
      })().finally(() => {
        if (
          pendingUnhandledFetch?.epoch === requestEpoch &&
          pendingUnhandledFetch.userId === requestUserId
        ) {
          pendingUnhandledFetch = null
        }
      })
      pendingUnhandledFetch = { epoch: requestEpoch, userId: requestUserId, promise }
      return promise
    },
    /**
     * WS æ”¶åˆ° 1503:拉最新内容并置顶
     *
     * åŒä¸€å¯¹ group_id, user_id å¤ç”¨è®°å½•æ—¶ requestId ä¸å˜ä½† applyContent / inviterUserId ä¼šåˆ·æ–°ï¼Œæ‰€ä»¥æ— æ¡ä»¶ fetch + æŽ’到头部
     */
    async addGroupRequestById(requestId: number) {
      const requestEpoch = storeEpoch
      const requestUserId = getCurrentUserId()
      const request = await apiGetMyGroupRequest(requestId)
      if (!request) {
        return
      }
      if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) {
        return
      }
      this.upsertGroupRequest(request)
    },
    /**
     * æœ¬åœ°åˆå¹¶ / æ–°å¢žå•条加群申请(WS æŽ¨é€ & å¢žé‡æ‹‰å–共用)
     *
     * æœªå¤„理的按 id åŽ»é‡åŽç½®é¡¶ï¼›å·²å¤„ç†çš„ä»Žæœªå¤„ç†åˆ—è¡¨ç§»é™¤ï¼Œé¿å…è¡¥å¿æ—¶æŠŠå·²åŒæ„ / æ‹’绝的记录塞回红点
     */
    upsertGroupRequest(request: ImGroupRequestApi.GroupRequestRespVO) {
      void this.upsertGroupRequestForPull(request).catch((error) =>
        console.warn('[IM groupRequestStore] æœ¬åœ°åŠ ç¾¤ç”³è¯·å†™å…¥å¤±è´¥', error)
      )
    },
    /** æœ¬åœ°åˆå¹¶ / æ–°å¢žå•条加群申请 */
    async upsertGroupRequestForPull(request: ImGroupRequestApi.GroupRequestRespVO): Promise<void> {
      if (request.handleResult !== ImGroupRequestHandleResult.UNHANDLED) {
        await this.removeGroupRequestByIdForPull(request.id)
        return
      }
      this.unhandledList = [request, ...this.unhandledList.filter((r) => r.id !== request.id)]
      await this.saveGroupRequestRecord(request)
    },
    /**
     * å¢žé‡æ‹‰å–加群申请变更并合并;含已处理的按 handleResult èµ°ç§»é™¤ï¼ˆå·²å¤„理 â†’ ä»Žçº¢ç‚¹åˆ—表剔除)
     *
     * åªåšé‡è¿ž / åŽç»­ç¦»çº¿è¡¥å¿ï¼Œä¸è´Ÿè´£é¦–登:首登红点走 fetchUnhandledGroupRequestList(服务端直接过滤未处理,语义更精准、启动更轻)。
     * æ•…首次重连时游标为空 = ä¸€æ¬¡æ€§å…¨é‡èµ°ä¸€éï¼ˆå·²å¤„理记录命中 removeGroupRequestById ä¸º no-op,红点不受影响),之后增量。
     */
    async pullGroupRequests() {
      // å¿«ç…§ epoch;账号在拉取途中切换(clear() â†’ epoch++)时丢弃旧账号那几页结果,防跨账号红点污染
      const requestEpoch = storeEpoch
      const requestUserId = getCurrentUserId()
      const isActive = () => requestEpoch === storeEpoch && getCurrentUserId() === requestUserId
      await runIncrementalPull(
        StorageKeys.settings.groupRequestPullCursor,
        apiPullMyGroupRequestList,
        async (records) => {
          if (!isActive()) {
            return false
          }
          await Promise.all(records.map((vo) => this.upsertGroupRequestForPull(vo)))
          return true
        },
        isActive
      )
      if (isActive()) {
        this.loaded = true
      }
    },
    /** WS æ”¶åˆ° 1505 / 1506 æˆ–本端处理完一条:按 requestId ä»Žåˆ—表移除 */
    removeGroupRequestById(requestId: number) {
      this.unhandledList = this.unhandledList.filter((r) => r.id !== requestId)
      void getDb()
        .delete('groupRequests', requestId)
        .catch((error) => console.warn('[IM groupRequestStore] æœ¬åœ°åŠ ç¾¤ç”³è¯·åˆ é™¤å¤±è´¥', error))
    },
    /** åˆ é™¤å•条加群申请 */
    async removeGroupRequestByIdForPull(requestId: number): Promise<void> {
      this.unhandledList = this.unhandledList.filter((r) => r.id !== requestId)
      await getDb().delete('groupRequests', requestId)
    },
    /** åŒæ„ç”³è¯·ï¼›æœ¬ç«¯å¤„理后立即从列表移除,避免被反复点击 */
    async agreeGroupRequest(requestId: number) {
      await apiAgreeGroupRequest(requestId)
      this.removeGroupRequestById(requestId)
    },
    /** æ‹’绝申请 */
    async refuseGroupRequest(requestId: number, handleContent?: string) {
      await apiRefuseGroupRequest(requestId, handleContent)
      this.removeGroupRequestById(requestId)
    },
    /** æ¸…空加群申请内存 */
    clear() {
      this.unhandledList = []
      this.loaded = false
      // è´¦å·åˆ‡æ¢ï¼šé€’增 epoch åºŸå¼ƒæ—§è´¦å· in-flight çš„ pullGroupRequests ç»“果,避免写进新账号红点列表
      storeEpoch++
      pendingUnhandledFetch = null
    }
  }
})
export const useGroupRequestStoreWithOut = () => useGroupRequestStore()
if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useGroupRequestStore, import.meta.hot))
}