Files
hms/apps/web/src/hooks/usePaginatedData.ts
iven fdceed7284 feat(web): useApiRequest 添加 loading + usePaginatedData 泛型筛选
- useApiRequest 新增 loading 状态,execute 自动管理 loading 生命周期
- usePaginatedData 支持泛型筛选参数 (filters: F),函数重载保持旧签名兼容
- 新增 filters/setFilters 状态,fetchFn 调用时传入当前 filters
- 向后兼容:旧调用点无需修改
2026-04-27 20:26:00 +08:00

97 lines
3.2 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.
import { useState, useCallback, useRef, useEffect } from 'react';
import { message } from 'antd';
interface PaginatedState<T> {
data: T[];
total: number;
page: number;
loading: boolean;
}
/**
* 通用分页数据 Hook封装 data / total / page / loading / fetch 逻辑。
*
* 支持三种签名:
* 1. 泛型筛选 (page, pageSize, filters: F) — 带结构化筛选的列表页
* 2. 三参数 (page, pageSize, search: string) — 带搜索的列表页
* 3. 两参数 (page, pageSize) — 纯分页,不含搜索
*/
// 重载签名
export function usePaginatedData<T, F>(
fetchFn: (page: number, pageSize: number, filters: F) => Promise<{ data: T[]; total: number }>,
options: { pageSize?: number; defaultFilters: F; autoFetch?: boolean },
): PaginatedResult<T, F>;
export function usePaginatedData<T>(
fetchFn:
| ((page: number, pageSize: number, search: string) => Promise<{ data: T[]; total: number }>)
| ((page: number, pageSize: number) => Promise<{ data: T[]; total: number }>),
pageSize?: number,
autoFetch?: boolean,
): PaginatedResult<T, string>;
export function usePaginatedData<T, F = string>(
fetchFn: (...args: any[]) => Promise<{ data: T[]; total: number }>,
pageSizeOrOptions?: number | { pageSize?: number; defaultFilters: F; autoFetch?: boolean },
autoFetch = true,
): PaginatedResult<T, F> {
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 [state, setState] = useState<PaginatedState<T>>({
data: [],
total: 0,
page: 1,
loading: false,
});
const [searchText, setSearchText] = useState('');
const [filters, setFilters] = useState<F>(defaultFilters);
const fetchFnRef = useRef(fetchFn);
fetchFnRef.current = fetchFn;
const searchTextRef = useRef(searchText);
searchTextRef.current = searchText;
const filtersRef = useRef(filters);
filtersRef.current = filters;
const refresh = useCallback(
async (p?: number) => {
const targetPage = p ?? state.page;
setState((s) => ({ ...s, loading: true }));
try {
const result = await (fetchFnRef.current as any)(
targetPage,
pageSize,
filtersRef.current ?? searchTextRef.current,
);
setState({ data: result.data, total: result.total, page: targetPage, loading: false });
} catch {
message.error('加载数据失败');
setState((s) => ({ ...s, loading: false }));
}
},
[pageSize, state.page],
);
useEffect(() => {
if (shouldAutoFetch) {
refresh(1);
}
}, [shouldAutoFetch, refresh]);
return { ...state, searchText, setSearchText, filters, setFilters, refresh };
}
interface PaginatedResult<T, F> extends PaginatedState<T> {
searchText: string;
setSearchText: (text: string) => void;
filters: F;
setFilters: (filters: F | ((prev: F) => F)) => void;
refresh: (page?: number) => Promise<void>;
}