diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index a31dcc91..e58ddd5b 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -2,5 +2,8 @@ import useBoolean from './use-boolean'; import useLoading from './use-loading'; import useContext from './use-context'; import useSvgIconRender from './use-svg-icon-render'; +import useTable from './use-table'; -export { useBoolean, useLoading, useContext, useSvgIconRender }; +export { useBoolean, useLoading, useContext, useSvgIconRender, useTable }; + +export * from './use-table'; diff --git a/packages/hooks/src/use-table.ts b/packages/hooks/src/use-table.ts new file mode 100644 index 00000000..4539f3cf --- /dev/null +++ b/packages/hooks/src/use-table.ts @@ -0,0 +1,163 @@ +import { computed, reactive, ref } from 'vue'; +import type { Ref } from 'vue'; +import useBoolean from './use-boolean'; +import useLoading from './use-loading'; + +export type MaybePromise = T | Promise; + +export type ApiFn = (args: any) => Promise; + +export type TableData = Record; + +export type TableColumn = Record; + +export type TableColumnCheck = { + key: string; + title: string; + checked: boolean; +}; + +export type TransformedData = { + data: T[]; + pageNum: number; + pageSize: number; + total: number; +}; + +export type Transformer = ( + response: Response +) => TransformedData; + +export type TableConfig< + A extends ApiFn = ApiFn, + T extends TableData = TableData, + C extends TableColumn = TableColumn +> = { + /** api function to get table data */ + apiFn: A; + /** api params */ + apiParams?: Parameters[0]; + /** transform api response to table data */ + transformer: Transformer>>; + /** columns factory */ + columns: () => C[]; + /** + * get column checks + * + * @param columns + */ + getColumnChecks: (columns: C[]) => TableColumnCheck[]; + /** + * get columns + * + * @param columns + */ + getColumns: (columns: C[], checks: TableColumnCheck[]) => C[]; + /** + * callback when response fetched + * + * @param transformed transformed data + */ + onFetched?: (transformed: TransformedData) => MaybePromise; + /** + * whether to get data immediately + * + * @default true + */ + immediate?: boolean; +}; + +export default function useTable< + A extends ApiFn = ApiFn, + T extends TableData = TableData, + C extends TableColumn = TableColumn +>(config: TableConfig) { + const { loading, startLoading, endLoading } = useLoading(); + const { bool: empty, setBool: setEmpty } = useBoolean(); + + const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config; + + const searchParams: NonNullable[0]> = reactive({ ...apiParams }); + + const allColumns = ref(config.columns()) as Ref; + + const data: Ref = ref([]); + + const columnChecks: Ref = ref(getColumnChecks(config.columns())); + + const columns = computed(() => getColumns(allColumns.value, columnChecks.value)); + + function reloadColumns() { + allColumns.value = config.columns(); + + const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked])); + + const defaultChecks = getColumnChecks(allColumns.value); + + columnChecks.value = defaultChecks.map(col => ({ + ...col, + checked: checkMap.get(col.key) ?? col.checked + })); + } + + async function getData() { + startLoading(); + + const formattedParams = formatSearchParams(searchParams); + + const response = await apiFn(formattedParams); + + const transformed = transformer(response as Awaited>); + + data.value = transformed.data; + + setEmpty(transformed.data.length === 0); + + await config.onFetched?.(transformed); + + endLoading(); + } + + function formatSearchParams(params: Record) { + const formattedParams: Record = {}; + + Object.entries(params).forEach(([key, value]) => { + if (value !== null && value !== undefined) { + formattedParams[key] = value; + } + }); + + return formattedParams; + } + + /** + * update search params + * + * @param params + */ + function updateSearchParams(params: Partial[0]>) { + Object.assign(searchParams, params); + } + + /** reset search params */ + function resetSearchParams() { + Object.assign(searchParams, apiParams); + } + + if (immediate) { + getData(); + } + + return { + loading, + empty, + data, + columns, + columnChecks, + reloadColumns, + getData, + searchParams, + updateSearchParams, + resetSearchParams + }; +} diff --git a/src/components/advanced/table-column-setting.vue b/src/components/advanced/table-column-setting.vue index f6a0e482..817dc11e 100644 --- a/src/components/advanced/table-column-setting.vue +++ b/src/components/advanced/table-column-setting.vue @@ -1,13 +1,12 @@ diff --git a/src/components/advanced/table-header-operation.vue b/src/components/advanced/table-header-operation.vue index f27554fe..934fded7 100644 --- a/src/components/advanced/table-header-operation.vue +++ b/src/components/advanced/table-header-operation.vue @@ -1,6 +1,4 @@ + + + +