| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { Group, GroupDO, GroupMember, Message } from '../types' |
| | | |
| | | import type { ImGroupApi } from '#/api/im/group' |
| | | import type { ImGroupMemberApi } from '#/api/im/group/member' |
| | | |
| | | import { CommonStatusEnum } from '#/packages/constants/src' |
| | | |
| | | import { acceptHMRUpdate, defineStore } from 'pinia' |
| | | |
| | | import { getGroup as apiGetGroup, getMyGroupList as apiGetMyGroupList } from '#/api/im/group' |
| | | import { getGroupMember as apiGetGroupMember, getGroupMemberList as apiGetGroupMemberList, updateGroupMember as apiUpdateGroupMember } from '#/api/im/group/member' |
| | | import { getCurrentUserId } from '#/views/im/utils/auth' |
| | | |
| | | import { |
| | | ImContentType, |
| | | ImConversationType, |
| | | ImGroupMemberRole, |
| | | ImMessageStatus |
| | | } from '../../utils/constants' |
| | | import { getDb } from '../../utils/db' |
| | | import { type GroupNotificationPayload } from '../../utils/message' |
| | | import { getGroupDisplayName } from '../../utils/user' |
| | | import { useConversationStore } from './conversationStore' |
| | | import { useGroupRequestStore } from './groupRequestStore' |
| | | |
| | | /** clear() æ¶éå¢ï¼æ§è´¦å· in-flight çæå请æ±è¿å忝坹ä¸è´æå store */ |
| | | let storeEpoch = 0 |
| | | |
| | | /** |
| | | * fetchGroupMemberList å¹¶åå»é表ï¼å groupId åæ¶è¿ç请æ±å
±ç¨ä¸ä¸ª Promise |
| | | * |
| | | * key å¿
须带 userIdââè´¦å·åæ¢æ¶ A ç请æ±ä¸è½è¢« B å¤ç¨ï¼å¦å IIFE å
é¨ç saveGroupMemberList 伿 A çæåæ°æ®åè¿ B ç IDB æ¡¶ |
| | | */ |
| | | const pendingMemberFetches = new Map<string, Promise<GroupMember[]>>() |
| | | const pendingMemberKey = (userId: number, groupId: number) => `${userId}:${groupId}` |
| | | |
| | | /** |
| | | * fetchGroupMember åæåå¹¶åå»é表ï¼å (groupId, memberUserId) åæ¶è¿ç请æ±å
±ç¨ä¸ä¸ª Promise |
| | | * |
| | | * è·æ´ç¾¤è¡¨åå¼ï¼åæå fetch è·æ´ç¾¤ fetch è¯ä¹ä¸åï¼åæåä¸åå¡« me ç silentï¼ï¼ä¸è½äºç¸ä»£æ¿ |
| | | */ |
| | | const pendingSingleMemberFetches = new Map<string, Promise<GroupMember | null>>() |
| | | |
| | | const pendingSingleMemberKey = (userId: number, groupId: number, memberUserId: number) => |
| | | `${userId}:${groupId}:${memberUserId}` |
| | | |
| | | /** æå»ºç¾¤ IndexedDB è®°å½ */ |
| | | function buildGroupDO(group: Group): GroupDO { |
| | | const { |
| | | activeCallExpired: _activeCallExpired, |
| | | activeCallLoaded: _activeCallLoaded, |
| | | infoLoaded: _infoLoaded, |
| | | members: _members, |
| | | membersLoaded: _membersLoaded, |
| | | membersExpired: _membersExpired, |
| | | ...record |
| | | } = group |
| | | return record |
| | | } |
| | | |
| | | /** 夿å½åç¨æ·æ¯å¦å¨ payload.memberUserIds éï¼GROUP_CREATE / INVITE / KICK èªå¤ç¨ï¼ */ |
| | | function isSelfInPayloadMembers(payload: GroupNotificationPayload): boolean { |
| | | const selfUserId = getCurrentUserId() |
| | | return !!selfUserId && (payload.memberUserIds || []).includes(selfUserId) |
| | | } |
| | | |
| | | /** å·æ°æç®¡çç群ç³è¯·çº¢ç¹ */ |
| | | function refreshUnhandledGroupRequests(): void { |
| | | useGroupRequestStore() |
| | | .fetchUnhandledGroupRequestList() |
| | | .catch(() => undefined) |
| | | } |
| | | |
| | | /** |
| | | * IM 群 Store |
| | | * |
| | | * è´è´£ï¼ |
| | | * - æå / ç¼åå½åç»å½ç¨æ·å å
¥ç群å表 |
| | | * - æ groupId æå 载群æåï¼ä¾ ConversationGroupSide / MentionPicker / MessageReadStatus æ¶è´¹ï¼ |
| | | * - æå"已读 / æªè¯»"çèåæ¥è¯¢ç± MessageReadStatus å¦è¡ç»å |
| | | */ |
| | | export const useGroupStore = defineStore('imGroupStore', { |
| | | state: () => ({ |
| | | groups: [] as Group[], |
| | | loaded: false, // ä»
fetchGroupList æååç½®ä½ï¼loadGroupListï¼IDBï¼ä¸ç½®ä½ï¼å¦ååå° SWR å·æ°ä¼è¢«ç¼åå½ä¸è·³è¿ |
| | | groupMembersExpired: false // è¿å
¥ IM / éè¿åç½®ä½ï¼IDB éçæåæ¡¶å»¶è¿å è½½å°å
åæ¶ï¼ä¹è¦æè¿æå¤ç |
| | | }), |
| | | |
| | | getters: { |
| | | getGroup: |
| | | (state) => |
| | | (id: number): Group | undefined => { |
| | | return state.groups.find((g) => g.id === id) |
| | | }, |
| | | /** 群æå userId â GroupMember ç´¢å¼ï¼è°ç¨æ¹æ userId 忥æµç§° / 头åçå
ä¿¡æ¯ */ |
| | | getGroupMemberMap: |
| | | (state) => |
| | | (id: number): Map<number, GroupMember> => { |
| | | const group = state.groups.find((g) => g.id === id) |
| | | return new Map((group?.members || []).map((m) => [m.userId, m])) |
| | | } |
| | | }, |
| | | |
| | | actions: { |
| | | // ==================== æ¬å°ç¼å ==================== |
| | | |
| | | /** ä» IndexedDB æ¢å¤ç¾¤å表 */ |
| | | async loadGroupList(): Promise<boolean> { |
| | | try { |
| | | const cached = await getDb().getAll<GroupDO>('groups') |
| | | if (!cached || cached.length === 0) { |
| | | return false |
| | | } |
| | | this.groups = cached |
| | | return true |
| | | } catch (error) { |
| | | console.warn('[IM groupStore] æ¬å°ç¾¤ç¼å读å失败', error) |
| | | return false |
| | | } |
| | | }, |
| | | |
| | | /** ä¿å群å表 */ |
| | | saveGroupList(): void { |
| | | void getDb() |
| | | .transaction(['groups'], 'readwrite', async (tx) => { |
| | | const db = getDb() |
| | | await db.clearStore('groups', tx) |
| | | for (const group of this.groups) { |
| | | await db.put('groups', buildGroupDO(group), tx) |
| | | } |
| | | }) |
| | | .catch((error) => console.warn('[IM groupStore] æ¬å°ç¾¤ç¼ååå
¥å¤±è´¥', error)) |
| | | }, |
| | | |
| | | /** ä¿åå个群 */ |
| | | async saveGroupRecord(group: Group | undefined): Promise<void> { |
| | | if (!group) { |
| | | return |
| | | } |
| | | await getDb().put('groups', buildGroupDO(group)) |
| | | }, |
| | | |
| | | /** ä¿åå个群 */ |
| | | saveGroup(group: Group | undefined): void { |
| | | void this.saveGroupRecord(group).catch((error) => |
| | | console.warn('[IM groupStore] æ¬å°ç¾¤åå
¥å¤±è´¥', error) |
| | | ) |
| | | }, |
| | | |
| | | /** ä» IndexedDB æ¢å¤æå®ç¾¤æå */ |
| | | async loadGroupMemberList(groupId: number): Promise<GroupMember[] | null> { |
| | | // in-memory å·²"宿´"å è½½ï¼fetchGroupMemberList è·è¿æä¸æ¬¡å·å¯å¨ä» IDB æ´æ¡¶æ¢å¤è¿ï¼ï¼ç´æ¥å¤ç¨ï¼ |
| | | // åæåè¡¥é½ï¼fetchGroupMemberï¼åè¿ç partial members ä¸å¨æ¤è¿åç¼åââå
¶ membersLoaded=false |
| | | const cachedGroup = this.getGroup(groupId) |
| | | if (cachedGroup?.members && cachedGroup.membersLoaded) { |
| | | return cachedGroup.members |
| | | } |
| | | try { |
| | | const cached = await getDb().getAllByIndex<GroupMember>( |
| | | 'groupMembers', |
| | | 'groupId', |
| | | groupId |
| | | ) |
| | | if (!cached || cached.length === 0) { |
| | | return null |
| | | } |
| | | // æ IDB æ¿å°çæåè½å°å¯¹åº group |
| | | const group = this.getGroup(groupId) |
| | | if (group) { |
| | | group.members = cached |
| | | group.memberCount = cached.length |
| | | group.membersLoaded = true |
| | | group.membersExpired = this.groupMembersExpired |
| | | } else { |
| | | // group è¿æ²¡å°±ä½ï¼ä»
in-memory å ä½ï¼name='' 表示æªç¥ï¼ï¼ä¸è° upsertGroup ââ é¿å
æååçè¿ conversation.name + groups IDB æ¡¶ï¼ |
| | | // åç»ï¼ç fetchGroupList æµ
åå¹¶æ¶ï¼è¢«çåè¦ç |
| | | this.groups.push({ |
| | | id: groupId, |
| | | name: '', |
| | | members: cached, |
| | | memberCount: cached.length, |
| | | membersLoaded: true, |
| | | membersExpired: this.groupMembersExpired |
| | | }) |
| | | } |
| | | return cached |
| | | } catch (error) { |
| | | console.warn('[IM groupStore] æ¬å°ç¾¤æåç¼å读å失败', { groupId }, error) |
| | | return null |
| | | } |
| | | }, |
| | | |
| | | /** ä¿åæå®ç¾¤æå */ |
| | | saveGroupMemberList(groupId: number): void { |
| | | const members = this.getGroup(groupId)?.members |
| | | if (!members) { |
| | | return |
| | | } |
| | | void getDb() |
| | | .transaction(['groupMembers'], 'readwrite', async (tx) => { |
| | | const db = getDb() |
| | | await db.deleteByIndex('groupMembers', 'groupId', groupId, tx) |
| | | for (const member of members) { |
| | | if (member.id) { |
| | | await db.put('groupMembers', member, tx) |
| | | } |
| | | } |
| | | }) |
| | | .catch((error) => |
| | | console.warn(`[IM groupStore] æ¬å°ç¾¤æåç¼ååå
¥å¤±è´¥ (groupId=${groupId})`, error) |
| | | ) |
| | | }, |
| | | |
| | | // ==================== è¿ç«¯æå ==================== |
| | | |
| | | /** æå群å表ï¼åæ¥å·æ°å¯¹åºç¾¤èä¼è¯çå±ç¤ºå / 头å + è½ IDB */ |
| | | async fetchGroupList(force = false) { |
| | | if (this.loaded && !force) { |
| | | return |
| | | } |
| | | const requestEpoch = storeEpoch |
| | | const requestUserId = getCurrentUserId() |
| | | // æåå½åç»å½ç¨æ·å å
¥çææç¾¤ï¼ä¸å¸¦æåï¼æåæéåèµ° fetchGroupMemberListï¼ |
| | | const list = await apiGetMyGroupList() |
| | | if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) { |
| | | return |
| | | } |
| | | const fresh = (list || []).map((group) => convertGroup(group)) |
| | | // åå¹¶èéå
¨éæ¿æ¢ï¼æåç¼ååªå¨æåå表æ¥å£ç»´æ¤ï¼ç¾¤ä¸ªäººè®¾ç½®ä»¥ç¾¤å表æ¥å£ä¸ºå |
| | | const groupMap = new Map(this.groups.map((group) => [group.id, group])) |
| | | this.groups = fresh.map((group) => { |
| | | const existing = groupMap.get(group.id) |
| | | if (!existing) { |
| | | return { ...group, activeCallExpired: true, infoLoaded: true } |
| | | } |
| | | return { |
| | | ...group, |
| | | infoLoaded: true, |
| | | activeCallExpired: existing.activeCallExpired, |
| | | activeCallLoaded: existing.activeCallLoaded, |
| | | members: existing.members, |
| | | memberCount: existing.memberCount ?? group.memberCount, |
| | | membersLoaded: existing.membersLoaded, |
| | | membersExpired: existing.membersExpired |
| | | } |
| | | }) |
| | | this.loaded = true |
| | | const conversationStore = useConversationStore() |
| | | for (const group of this.groups) { |
| | | conversationStore.updateConversation(ImConversationType.GROUP, group.id, { |
| | | name: getGroupDisplayName(group), |
| | | avatar: group.avatar, |
| | | silent: group.silent |
| | | }) |
| | | } |
| | | this.saveGroupList() |
| | | this.preloadMembersForEmptyAvatarGroups() |
| | | }, |
| | | |
| | | /** 失æå
¨é¨ç¾¤è¯¦æ
ç¼å */ |
| | | markAllGroupInfoExpired() { |
| | | for (const group of this.groups) { |
| | | group.infoLoaded = false |
| | | } |
| | | }, |
| | | |
| | | /** é¢å 载空群头åçæåå表ï¼ä¾ GroupAvatar 弿¥åæç¾¤å¤´å */ |
| | | preloadMembersForEmptyAvatarGroups() { |
| | | for (const group of this.groups) { |
| | | if ( |
| | | group.avatar || |
| | | group.joinStatus === CommonStatusEnum.DISABLE || |
| | | (group.membersLoaded && !group.membersExpired && group.members?.length) |
| | | ) { |
| | | continue |
| | | } |
| | | const force = !!group.membersLoaded && !group.membersExpired && !group.members?.length |
| | | this.fetchGroupMemberList(group.id, force).catch((error) => { |
| | | console.warn('[IM groupStore] é¢å 载群头åæå失败', { groupId: group.id }, error) |
| | | }) |
| | | } |
| | | }, |
| | | |
| | | /** 失æå
¨é¨ç¾¤æåç¼å */ |
| | | markAllGroupMembersExpired() { |
| | | this.groupMembersExpired = true |
| | | for (const group of this.groups) { |
| | | if (group.membersLoaded) { |
| | | group.membersExpired = true |
| | | } |
| | | } |
| | | }, |
| | | |
| | | /** 失æå
¨é¨ç¾¤éè¯æ¢æµç¼å */ |
| | | markAllGroupActiveCallsExpired() { |
| | | for (const group of this.groups) { |
| | | group.activeCallExpired = true |
| | | } |
| | | }, |
| | | |
| | | /** æ 记群éè¯æ¢æµå·²å è½½ */ |
| | | markGroupActiveCallLoaded(groupId: number) { |
| | | const group = this.getGroup(groupId) |
| | | if (!group) { |
| | | return |
| | | } |
| | | group.activeCallLoaded = true |
| | | group.activeCallExpired = false |
| | | }, |
| | | |
| | | /** å¤æç¾¤éè¯æ¯å¦éè¦éæ°æ¢æµ */ |
| | | isGroupActiveCallExpired(groupId: number): boolean { |
| | | const group = this.getGroup(groupId) |
| | | return !group?.activeCallLoaded || !!group.activeCallExpired |
| | | }, |
| | | |
| | | /** 失ææå®ç¾¤æåç¼å */ |
| | | markGroupMembersExpired(groupId: number) { |
| | | const group = this.getGroup(groupId) |
| | | if (group?.membersLoaded) { |
| | | group.membersExpired = true |
| | | } |
| | | }, |
| | | |
| | | /** åç¾¤å·æ°ï¼ç¨ /im/group/get æä¸ä»½ææ°å
æ°æ®å upsertï¼å¸¸ç¨äº GROUP_UPDATE æ¨éåææå¨ reload */ |
| | | async fetchGroupInfo(groupId: number, force = false) { |
| | | const cached = this.getGroup(groupId) |
| | | if (cached?.infoLoaded && !force) { |
| | | return |
| | | } |
| | | try { |
| | | const data = await apiGetGroup(groupId) |
| | | if (!data) { |
| | | return |
| | | } |
| | | this.upsertGroup({ ...convertGroup(data), infoLoaded: true }) |
| | | } catch (error) { |
| | | console.warn('[IM groupStore] fetchGroupInfo 失败', error) |
| | | } |
| | | }, |
| | | |
| | | /** æç¾¤æåæåï¼in-memory ç¼å + å¹¶åå»éï¼force=true 强å·ï¼+ è½ IDB */ |
| | | fetchGroupMemberList(groupId: number, force = false): Promise<GroupMember[]> { |
| | | // in-memory "宿´"å è½½è¿æå½ä¸ââåæåè¡¥é½åå
¥ç partial members ä¸å¨æ¤è¿åï¼membersLoaded=falseï¼ |
| | | const cached = this.getGroup(groupId) |
| | | if (cached && cached.members && cached.membersLoaded && !cached.membersExpired && !force) { |
| | | return Promise.resolve(cached.members) |
| | | } |
| | | // æªç»å½ï¼ä¸å起请æ±ä¹ä¸ç»è®° in-flightï¼é¿å
污æåé£è¡¨ |
| | | const requestUserId = getCurrentUserId() |
| | | if (!requestUserId) { |
| | | return Promise.resolve([]) |
| | | } |
| | | const requestEpoch = storeEpoch |
| | | // å (userId, groupId) å·²ç»ææ£å¨é£ç请æ±ï¼ç´æ¥å¤ç¨ï¼é¿å
é夿æ¥å£ |
| | | const key = pendingMemberKey(requestUserId, groupId) |
| | | const inflight = pendingMemberFetches.get(key) |
| | | if (inflight) { |
| | | return inflight |
| | | } |
| | | const promise = (async () => { |
| | | // ææ¥å£ + å pass 转æ¢ï¼åæ¶æè· me çåå§ VOï¼ç»ä¸é¢åå¡« user-per-group åæ®µï¼silent / groupRemarkï¼ç¨ |
| | | const list = await apiGetGroupMemberList(groupId) |
| | | if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) { |
| | | return [] |
| | | } |
| | | let meRaw: ImGroupMemberApi.GroupMemberRespVO | undefined |
| | | const members = (list || []).map((member) => { |
| | | if (member.userId === requestUserId) { |
| | | meRaw = member |
| | | } |
| | | return convertGroupMember(member, groupId) |
| | | }) |
| | | const silent = !!meRaw?.silent |
| | | const groupRemark = meRaw?.groupRemark || '' |
| | | |
| | | // å¿
é¡» await ä¹åéæ° getGroupï¼é¿å
fetchGroupList 已并ååå
¥çå® group ç race |
| | | const group = this.getGroup(groupId) |
| | | const isPlaceholder = !group |
| | | let groupFieldsChanged = false |
| | | if (group) { |
| | | group.members = members |
| | | group.memberCount = members.length |
| | | group.membersLoaded = true |
| | | group.membersExpired = false |
| | | // silent / groupRemark ä»»ä¸ååæåæ¥å° conversation å IDBï¼groupRemark ååè¦é¡ºå¸¦å·ä¼è¯å |
| | | if (group.silent !== silent || group.groupRemark !== groupRemark) { |
| | | group.silent = silent |
| | | group.groupRemark = groupRemark |
| | | groupFieldsChanged = true |
| | | const conversationStore = useConversationStore() |
| | | conversationStore.updateConversation(ImConversationType.GROUP, groupId, { |
| | | name: getGroupDisplayName(group), |
| | | silent |
| | | }) |
| | | } |
| | | } else { |
| | | // group è¿æ²¡å°±ä½ï¼ä»
in-memory push å ä½ï¼name='' 表示æªç¥ï¼ï¼ä¸è° upsertGroupââé¿å
æååçè¿ conversation.name + groups IDB æ¡¶ï¼ |
| | | // åç»ï¼ç fetchGroupList æµ
åå¹¶æ¶ï¼è¢«çåè¦ç |
| | | this.groups.push({ |
| | | id: groupId, |
| | | name: '', |
| | | members, |
| | | memberCount: members.length, |
| | | silent, |
| | | groupRemark, |
| | | membersLoaded: true, |
| | | membersExpired: false |
| | | }) |
| | | } |
| | | |
| | | // groups æ¡¶ä»
å¨ user-per-group åæ®µå®é
ååæ¶åââé¿å
䏿¬¡æ¹éè¿ç¾¤å¼å夿¬¡æ´æ¡¶éå |
| | | this.saveGroupMemberList(groupId) |
| | | if (!isPlaceholder && groupFieldsChanged) { |
| | | this.saveGroup(group) |
| | | } |
| | | return members |
| | | // æ 论æå / 失败é½è¦ä»åé£è¡¨æ¸
æï¼å¦ååç»å group è¯·æ±æ°¸è¿æ¿å°è¿ä¸ª stale Promise |
| | | })().finally(() => pendingMemberFetches.delete(key)) |
| | | |
| | | // æ Promise ç»è®°è¿åé£è¡¨ï¼è®©æ¤åçæ¶é´å
çå (userId, groupId) 请æ±å¤ç¨ |
| | | pendingMemberFetches.set(key, promise) |
| | | return promise |
| | | }, |
| | | |
| | | /** |
| | | * æ (groupId, memberUserId) åæåè¡¥é½ââderiveLastSenderDisplayName å
åºåºæ¯ç¨ |
| | | * |
| | | * è· fetchGroupMemberList åºå«ï¼åªæè¿ä¸ä¸ªæåï¼ä¸å¨ me ç silent / groupRemarkï¼ä¸æ¯ me çè¯æ¿ä¸å°ï¼ï¼ |
| | | * å½ä¸æ¶ææå upsert è¿ group.members æ°ç»å¹¶è½ IDBï¼è®©åç»æ¸²æè½ç¨ displayUserName |
| | | */ |
| | | fetchGroupMember(groupId: number, memberUserId: number): Promise<GroupMember | null> { |
| | | // in-memory å½ä¸ç´æ¥è¿åï¼ä¸ææ¥å£ |
| | | const cached = this.getGroup(groupId)?.members?.find((m) => m.userId === memberUserId) |
| | | if (cached) { |
| | | return Promise.resolve(cached) |
| | | } |
| | | // æªç»å½ï¼ä¸å起请æ±ä¹ä¸ç»è®° in-flightï¼é¿å
污æåé£è¡¨ |
| | | const requestUserId = getCurrentUserId() |
| | | if (!requestUserId) { |
| | | return Promise.resolve(null) |
| | | } |
| | | const requestEpoch = storeEpoch |
| | | // å (userId, groupId, memberUserId) å·²ç»ææ£å¨é£ç请æ±ï¼ç´æ¥å¤ç¨ |
| | | const key = pendingSingleMemberKey(requestUserId, groupId, memberUserId) |
| | | const inflight = pendingSingleMemberFetches.get(key) |
| | | if (inflight) { |
| | | return inflight |
| | | } |
| | | const promise = (async () => { |
| | | const data = await apiGetGroupMember(groupId, memberUserId) |
| | | if (!data) { |
| | | return null |
| | | } |
| | | if (requestEpoch !== storeEpoch || getCurrentUserId() !== requestUserId) { |
| | | return null |
| | | } |
| | | const member = convertGroupMember(data, groupId) |
| | | // æè¿ä¸æ¡ upsert è¿ group.members ä»
ä¾ in-memory 渲æå
åºï¼group è¿æ²¡å°±ä½åç¨ placeholder |
| | | // 注æï¼ä¸å IDBââæåæ¡¶è¯ä¹æ¯"å
¨é"ï¼å"1 人桶"伿±¡æä¸æ¬¡å·å¯å¨ç loadGroupMemberList |
| | | const group = this.getGroup(groupId) |
| | | if (group) { |
| | | const memberList = group.members ?? [] |
| | | const index = memberList.findIndex((m) => m.userId === memberUserId) |
| | | if (index === -1) { |
| | | memberList.push(member) |
| | | } else { |
| | | memberList[index] = member |
| | | } |
| | | group.members = memberList |
| | | } else { |
| | | // memberCount ä¸è®¾ï¼åç» fetchGroupList åå¹¶ `existing.memberCount ?? fresh.memberCount` æ¶ï¼ |
| | | // å ä½å¼ä¼é¡¶æ¿çå®å¼ï¼fresh ä¸å¸¦ memberCountï¼ï¼ç fetchGroupMemberList è·è¿æè½æ¿å°ç宿° |
| | | this.groups.push({ |
| | | id: groupId, |
| | | name: '', |
| | | members: [member] |
| | | }) |
| | | } |
| | | return member |
| | | })().finally(() => pendingSingleMemberFetches.delete(key)) |
| | | pendingSingleMemberFetches.set(key, promise) |
| | | return promise |
| | | }, |
| | | |
| | | /** æ id æå
¥æå并群ï¼å½ä¸åæµ
åå¹¶ä¿çæ§åæ®µï¼æªå½ä¸å追å ï¼ï¼åæ¥æå±ç¤ºå / 头å / å
ææ°æ¨å°å¯¹åºä¼è¯ */ |
| | | upsertGroup(group: Group) { |
| | | void this.upsertGroupAndSave(group).catch((error) => |
| | | console.warn('[IM groupStore] æ¬å°ç¾¤åå
¥å¤±è´¥', error) |
| | | ) |
| | | }, |
| | | |
| | | /** æ id æå
¥æå并群 */ |
| | | async upsertGroupAndSave(group: Group): Promise<void> { |
| | | const index = this.groups.findIndex((g) => g.id === group.id) |
| | | if (index === -1) { |
| | | this.groups.push(group) |
| | | } else { |
| | | this.groups[index] = { ...this.groups[index], ...group } |
| | | } |
| | | // 忥æ¨å° conversationï¼ç¾¤å / 头å / å
ææ°æ¯ä¼è¯å表å±ç¤ºç¨çï¼å¿
须紧é group åæ´ |
| | | const merged = this.getGroup(group.id) ?? group |
| | | const conversationStore = useConversationStore() |
| | | conversationStore.updateConversation(ImConversationType.GROUP, group.id, { |
| | | name: getGroupDisplayName(merged), |
| | | avatar: merged.avatar, |
| | | silent: merged.silent |
| | | }) |
| | | // æä¹
åå° IDBï¼fire-and-forgetï¼ |
| | | await this.saveGroupRecord(merged) |
| | | }, |
| | | |
| | | /** æ¬å°ç§»é¤ç¾¤ç¼åå群ä¼è¯ï¼ç¾¤è§£æ£ï¼GROUP_DELï¼ãé群ã被踢é½å¤ç¨ */ |
| | | removeGroup(id: number) { |
| | | // æ¬å°ç¡¬å ï¼åºå«äºå¥½åå é¤ç软å ä¿çè®°å½ï¼ï¼çº§èæ¸
群èä¼è¯é¿å
å表éçæ»ç¾¤ |
| | | this.groups = this.groups.filter((g) => g.id !== id) |
| | | const conversationStore = useConversationStore() |
| | | conversationStore.removeGroupConversation(id) |
| | | void getDb() |
| | | .transaction(['groups', 'groupMembers'], 'readwrite', async (tx) => { |
| | | const db = getDb() |
| | | await db.delete('groups', id, tx) |
| | | await db.deleteByIndex('groupMembers', 'groupId', id, tx) |
| | | }) |
| | | .catch((error) => console.warn(`[IM groupStore] 群ç¼åå é¤å¤±è´¥ (groupId=${id})`, error)) |
| | | }, |
| | | |
| | | /** 忢å
ææ°ï¼æ¨å端 + è½æ¬å° + 忥ä¼è¯å表ç silentï¼é¿å
silent 徿 / æ»æªè¯» / æç¤ºé³å¤æä¸è®¾ç½®æ¼ç§»ï¼å friendStore.setFriendSilent å¯¹é½ */ |
| | | async setGroupSilent(id: number, silent: boolean) { |
| | | await apiUpdateGroupMember({ groupId: id, silent }) |
| | | const group = this.getGroup(id) |
| | | if (!group) { |
| | | return |
| | | } |
| | | group.silent = silent |
| | | const conversationStore = useConversationStore() |
| | | conversationStore.updateConversation(ImConversationType.GROUP, id, { silent }) |
| | | this.saveGroup(group) |
| | | }, |
| | | |
| | | /** æ¹éæ´æ°ç¾¤æåè§è²ï¼æ¬å°ä¸å½ä¸å忽ç¥ï¼ç fetchGroupMemberList å
åº */ |
| | | updateGroupMemberRoleList(groupId: number, userIds: number[], role: number) { |
| | | const group = this.getGroup(groupId) |
| | | if (!group?.members?.length) { |
| | | return |
| | | } |
| | | // å½ä¸ç®æ ä¸è§è²å·²ååææ è®° changedï¼é¿å
æ ååæ¶æ´æ°ç»é建触åååºå¼ |
| | | const idSet = new Set(userIds) |
| | | let changed = false |
| | | const newMembers = group.members.map((member) => { |
| | | if (!idSet.has(member.userId) || member.role === role) { |
| | | return member |
| | | } |
| | | changed = true |
| | | return { ...member, role } |
| | | }) |
| | | // æååææ´ç»æ¿æ¢ï¼è®©ååºå¼åªå¨çææ´æ°æ¶éç¥ä¸æ¸¸ |
| | | if (changed) { |
| | | group.members = newMembers |
| | | this.saveGroupMemberList(groupId) |
| | | } |
| | | }, |
| | | |
| | | /** 群主转让ï¼ç¾¤è¡¨ ownerUserId æ¹ä¸ºæ°å¼ï¼æ§ç¾¤ä¸» role â NORMALï¼æ°ç¾¤ä¸» role â OWNER */ |
| | | transferGroupOwner(groupId: number, oldOwnerId: number, newOwnerId: number) { |
| | | const group = this.getGroup(groupId) |
| | | if (!group) { |
| | | return |
| | | } |
| | | if (group.ownerUserId !== newOwnerId) { |
| | | group.ownerUserId = newOwnerId |
| | | } |
| | | this.updateGroupMemberRoleList(groupId, [oldOwnerId], ImGroupMemberRole.NORMAL) |
| | | this.updateGroupMemberRoleList(groupId, [newOwnerId], ImGroupMemberRole.OWNER) |
| | | this.saveGroup(group) |
| | | }, |
| | | |
| | | /** æ¬å°åé¤ç¾¤æåï¼GROUP_MEMBER_QUIT / KICK äºä»¶ï¼ï¼ä¸å½ä¸åç fetchGroupMemberList å
åº */ |
| | | removeLocalGroupMemberList(groupId: number, userIds: number[]) { |
| | | const group = this.getGroup(groupId) |
| | | if (!group?.members?.length || userIds.length === 0) { |
| | | return |
| | | } |
| | | const idSet = new Set(userIds) |
| | | const next = group.members.filter((member) => !idSet.has(member.userId)) |
| | | if (next.length === group.members.length) { |
| | | return |
| | | } |
| | | group.members = next |
| | | group.memberCount = next.length |
| | | this.saveGroupMemberList(groupId) |
| | | }, |
| | | |
| | | /** æ¬å°æ´æ°ç¾¤æåç statusï¼èªå·±é群 / è¢«è¸¢çæ¬å°é¢ç½®ï¼è®© isMember ç«å³æ¶æå° strangerï¼ä¸ä¾èµ removeGroup çæ´ç¾¤ç§»é¤ï¼ */ |
| | | updateGroupMemberStatus(groupId: number, userId: number, status: number) { |
| | | const group = this.getGroup(groupId) |
| | | const member = group?.members?.find((m) => m.userId === userId) |
| | | if (!member || member.status === status) { |
| | | return |
| | | } |
| | | member.status = status |
| | | this.saveGroupMemberList(groupId) |
| | | }, |
| | | |
| | | /** æ¬å°æ´æ°ç¾¤æåç displayUserNameï¼GROUP_MEMBER_NICKNAME_UPDATE äºä»¶ï¼ï¼ä¸å½ä¸åç fetchGroupMemberList å
åº */ |
| | | updateGroupMemberDisplayUserName(groupId: number, userId: number, displayUserName: string) { |
| | | const group = this.getGroup(groupId) |
| | | const member = group?.members?.find((m) => m.userId === userId) |
| | | if (!member || member.displayUserName === displayUserName) { |
| | | return |
| | | } |
| | | member.displayUserName = displayUserName |
| | | this.saveGroupMemberList(groupId) |
| | | }, |
| | | |
| | | /** å±é¨æ´æ°ç¾¤å段ï¼name / notice / avatar çï¼ï¼æªå½ä¸æ¬å°ç¼åæ¶éé»å¿½ç¥ï¼ç fetchGroupList å
åºï¼æ°å¼è·æ§å¼é½ç¸åæ¶è·³è¿ååºå¼ + IDB å */ |
| | | updateGroupFields(groupId: number, fields: Partial<Group>) { |
| | | const group = this.getGroup(groupId) |
| | | if (!group) { |
| | | return |
| | | } |
| | | const changed = (Object.keys(fields) as (keyof Group)[]).some((k) => group[k] !== fields[k]) |
| | | if (!changed) { |
| | | return |
| | | } |
| | | Object.assign(group, fields) |
| | | const conversationStore = useConversationStore() |
| | | conversationStore.updateConversation(ImConversationType.GROUP, groupId, { |
| | | name: getGroupDisplayName(group), |
| | | avatar: group.avatar, |
| | | silent: group.silent |
| | | }) |
| | | this.saveGroup(group) |
| | | }, |
| | | |
| | | /** |
| | | * æ¥æ¶ GROUP_* 群广æäºä»¶ï¼æ type ååå°å¯¹åºç§æ action |
| | | * |
| | | * WebSocket 宿¶æ¶èµ° messageStore.insertMessage æè·¯è°ç¨ |
| | | * store éæ²¡ç¼åç群éé»å¿½ç¥ï¼ç fetchGroupList å
åº |
| | | */ |
| | | applyGroupNotification(groupId: number, type: number, content?: string) { |
| | | if (!groupId) { |
| | | return |
| | | } |
| | | let payload: GroupNotificationPayload |
| | | try { |
| | | payload = content ? JSON.parse(content) : {} |
| | | } catch (error) { |
| | | console.warn( |
| | | '[IM groupStore] applyGroupNotification è§£æ content 失败', |
| | | { groupId, type, contentLength: content?.length ?? 0 }, |
| | | error |
| | | ) |
| | | return |
| | | } |
| | | switch (type) { |
| | | case ImContentType.GROUP_ADMIN_ADD: { |
| | | this.updateGroupMemberRoleList( |
| | | groupId, |
| | | payload.memberUserIds || [], |
| | | ImGroupMemberRole.ADMIN |
| | | ) |
| | | this.markGroupMembersExpired(groupId) |
| | | // èªå·±è¢«å 为管çåï¼åæ¬çä¸å°çç¾¤ä¸æªå¤çç³è¯·ç°å¨åå¯è§ï¼éæ°æä¸æ¬¡ unhandledList |
| | | if (isSelfInPayloadMembers(payload)) { |
| | | refreshUnhandledGroupRequests() |
| | | } |
| | | break |
| | | } |
| | | case ImContentType.GROUP_ADMIN_REMOVE: { |
| | | this.updateGroupMemberRoleList( |
| | | groupId, |
| | | payload.memberUserIds || [], |
| | | ImGroupMemberRole.NORMAL |
| | | ) |
| | | this.markGroupMembersExpired(groupId) |
| | | if (isSelfInPayloadMembers(payload)) { |
| | | refreshUnhandledGroupRequests() |
| | | } |
| | | break |
| | | } |
| | | case ImContentType.GROUP_BANNED: { |
| | | this.updateGroupFields(groupId, { banned: !!payload.banned }) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_CANCEL_MUTED: { |
| | | this.updateGroupFields(groupId, { mutedAll: false }) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_CREATE: { |
| | | this.applyGroupCreateNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_DISSOLVE: { |
| | | this.removeGroup(groupId) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_INFO_UPDATE: { |
| | | this.applyGroupInfoUpdateNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MEMBER_CANCEL_MUTED: { |
| | | this.applyGroupMemberCancelMutedNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MEMBER_ENTER: { |
| | | this.applyGroupMemberEnterNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MEMBER_INVITE: { |
| | | this.applyGroupMemberInviteNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MEMBER_KICK: { |
| | | this.applyGroupMemberKickNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MEMBER_MUTED: { |
| | | this.applyGroupMemberMutedNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MEMBER_NICKNAME_UPDATE: { |
| | | this.applyGroupMemberNicknameUpdateNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MEMBER_QUIT: { |
| | | this.applyGroupMemberQuitNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MESSAGE_PIN: { |
| | | this.applyGroupMessagePinNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MESSAGE_UNPIN: { |
| | | this.applyGroupMessageUnpinNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_MUTED: { |
| | | this.updateGroupFields(groupId, { mutedAll: true }) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_NAME_UPDATE: { |
| | | this.applyGroupNameUpdateNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_NOTICE_UPDATE: { |
| | | this.applyGroupNoticeUpdateNotification(groupId, payload) |
| | | break |
| | | } |
| | | case ImContentType.GROUP_OWNER_TRANSFER: { |
| | | this.applyGroupOwnerTransferNotification(groupId, payload) |
| | | break |
| | | } |
| | | } |
| | | }, |
| | | |
| | | /** å建群广æï¼ç¾¤æªå°±ä½æ¶æç¾¤è¯¦æ
*/ |
| | | async applyGroupCreateNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | if (!isSelfInPayloadMembers(payload)) { |
| | | return |
| | | } |
| | | const selfUserId = getCurrentUserId() |
| | | const selfIsOperator = !!selfUserId && payload.operatorUserId === selfUserId |
| | | if (selfIsOperator && this.getGroup(groupId)) { |
| | | return |
| | | } |
| | | await this.fetchGroupInfo(groupId, true) |
| | | }, |
| | | |
| | | /** 群ååæ´ï¼æ newName å±é¨æ´æ°æ¬å°ç¾¤å */ |
| | | applyGroupNameUpdateNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | if (payload.newName) { |
| | | this.updateGroupFields(groupId, { name: payload.newName }) |
| | | } |
| | | }, |
| | | |
| | | /** 群å
¬ååæ´ï¼æ newNotice å±é¨æ´æ°ï¼å
许空串ä½ä¸ºãæ¸
空å
¬åãï¼ */ |
| | | applyGroupNoticeUpdateNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | this.updateGroupFields(groupId, { notice: payload.newNotice ?? '' }) |
| | | }, |
| | | |
| | | /** 群信æ¯åæ´ï¼åæ¥å¤´åãè¿ç¾¤å®¡æ¹ */ |
| | | applyGroupInfoUpdateNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | const fields: Partial<Group> = {} |
| | | if (payload.newAvatar) { |
| | | fields.avatar = payload.newAvatar |
| | | } |
| | | if (payload.newJoinApproval != null) { |
| | | fields.joinApproval = payload.newJoinApproval |
| | | } |
| | | if (Object.keys(fields).length > 0) { |
| | | this.updateGroupFields(groupId, fields) |
| | | } |
| | | }, |
| | | |
| | | /** æåå å
¥ï¼è¢«é请è
æ¬ç«¯ group æªå°±ä½å
fetchGroupInfo 忬¡æåï¼ææäººé½å·æååè¡¨ï¼æ°æå nickname / avatar ä¸å¨ payloadï¼ */ |
| | | async applyGroupMemberInviteNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | // èªå·±å被æè¿æ¥ï¼å¿
é¡» await fetchGroupInfo 让群å
¥ state.groupsï¼å¦å fetchGroupMemberList ç guard ä¼å
空 |
| | | if (isSelfInPayloadMembers(payload) && !this.getGroup(groupId)) { |
| | | await this.fetchGroupInfo(groupId, true) |
| | | } |
| | | this.markGroupMembersExpired(groupId) |
| | | this.fetchGroupMemberList(groupId, true).catch(() => undefined) |
| | | }, |
| | | |
| | | /** èªç±è¿ç¾¤ï¼è¿ç¾¤è
æ¬ç«¯ group æªå°±ä½å
fetchGroupInfo 忬¡æåï¼ææäººé½å·æåå表 */ |
| | | async applyGroupMemberEnterNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | const selfUserId = getCurrentUserId() |
| | | // èªå·±èªç±è¿ç¾¤ï¼å¿
é¡» await fetchGroupInfo 让群å
¥ state.groupsï¼å¦å fetchGroupMemberList ç guard ä¼å
空 |
| | | if (selfUserId && payload.entrantUserId === selfUserId && !this.getGroup(groupId)) { |
| | | await this.fetchGroupInfo(groupId, true) |
| | | } |
| | | this.markGroupMembersExpired(groupId) |
| | | this.fetchGroupMemberList(groupId, true).catch(() => undefined) |
| | | }, |
| | | |
| | | /** æåé群ï¼é群è
æ¬äººå
æ self.status ç½® DISABLE å removeGroupï¼ä¿çç¶æè¯ä¹ + ç»´æ groups å表干åï¼ï¼å
¶ä»æå仿¬å°åè¡¨ç§»é¤ quitter */ |
| | | applyGroupMemberQuitNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | const selfUserId = getCurrentUserId() |
| | | if (selfUserId && payload.operatorUserId === selfUserId) { |
| | | this.updateGroupMemberStatus(groupId, selfUserId, CommonStatusEnum.DISABLE) |
| | | this.removeGroup(groupId) |
| | | } else if (payload.operatorUserId) { |
| | | this.removeLocalGroupMemberList(groupId, [payload.operatorUserId]) |
| | | this.markGroupMembersExpired(groupId) |
| | | } |
| | | }, |
| | | |
| | | /** æå被移åºï¼è¢«è¸¢è
æ¬äººå
æ self.status ç½® DISABLE å removeGroupï¼å
¶ä»æå仿¬å°å表移é¤è¢«è¸¢è
*/ |
| | | applyGroupMemberKickNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | const memberIds = payload.memberUserIds || [] |
| | | const selfUserId = getCurrentUserId() |
| | | if (isSelfInPayloadMembers(payload)) { |
| | | if (selfUserId) { |
| | | this.updateGroupMemberStatus(groupId, selfUserId, CommonStatusEnum.DISABLE) |
| | | } |
| | | this.removeGroup(groupId) |
| | | } else if (memberIds.length > 0) { |
| | | this.removeLocalGroupMemberList(groupId, memberIds) |
| | | this.markGroupMembersExpired(groupId) |
| | | } |
| | | }, |
| | | |
| | | /** æåæµç§°åæ´ï¼æ operatorUserId å±é¨æ´æ°å¯¹åº member.displayUserName */ |
| | | applyGroupMemberNicknameUpdateNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | if (payload.operatorUserId) { |
| | | this.updateGroupMemberDisplayUserName( |
| | | groupId, |
| | | payload.operatorUserId, |
| | | payload.displayUserName ?? '' |
| | | ) |
| | | this.markGroupMembersExpired(groupId) |
| | | } |
| | | }, |
| | | |
| | | /** ç¾¤ä¸»è½¬è®©ï¼æ§ç¾¤ä¸» â NORMALï¼æ°ç¾¤ä¸» â OWNERï¼æ°ç¾¤ä¸»èªå·±ä¾§éæ°æç³è¯·å表 */ |
| | | applyGroupOwnerTransferNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | if (payload.operatorUserId && payload.newOwnerUserId) { |
| | | this.transferGroupOwner(groupId, payload.operatorUserId, payload.newOwnerUserId) |
| | | this.markGroupMembersExpired(groupId) |
| | | } |
| | | // èªå·±æ¥ç®¡ç¾¤ä¸»ï¼åæ¬çä¸å°çç¾¤ä¸æªå¤çç³è¯·ç°å¨åå¯è§ï¼éæ°æä¸æ¬¡ unhandledList |
| | | const selfUserId = getCurrentUserId() |
| | | if (selfUserId && payload.newOwnerUserId === selfUserId) { |
| | | refreshUnhandledGroupRequests() |
| | | } else if (selfUserId && payload.operatorUserId === selfUserId) { |
| | | refreshUnhandledGroupRequests() |
| | | } |
| | | }, |
| | | |
| | | /** ç¾¤æ¶æ¯ç½®é¡¶ï¼ä» payload åæ¶æ¯å±ç¤ºæ°æ®å å
¥ç½®é¡¶å表 */ |
| | | applyGroupMessagePinNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | const message = payload.message |
| | | if (!message) { |
| | | return |
| | | } |
| | | const group = this.getGroup(groupId) |
| | | if (!group) { |
| | | return |
| | | } |
| | | // å¹çï¼å·²åå¨å messageId ä¸éå¤ push |
| | | const existing = group.pinnedMessages || [] |
| | | if (existing.some((msg) => msg.id === message.id)) { |
| | | return |
| | | } |
| | | group.pinnedMessages = [ |
| | | ...existing, |
| | | { |
| | | id: message.id, |
| | | clientMessageId: '', |
| | | senderId: message.senderId, |
| | | type: message.type, |
| | | content: message.content, |
| | | status: ImMessageStatus.NORMAL, |
| | | sendTime: new Date(message.sendTime).getTime(), |
| | | targetId: message.groupId || groupId, |
| | | selfSend: message.senderId === getCurrentUserId(), |
| | | atUserIds: message.atUserIds ? [...message.atUserIds] : [], |
| | | receiverUserIds: message.receiverUserIds ? [...message.receiverUserIds] : [] |
| | | } |
| | | ] |
| | | this.saveGroup(group) |
| | | }, |
| | | |
| | | /** ç¾¤æ¶æ¯åæ¶ç½®é¡¶ï¼æ messageId 仿¬å°ç½®é¡¶å表ä¸ç§»é¤ */ |
| | | applyGroupMessageUnpinNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | if (!payload.messageId) { |
| | | return |
| | | } |
| | | const group = this.getGroup(groupId) |
| | | if (!group?.pinnedMessages?.length) { |
| | | return |
| | | } |
| | | const newPinnedMessages = group.pinnedMessages.filter((m) => m.id !== payload.messageId) |
| | | if (newPinnedMessages.length === group.pinnedMessages.length) { |
| | | return |
| | | } |
| | | group.pinnedMessages = newPinnedMessages |
| | | this.saveGroup(group) |
| | | }, |
| | | |
| | | /** åæåç¦è¨ï¼æ´æ°ç®æ æåç muteEndTime */ |
| | | applyGroupMemberMutedNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | const group = this.getGroup(groupId) |
| | | const member = group?.members?.find((m) => m.userId === payload.mutedUserId) |
| | | if (member && payload.muteEndTime) { |
| | | member.muteEndTime = payload.muteEndTime |
| | | this.saveGroupMemberList(groupId) |
| | | this.markGroupMembersExpired(groupId) |
| | | } |
| | | }, |
| | | |
| | | /** åæååæ¶ç¦è¨ï¼æ¸
ç©ºç®æ æåç muteEndTime */ |
| | | applyGroupMemberCancelMutedNotification(groupId: number, payload: GroupNotificationPayload) { |
| | | const group = this.getGroup(groupId) |
| | | const member = group?.members?.find((m) => m.userId === payload.mutedUserId) |
| | | if (member) { |
| | | member.muteEndTime = undefined |
| | | this.saveGroupMemberList(groupId) |
| | | this.markGroupMembersExpired(groupId) |
| | | } |
| | | }, |
| | | |
| | | /** åè´¦å·æ¶ä»
æ¸
in-memoryï¼IDB æ userId åæ¡¶å¤©ç¶é离ï¼ååç§å¼ */ |
| | | clear() { |
| | | this.groups = [] |
| | | this.loaded = false |
| | | this.groupMembersExpired = false |
| | | // è´¦å·åæ¢ï¼éå¢ epoch åºå¼æ§è´¦å· in-flight çæåè¯·æ± |
| | | storeEpoch++ |
| | | // åé£è¡¨è· in-memory state ä¸èµ·éç½®ï¼æ§è´¦å· in-flight çè¯·æ± finally ä¹ä¼èªå·± delete keyï¼æåæ¸
ç©ºåªæ¯æ´å¹²è |
| | | pendingMemberFetches.clear() |
| | | pendingSingleMemberFetches.clear() |
| | | } |
| | | } |
| | | }) |
| | | |
| | | function convertGroup(group: ImGroupApi.GroupRespVO): Group { |
| | | return { |
| | | id: group.id, |
| | | name: group.name, |
| | | avatar: group.avatar, |
| | | notice: group.notice, |
| | | ownerUserId: group.ownerUserId, |
| | | pinnedMessages: group.pinnedMessages?.map((message) => convertGroupMessageVO(message)), |
| | | mutedAll: group.mutedAll, |
| | | banned: group.banned, |
| | | joinApproval: group.joinApproval, |
| | | joinStatus: group.joinStatus, |
| | | groupRemark: group.groupRemark, |
| | | silent: group.silent |
| | | } |
| | | } |
| | | |
| | | /** å端 ImGroupMessageApi.GroupMessageRespVO -> å端 Messageï¼è¡¥ targetId / selfSend / sendTime çæ´¾çåæ®µ */ |
| | | function convertGroupMessageVO( |
| | | message: NonNullable<ImGroupApi.GroupRespVO['pinnedMessages']>[number] |
| | | ): Message { |
| | | const currentUserId = getCurrentUserId() |
| | | return { |
| | | id: message.id, |
| | | clientMessageId: message.clientMessageId || '', |
| | | type: message.type, |
| | | content: message.content, |
| | | status: message.status, |
| | | sendTime: new Date(message.sendTime).getTime(), |
| | | senderId: message.senderId, |
| | | targetId: message.groupId, |
| | | selfSend: !!currentUserId && message.senderId === currentUserId, |
| | | atUserIds: message.atUserIds || [], |
| | | receiverUserIds: message.receiverUserIds || [], |
| | | receiptStatus: message.receiptStatus, |
| | | readCount: message.readCount |
| | | } |
| | | } |
| | | |
| | | function convertGroupMember(member: ImGroupMemberApi.GroupMemberRespVO, groupId: number): GroupMember { |
| | | return { |
| | | id: member.id, |
| | | userId: member.userId, |
| | | groupId, |
| | | nickname: member.nickname || String(member.userId), |
| | | avatar: member.avatar, |
| | | displayUserName: member.displayUserName, |
| | | status: member.status, |
| | | role: member.role, |
| | | muteEndTime: member.muteEndTime |
| | | } |
| | | } |
| | | |
| | | export const useGroupStoreWithOut = () => useGroupStore() |
| | | |
| | | // dev: 让 Pinia ç actions æ¹å¨æ¯æ HMRï¼å
廿¯æ¬¡æ¹ store é½è¦ç¡¬å· |
| | | if (import.meta.hot) { |
| | | import.meta.hot.accept(acceptHMRUpdate(useGroupStore, import.meta.hot)) |
| | | } |