feat(profile): add chat settings and language options to profile settings
- Introduced hrefProfileChatSettings() for navigation to chat settings. - Updated SettingsScreen to include new settings items for chat settings, language selection, data storage, terms, and privacy policy. - Enhanced user experience by providing alerts for language and data storage options. This update improves the profile settings interface by adding more customization options for users.
This commit is contained in:
326
src/stores/group/GroupManager.ts
Normal file
326
src/stores/group/GroupManager.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
/**
|
||||
* GroupManager - 蝢斤<E89DA2>蝞∠<E89D9E><E288A0>詨<EFBFBD>璅∪<E79285>嚗<EFBFBD><E59A97><EFBFBD><EFBFBD><EFBFBD>嚗?
|
||||
*
|
||||
* 雿輻鍂 Zustand store 餈𥡝<E9A488><F0A5A19D>嗆<EFBFBD><E59786>恣<EFBFBD>?
|
||||
* 靽脲<E99DBD>銝擧唂 API <20><><EFBFBD><EFBFBD>𤾸<EFBFBD>摰?
|
||||
*/
|
||||
|
||||
import type {
|
||||
GroupListResponse,
|
||||
GroupMemberListResponse,
|
||||
GroupMemberResponse,
|
||||
GroupResponse,
|
||||
} from '../../types/dto';
|
||||
import { groupService } from '../../services/groupService';
|
||||
import {
|
||||
getAllGroupsCache,
|
||||
getGroupCache,
|
||||
getGroupMembersCache,
|
||||
saveGroupCache,
|
||||
saveGroupMembersCache,
|
||||
saveGroupsCache,
|
||||
saveUsersCache,
|
||||
} from '../../services/database';
|
||||
import { useGroupManagerStore } from './groupStore';
|
||||
import { createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
|
||||
import {
|
||||
IGroupListPagedSource,
|
||||
IGroupMemberListPagedSource,
|
||||
createRemoteGroupListSource,
|
||||
createRemoteGroupMemberListSource,
|
||||
RemoteGroupListSourceKind,
|
||||
GROUP_LIST_PAGE_SIZE,
|
||||
GROUP_MEMBER_LIST_PAGE_SIZE,
|
||||
} from '../groupListSources';
|
||||
|
||||
// ==================== 颲<>𨭌<EFBFBD>賣㺭 ====================
|
||||
|
||||
function buildMembersKey(groupId: string, page: number, pageSize: number): string {
|
||||
return `members:${groupId}:${page}:${pageSize}`;
|
||||
}
|
||||
|
||||
function buildGroupListResponse(groups: GroupResponse[], pageSize: number): GroupListResponse {
|
||||
return {
|
||||
list: groups,
|
||||
total: groups.length,
|
||||
page: 1,
|
||||
page_size: pageSize,
|
||||
total_pages: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function buildMemberListResponse(
|
||||
members: GroupMemberResponse[],
|
||||
page: number,
|
||||
pageSize: number
|
||||
): GroupMemberListResponse {
|
||||
return {
|
||||
list: members,
|
||||
total: members.length,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
total_pages: 1,
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== GroupManager 蝐?====================
|
||||
|
||||
class GroupManager {
|
||||
// ==================== 蝢斤<E89DA2><E696A4>𡑒”<F0A19192>詨<EFBFBD> ====================
|
||||
|
||||
/**
|
||||
* <20>瑕<EFBFBD>蝢斤<E89DA2><E696A4>𡑒”
|
||||
*/
|
||||
async getGroups(
|
||||
page = 1,
|
||||
pageSize = GROUP_LIST_PAGE_SIZE,
|
||||
forceRefresh = false
|
||||
): Promise<GroupListResponse> {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const entry = store.groupsListEntry;
|
||||
|
||||
// 蝚砌<E89D9A>憿萎<E686BF><E8908E>㗇<EFBFBD><E39787><EFBFBD><EFBFBD>摮?
|
||||
if (page === 1 && !forceRefresh && entry && !isCacheExpired(entry)) {
|
||||
return buildGroupListResponse(entry.data, pageSize);
|
||||
}
|
||||
|
||||
// 蝚砌<E89D9A>憿萎<E686BF>蝻枏<E89DBB>餈<EFBFBD><E9A488>嚗𡁜<E59A97><F0A1819C>啣<EFBFBD><E595A3>?
|
||||
if (page === 1 && !forceRefresh && entry && isCacheExpired(entry)) {
|
||||
this.refreshGroupsInBackground(page, pageSize);
|
||||
return buildGroupListResponse(entry.data, pageSize);
|
||||
}
|
||||
|
||||
// 蝚砌<E89D9A>憿蛛<E686BF>撠肽<E692A0><E882BD>砍𧑐蝻枏<E89DBB>
|
||||
if (page === 1 && !forceRefresh) {
|
||||
const localGroups = await getAllGroupsCache();
|
||||
if (localGroups.length > 0) {
|
||||
const newEntry = createCacheEntry(localGroups, DEFAULT_TTL.GROUP);
|
||||
store.setGroupsListEntry(newEntry);
|
||||
this.refreshGroupsInBackground(page, pageSize);
|
||||
return buildGroupListResponse(localGroups, pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
// <20>𤏸絲霂瑟<E99C82>
|
||||
return this.dedupe(`groups:list:${page}:${pageSize}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getGroups(page, pageSize);
|
||||
const groups = response.list || [];
|
||||
if (page === 1) {
|
||||
store.setGroupsList(groups);
|
||||
}
|
||||
await saveGroupsCache(groups).catch(() => {});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
private refreshGroupsInBackground(page = 1, pageSize = GROUP_LIST_PAGE_SIZE): void {
|
||||
this.dedupe(`groups:bg:list:${page}:${pageSize}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getGroups(page, pageSize);
|
||||
const groups = response.list || [];
|
||||
if (page === 1) {
|
||||
store.setGroupsList(groups);
|
||||
}
|
||||
saveGroupsCache(groups).catch(() => {});
|
||||
return response;
|
||||
}).catch((error) => {
|
||||
console.error('[GroupManager] refreshGroupsInBackground error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <20>𥕦遣蝢斤<E89DA2><E696A4>𡑒”<F0A19192>唳旿皞琜<E79A9E><E7909C>其<EFBFBD> Sources 璅∪<E79285>嚗?
|
||||
*/
|
||||
createGroupListSource(
|
||||
kind: RemoteGroupListSourceKind = 'cursor',
|
||||
pageSize: number = GROUP_LIST_PAGE_SIZE
|
||||
): IGroupListPagedSource {
|
||||
return createRemoteGroupListSource(kind, pageSize);
|
||||
}
|
||||
|
||||
// ==================== 蝢斤<E89DA2>霂行<E99C82><E8A18C>詨<EFBFBD> ====================
|
||||
|
||||
/**
|
||||
* <20>瑕<EFBFBD>蝢斤<E89DA2>霂行<E99C82>
|
||||
*/
|
||||
async getGroup(groupId: string, forceRefresh = false): Promise<GroupResponse> {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const entry = store.groupsMap.get(groupId);
|
||||
|
||||
// <20>㗇<EFBFBD>蝻枏<E89DBB>
|
||||
if (!forceRefresh && entry && !isCacheExpired(entry)) {
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
// 餈<><E9A488>蝻枏<E89DBB>嚗𡁜<E59A97><F0A1819C>啣<EFBFBD><E595A3>?
|
||||
if (!forceRefresh && entry && isCacheExpired(entry)) {
|
||||
this.refreshGroupInBackground(groupId);
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
// 撠肽<E692A0><E882BD>砍𧑐蝻枏<E89DBB>
|
||||
if (!forceRefresh) {
|
||||
const localGroup = await getGroupCache(groupId);
|
||||
if (localGroup) {
|
||||
store.setGroup(groupId, localGroup);
|
||||
this.refreshGroupInBackground(groupId);
|
||||
return localGroup;
|
||||
}
|
||||
}
|
||||
|
||||
// <20>𤏸絲霂瑟<E99C82>
|
||||
return this.dedupe(`groups:detail:${groupId}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const group = await groupService.getGroup(groupId);
|
||||
store.setGroup(groupId, group);
|
||||
await saveGroupCache(group).catch(() => {});
|
||||
return group;
|
||||
});
|
||||
}
|
||||
|
||||
private refreshGroupInBackground(groupId: string): void {
|
||||
this.dedupe(`groups:detail:bg:${groupId}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const group = await groupService.getGroup(groupId);
|
||||
store.setGroup(groupId, group);
|
||||
saveGroupCache(group).catch(() => {});
|
||||
return group;
|
||||
}).catch((error) => {
|
||||
console.error('[GroupManager] refreshGroupInBackground error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 蝢斤<E89DA2><E696A4>𣂼<EFBFBD><F0A382BC>詨<EFBFBD> ====================
|
||||
|
||||
/**
|
||||
* <20>瑕<EFBFBD>蝢斤<E89DA2><E696A4>𣂼<EFBFBD><F0A382BC>𡑒”
|
||||
*/
|
||||
async getMembers(
|
||||
groupId: string,
|
||||
page = 1,
|
||||
pageSize = GROUP_MEMBER_LIST_PAGE_SIZE,
|
||||
forceRefresh = false
|
||||
): Promise<GroupMemberListResponse> {
|
||||
const key = buildMembersKey(groupId, page, pageSize);
|
||||
const store = useGroupManagerStore.getState();
|
||||
const entry = store.membersMap.get(key);
|
||||
|
||||
// <20>㗇<EFBFBD>蝻枏<E89DBB>
|
||||
if (!forceRefresh && entry && !isCacheExpired(entry)) {
|
||||
return buildMemberListResponse(entry.data, page, pageSize);
|
||||
}
|
||||
|
||||
// 餈<><E9A488>蝻枏<E89DBB>嚗𡁜<E59A97><F0A1819C>啣<EFBFBD><E595A3>?
|
||||
if (!forceRefresh && entry && isCacheExpired(entry)) {
|
||||
this.refreshMembersInBackground(groupId, page, pageSize);
|
||||
return buildMemberListResponse(entry.data, page, pageSize);
|
||||
}
|
||||
|
||||
// 蝚砌<E89D9A>憿蛛<E686BF>撠肽<E692A0><E882BD>砍𧑐蝻枏<E89DBB>
|
||||
if (page === 1 && !forceRefresh) {
|
||||
const localMembers = await getGroupMembersCache(groupId);
|
||||
if (localMembers.length > 0) {
|
||||
store.setMembers(groupId, localMembers, page, pageSize);
|
||||
this.refreshMembersInBackground(groupId, page, pageSize);
|
||||
return buildMemberListResponse(localMembers, page, pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
// <20>𤏸絲霂瑟<E99C82>
|
||||
return this.dedupe(`groups:members:${key}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getMembers(groupId, page, pageSize);
|
||||
const members = response.list || [];
|
||||
store.setMembers(groupId, members, page, pageSize);
|
||||
if (page === 1) {
|
||||
await saveGroupMembersCache(groupId, members).catch(() => {});
|
||||
}
|
||||
await saveUsersCache(
|
||||
members
|
||||
.map((member) => member.user)
|
||||
.filter((user): user is NonNullable<typeof user> => Boolean(user))
|
||||
).catch(() => {});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
private refreshMembersInBackground(
|
||||
groupId: string,
|
||||
page = 1,
|
||||
pageSize = GROUP_MEMBER_LIST_PAGE_SIZE
|
||||
): void {
|
||||
const key = buildMembersKey(groupId, page, pageSize);
|
||||
this.dedupe(`groups:members:bg:${key}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getMembers(groupId, page, pageSize);
|
||||
const members = response.list || [];
|
||||
store.setMembers(groupId, members, page, pageSize);
|
||||
if (page === 1) {
|
||||
saveGroupMembersCache(groupId, members).catch(() => {});
|
||||
}
|
||||
saveUsersCache(
|
||||
members
|
||||
.map((member) => member.user)
|
||||
.filter((user): user is NonNullable<typeof user> => Boolean(user))
|
||||
).catch(() => {});
|
||||
return response;
|
||||
}).catch((error) => {
|
||||
console.error('[GroupManager] refreshMembersInBackground error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <20>𥕦遣蝢斤<E89DA2><E696A4>𣂼<EFBFBD><F0A382BC>𡑒”<F0A19192>唳旿皞琜<E79A9E><E7909C>其<EFBFBD> Sources 璅∪<E79285>嚗?
|
||||
*/
|
||||
createGroupMemberListSource(
|
||||
groupId: string,
|
||||
kind: RemoteGroupListSourceKind = 'cursor',
|
||||
pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE
|
||||
): IGroupMemberListPagedSource {
|
||||
return createRemoteGroupMemberListSource(groupId, kind, pageSize);
|
||||
}
|
||||
|
||||
// ==================== 蝻枏<E89DBB>蝞∠<E89D9E> ====================
|
||||
|
||||
/**
|
||||
* 雿輻<E99BBF>摮睃仃<E79D83>?
|
||||
*/
|
||||
invalidate(groupId?: string): void {
|
||||
if (!groupId) {
|
||||
useGroupManagerStore.getState().invalidateAll();
|
||||
return;
|
||||
}
|
||||
useGroupManagerStore.getState().invalidateGroup(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 皜<>膄<EFBFBD><E88684><EFBFBD>㗇㺭<E39787>?
|
||||
*/
|
||||
clear(): void {
|
||||
useGroupManagerStore.getState().reset();
|
||||
}
|
||||
|
||||
// ==================== 霂瑟<E99C82><E7919F>駁<EFBFBD> ====================
|
||||
|
||||
private async dedupe<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const pending = store.getPendingRequest<T>(key);
|
||||
if (pending) return pending;
|
||||
|
||||
const request = fetcher().finally(() => {
|
||||
useGroupManagerStore.getState().deletePendingRequest(key);
|
||||
});
|
||||
store.setPendingRequest(key, request);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== <20>蓥<EFBFBD>撖澆枂 ====================
|
||||
|
||||
export const groupManager = new GroupManager();
|
||||
|
||||
// 撖澆枂蝐颱誑<E9A2B1>舀<EFBFBD>蝐餃<E89D90>璉<EFBFBD><E79289>亙<EFBFBD>瘚贝<E7989A>
|
||||
export { GroupManager };
|
||||
|
||||
export default groupManager;
|
||||
287
src/stores/group/groupStore.ts
Normal file
287
src/stores/group/groupStore.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* 群组状态 Zustand Store
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import type {
|
||||
GroupResponse,
|
||||
GroupMemberResponse,
|
||||
} from '../../types/dto';
|
||||
import { CacheEntry, createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
|
||||
|
||||
// ==================== 状态接口 ====================
|
||||
|
||||
export interface GroupManagerState {
|
||||
// 群组列表缓存
|
||||
groupsListEntry: CacheEntry<GroupResponse[]> | null;
|
||||
|
||||
// 群组详情缓存
|
||||
groupsMap: Map<string, CacheEntry<GroupResponse>>;
|
||||
|
||||
// 群组成员缓存 (key: members:groupId:page:pageSize)
|
||||
membersMap: Map<string, CacheEntry<GroupMemberResponse[]>>;
|
||||
|
||||
// 加载状态
|
||||
isLoadingGroups: boolean;
|
||||
loadingGroupIds: Set<string>;
|
||||
loadingMemberKeys: Set<string>;
|
||||
|
||||
// 待处理请求(去重)
|
||||
pendingRequests: Map<string, Promise<any>>;
|
||||
}
|
||||
|
||||
export interface GroupManagerActions {
|
||||
// 获取状态
|
||||
getGroupsList: () => GroupResponse[] | null;
|
||||
getGroup: (groupId: string) => GroupResponse | null;
|
||||
getMembers: (groupId: string, page?: number, pageSize?: number) => GroupMemberResponse[] | null;
|
||||
isGroupsListExpired: () => boolean;
|
||||
isGroupExpired: (groupId: string) => boolean;
|
||||
isMembersExpired: (groupId: string, page?: number, pageSize?: number) => boolean;
|
||||
isLoadingGroup: (groupId: string) => boolean;
|
||||
isLoadingMembers: (groupId: string, page?: number, pageSize?: number) => boolean;
|
||||
|
||||
// 设置状态
|
||||
setGroupsList: (groups: GroupResponse[]) => void;
|
||||
setGroupsListEntry: (entry: CacheEntry<GroupResponse[]>) => void;
|
||||
setGroup: (groupId: string, group: GroupResponse) => void;
|
||||
setMembers: (groupId: string, members: GroupMemberResponse[], page?: number, pageSize?: number) => void;
|
||||
setLoadingGroups: (loading: boolean) => void;
|
||||
setLoadingGroup: (groupId: string, loading: boolean) => void;
|
||||
setLoadingMembers: (groupId: string, loading: boolean, page?: number, pageSize?: number) => void;
|
||||
|
||||
// 缓存管理
|
||||
invalidateGroupsList: () => void;
|
||||
invalidateGroup: (groupId: string) => void;
|
||||
invalidateMembers: (groupId: string) => void;
|
||||
invalidateAll: () => void;
|
||||
|
||||
// 请求去重
|
||||
getPendingRequest: <T>(key: string) => Promise<T> | undefined;
|
||||
setPendingRequest: <T>(key: string, request: Promise<T>) => void;
|
||||
deletePendingRequest: (key: string) => void;
|
||||
clearPendingRequests: () => void;
|
||||
|
||||
// 重置
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
|
||||
function buildMembersKey(groupId: string, page: number = 1, pageSize: number = 20): string {
|
||||
return `members:${groupId}:${page}:${pageSize}`;
|
||||
}
|
||||
|
||||
// ==================== 初始状态 ====================
|
||||
|
||||
const initialState: GroupManagerState = {
|
||||
groupsListEntry: null,
|
||||
groupsMap: new Map(),
|
||||
membersMap: new Map(),
|
||||
isLoadingGroups: false,
|
||||
loadingGroupIds: new Set(),
|
||||
loadingMemberKeys: new Set(),
|
||||
pendingRequests: new Map(),
|
||||
};
|
||||
|
||||
// ==================== Store 创建 ====================
|
||||
|
||||
export type GroupManagerStore = GroupManagerState & GroupManagerActions;
|
||||
|
||||
export const useGroupManagerStore = create<GroupManagerStore>((set, get) => ({
|
||||
// ==================== 初始状态 ====================
|
||||
...initialState,
|
||||
|
||||
// ==================== 获取状态 ====================
|
||||
|
||||
getGroupsList: () => {
|
||||
const entry = get().groupsListEntry;
|
||||
if (!entry) return null;
|
||||
if (isCacheExpired(entry)) return null;
|
||||
return entry.data;
|
||||
},
|
||||
|
||||
getGroup: (groupId: string) => {
|
||||
const entry = get().groupsMap.get(groupId);
|
||||
if (!entry) return null;
|
||||
if (isCacheExpired(entry)) return null;
|
||||
return entry.data;
|
||||
},
|
||||
|
||||
getMembers: (groupId: string, page = 1, pageSize = 20) => {
|
||||
const key = buildMembersKey(groupId, page, pageSize);
|
||||
const entry = get().membersMap.get(key);
|
||||
if (!entry) return null;
|
||||
if (isCacheExpired(entry)) return null;
|
||||
return entry.data;
|
||||
},
|
||||
|
||||
isGroupsListExpired: () => {
|
||||
const entry = get().groupsListEntry;
|
||||
if (!entry) return true;
|
||||
return isCacheExpired(entry);
|
||||
},
|
||||
|
||||
isGroupExpired: (groupId: string) => {
|
||||
const entry = get().groupsMap.get(groupId);
|
||||
if (!entry) return true;
|
||||
return isCacheExpired(entry);
|
||||
},
|
||||
|
||||
isMembersExpired: (groupId: string, page = 1, pageSize = 20) => {
|
||||
const key = buildMembersKey(groupId, page, pageSize);
|
||||
const entry = get().membersMap.get(key);
|
||||
if (!entry) return true;
|
||||
return isCacheExpired(entry);
|
||||
},
|
||||
|
||||
isLoadingGroup: (groupId: string) => {
|
||||
return get().loadingGroupIds.has(groupId);
|
||||
},
|
||||
|
||||
isLoadingMembers: (groupId: string, page = 1, pageSize = 20) => {
|
||||
const key = buildMembersKey(groupId, page, pageSize);
|
||||
return get().loadingMemberKeys.has(key);
|
||||
},
|
||||
|
||||
// ==================== 设置状态 ====================
|
||||
|
||||
setGroupsList: (groups: GroupResponse[]) => {
|
||||
set({
|
||||
groupsListEntry: createCacheEntry(groups, DEFAULT_TTL.GROUP),
|
||||
});
|
||||
},
|
||||
|
||||
setGroupsListEntry: (entry: CacheEntry<GroupResponse[]>) => {
|
||||
set({ groupsListEntry: entry });
|
||||
},
|
||||
|
||||
setGroup: (groupId: string, group: GroupResponse) => {
|
||||
set(state => {
|
||||
const newGroupsMap = new Map(state.groupsMap);
|
||||
newGroupsMap.set(groupId, createCacheEntry(group, DEFAULT_TTL.GROUP));
|
||||
return { groupsMap: newGroupsMap };
|
||||
});
|
||||
},
|
||||
|
||||
setMembers: (groupId: string, members: GroupMemberResponse[], page = 1, pageSize = 20) => {
|
||||
const key = buildMembersKey(groupId, page, pageSize);
|
||||
set(state => {
|
||||
const newMembersMap = new Map(state.membersMap);
|
||||
newMembersMap.set(key, createCacheEntry(members, DEFAULT_TTL.GROUP_MEMBERS));
|
||||
return { membersMap: newMembersMap };
|
||||
});
|
||||
},
|
||||
|
||||
setLoadingGroups: (loading: boolean) => {
|
||||
set({ isLoadingGroups: loading });
|
||||
},
|
||||
|
||||
setLoadingGroup: (groupId: string, loading: boolean) => {
|
||||
set(state => {
|
||||
const newLoadingGroupIds = new Set(state.loadingGroupIds);
|
||||
if (loading) {
|
||||
newLoadingGroupIds.add(groupId);
|
||||
} else {
|
||||
newLoadingGroupIds.delete(groupId);
|
||||
}
|
||||
return { loadingGroupIds: newLoadingGroupIds };
|
||||
});
|
||||
},
|
||||
|
||||
setLoadingMembers: (groupId: string, loading: boolean, page = 1, pageSize = 20) => {
|
||||
const key = buildMembersKey(groupId, page, pageSize);
|
||||
set(state => {
|
||||
const newLoadingMemberKeys = new Set(state.loadingMemberKeys);
|
||||
if (loading) {
|
||||
newLoadingMemberKeys.add(key);
|
||||
} else {
|
||||
newLoadingMemberKeys.delete(key);
|
||||
}
|
||||
return { loadingMemberKeys: newLoadingMemberKeys };
|
||||
});
|
||||
},
|
||||
|
||||
// ==================== 缓存管理 ====================
|
||||
|
||||
invalidateGroupsList: () => {
|
||||
set({ groupsListEntry: null });
|
||||
},
|
||||
|
||||
invalidateGroup: (groupId: string) => {
|
||||
set(state => {
|
||||
const newGroupsMap = new Map(state.groupsMap);
|
||||
newGroupsMap.delete(groupId);
|
||||
|
||||
// 删除相关的成员缓存
|
||||
const newMembersMap = new Map(state.membersMap);
|
||||
for (const key of newMembersMap.keys()) {
|
||||
if (key.startsWith(`members:${groupId}:`)) {
|
||||
newMembersMap.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { groupsMap: newGroupsMap, membersMap: newMembersMap };
|
||||
});
|
||||
},
|
||||
|
||||
invalidateMembers: (groupId: string) => {
|
||||
set(state => {
|
||||
const newMembersMap = new Map(state.membersMap);
|
||||
for (const key of newMembersMap.keys()) {
|
||||
if (key.startsWith(`members:${groupId}:`)) {
|
||||
newMembersMap.delete(key);
|
||||
}
|
||||
}
|
||||
return { membersMap: newMembersMap };
|
||||
});
|
||||
},
|
||||
|
||||
invalidateAll: () => {
|
||||
set({
|
||||
groupsListEntry: null,
|
||||
groupsMap: new Map(),
|
||||
membersMap: new Map(),
|
||||
pendingRequests: new Map(),
|
||||
});
|
||||
},
|
||||
|
||||
// ==================== 请求去重 ====================
|
||||
|
||||
getPendingRequest: <T>(key: string) => {
|
||||
return get().pendingRequests.get(key) as Promise<T> | undefined;
|
||||
},
|
||||
|
||||
setPendingRequest: <T>(key: string, request: Promise<T>) => {
|
||||
set(state => {
|
||||
const newPendingRequests = new Map(state.pendingRequests);
|
||||
newPendingRequests.set(key, request);
|
||||
return { pendingRequests: newPendingRequests };
|
||||
});
|
||||
},
|
||||
|
||||
deletePendingRequest: (key: string) => {
|
||||
set(state => {
|
||||
const newPendingRequests = new Map(state.pendingRequests);
|
||||
newPendingRequests.delete(key);
|
||||
return { pendingRequests: newPendingRequests };
|
||||
});
|
||||
},
|
||||
|
||||
clearPendingRequests: () => {
|
||||
set({ pendingRequests: new Map() });
|
||||
},
|
||||
|
||||
// ==================== 重置 ====================
|
||||
|
||||
reset: () => {
|
||||
set({
|
||||
...initialState,
|
||||
groupsMap: new Map(),
|
||||
membersMap: new Map(),
|
||||
loadingGroupIds: new Set(),
|
||||
loadingMemberKeys: new Set(),
|
||||
pendingRequests: new Map(),
|
||||
});
|
||||
},
|
||||
}));
|
||||
140
src/stores/group/hooks.ts
Normal file
140
src/stores/group/hooks.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 群组相关 React Hooks
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useGroupManagerStore } from './groupStore';
|
||||
import { groupManager } from './GroupManager';
|
||||
import type { GroupResponse, GroupMemberResponse } from '../../types/dto';
|
||||
import { GROUP_LIST_PAGE_SIZE, GROUP_MEMBER_LIST_PAGE_SIZE } from '../groupListSources';
|
||||
|
||||
// ==================== useGroups ====================
|
||||
|
||||
export interface UseGroupsResult {
|
||||
groups: GroupResponse[];
|
||||
isLoading: boolean;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群组列表 hook
|
||||
*/
|
||||
export function useGroups(): UseGroupsResult {
|
||||
const groups = useGroupManagerStore(state => state.groupsListEntry?.data ?? []);
|
||||
const isLoading = useGroupManagerStore(state => state.isLoadingGroups);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasFetched) {
|
||||
setHasFetched(true);
|
||||
useGroupManagerStore.getState().setLoadingGroups(true);
|
||||
groupManager.getGroups(1, GROUP_LIST_PAGE_SIZE).finally(() => {
|
||||
useGroupManagerStore.getState().setLoadingGroups(false);
|
||||
});
|
||||
}
|
||||
}, [hasFetched]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
useGroupManagerStore.getState().setLoadingGroups(true);
|
||||
try {
|
||||
await groupManager.getGroups(1, GROUP_LIST_PAGE_SIZE, true);
|
||||
} finally {
|
||||
useGroupManagerStore.getState().setLoadingGroups(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { groups, isLoading, refresh };
|
||||
}
|
||||
|
||||
// ==================== useGroup ====================
|
||||
|
||||
export interface UseGroupResult {
|
||||
group: GroupResponse | null;
|
||||
isLoading: boolean;
|
||||
refresh: () => Promise<GroupResponse | null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个群组 hook
|
||||
*/
|
||||
export function useGroup(groupId: string | undefined | null): UseGroupResult {
|
||||
const group = useGroupManagerStore(state =>
|
||||
groupId ? (state.groupsMap.get(groupId)?.data ?? null) : null
|
||||
);
|
||||
const isLoading = useGroupManagerStore(state =>
|
||||
groupId ? state.loadingGroupIds.has(groupId) : false
|
||||
);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!groupId) return;
|
||||
if (!hasFetched) {
|
||||
setHasFetched(true);
|
||||
useGroupManagerStore.getState().setLoadingGroup(groupId, true);
|
||||
groupManager.getGroup(groupId).finally(() => {
|
||||
useGroupManagerStore.getState().setLoadingGroup(groupId, false);
|
||||
});
|
||||
}
|
||||
}, [groupId, hasFetched]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!groupId) return null;
|
||||
useGroupManagerStore.getState().setLoadingGroup(groupId, true);
|
||||
try {
|
||||
return await groupManager.getGroup(groupId, true);
|
||||
} finally {
|
||||
useGroupManagerStore.getState().setLoadingGroup(groupId, false);
|
||||
}
|
||||
}, [groupId]);
|
||||
|
||||
return { group, isLoading, refresh };
|
||||
}
|
||||
|
||||
// ==================== useGroupMembers ====================
|
||||
|
||||
export interface UseGroupMembersResult {
|
||||
members: GroupMemberResponse[];
|
||||
isLoading: boolean;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群组成员 hook
|
||||
*/
|
||||
export function useGroupMembers(
|
||||
groupId: string | undefined | null,
|
||||
page = 1,
|
||||
pageSize = GROUP_MEMBER_LIST_PAGE_SIZE
|
||||
): UseGroupMembersResult {
|
||||
const membersKey = groupId ? `members:${groupId}:${page}:${pageSize}` : '';
|
||||
const members = useGroupManagerStore(state =>
|
||||
membersKey ? (state.membersMap.get(membersKey)?.data ?? []) : []
|
||||
);
|
||||
const isLoading = useGroupManagerStore(state =>
|
||||
membersKey ? state.loadingMemberKeys.has(membersKey) : false
|
||||
);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!groupId) return;
|
||||
if (!hasFetched) {
|
||||
setHasFetched(true);
|
||||
useGroupManagerStore.getState().setLoadingMembers(groupId, true, page, pageSize);
|
||||
groupManager.getMembers(groupId, page, pageSize).finally(() => {
|
||||
useGroupManagerStore.getState().setLoadingMembers(groupId, false, page, pageSize);
|
||||
});
|
||||
}
|
||||
}, [groupId, page, pageSize, hasFetched]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!groupId) return;
|
||||
useGroupManagerStore.getState().setLoadingMembers(groupId, true, page, pageSize);
|
||||
try {
|
||||
await groupManager.getMembers(groupId, page, pageSize, true);
|
||||
} finally {
|
||||
useGroupManagerStore.getState().setLoadingMembers(groupId, false, page, pageSize);
|
||||
}
|
||||
}, [groupId, page, pageSize]);
|
||||
|
||||
return { members, isLoading, refresh };
|
||||
}
|
||||
27
src/stores/group/index.ts
Normal file
27
src/stores/group/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 群组模块统一导出
|
||||
*/
|
||||
|
||||
// ==================== Store ====================
|
||||
export {
|
||||
useGroupManagerStore,
|
||||
type GroupManagerState,
|
||||
type GroupManagerActions,
|
||||
type GroupManagerStore,
|
||||
} from './groupStore';
|
||||
|
||||
// ==================== Manager ====================
|
||||
export { groupManager, GroupManager } from './GroupManager';
|
||||
|
||||
// ==================== Hooks ====================
|
||||
export {
|
||||
useGroups,
|
||||
useGroup,
|
||||
useGroupMembers,
|
||||
type UseGroupsResult,
|
||||
type UseGroupResult,
|
||||
type UseGroupMembersResult,
|
||||
} from './hooks';
|
||||
|
||||
// ==================== 默认导出 ====================
|
||||
export { default } from './GroupManager';
|
||||
Reference in New Issue
Block a user