Compare commits

..

No commits in common. "8a7cd5934b01ec884c1085048b01fc7513262d32" and "936b834e6267dedfb9870294deac7275d17ce51e" have entirely different histories.

13 changed files with 704 additions and 707 deletions

View File

@ -66,16 +66,16 @@
"tailwind-merge": "3.3.1", "tailwind-merge": "3.3.1",
"vue": "3.5.17", "vue": "3.5.17",
"vue-draggable-plus": "0.6.0", "vue-draggable-plus": "0.6.0",
"vue-i18n": "11.1.10", "vue-i18n": "11.1.9",
"vue-router": "4.5.1" "vue-router": "4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@elegant-router/vue": "0.3.8", "@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.359", "@iconify/json": "2.2.357",
"@sa/scripts": "workspace:*", "@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*", "@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.7.1", "@soybeanjs/eslint-config": "1.7.1",
"@types/node": "24.0.15", "@types/node": "24.0.13",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.3.3", "@unocss/eslint-config": "66.3.3",
"@unocss/preset-icons": "66.3.3", "@unocss/preset-icons": "66.3.3",
@ -95,12 +95,12 @@
"typescript": "5.8.3", "typescript": "5.8.3",
"unplugin-icons": "22.1.0", "unplugin-icons": "22.1.0",
"unplugin-vue-components": "28.8.0", "unplugin-vue-components": "28.8.0",
"vite": "7.0.5", "vite": "7.0.4",
"vite-plugin-progress": "0.0.7", "vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1", "vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "7.7.7", "vite-plugin-vue-devtools": "7.7.7",
"vue-eslint-parser": "10.2.0", "vue-eslint-parser": "10.2.0",
"vue-tsc": "3.0.3" "vue-tsc": "3.0.1"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify", "commit-msg": "pnpm sa git-commit-verify",

View File

@ -116,7 +116,7 @@ export function createRequest<ResponseData, ApiData, State extends Record<string
const responseType = response.config?.responseType || 'json'; const responseType = response.config?.responseType || 'json';
if (responseType === 'json') { if (responseType === 'json') {
return opts.transform(response); return opts.transformBackendResponse(response);
} }
return response.data as MappedType<R, T>; return response.data as MappedType<R, T>;
@ -152,7 +152,7 @@ export function createFlatRequest<ResponseData, ApiData, State extends Record<st
const responseType = response.config?.responseType || 'json'; const responseType = response.config?.responseType || 'json';
if (responseType === 'json') { if (responseType === 'json') {
const data = await opts.transform(response); const data = await opts.transformBackendResponse(response);
return { data, error: null, response }; return { data, error: null, response };
} }

View File

@ -10,21 +10,13 @@ export function createDefaultOptions<
State extends Record<string, unknown> = Record<string, unknown> State extends Record<string, unknown> = Record<string, unknown>
>(options?: Partial<RequestOption<ResponseData, ApiData, State>>) { >(options?: Partial<RequestOption<ResponseData, ApiData, State>>) {
const opts: RequestOption<ResponseData, ApiData, State> = { const opts: RequestOption<ResponseData, ApiData, State> = {
defaultState: {} as State,
transform: async response => response.data as unknown as ApiData,
transformBackendResponse: async response => response.data as unknown as ApiData,
onRequest: async config => config, onRequest: async config => config,
isBackendSuccess: _response => true, isBackendSuccess: _response => true,
onBackendFail: async () => {}, onBackendFail: async () => {},
transformBackendResponse: async response => response.data as unknown as ApiData,
onError: async () => {} onError: async () => {}
}; };
if (options?.transform) {
opts.transform = options.transform;
} else {
opts.transform = options?.transformBackendResponse || opts.transform;
}
Object.assign(opts, options); Object.assign(opts, options);
return opts; return opts;

View File

@ -24,13 +24,6 @@ export interface RequestOption<
* *
* @param response Axios response * @param response Axios response
*/ */
transform: ResponseTransform<AxiosResponse<ResponseData>, ApiData>;
/**
* transform the response data to the api data
*
* @deprecated use `transform` instead, will be removed in the next major version v3
* @param response Axios response
*/
transformBackendResponse: ResponseTransform<AxiosResponse<ResponseData>, ApiData>; transformBackendResponse: ResponseTransform<AxiosResponse<ResponseData>, ApiData>;
/** /**
* The hook before request * The hook before request

View File

@ -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 useTable from './use-table'; import useHookTable from './use-table';
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useTable }; export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable };
export * from './use-signal'; export * from './use-signal';
export type * from './use-table'; export * from './use-table';

View File

@ -1,20 +1,12 @@
import { computed, ref } from 'vue'; import { computed, reactive, 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 interface PaginationData<T> { export type MaybePromise<T> = T | Promise<T>;
data: T[];
pageNum: number;
pageSize: number;
total: number;
}
type GetApiData<ApiData, Pagination extends boolean> = Pagination extends true ? PaginationData<ApiData> : ApiData[]; export type ApiFn = (args: any) => Promise<unknown>;
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);
@ -22,64 +14,72 @@ export type TableColumnCheck = {
key: string; key: string;
title: TableColumnCheckTitle; title: TableColumnCheckTitle;
checked: boolean; checked: boolean;
visible: boolean;
}; };
export interface UseTableOptions<ResponseData, ApiData, Column, Pagination extends boolean> { export type TableDataWithIndex<T> = T & { index: number };
/**
* api function to get table data export type TransformedData<T> = {
*/ data: TableDataWithIndex<T>[];
api: () => Promise<ResponseData>; pageNum: number;
/** pageSize: number;
* whether to enable pagination total: number;
*/ };
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> = {
transform: Transform<ResponseData, ApiData, Pagination>; /** api function to get table data */
/** apiFn: A;
* columns factory /** api params */
*/ apiParams?: Parameters<A>[0];
columns: () => Column[]; /** transform api response to table data */
transformer: Transformer<T, Awaited<ReturnType<A>>>;
/** columns factory */
columns: () => C[];
/** /**
* get column checks * get column checks
*
* @param columns
*/ */
getColumnChecks: (columns: Column[]) => TableColumnCheck[]; getColumnChecks: (columns: C[]) => TableColumnCheck[];
/** /**
* get columns * get columns
*
* @param columns
*/ */
getColumns: (columns: Column[], checks: TableColumnCheck[]) => Column[]; getColumns: (columns: C[], checks: TableColumnCheck[]) => C[];
/** /**
* callback when response fetched * callback when response fetched
*
* @param transformed transformed data
*/ */
onFetched?: (data: GetApiData<ApiData, Pagination>) => void | Promise<void>; onFetched?: (transformed: TransformedData<T>) => MaybePromise<void>;
/** /**
* whether to get data immediately * whether to get data immediately
* *
* @default true * @default true
*/ */
immediate?: boolean; immediate?: boolean;
} };
export default function useTable<ResponseData, ApiData, Column, Pagination extends boolean>( export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<A, T, C>) {
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 { api, pagination, transform, columns, getColumnChecks, getColumns, onFetched, immediate = true } = options; const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config;
const data = ref([]) as Ref<ApiData[]>; const searchParams: NonNullable<Parameters<A>[0]> = reactive(jsonClone({ ...apiParams }));
const allColumns = ref(columns()) as Ref<Column[]>; const allColumns = ref(config.columns()) as Ref<C[]>;
const columnChecks = ref(getColumnChecks(columns())) as Ref<TableColumnCheck[]>; const data: Ref<TableDataWithIndex<T>[]> = ref([]);
const $columns = computed(() => getColumns(columns(), columnChecks.value)); const columnChecks: Ref<TableColumnCheck[]> = ref(getColumnChecks(config.columns()));
const columns = computed(() => getColumns(allColumns.value, columnChecks.value));
function reloadColumns() { function reloadColumns() {
allColumns.value = columns(); allColumns.value = config.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,21 +92,47 @@ export default function useTable<ResponseData, ApiData, Column, Pagination exten
} }
async function getData() { async function getData() {
try {
startLoading(); startLoading();
const response = await api(); const formattedParams = formatSearchParams(searchParams);
const transformed = transform(response); const response = await apiFn(formattedParams);
data.value = getTableData(transformed, pagination); const transformed = transformer(response as Awaited<ReturnType<A>>);
setEmpty(data.value.length === 0); data.value = transformed.data;
setEmpty(transformed.data.length === 0);
await config.onFetched?.(transformed);
await onFetched?.(transformed);
} finally {
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) {
@ -117,20 +143,12 @@ export default function useTable<ResponseData, ApiData, Column, Pagination exten
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[];
}

View File

@ -15,7 +15,7 @@
"devDependencies": { "devDependencies": {
"@soybeanjs/changelog": "0.3.24", "@soybeanjs/changelog": "0.3.24",
"bumpp": "10.2.0", "bumpp": "10.2.0",
"c12": "3.1.0", "c12": "3.0.4",
"cac": "6.7.14", "cac": "6.7.14",
"consola": "3.4.2", "consola": "3.4.2",
"enquirer": "2.4.1", "enquirer": "2.4.1",

File diff suppressed because it is too large Load Diff

View File

@ -113,7 +113,7 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
/** is chart rendered */ /** is chart rendered */
function isRendered() { function isRendered() {
return Boolean(domRef.value && chart.value); return Boolean(domRef.value && chart);
} }
/** /**
@ -122,14 +122,12 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
* @param callback callback function * @param callback callback function
*/ */
async function updateOptions(callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions) { async function updateOptions(callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions) {
if (!isRendered()) return;
const updatedOpts = callback(chartOptions, optionsFactory); const updatedOpts = callback(chartOptions, optionsFactory);
Object.assign(chartOptions, updatedOpts); Object.assign(chartOptions, updatedOpts);
await nextTick();
if (!isRendered()) return;
if (isRendered()) { if (isRendered()) {
chart.value?.clear(); chart.value?.clear();
} }
@ -149,6 +147,8 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
const chartTheme = darkMode.value ? 'dark' : 'light'; const chartTheme = darkMode.value ? 'dark' : 'light';
await nextTick();
chart.value = echarts.init(domRef.value, chartTheme); chart.value = echarts.init(domRef.value, chartTheme);
chart.value?.setOption({ ...chartOptions, backgroundColor: 'transparent' }); chart.value?.setOption({ ...chartOptions, backgroundColor: 'transparent' });
@ -190,16 +190,12 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
// resize chart // resize chart
if (isRendered()) { if (isRendered()) {
resize(); resize();
return;
} }
// render chart // render chart
await render(); await render();
if (chart.value) { await onUpdated?.(chart.value!);
await onUpdated?.(chart.value);
}
} }
scope.run(() => { scope.run(() => {

View File

@ -1,152 +1,194 @@
import { computed, effectScope, onScopeDispose, reactive, shallowRef, watch } from 'vue'; import { computed, effectScope, onScopeDispose, reactive, ref, 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';
export type UseNaiveTableOptions<ResponseData, ApiData, Pagination extends boolean> = Omit< type TableData = NaiveUI.TableData;
UseTableOptions<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, Pagination>, type GetTableData<A extends NaiveUI.TableApiFn> = NaiveUI.GetTableData<A>;
'pagination' | 'getColumnChecks' | 'getColumns' type TableColumn<T> = NaiveUI.TableColumn<T>;
> & {
/**
* get column visible
*
* @param column
*
* @default true
*
* @returns true if the column is visible, false otherwise
*/
getColumnVisible?: (column: NaiveUI.TableColumn<ApiData>) => boolean;
};
const SELECTION_KEY = '__selection__'; export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTableConfig<A>) {
const EXPAND_KEY = '__expand__';
export function useNaiveTable<ResponseData, ApiData>(options: UseNaiveTableOptions<ResponseData, ApiData, false>) {
const scope = effectScope();
const appStore = useAppStore();
const result = useTable<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, false>({
...options,
getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible),
getColumns
});
scope.run(() => {
watch(
() => appStore.locale,
() => {
result.reloadColumns();
}
);
});
onScopeDispose(() => {
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 scope = effectScope();
const appStore = useAppStore(); const appStore = useAppStore();
const isMobile = computed(() => appStore.isMobile); const isMobile = computed(() => appStore.isMobile);
const showTotal = computed(() => options.showTotal ?? true); const { apiFn, apiParams, immediate, showTotal } = config;
const pagination = reactive({ 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, page: 1,
pageSize: 10, pageSize: 10,
itemCount: 0,
showSizePicker: true, showSizePicker: true,
itemCount: 0,
pageSizes: [10, 15, 20, 25, 30], pageSizes: [10, 15, 20, 25, 30],
prefix: showTotal.value ? page => $t('datatable.itemCount', { total: page.itemCount }) : undefined, onUpdatePage: async (page: number) => {
onUpdatePage(page) {
pagination.page = page; pagination.page = page;
updateSearchParams({
current: page,
size: pagination.pageSize!
});
getData();
}, },
onUpdatePageSize(pageSize) { onUpdatePageSize: async (pageSize: number) => {
pagination.pageSize = pageSize; pagination.pageSize = pageSize;
pagination.page = 1; pagination.page = 1;
updateSearchParams({
current: pagination.page,
size: pageSize
});
getData();
}, },
...options.paginationProps ...(showTotal
}) as PaginationProps; ? {
prefix: page => $t('datatable.itemCount', { total: page.itemCount })
}
: {})
});
// this is for mobile, if the system does not support mobile, you can use `pagination` directly // this is for mobile, if the system does not support mobile, you can use `pagination` directly
const mobilePagination = computed(() => { const mobilePagination = computed(() => {
const p: PaginationProps = { const p: PaginationProps = {
...pagination, ...pagination,
pageSlot: isMobile.value ? 3 : 9, pageSlot: isMobile.value ? 3 : 9,
prefix: !isMobile.value && showTotal.value ? pagination.prefix : undefined prefix: !isMobile.value && showTotal ? pagination.prefix : undefined
}; };
return p; return p;
}); });
const paginationParams = computed(() => { function updatePagination(update: Partial<PaginationProps>) {
const { page, pageSize } = pagination; Object.assign(pagination, update);
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(); /**
* 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,
() => { () => {
result.reloadColumns(); reloadColumns();
} }
); );
watch(paginationParams, async newVal => {
await options.onPaginationParamsChange?.(newVal);
await result.getData();
});
}); });
onScopeDispose(() => { onScopeDispose(() => {
@ -154,21 +196,27 @@ export function useNaivePaginatedTable<ResponseData, ApiData>(
}); });
return { return {
...result, loading,
getDataByPage, empty,
data,
columns,
columnChecks,
reloadColumns,
pagination, pagination,
mobilePagination mobilePagination,
updatePagination,
getData,
getDataByPage,
searchParams,
updateSearchParams,
resetSearchParams
}; };
} }
export function useTableOperate<TableData>( export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>, getData: () => Promise<void>) {
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 = shallowRef<NaiveUI.TableOperateType>('add'); const operateType = ref<NaiveUI.TableOperateType>('add');
function handleAdd() { function handleAdd() {
operateType.value = 'add'; operateType.value = 'add';
@ -176,18 +224,18 @@ export function useTableOperate<TableData>(
} }
/** the editing row data */ /** the editing row data */
const editingData = shallowRef<TableData | null>(null); const editingData: Ref<T | null> = ref(null);
function handleEdit(id: TableData[keyof TableData]) { function handleEdit(id: T['id']) {
operateType.value = 'edit'; operateType.value = 'edit';
const findItem = data.value.find(item => item[idKey] === id) || null; const findItem = data.value.find(item => item.id === 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 = shallowRef<string[]>([]); const checkedRowKeys = ref<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() {
@ -219,84 +267,6 @@ export function useTableOperate<TableData>(
}; };
} }
export function defaultTransform<ApiData>( function isTableColumnHasKey<T>(column: TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {
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);
} }

View File

@ -22,7 +22,7 @@ export const request = createFlatRequest(
errMsgStack: [], errMsgStack: [],
refreshTokenPromise: null refreshTokenPromise: null
} as RequestInstanceState, } as RequestInstanceState,
transform(response: AxiosResponse<App.Service.Response<any>>) { transformBackendResponse(response: AxiosResponse<App.Service.Response<any>>) {
return response.data.data; return response.data.data;
}, },
async onRequest(config) { async onRequest(config) {
@ -132,7 +132,7 @@ export const demoRequest = createRequest(
baseURL: otherBaseURL.demo baseURL: otherBaseURL.demo
}, },
{ {
transform(response: AxiosResponse<App.Service.DemoResponse>) { transformBackendResponse(response: AxiosResponse<App.Service.DemoResponse>) {
return response.data.result; return response.data.result;
}, },
async onRequest(config) { async onRequest(config) {

View File

@ -6,14 +6,30 @@ 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
* *
@ -21,4 +37,18 @@ 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;
};
} }