refactor(stores): reorganize flat store files into modular directory structure
Migrate from flat file organization to modular directory structure under src/stores/: - auth/: authStore, registerStore, verificationStore, sessionStore - call/: callStore - settings/: chatSettingsStore, themeStore - ui/: homeTabBarVisibilityStore, homeTabPressStore - utils/: routePayloadCache - group/sources.ts, group/profileResolver.ts - message/sources.ts - post/sources.ts Update all import paths across components and screens to use new module paths. Maintain backward compatibility through deprecated re-export files for gradual migration.
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
RemoteGroupListSourceKind,
|
||||
GROUP_LIST_PAGE_SIZE,
|
||||
GROUP_MEMBER_LIST_PAGE_SIZE,
|
||||
} from '../groupListSources';
|
||||
} from './sources';
|
||||
|
||||
const dedupe = createDedupe(() => useGroupManagerStore);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef } 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';
|
||||
import { GROUP_LIST_PAGE_SIZE, GROUP_MEMBER_LIST_PAGE_SIZE } from './sources';
|
||||
|
||||
export interface UseGroupsResult {
|
||||
groups: GroupResponse[];
|
||||
|
||||
@@ -23,5 +23,32 @@ export {
|
||||
type UseGroupMembersResult,
|
||||
} from './hooks';
|
||||
|
||||
// ==================== 数据源 ====================
|
||||
export {
|
||||
GROUP_LIST_PAGE_SIZE,
|
||||
GROUP_MEMBER_LIST_PAGE_SIZE,
|
||||
type GroupListPage,
|
||||
type IGroupListPagedSource,
|
||||
type GroupMemberListPage,
|
||||
type IGroupMemberListPagedSource,
|
||||
type GroupMemberListSourceKind,
|
||||
type ICursorGroupMemberListPagedSource,
|
||||
type RemoteGroupListSourceKind,
|
||||
NetworkCursorGroupListPagedSource,
|
||||
NetworkOffsetGroupListPagedSource,
|
||||
SqliteGroupListPagedSource,
|
||||
NetworkCursorGroupMemberListPagedSource,
|
||||
NetworkOffsetGroupMemberListPagedSource,
|
||||
SqliteGroupMemberListPagedSource,
|
||||
CursorGroupMemberListSource,
|
||||
OffsetGroupMemberListSource,
|
||||
createRemoteGroupListSource,
|
||||
createRemoteGroupMemberListSource,
|
||||
createCursorGroupMemberListSource,
|
||||
} from './sources';
|
||||
|
||||
// ==================== 工具 ====================
|
||||
export { enrichGroupMembersWithUserProfiles } from './profileResolver';
|
||||
|
||||
// ==================== 默认导出 ====================
|
||||
export { default } from './GroupManager';
|
||||
46
src/stores/group/profileResolver.ts
Normal file
46
src/stores/group/profileResolver.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { GroupMemberResponse, UserDTO } from '../../types/dto';
|
||||
import { userCacheRepository } from '@/database';
|
||||
import { userManager } from '../user';
|
||||
|
||||
/**
|
||||
* 为群成员列表补全用户资料(昵称、头像、用户名)。
|
||||
* cursor 接口仅返回 user_id 时,通过本地缓存和用户接口回填。
|
||||
*/
|
||||
export async function enrichGroupMembersWithUserProfiles(
|
||||
members: GroupMemberResponse[]
|
||||
): Promise<GroupMemberResponse[]> {
|
||||
if (!Array.isArray(members) || members.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const userIds = [...new Set(members.map((member) => String(member.user_id)))];
|
||||
const userMap = new Map<string, UserDTO>();
|
||||
|
||||
await Promise.all(
|
||||
userIds.map(async (userId) => {
|
||||
try {
|
||||
const cachedUser = await userCacheRepository.get(userId);
|
||||
if (cachedUser) {
|
||||
userMap.set(userId, cachedUser);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchedUser = await userManager.getUserById(userId);
|
||||
if (fetchedUser) {
|
||||
userMap.set(userId, fetchedUser);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('补全群成员用户资料失败:', error);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return members.map((member) => {
|
||||
if (member.user?.nickname || member.user?.username) {
|
||||
return member;
|
||||
}
|
||||
const user = userMap.get(String(member.user_id));
|
||||
return user ? { ...member, user } : member;
|
||||
});
|
||||
}
|
||||
|
||||
380
src/stores/group/sources.ts
Normal file
380
src/stores/group/sources.ts
Normal file
@@ -0,0 +1,380 @@
|
||||
/**
|
||||
* 群组数据源抽象:群组列表 + 群组成员列表的游标/偏移分页实现。
|
||||
*/
|
||||
|
||||
import {
|
||||
GroupResponse,
|
||||
GroupMemberResponse,
|
||||
GroupListResponse,
|
||||
GroupMemberListResponse,
|
||||
CursorPaginationRequest,
|
||||
CursorPaginationResponse,
|
||||
} from '../../types/dto';
|
||||
import { groupService } from '@/services/message';
|
||||
import { groupCacheRepository } from '@/database';
|
||||
|
||||
export const GROUP_LIST_PAGE_SIZE = 20;
|
||||
export const GROUP_MEMBER_LIST_PAGE_SIZE = 50;
|
||||
|
||||
// ==================== 群组列表数据源 ====================
|
||||
|
||||
export interface GroupListPage {
|
||||
items: GroupResponse[];
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
export interface IGroupListPagedSource {
|
||||
restart(): void;
|
||||
loadNext(): Promise<GroupListPage>;
|
||||
readonly hasMore: boolean;
|
||||
}
|
||||
|
||||
export class NetworkCursorGroupListPagedSource implements IGroupListPagedSource {
|
||||
private nextCursor: string | null = null;
|
||||
private hasMoreAfterLastLoad = false;
|
||||
private loadedOnce = false;
|
||||
private readonly pageSize: number;
|
||||
|
||||
constructor(pageSize: number = GROUP_LIST_PAGE_SIZE) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.nextCursor = null;
|
||||
this.hasMoreAfterLastLoad = false;
|
||||
this.loadedOnce = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<GroupListPage> {
|
||||
const params: CursorPaginationRequest = {
|
||||
cursor: this.nextCursor ?? '',
|
||||
page_size: this.pageSize,
|
||||
};
|
||||
|
||||
const result = await groupService.getGroupsCursor(params);
|
||||
|
||||
this.hasMoreAfterLastLoad = result.has_more ?? false;
|
||||
this.nextCursor = result.next_cursor ?? null;
|
||||
this.loadedOnce = true;
|
||||
|
||||
return { items: result.list ?? [], hasMore: this.hasMoreAfterLastLoad };
|
||||
}
|
||||
}
|
||||
|
||||
export class NetworkOffsetGroupListPagedSource implements IGroupListPagedSource {
|
||||
private nextPage = 1;
|
||||
private hasMoreAfterLastLoad = false;
|
||||
private loadedOnce = false;
|
||||
private readonly pageSize: number;
|
||||
|
||||
constructor(pageSize: number = GROUP_LIST_PAGE_SIZE) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.nextPage = 1;
|
||||
this.hasMoreAfterLastLoad = false;
|
||||
this.loadedOnce = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<GroupListPage> {
|
||||
const result = await groupService.getGroups(this.nextPage, this.pageSize);
|
||||
|
||||
const items = result.list ?? [];
|
||||
const totalPages = result.total_pages ?? 1;
|
||||
|
||||
this.hasMoreAfterLastLoad = this.nextPage < totalPages;
|
||||
this.nextPage++;
|
||||
this.loadedOnce = true;
|
||||
|
||||
return { items, hasMore: this.hasMoreAfterLastLoad };
|
||||
}
|
||||
}
|
||||
|
||||
export class SqliteGroupListPagedSource implements IGroupListPagedSource {
|
||||
private consumed = false;
|
||||
|
||||
restart(): void {
|
||||
this.consumed = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<GroupListPage> {
|
||||
if (this.consumed) {
|
||||
return { items: [], hasMore: false };
|
||||
}
|
||||
this.consumed = true;
|
||||
try {
|
||||
const items = await groupCacheRepository.getAll();
|
||||
return { items, hasMore: false };
|
||||
} catch (e) {
|
||||
console.warn('[SqliteGroupListPagedSource] 读取群组列表缓存失败:', e);
|
||||
return { items: [], hasMore: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 群组成员列表数据源(Manager 内部用) ====================
|
||||
|
||||
export interface GroupMemberListPage {
|
||||
items: GroupMemberResponse[];
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
export interface IGroupMemberListPagedSource {
|
||||
restart(): void;
|
||||
loadNext(): Promise<GroupMemberListPage>;
|
||||
readonly hasMore: boolean;
|
||||
}
|
||||
|
||||
export class NetworkCursorGroupMemberListPagedSource implements IGroupMemberListPagedSource {
|
||||
private nextCursor: string | null = null;
|
||||
private hasMoreAfterLastLoad = false;
|
||||
private loadedOnce = false;
|
||||
private readonly pageSize: number;
|
||||
private readonly groupId: string;
|
||||
|
||||
constructor(groupId: string, pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE) {
|
||||
this.groupId = groupId;
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.nextCursor = null;
|
||||
this.hasMoreAfterLastLoad = false;
|
||||
this.loadedOnce = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<GroupMemberListPage> {
|
||||
const params: CursorPaginationRequest = {
|
||||
cursor: this.nextCursor ?? '',
|
||||
page_size: this.pageSize,
|
||||
};
|
||||
|
||||
const result = await groupService.getGroupMembersCursor(this.groupId, params);
|
||||
|
||||
this.hasMoreAfterLastLoad = result.has_more ?? false;
|
||||
this.nextCursor = result.next_cursor ?? null;
|
||||
this.loadedOnce = true;
|
||||
|
||||
return { items: result.list ?? [], hasMore: this.hasMoreAfterLastLoad };
|
||||
}
|
||||
}
|
||||
|
||||
export class NetworkOffsetGroupMemberListPagedSource implements IGroupMemberListPagedSource {
|
||||
private nextPage = 1;
|
||||
private hasMoreAfterLastLoad = false;
|
||||
private loadedOnce = false;
|
||||
private readonly pageSize: number;
|
||||
private readonly groupId: string;
|
||||
|
||||
constructor(groupId: string, pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE) {
|
||||
this.groupId = groupId;
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.nextPage = 1;
|
||||
this.hasMoreAfterLastLoad = false;
|
||||
this.loadedOnce = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<GroupMemberListPage> {
|
||||
const result = await groupService.getMembers(
|
||||
this.groupId,
|
||||
this.nextPage,
|
||||
this.pageSize
|
||||
);
|
||||
|
||||
const items = result.list ?? [];
|
||||
const totalPages = result.total_pages ?? 1;
|
||||
|
||||
this.hasMoreAfterLastLoad = this.nextPage < totalPages;
|
||||
this.nextPage++;
|
||||
this.loadedOnce = true;
|
||||
|
||||
return { items, hasMore: this.hasMoreAfterLastLoad };
|
||||
}
|
||||
}
|
||||
|
||||
export class SqliteGroupMemberListPagedSource implements IGroupMemberListPagedSource {
|
||||
private consumed = false;
|
||||
private readonly groupId: string;
|
||||
|
||||
constructor(groupId: string) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.consumed = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<GroupMemberListPage> {
|
||||
if (this.consumed) {
|
||||
return { items: [], hasMore: false };
|
||||
}
|
||||
this.consumed = true;
|
||||
try {
|
||||
const items = await groupCacheRepository.getMembers(this.groupId);
|
||||
return { items, hasMore: false };
|
||||
} catch (e) {
|
||||
console.warn('[SqliteGroupMemberListPagedSource] 读取成员列表缓存失败:', e);
|
||||
return { items: [], hasMore: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 群组成员列表数据源(Screen 层用,返回 CursorPaginationResponse) ====================
|
||||
|
||||
export type GroupMemberListSourceKind = 'cursor' | 'offset';
|
||||
|
||||
export interface ICursorGroupMemberListPagedSource {
|
||||
restart(): void;
|
||||
loadNext(): Promise<CursorPaginationResponse<GroupMemberResponse>>;
|
||||
readonly hasMore: boolean;
|
||||
}
|
||||
|
||||
export class CursorGroupMemberListSource implements ICursorGroupMemberListPagedSource {
|
||||
private nextCursor: string | null = null;
|
||||
private hasMoreAfterLastLoad = false;
|
||||
private loadedOnce = false;
|
||||
private readonly groupId: string;
|
||||
private readonly pageSize: number;
|
||||
|
||||
constructor(groupId: string, pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE) {
|
||||
this.groupId = groupId;
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.nextCursor = null;
|
||||
this.hasMoreAfterLastLoad = false;
|
||||
this.loadedOnce = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<CursorPaginationResponse<GroupMemberResponse>> {
|
||||
const response = await groupService.getGroupMembersCursor(
|
||||
this.groupId,
|
||||
this.nextCursor == null
|
||||
? { page_size: this.pageSize }
|
||||
: { cursor: this.nextCursor, page_size: this.pageSize }
|
||||
);
|
||||
|
||||
this.nextCursor = response.next_cursor ?? null;
|
||||
this.hasMoreAfterLastLoad = Boolean(response.has_more && response.next_cursor != null);
|
||||
this.loadedOnce = true;
|
||||
|
||||
return {
|
||||
list: response.list || [],
|
||||
next_cursor: response.next_cursor ?? null,
|
||||
prev_cursor: response.prev_cursor ?? null,
|
||||
has_more: response.has_more ?? false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class OffsetGroupMemberListSource implements ICursorGroupMemberListPagedSource {
|
||||
private nextPage = 1;
|
||||
private hasMoreAfterLastLoad = false;
|
||||
private loadedOnce = false;
|
||||
private readonly groupId: string;
|
||||
private readonly pageSize: number;
|
||||
|
||||
constructor(groupId: string, pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE) {
|
||||
this.groupId = groupId;
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
restart(): void {
|
||||
this.nextPage = 1;
|
||||
this.hasMoreAfterLastLoad = false;
|
||||
this.loadedOnce = false;
|
||||
}
|
||||
|
||||
get hasMore(): boolean {
|
||||
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
||||
}
|
||||
|
||||
async loadNext(): Promise<CursorPaginationResponse<GroupMemberResponse>> {
|
||||
const response = await groupService.getMembers(this.groupId, this.nextPage, this.pageSize);
|
||||
const list = response.list || [];
|
||||
const currentPage = response.page ?? this.nextPage;
|
||||
const totalPages = response.total_pages ?? currentPage;
|
||||
const hasMore = currentPage < totalPages;
|
||||
const nextPageCursor = hasMore ? String(currentPage + 1) : null;
|
||||
const prevPageCursor = currentPage > 1 ? String(currentPage - 1) : null;
|
||||
|
||||
this.nextPage = currentPage + 1;
|
||||
this.hasMoreAfterLastLoad = hasMore;
|
||||
this.loadedOnce = true;
|
||||
|
||||
return {
|
||||
list,
|
||||
next_cursor: nextPageCursor,
|
||||
prev_cursor: prevPageCursor,
|
||||
has_more: hasMore,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function createCursorGroupMemberListSource(
|
||||
kind: GroupMemberListSourceKind,
|
||||
groupId: string,
|
||||
pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE
|
||||
): ICursorGroupMemberListPagedSource {
|
||||
return kind === 'offset'
|
||||
? new OffsetGroupMemberListSource(groupId, pageSize)
|
||||
: new CursorGroupMemberListSource(groupId, pageSize);
|
||||
}
|
||||
|
||||
// ==================== 工厂函数 ====================
|
||||
|
||||
export type RemoteGroupListSourceKind = 'cursor' | 'offset';
|
||||
|
||||
export function createRemoteGroupListSource(
|
||||
kind: RemoteGroupListSourceKind = 'cursor',
|
||||
pageSize: number = GROUP_LIST_PAGE_SIZE
|
||||
): IGroupListPagedSource {
|
||||
if (kind === 'offset') {
|
||||
return new NetworkOffsetGroupListPagedSource(pageSize);
|
||||
}
|
||||
return new NetworkCursorGroupListPagedSource(pageSize);
|
||||
}
|
||||
|
||||
export function createRemoteGroupMemberListSource(
|
||||
groupId: string,
|
||||
kind: RemoteGroupListSourceKind = 'cursor',
|
||||
pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE
|
||||
): IGroupMemberListPagedSource {
|
||||
if (kind === 'offset') {
|
||||
return new NetworkOffsetGroupMemberListPagedSource(groupId, pageSize);
|
||||
}
|
||||
return new NetworkCursorGroupMemberListPagedSource(groupId, pageSize);
|
||||
}
|
||||
Reference in New Issue
Block a user