refactor(stores): reorganize flat store files into modular directory structure
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m29s
Frontend CI / ota-android (push) Successful in 10m35s
Frontend CI / build-android-apk (push) Successful in 37m48s

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:
lafay
2026-04-13 04:56:58 +08:00
parent 4f31926eb5
commit 57d7c7405c
69 changed files with 1163 additions and 1219 deletions

View File

@@ -17,7 +17,7 @@ import {
RemoteGroupListSourceKind,
GROUP_LIST_PAGE_SIZE,
GROUP_MEMBER_LIST_PAGE_SIZE,
} from '../groupListSources';
} from './sources';
const dedupe = createDedupe(() => useGroupManagerStore);

View File

@@ -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[];

View File

@@ -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';

View 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
View 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);
}