- useApiRequest 新增 loading 状态,execute 自动管理 loading 生命周期 - usePaginatedData 支持泛型筛选参数 (filters: F),函数重载保持旧签名兼容 - 新增 filters/setFilters 状态,fetchFn 调用时传入当前 filters - 向后兼容:旧调用点无需修改
97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
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>;
|
||
}
|