diff --git a/src/hooks/business/use-hook-table.ts b/src/hooks/business/use-hook-table.ts new file mode 100644 index 000000000..d394e7a02 --- /dev/null +++ b/src/hooks/business/use-hook-table.ts @@ -0,0 +1,170 @@ +import { ref, reactive } from 'vue'; +import type { Ref } from 'vue'; +import type { PaginationProps, DataTableBaseColumn, DataTableSelectionColumn, DataTableExpandColumn } from 'naive-ui'; +import type { TableColumnGroup } from 'naive-ui/es/data-table/src/interface'; +import { useLoadingEmpty } from '../common'; + +/** + * 接口请求函数 + */ +type ApiFn = (args: T) => Promise>; + +/** + * 接口请求函数的参数 + */ +type GetApiFnParameters = T extends (args: infer P) => Promise> + ? P + : never; + +/** + * 接口请求函数的返回值 + */ +type GetApiFnReturnType = T extends (args: P) => Promise> + ? R + : never; + +/** + * 表格接口请求后转换后的数据 + */ +type Transformer = (response: Response) => { + data: TableData[]; + pageNum: number; + pageSize: number; + total: number; +}; + +/** + * 列表接口参数更新 + */ +type ApiParamsUpdater = (params: P) => R; + +/** + * 分页参数 + */ +type PagePropsOfPagination = Pick; + +/** + * 表格的列 + */ +type HookTableColumn> = + | (Omit, 'key'> & { key: keyof T }) + | (Omit, 'key'> & { key: keyof T }) + | DataTableSelectionColumn + | DataTableExpandColumn; + +/** + * 表格配置 + */ +type HookTableConfig = { + /** + * 列表接口参数 + */ + apiParams: GetApiFnParameters; + /** + * 列表接口返回数据转换 + */ + transformer: Transformer>; + /** + * 列表列 + */ + columns: HookTableColumn[]; + /** + * 列表接口参数更新 + * @description 用于更新分页参数, 如果列表接口的参数不包含同名分页参数属性 `page` 和 `pageSize`, 需要通过此函数更新 + * @default p => p + */ + apiParamsUpdater?: ApiParamsUpdater & Partial, GetApiFnParameters>; + /** + * 列表分页参数 + */ + pagination?: PaginationProps; + /** + * 是否立即请求 + * @default true + */ + immediate?: boolean; +}; + +/** + * 通用表格 hook + * @param apiFn 接口请求函数 + * @param config 表格配置 + */ +export default function useHookTable(apiFn: Fn, config: HookTableConfig) { + const { loading, startLoading, endLoading, empty, setEmpty } = useLoadingEmpty(); + + const { apiParams, transformer, apiParamsUpdater = p => p, immediate = true } = config; + + const data: Ref = ref([]); + + function updateData(update: TableData[]) { + data.value = update; + } + + const columns = ref(config.columns) as Ref[]>; + + const requestParams = ref(apiParams) as Ref['apiParams']>; + + function updateRequestParamsByPagination(p: PagePropsOfPagination) { + requestParams.value = apiParamsUpdater({ ...requestParams.value, ...p }); + } + + const pagination = reactive({ + page: 1, + pageSize: 10, + showSizePicker: true, + pageSizes: [10, 15, 20, 25, 30], + onChange: (page: number) => { + pagination.page = page; + + updateRequestParamsByPagination({ page }); + getData(); + }, + onUpdatePageSize: (pageSize: number) => { + pagination.pageSize = pageSize; + pagination.page = 1; + + updateRequestParamsByPagination({ pageSize }); + getData(); + }, + ...config.pagination + }) as PaginationProps; + + function updatePagination(update: Partial) { + Object.assign(pagination, update); + + updateRequestParamsByPagination({ page: pagination.page, pageSize: pagination.pageSize }); + } + + async function getData() { + startLoading(); + + const { data: apiData, error } = await apiFn(requestParams.value); + + if (!error && data) { + const { data: tableData, pageNum, pageSize, total } = transformer(apiData); + + updateData(tableData); + + setEmpty(tableData.length === 0); + + updatePagination({ page: pageNum, pageSize, itemCount: total }); + } + + endLoading(); + } + + if (immediate) { + getData(); + } + + return { + data, + columns, + loading, + empty, + pagination, + getData, + updatePagination + }; +}