diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts index 214400b..c568930 100644 --- a/apps/web/src/api/client.ts +++ b/apps/web/src/api/client.ts @@ -34,7 +34,7 @@ const client = axios.create({ data: entry.data, status: 200, statusText: "OK (cached)", - headers: {} as any, + headers: new axios.AxiosHeaders(), config, }); } @@ -203,6 +203,9 @@ function showGlobalError(msg: string) { // 全局错误拦截 — 在响应拦截器之后、组件 catch 之前执行 // 组件可通过 axios config 中设置 skipGlobalError: true 来抑制全局提示 declare module "axios" { + interface AxiosRequestConfig { + skipGlobalError?: boolean; + } interface InternalAxiosRequestConfig { skipGlobalError?: boolean; } diff --git a/apps/web/src/api/health/points.ts b/apps/web/src/api/health/points.ts index 2689d47..8cfd248 100644 --- a/apps/web/src/api/health/points.ts +++ b/apps/web/src/api/health/points.ts @@ -388,7 +388,7 @@ export const pointsApi = { const { data } = await client.get<{ success: boolean; data: PointsStatistics; - }>('/health/admin/points/statistics', { skipGlobalError: opts?.silent } as any); + }>('/health/admin/points/statistics', { skipGlobalError: opts?.silent }); return data.data; }, @@ -398,7 +398,7 @@ export const pointsApi = { const { data } = await client.get<{ success: boolean; data: PatientStatistics; - }>('/health/admin/statistics/patients', { skipGlobalError: opts?.silent } as any); + }>('/health/admin/statistics/patients', { skipGlobalError: opts?.silent }); return data.data; }, @@ -406,7 +406,7 @@ export const pointsApi = { const { data } = await client.get<{ success: boolean; data: ConsultationStatistics; - }>('/health/admin/statistics/consultations', { skipGlobalError: opts?.silent } as any); + }>('/health/admin/statistics/consultations', { skipGlobalError: opts?.silent }); return data.data; }, @@ -414,7 +414,7 @@ export const pointsApi = { const { data } = await client.get<{ success: boolean; data: FollowUpStatistics; - }>('/health/admin/statistics/follow-ups', { skipGlobalError: opts?.silent } as any); + }>('/health/admin/statistics/follow-ups', { skipGlobalError: opts?.silent }); return data.data; }, @@ -422,7 +422,7 @@ export const pointsApi = { const { data } = await client.get<{ success: boolean; data: HealthDataStats; - }>('/health/admin/statistics/health-data', { skipGlobalError: opts?.silent } as any); + }>('/health/admin/statistics/health-data', { skipGlobalError: opts?.silent }); return data.data; }, @@ -430,7 +430,7 @@ export const pointsApi = { const { data } = await client.get<{ success: boolean; data: DialysisStatistics; - }>('/health/admin/statistics/dialysis', { skipGlobalError: opts?.silent } as any); + }>('/health/admin/statistics/dialysis', { skipGlobalError: opts?.silent }); return data.data; }, @@ -438,7 +438,7 @@ export const pointsApi = { const { data } = await client.get<{ success: boolean; data: PersonalStats; - }>('/health/admin/statistics/personal-stats', { skipGlobalError: opts?.silent } as any); + }>('/health/admin/statistics/personal-stats', { skipGlobalError: opts?.silent }); return data.data; }, }; diff --git a/apps/web/src/hooks/usePaginatedData.ts b/apps/web/src/hooks/usePaginatedData.ts index c6efc09..9c47ca7 100644 --- a/apps/web/src/hooks/usePaginatedData.ts +++ b/apps/web/src/hooks/usePaginatedData.ts @@ -8,6 +8,9 @@ interface PaginatedState { loading: boolean; } +type FetchResult = { data: T[]; total: number }; +type OptionsConfig = { pageSize?: number; defaultFilters: F; autoFetch?: boolean }; + /** * 通用分页数据 Hook,封装 data / total / page / loading / fetch 逻辑。 * @@ -19,27 +22,30 @@ interface PaginatedState { // 重载签名 export function usePaginatedData( - fetchFn: (page: number, pageSize: number, filters: F) => Promise<{ data: T[]; total: number }>, - options: { pageSize?: number; defaultFilters: F; autoFetch?: boolean }, + fetchFn: (page: number, pageSize: number, filters: F) => Promise>, + options: OptionsConfig, ): PaginatedResult; export function usePaginatedData( fetchFn: - | ((page: number, pageSize: number, search: string) => Promise<{ data: T[]; total: number }>) - | ((page: number, pageSize: number) => Promise<{ data: T[]; total: number }>), + | ((page: number, pageSize: number, search: string) => Promise>) + | ((page: number, pageSize: number) => Promise>), pageSize?: number, autoFetch?: boolean, ): PaginatedResult; +/* eslint-disable @typescript-eslint/no-explicit-any -- 实现签名必须兼容所有重载 */ export function usePaginatedData( - fetchFn: (...args: any[]) => Promise<{ data: T[]; total: number }>, - pageSizeOrOptions?: number | { pageSize?: number; defaultFilters: F; autoFetch?: boolean }, + fetchFn: (...args: any[]) => Promise>, + pageSizeOrOptions?: number | OptionsConfig, autoFetch = true, ): PaginatedResult { + /* eslint-enable @typescript-eslint/no-explicit-any */ const isOptions = typeof pageSizeOrOptions === 'object' && pageSizeOrOptions !== null; - const pageSize = isOptions ? (pageSizeOrOptions as any).pageSize ?? 20 : (pageSizeOrOptions as number) ?? 20; - const shouldAutoFetch = isOptions ? (pageSizeOrOptions as any).autoFetch ?? true : autoFetch; - const defaultFilters = isOptions ? (pageSizeOrOptions as any).defaultFilters : ('' as unknown as F); + const options = pageSizeOrOptions as OptionsConfig; + const pageSize = isOptions ? options.pageSize ?? 20 : (pageSizeOrOptions as number) ?? 20; + const shouldAutoFetch = isOptions ? options.autoFetch ?? true : autoFetch; + const defaultFilters = isOptions ? options.defaultFilters : ('' as unknown as F); const [state, setState] = useState>({ data: [], @@ -50,6 +56,7 @@ export function usePaginatedData( const [searchText, setSearchText] = useState(''); const [filters, setFilters] = useState(defaultFilters); + const fetchFnRef = useRef(fetchFn); fetchFnRef.current = fetchFn; @@ -61,13 +68,14 @@ export function usePaginatedData( const stateRef = useRef(state); stateRef.current = state; + const refresh = useCallback( async (p?: number) => { const targetPage = p ?? stateRef.current.page; setState((s) => ({ ...s, loading: true })); try { - const result = await (fetchFnRef.current as any)( + const result = await fetchFnRef.current( targetPage, pageSize, filtersRef.current ?? searchTextRef.current, @@ -83,6 +91,7 @@ export function usePaginatedData( useEffect(() => { if (shouldAutoFetch) { + refresh(1); } }, [shouldAutoFetch, refresh]); @@ -95,8 +104,10 @@ export function usePaginatedData( return; } if (shouldAutoFetch) { + refresh(1); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [filters]); return { ...state, searchText, setSearchText, filters, setFilters, refresh }; diff --git a/apps/web/src/pages/health/MediaLibrary.tsx b/apps/web/src/pages/health/MediaLibrary.tsx index 5c7ceba..87d9f44 100644 --- a/apps/web/src/pages/health/MediaLibrary.tsx +++ b/apps/web/src/pages/health/MediaLibrary.tsx @@ -194,9 +194,9 @@ export default function MediaLibrary() {
{foldersLoading ?
: ( - { + titleRender={(node: TreeNode) => { if (node.id === '__all__') return {node.name}; const matched = folders.find((f) => f.id === node.id); return ( diff --git a/apps/web/src/pages/health/articleEditor/ArticleEditor.tsx b/apps/web/src/pages/health/articleEditor/ArticleEditor.tsx index 2055c68..19b553b 100644 --- a/apps/web/src/pages/health/articleEditor/ArticleEditor.tsx +++ b/apps/web/src/pages/health/articleEditor/ArticleEditor.tsx @@ -200,7 +200,8 @@ export default function ArticleEditor() { style: el.getAttribute('style') || '', innerHtml: el.innerHTML, children: [{ text: '' }], - } as any); // eslint-disable-line @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- wangEditor custom element type not in Slate types + } as any); } else { editor.dangerouslyInsertHtml(html); }