mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-23 20:06:37 +08:00
refactor(hooks)!: refactor useTable and enhance type definitions
This commit is contained in:
parent
3a343eea33
commit
8cc5177cda
@ -3,9 +3,9 @@ import useLoading from './use-loading';
|
|||||||
import useCountDown from './use-count-down';
|
import useCountDown from './use-count-down';
|
||||||
import useContext from './use-context';
|
import useContext from './use-context';
|
||||||
import useSvgIconRender from './use-svg-icon-render';
|
import useSvgIconRender from './use-svg-icon-render';
|
||||||
import useHookTable from './use-table';
|
import useTable from './use-table';
|
||||||
|
|
||||||
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable };
|
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useTable };
|
||||||
|
|
||||||
export * from './use-signal';
|
export * from './use-signal';
|
||||||
export * from './use-table';
|
export type * from './use-table';
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
import { computed, reactive, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import type { Ref, VNodeChild } from 'vue';
|
import type { Ref, VNodeChild } from 'vue';
|
||||||
import { jsonClone } from '@sa/utils';
|
|
||||||
import useBoolean from './use-boolean';
|
import useBoolean from './use-boolean';
|
||||||
import useLoading from './use-loading';
|
import useLoading from './use-loading';
|
||||||
|
|
||||||
export type MaybePromise<T> = T | Promise<T>;
|
export interface PaginationData<T> {
|
||||||
|
data: T[];
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type ApiFn = (args: any) => Promise<unknown>;
|
type GetApiData<ApiData, Pagination extends boolean> = Pagination extends true ? PaginationData<ApiData> : ApiData[];
|
||||||
|
|
||||||
|
type Transform<ResponseData, ApiData, Pagination extends boolean> = (
|
||||||
|
response: ResponseData
|
||||||
|
) => GetApiData<ApiData, Pagination>;
|
||||||
|
|
||||||
export type TableColumnCheckTitle = string | ((...args: any) => VNodeChild);
|
export type TableColumnCheckTitle = string | ((...args: any) => VNodeChild);
|
||||||
|
|
||||||
@ -14,72 +22,64 @@ export type TableColumnCheck = {
|
|||||||
key: string;
|
key: string;
|
||||||
title: TableColumnCheckTitle;
|
title: TableColumnCheckTitle;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
|
visible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TableDataWithIndex<T> = T & { index: number };
|
export interface UseTableOptions<ResponseData, ApiData, Column, Pagination extends boolean> {
|
||||||
|
/**
|
||||||
export type TransformedData<T> = {
|
* api function to get table data
|
||||||
data: TableDataWithIndex<T>[];
|
*/
|
||||||
pageNum: number;
|
api: () => Promise<ResponseData>;
|
||||||
pageSize: number;
|
/**
|
||||||
total: number;
|
* whether to enable pagination
|
||||||
};
|
*/
|
||||||
|
pagination?: Pagination;
|
||||||
export type Transformer<T, Response> = (response: Response) => TransformedData<T>;
|
/**
|
||||||
|
* transform api response to table data
|
||||||
export type TableConfig<A extends ApiFn, T, C> = {
|
*/
|
||||||
/** api function to get table data */
|
transform: Transform<ResponseData, ApiData, Pagination>;
|
||||||
apiFn: A;
|
/**
|
||||||
/** api params */
|
* columns factory
|
||||||
apiParams?: Parameters<A>[0];
|
*/
|
||||||
/** transform api response to table data */
|
columns: () => Column[];
|
||||||
transformer: Transformer<T, Awaited<ReturnType<A>>>;
|
|
||||||
/** columns factory */
|
|
||||||
columns: () => C[];
|
|
||||||
/**
|
/**
|
||||||
* get column checks
|
* get column checks
|
||||||
*
|
|
||||||
* @param columns
|
|
||||||
*/
|
*/
|
||||||
getColumnChecks: (columns: C[]) => TableColumnCheck[];
|
getColumnChecks: (columns: Column[]) => TableColumnCheck[];
|
||||||
/**
|
/**
|
||||||
* get columns
|
* get columns
|
||||||
*
|
|
||||||
* @param columns
|
|
||||||
*/
|
*/
|
||||||
getColumns: (columns: C[], checks: TableColumnCheck[]) => C[];
|
getColumns: (columns: Column[], checks: TableColumnCheck[]) => Column[];
|
||||||
/**
|
/**
|
||||||
* callback when response fetched
|
* callback when response fetched
|
||||||
*
|
|
||||||
* @param transformed transformed data
|
|
||||||
*/
|
*/
|
||||||
onFetched?: (transformed: TransformedData<T>) => MaybePromise<void>;
|
onFetched?: (data: GetApiData<ApiData, Pagination>) => void | Promise<void>;
|
||||||
/**
|
/**
|
||||||
* whether to get data immediately
|
* whether to get data immediately
|
||||||
*
|
*
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
immediate?: boolean;
|
immediate?: boolean;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<A, T, C>) {
|
export default function useTable<ResponseData, ApiData, Column, Pagination extends boolean>(
|
||||||
|
options: UseTableOptions<ResponseData, ApiData, Column, Pagination>
|
||||||
|
) {
|
||||||
const { loading, startLoading, endLoading } = useLoading();
|
const { loading, startLoading, endLoading } = useLoading();
|
||||||
const { bool: empty, setBool: setEmpty } = useBoolean();
|
const { bool: empty, setBool: setEmpty } = useBoolean();
|
||||||
|
|
||||||
const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config;
|
const { api, pagination, transform, columns, getColumnChecks, getColumns, onFetched, immediate = true } = options;
|
||||||
|
|
||||||
const searchParams: NonNullable<Parameters<A>[0]> = reactive(jsonClone({ ...apiParams }));
|
const data = ref([]) as Ref<ApiData[]>;
|
||||||
|
|
||||||
const allColumns = ref(config.columns()) as Ref<C[]>;
|
const allColumns = ref(columns()) as Ref<Column[]>;
|
||||||
|
|
||||||
const data: Ref<TableDataWithIndex<T>[]> = ref([]);
|
const columnChecks = ref(getColumnChecks(columns())) as Ref<TableColumnCheck[]>;
|
||||||
|
|
||||||
const columnChecks: Ref<TableColumnCheck[]> = ref(getColumnChecks(config.columns()));
|
const $columns = computed(() => getColumns(columns(), columnChecks.value));
|
||||||
|
|
||||||
const columns = computed(() => getColumns(allColumns.value, columnChecks.value));
|
|
||||||
|
|
||||||
function reloadColumns() {
|
function reloadColumns() {
|
||||||
allColumns.value = config.columns();
|
allColumns.value = columns();
|
||||||
|
|
||||||
const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked]));
|
const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked]));
|
||||||
|
|
||||||
@ -92,47 +92,21 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getData() {
|
async function getData() {
|
||||||
startLoading();
|
try {
|
||||||
|
startLoading();
|
||||||
|
|
||||||
const formattedParams = formatSearchParams(searchParams);
|
const response = await api();
|
||||||
|
|
||||||
const response = await apiFn(formattedParams);
|
const transformed = transform(response);
|
||||||
|
|
||||||
const transformed = transformer(response as Awaited<ReturnType<A>>);
|
data.value = getTableData(transformed, pagination);
|
||||||
|
|
||||||
data.value = transformed.data;
|
setEmpty(data.value.length === 0);
|
||||||
|
|
||||||
setEmpty(transformed.data.length === 0);
|
await onFetched?.(transformed);
|
||||||
|
} finally {
|
||||||
await config.onFetched?.(transformed);
|
endLoading();
|
||||||
|
}
|
||||||
endLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatSearchParams(params: Record<string, unknown>) {
|
|
||||||
const formattedParams: Record<string, unknown> = {};
|
|
||||||
|
|
||||||
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<Parameters<A>[0]>) {
|
|
||||||
Object.assign(searchParams, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** reset search params */
|
|
||||||
function resetSearchParams() {
|
|
||||||
Object.assign(searchParams, jsonClone(apiParams));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (immediate) {
|
if (immediate) {
|
||||||
@ -143,12 +117,20 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
|
|||||||
loading,
|
loading,
|
||||||
empty,
|
empty,
|
||||||
data,
|
data,
|
||||||
columns,
|
columns: $columns,
|
||||||
columnChecks,
|
columnChecks,
|
||||||
reloadColumns,
|
reloadColumns,
|
||||||
getData,
|
getData
|
||||||
searchParams,
|
|
||||||
updateSearchParams,
|
|
||||||
resetSearchParams
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTableData<ApiData, Pagination extends boolean>(
|
||||||
|
data: GetApiData<ApiData, Pagination>,
|
||||||
|
pagination?: Pagination
|
||||||
|
) {
|
||||||
|
if (pagination) {
|
||||||
|
return (data as PaginationData<ApiData>).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data as ApiData[];
|
||||||
|
}
|
||||||
|
@ -1,192 +1,48 @@
|
|||||||
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';
|
import { computed, effectScope, onScopeDispose, reactive, shallowRef, watch } from 'vue';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import type { PaginationProps } from 'naive-ui';
|
import type { PaginationProps } from 'naive-ui';
|
||||||
|
import { useBoolean, useTable } from '@sa/hooks';
|
||||||
|
import type { PaginationData, TableColumnCheck, UseTableOptions } from '@sa/hooks';
|
||||||
|
import type { FlatResponseData } from '@sa/axios';
|
||||||
import { jsonClone } from '@sa/utils';
|
import { jsonClone } from '@sa/utils';
|
||||||
import { useBoolean, useHookTable } from '@sa/hooks';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
|
|
||||||
type TableData = NaiveUI.TableData;
|
export type UseNaiveTableOptions<ResponseData, ApiData, Pagination extends boolean> = Omit<
|
||||||
type GetTableData<A extends NaiveUI.TableApiFn> = NaiveUI.GetTableData<A>;
|
UseTableOptions<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, Pagination>,
|
||||||
type TableColumn<T> = NaiveUI.TableColumn<T>;
|
'pagination' | 'getColumnChecks' | 'getColumns'
|
||||||
|
> & {
|
||||||
|
/**
|
||||||
|
* get column visible
|
||||||
|
*
|
||||||
|
* @param column
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*
|
||||||
|
* @returns true if the column is visible, false otherwise
|
||||||
|
*/
|
||||||
|
getColumnVisible?: (column: NaiveUI.TableColumn<ApiData>) => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
|
const SELECTION_KEY = '__selection__';
|
||||||
|
|
||||||
|
const EXPAND_KEY = '__expand__';
|
||||||
|
|
||||||
|
export function useNaiveTable<ResponseData, ApiData>(options: UseNaiveTableOptions<ResponseData, ApiData, false>) {
|
||||||
const scope = effectScope();
|
const scope = effectScope();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
const isMobile = computed(() => appStore.isMobile);
|
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, false>({
|
||||||
|
...options,
|
||||||
const { apiFn, apiParams, immediate, showTotal } = config;
|
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
|
||||||
|
getColumns
|
||||||
const SELECTION_KEY = '__selection__';
|
|
||||||
|
|
||||||
const EXPAND_KEY = '__expand__';
|
|
||||||
|
|
||||||
const {
|
|
||||||
loading,
|
|
||||||
empty,
|
|
||||||
data,
|
|
||||||
columns,
|
|
||||||
columnChecks,
|
|
||||||
reloadColumns,
|
|
||||||
getData,
|
|
||||||
searchParams,
|
|
||||||
updateSearchParams,
|
|
||||||
resetSearchParams
|
|
||||||
} = useHookTable<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>({
|
|
||||||
apiFn,
|
|
||||||
apiParams,
|
|
||||||
columns: config.columns,
|
|
||||||
transformer: res => {
|
|
||||||
const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
|
|
||||||
|
|
||||||
// Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors.
|
|
||||||
const pageSize = size <= 0 ? 10 : size;
|
|
||||||
|
|
||||||
const recordsWithIndex = records.map((item: GetTableData<A>, index: number) => {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
index: (current - 1) * pageSize + index + 1
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: recordsWithIndex,
|
|
||||||
pageNum: current,
|
|
||||||
pageSize,
|
|
||||||
total
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getColumnChecks: cols => {
|
|
||||||
const checks: NaiveUI.TableColumnCheck[] = [];
|
|
||||||
|
|
||||||
cols.forEach(column => {
|
|
||||||
if (isTableColumnHasKey(column)) {
|
|
||||||
checks.push({
|
|
||||||
key: column.key as string,
|
|
||||||
title: column.title!,
|
|
||||||
checked: true
|
|
||||||
});
|
|
||||||
} else if (column.type === 'selection') {
|
|
||||||
checks.push({
|
|
||||||
key: SELECTION_KEY,
|
|
||||||
title: $t('common.check'),
|
|
||||||
checked: true
|
|
||||||
});
|
|
||||||
} else if (column.type === 'expand') {
|
|
||||||
checks.push({
|
|
||||||
key: EXPAND_KEY,
|
|
||||||
title: $t('common.expandColumn'),
|
|
||||||
checked: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return checks;
|
|
||||||
},
|
|
||||||
getColumns: (cols, checks) => {
|
|
||||||
const columnMap = new Map<string, TableColumn<GetTableData<A>>>();
|
|
||||||
|
|
||||||
cols.forEach(column => {
|
|
||||||
if (isTableColumnHasKey(column)) {
|
|
||||||
columnMap.set(column.key as string, column);
|
|
||||||
} else if (column.type === 'selection') {
|
|
||||||
columnMap.set(SELECTION_KEY, column);
|
|
||||||
} else if (column.type === 'expand') {
|
|
||||||
columnMap.set(EXPAND_KEY, column);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredColumns = checks
|
|
||||||
.filter(item => item.checked)
|
|
||||||
.map(check => columnMap.get(check.key) as TableColumn<GetTableData<A>>);
|
|
||||||
|
|
||||||
return filteredColumns;
|
|
||||||
},
|
|
||||||
onFetched: async transformed => {
|
|
||||||
const { pageNum, pageSize, total } = transformed;
|
|
||||||
|
|
||||||
updatePagination({
|
|
||||||
page: pageNum,
|
|
||||||
pageSize,
|
|
||||||
itemCount: total
|
|
||||||
});
|
|
||||||
},
|
|
||||||
immediate
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const pagination: PaginationProps = reactive({
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
showSizePicker: true,
|
|
||||||
itemCount: 0,
|
|
||||||
pageSizes: [10, 15, 20, 25, 30],
|
|
||||||
onUpdatePage: async (page: number) => {
|
|
||||||
pagination.page = page;
|
|
||||||
|
|
||||||
updateSearchParams({
|
|
||||||
current: page,
|
|
||||||
size: pagination.pageSize!
|
|
||||||
});
|
|
||||||
|
|
||||||
getData();
|
|
||||||
},
|
|
||||||
onUpdatePageSize: async (pageSize: number) => {
|
|
||||||
pagination.pageSize = pageSize;
|
|
||||||
pagination.page = 1;
|
|
||||||
|
|
||||||
updateSearchParams({
|
|
||||||
current: pagination.page,
|
|
||||||
size: pageSize
|
|
||||||
});
|
|
||||||
|
|
||||||
getData();
|
|
||||||
},
|
|
||||||
...(showTotal
|
|
||||||
? {
|
|
||||||
prefix: page => $t('datatable.itemCount', { total: page.itemCount })
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
});
|
|
||||||
|
|
||||||
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
|
|
||||||
const mobilePagination = computed(() => {
|
|
||||||
const p: PaginationProps = {
|
|
||||||
...pagination,
|
|
||||||
pageSlot: isMobile.value ? 3 : 9,
|
|
||||||
prefix: !isMobile.value && showTotal ? pagination.prefix : undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
return p;
|
|
||||||
});
|
|
||||||
|
|
||||||
function updatePagination(update: Partial<PaginationProps>) {
|
|
||||||
Object.assign(pagination, update);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get data by page number
|
|
||||||
*
|
|
||||||
* @param pageNum the page number. default is 1
|
|
||||||
*/
|
|
||||||
async function getDataByPage(pageNum: number = 1) {
|
|
||||||
updatePagination({
|
|
||||||
page: pageNum
|
|
||||||
});
|
|
||||||
|
|
||||||
updateSearchParams({
|
|
||||||
current: pageNum,
|
|
||||||
size: pagination.pageSize!
|
|
||||||
});
|
|
||||||
|
|
||||||
await getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.run(() => {
|
scope.run(() => {
|
||||||
watch(
|
watch(
|
||||||
() => appStore.locale,
|
() => appStore.locale,
|
||||||
() => {
|
() => {
|
||||||
reloadColumns();
|
result.reloadColumns();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -195,28 +51,124 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
|||||||
scope.stop();
|
scope.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaginationParams = Pick<PaginationProps, 'page' | 'pageSize'>;
|
||||||
|
|
||||||
|
type UseNaivePaginatedTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, true> & {
|
||||||
|
paginationProps?: Omit<PaginationProps, 'page' | 'pageSize' | 'itemCount'>;
|
||||||
|
/**
|
||||||
|
* whether to show the total count of the table
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
showTotal?: boolean;
|
||||||
|
onPaginationParamsChange?: (params: PaginationParams) => void | Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useNaivePaginatedTable<ResponseData, ApiData>(
|
||||||
|
options: UseNaivePaginatedTableOptions<ResponseData, ApiData>
|
||||||
|
) {
|
||||||
|
const scope = effectScope();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
const isMobile = computed(() => appStore.isMobile);
|
||||||
|
|
||||||
|
const showTotal = computed(() => options.showTotal ?? true);
|
||||||
|
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
itemCount: 0,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 15, 20, 25, 30],
|
||||||
|
prefix: showTotal.value ? page => $t('datatable.itemCount', { total: page.itemCount }) : undefined,
|
||||||
|
onUpdatePage(page) {
|
||||||
|
pagination.page = page;
|
||||||
|
},
|
||||||
|
onUpdatePageSize(pageSize) {
|
||||||
|
pagination.pageSize = pageSize;
|
||||||
|
pagination.page = 1;
|
||||||
|
},
|
||||||
|
...options.paginationProps
|
||||||
|
}) as PaginationProps;
|
||||||
|
|
||||||
|
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
|
||||||
|
const mobilePagination = computed(() => {
|
||||||
|
const p: PaginationProps = {
|
||||||
|
...pagination,
|
||||||
|
pageSlot: isMobile.value ? 3 : 9,
|
||||||
|
prefix: !isMobile.value && showTotal.value ? pagination.prefix : undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
|
||||||
|
const paginationParams = computed(() => {
|
||||||
|
const { page, pageSize } = pagination;
|
||||||
|
|
||||||
|
return {
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, true>({
|
||||||
|
...options,
|
||||||
|
pagination: true,
|
||||||
|
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
|
||||||
|
getColumns,
|
||||||
|
onFetched: data => {
|
||||||
|
pagination.itemCount = data.total;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getDataByPage(page: number = 1) {
|
||||||
|
if (page !== pagination.page) {
|
||||||
|
pagination.page = page;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await result.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.run(() => {
|
||||||
|
watch(
|
||||||
|
() => appStore.locale,
|
||||||
|
() => {
|
||||||
|
result.reloadColumns();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(paginationParams, async newVal => {
|
||||||
|
await options.onPaginationParamsChange?.(newVal);
|
||||||
|
|
||||||
|
await result.getData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onScopeDispose(() => {
|
||||||
|
scope.stop();
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
...result,
|
||||||
empty,
|
|
||||||
data,
|
|
||||||
columns,
|
|
||||||
columnChecks,
|
|
||||||
reloadColumns,
|
|
||||||
pagination,
|
|
||||||
mobilePagination,
|
|
||||||
updatePagination,
|
|
||||||
getData,
|
|
||||||
getDataByPage,
|
getDataByPage,
|
||||||
searchParams,
|
pagination,
|
||||||
updateSearchParams,
|
mobilePagination
|
||||||
resetSearchParams
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void>) {
|
export function useTableOperate<TableData>(
|
||||||
|
data: Ref<TableData[]>,
|
||||||
|
idKey: keyof TableData,
|
||||||
|
getData: () => Promise<void>
|
||||||
|
) {
|
||||||
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
|
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean();
|
||||||
|
|
||||||
const operateType = ref<NaiveUI.TableOperateType>('add');
|
const operateType = shallowRef<NaiveUI.TableOperateType>('add');
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
operateType.value = 'add';
|
operateType.value = 'add';
|
||||||
@ -224,18 +176,18 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** the editing row data */
|
/** the editing row data */
|
||||||
const editingData: Ref<T | null> = ref(null);
|
const editingData = shallowRef<TableData | null>(null);
|
||||||
|
|
||||||
function handleEdit(id: T['id']) {
|
function handleEdit(id: TableData[keyof TableData]) {
|
||||||
operateType.value = 'edit';
|
operateType.value = 'edit';
|
||||||
const findItem = data.value.find(item => item.id === id) || null;
|
const findItem = data.value.find(item => item[idKey] === id) || null;
|
||||||
editingData.value = jsonClone(findItem);
|
editingData.value = jsonClone(findItem);
|
||||||
|
|
||||||
openDrawer();
|
openDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** the checked row keys of table */
|
/** the checked row keys of table */
|
||||||
const checkedRowKeys = ref<string[]>([]);
|
const checkedRowKeys = shallowRef<string[]>([]);
|
||||||
|
|
||||||
/** the hook after the batch delete operation is completed */
|
/** the hook after the batch delete operation is completed */
|
||||||
async function onBatchDeleted() {
|
async function onBatchDeleted() {
|
||||||
@ -267,6 +219,84 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTableColumnHasKey<T>(column: TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
|
export function defaultTransform<ApiData>(
|
||||||
|
response: FlatResponseData<any, Api.Common.PaginatingQueryRecord<ApiData>>
|
||||||
|
): PaginationData<ApiData> {
|
||||||
|
const { data, error } = response;
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
const { records, current, size, total } = data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: records,
|
||||||
|
pageNum: current,
|
||||||
|
pageSize: size,
|
||||||
|
total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumnChecks<Column extends NaiveUI.TableColumn<any>>(
|
||||||
|
cols: Column[],
|
||||||
|
getColumnVisible?: (column: Column) => boolean
|
||||||
|
) {
|
||||||
|
const checks: TableColumnCheck[] = [];
|
||||||
|
|
||||||
|
cols.forEach(column => {
|
||||||
|
const visible = getColumnVisible?.(column) ?? true;
|
||||||
|
|
||||||
|
if (isTableColumnHasKey(column)) {
|
||||||
|
checks.push({
|
||||||
|
key: column.key as string,
|
||||||
|
title: column.title!,
|
||||||
|
checked: true,
|
||||||
|
visible
|
||||||
|
});
|
||||||
|
} else if (column.type === 'selection') {
|
||||||
|
checks.push({
|
||||||
|
key: SELECTION_KEY,
|
||||||
|
title: $t('common.check'),
|
||||||
|
checked: true,
|
||||||
|
visible
|
||||||
|
});
|
||||||
|
} else if (column.type === 'expand') {
|
||||||
|
checks.push({
|
||||||
|
key: EXPAND_KEY,
|
||||||
|
title: $t('common.expandColumn'),
|
||||||
|
checked: true,
|
||||||
|
visible
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return checks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumns<Column extends NaiveUI.TableColumn<any>>(cols: Column[], checks: TableColumnCheck[]) {
|
||||||
|
const columnMap = new Map<string, Column>();
|
||||||
|
|
||||||
|
cols.forEach(column => {
|
||||||
|
if (isTableColumnHasKey(column)) {
|
||||||
|
columnMap.set(column.key as string, column);
|
||||||
|
} else if (column.type === 'selection') {
|
||||||
|
columnMap.set(SELECTION_KEY, column);
|
||||||
|
} else if (column.type === 'expand') {
|
||||||
|
columnMap.set(EXPAND_KEY, column);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredColumns = checks.filter(item => item.checked).map(check => columnMap.get(check.key) as Column);
|
||||||
|
|
||||||
|
return filteredColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTableColumnHasKey<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
|
||||||
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
|
return Boolean((column as NaiveUI.TableColumnWithKey<T>).key);
|
||||||
}
|
}
|
||||||
|
32
src/typings/naive-ui.d.ts
vendored
32
src/typings/naive-ui.d.ts
vendored
@ -6,30 +6,14 @@ declare namespace NaiveUI {
|
|||||||
type DataTableExpandColumn<T> = import('naive-ui').DataTableExpandColumn<T>;
|
type DataTableExpandColumn<T> = import('naive-ui').DataTableExpandColumn<T>;
|
||||||
type DataTableSelectionColumn<T> = import('naive-ui').DataTableSelectionColumn<T>;
|
type DataTableSelectionColumn<T> = import('naive-ui').DataTableSelectionColumn<T>;
|
||||||
type TableColumnGroup<T> = import('naive-ui/es/data-table/src/interface').TableColumnGroup<T>;
|
type TableColumnGroup<T> = import('naive-ui/es/data-table/src/interface').TableColumnGroup<T>;
|
||||||
type PaginationProps = import('naive-ui').PaginationProps;
|
|
||||||
type TableColumnCheck = import('@sa/hooks').TableColumnCheck;
|
type TableColumnCheck = import('@sa/hooks').TableColumnCheck;
|
||||||
type TableDataWithIndex<T> = import('@sa/hooks').TableDataWithIndex<T>;
|
|
||||||
type FlatResponseData<T> = import('@sa/axios').FlatResponseData<T>;
|
|
||||||
|
|
||||||
/**
|
type SetTableColumnKey<C, T> = Omit<C, 'key'> & { key: keyof T | (string & {}) };
|
||||||
* the custom column key
|
|
||||||
*
|
|
||||||
* if you want to add a custom column, you should add a key to this type
|
|
||||||
*/
|
|
||||||
type CustomColumnKey = 'operate';
|
|
||||||
|
|
||||||
type SetTableColumnKey<C, T> = Omit<C, 'key'> & { key: keyof T | CustomColumnKey };
|
|
||||||
|
|
||||||
type TableData = Api.Common.CommonRecord<object>;
|
|
||||||
|
|
||||||
type TableColumnWithKey<T> = SetTableColumnKey<DataTableBaseColumn<T>, T> | SetTableColumnKey<TableColumnGroup<T>, T>;
|
type TableColumnWithKey<T> = SetTableColumnKey<DataTableBaseColumn<T>, T> | SetTableColumnKey<TableColumnGroup<T>, T>;
|
||||||
|
|
||||||
type TableColumn<T> = TableColumnWithKey<T> | DataTableSelectionColumn<T> | DataTableExpandColumn<T>;
|
type TableColumn<T> = TableColumnWithKey<T> | DataTableSelectionColumn<T> | DataTableExpandColumn<T>;
|
||||||
|
|
||||||
type TableApiFn<T = any, R = Api.Common.CommonSearchParams> = (
|
|
||||||
params: R
|
|
||||||
) => Promise<FlatResponseData<Api.Common.PaginatingQueryRecord<T>>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the type of table operation
|
* the type of table operation
|
||||||
*
|
*
|
||||||
@ -37,18 +21,4 @@ declare namespace NaiveUI {
|
|||||||
* - edit: edit table item
|
* - edit: edit table item
|
||||||
*/
|
*/
|
||||||
type TableOperateType = 'add' | 'edit';
|
type TableOperateType = 'add' | 'edit';
|
||||||
|
|
||||||
type GetTableData<A extends TableApiFn> = A extends TableApiFn<infer T> ? T : never;
|
|
||||||
|
|
||||||
type NaiveTableConfig<A extends TableApiFn> = Pick<
|
|
||||||
import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<TableDataWithIndex<GetTableData<A>>>>,
|
|
||||||
'apiFn' | 'apiParams' | 'columns' | 'immediate'
|
|
||||||
> & {
|
|
||||||
/**
|
|
||||||
* whether to display the total items count
|
|
||||||
*
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
showTotal?: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user