Files
gongxue-base/apps/admin/src/api/queryKeys.ts
wangziqi 9565a0f23c feat(admin): 深化 TanStack Query 数据层
- 新增 queryKeys.ts 统一 QueryKey 工厂(覆盖全部模块,invalidate/refetch/prefetch 同源)
- 新增 queryClient.ts 全局配置:retry 1、staleTime 30s、gcTime 5min、refetchOnWindowFocus false
- 新增 useApiQuery:zod schema 校验 + 类型收敛 + select 转换,消除页面重复 validateResponse 样板
- useApiMutation 支持 onMutate/onSettled/context(乐观更新)
- 示范迁移:Organizations(useApiQuery + 乐观更新归档/恢复)、Students(useApiQuery + queryKeys + 打开抽屉前 prefetch 档案聚合)
- StudentProfileContent queryKey 统一为 queryKeys.archive.detail,prefetch 可命中缓存
2026-08-08 18:37:55 +08:00

146 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 统一 QueryKey 工厂。
*
* 每个模块一个命名空间key 由工厂函数生成:
* - 避免散落字符串字面量导致拼写不一致、缓存串扰
* - invalidate / refetch / prefetch 与 useQuery 使用同一来源,不会对不上
*
* 约定:
* - `all` 是该模块的「根 key」用于整体失效invalidateQueries 会匹配前缀)
* - 列表 key 按过滤条件展开;详情/子资源用固定段 + 参数
*/
export const queryKeys = {
students: {
all: ['students'] as const,
list: (filters: {
search?: string;
status?: string;
archived?: boolean;
organizationId?: number;
classId?: number;
teacherId?: number;
}) => ['students', filters] as const,
organizations: (canView: boolean) => ['students', 'organizations', canView] as const,
filterLookups: () => ['students', 'filter-lookups'] as const,
},
classes: {
all: ['classes'] as const,
list: (filters: { status?: string; type?: string; archived?: boolean }) =>
['classes', filters] as const,
detail: (id: number) => ['classes', 'detail', id] as const,
schedule: (id: number, dateRange: unknown) => ['classes', 'schedule', id, dateRange] as const,
attendanceSummary: (id: number, dateRange: unknown) =>
['classes', 'attendance-summary', id, dateRange] as const,
},
classSchedules: {
all: ['class-schedules'] as const,
list: (params: { startDate?: string; endDate?: string; classroomIds?: unknown[] }) =>
['class-schedules', params] as const,
},
classrooms: {
all: ['classrooms'] as const,
list: (archived: boolean) => ['classrooms', archived] as const,
},
classroomRentals: {
all: ['classroom-rentals'] as const,
list: (month?: string) => ['classroom-rentals', month] as const,
schedule: (year: number, month: number) =>
['classroom-rentals', 'schedule', year, month] as const,
meta: () => ['classroom-rentals', 'meta'] as const,
},
organizations: {
all: ['organizations'] as const,
list: () => ['organizations'] as const,
options: () => ['organizations', 'options'] as const,
},
rbac: {
all: ['rbac'] as const,
users: (archived: boolean) => ['rbac', 'users', archived] as const,
allUsers: () => ['rbac', 'users', 'all'] as const,
teachers: (params: { page: number; pageSize: number; search?: string }) =>
['rbac', 'teachers', params] as const,
teacherWorkspace: () => ['rbac', 'teacher-workspace'] as const,
roles: () => ['rbac', 'roles'] as const,
permissionTree: () => ['rbac', 'roles', 'permission-tree'] as const,
permissionsTree: () => ['rbac', 'permissions', 'tree'] as const,
},
expenses: {
all: ['expenses'] as const,
list: (archived: boolean) => ['expenses', archived ? 'archived' : 'active'] as const,
},
expenseTypes: {
map: () => ['expense-types', 'map'] as const,
},
expenseLookups: {
all: ['expense-lookups'] as const,
},
bills: {
all: ['bills'] as const,
list: (filters: { status?: string; expenseType?: string }) => ['bills', filters] as const,
},
wallets: {
all: ['wallets'] as const,
list: (params: { keyword?: string; debtOnly?: boolean; roomType?: string }) =>
['wallets', params] as const,
transactions: (studentId: number) => ['wallets', 'transactions', studentId] as const,
roomTypes: () => ['wallets', 'room-types'] as const,
},
deposits: {
all: ['deposits'] as const,
list: () => ['deposits'] as const,
eligible: (roomType?: string) => ['deposits', 'eligible', roomType] as const,
},
occupancies: {
all: ['occupancies'] as const,
list: (params: { viewMode?: string; dateRange?: unknown }) => ['occupancies', params] as const,
},
rooms: {
all: ['rooms'] as const,
overview: (archived: boolean) => ['rooms', 'overview', archived] as const,
visual: (params: { historical: boolean; asOf?: unknown }) =>
['rooms', 'visual', params] as const,
},
exams: {
all: ['exams'] as const,
detail: (id: number) => ['exams', 'detail', id] as const,
classes: () => ['exams', 'classes'] as const,
},
operationLogs: {
all: ['operation-logs'] as const,
list: (params: { page: number; pageSize: number; module?: string; dateRange?: unknown }) =>
['operation-logs', params] as const,
},
attendance: {
all: ['attendance'] as const,
workspace: () => ['attendance', 'workspace'] as const,
syncStatus: () => ['attendance', 'sync-status'] as const,
schedules: (classId: number, date: string) => ['attendance', 'schedules', classId, date] as const,
meta: {
periods: () => ['attendance', 'meta', 'periods'] as const,
classes: () => ['attendance', 'meta', 'classes'] as const,
alerts: () => ['attendance', 'meta', 'alerts'] as const,
},
},
attendanceDevices: {
all: ['attendance-devices'] as const,
},
dashboard: {
all: ['dashboard'] as const,
summary: (period: unknown) => ['dashboard', period] as const,
},
integration: {
config: () => ['integration', 'config'] as const,
},
sync: {
jinshujuRules: () => ['sync', 'jinshuju', 'rules'] as const,
},
archive: {
detail: (studentId: number) => ['archive', studentId] as const,
},
ai: {
config: () => ['ai', 'config'] as const,
},
} as const;
export type QueryKeys = typeof queryKeys;