import { computed, effectScope, onScopeDispose, reactive, shallowRef, watch } from 'vue'; import type { Ref } from 'vue'; import type { PaginationEmits, PaginationProps } from 'element-plus'; 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 { useAppStore } from '@/store/modules/app'; import { $t } from '@/locales'; type RemoveReadonly = { -readonly [key in keyof T]: T[key]; }; export type UseUITableOptions = Omit< UseTableOptions, Pagination>, 'pagination' | 'getColumnChecks' | 'getColumns' > & { /** * get column visible * * @param column * * @default true * * @returns true if the column is visible, false otherwise */ getColumnVisible?: (column: UI.TableColumn) => boolean; }; const SELECTION_KEY = '__selection__'; const EXPAND_KEY = '__expand__'; const INDEX_KEY = '__index__'; export function useUITable(options: UseUITableOptions) { const scope = effectScope(); const appStore = useAppStore(); const result = useTable, false>({ ...options, getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible), getColumns }); // calculate the total width of the table this is used for horizontal scrolling const scrollX = computed(() => { return result.columns.value.reduce((acc, column) => { return acc + Number(column.width ?? column.minWidth ?? 120); }, 0); }); scope.run(() => { watch( () => appStore.locale, () => { result.reloadColumns(); } ); }); onScopeDispose(() => { scope.stop(); }); return { ...result, scrollX }; } type PaginationParams = Pick; type UseUIPaginatedTableOptions = UseUITableOptions & { paginationProps?: Partial>; /** * whether to show the total count of the table * * @default true */ showTotal?: boolean; onPaginationParamsChange?: (params: PaginationParams) => void | Promise; }; export function useUIPaginatedTable(options: UseUIPaginatedTableOptions) { const scope = effectScope(); const appStore = useAppStore(); const isMobile = computed(() => appStore.isMobile); const pagination: Partial> = reactive({ currentPage: 1, pageSize: 10, total: 0, pageSizes: [10, 15, 20, 25, 30], 'current-change': (page: number) => { pagination.currentPage = page; return true; }, 'size-change': (pageSize: number) => { pagination.currentPage = 1; pagination.pageSize = pageSize; return true; }, ...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: Partial> = { ...pagination, pagerCount: isMobile.value ? 3 : 9 }; return p; }); const paginationParams = computed(() => { const { currentPage, pageSize } = pagination; return { currentPage, pageSize }; }); const result = useTable, true>({ ...options, pagination: true, getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible), getColumns, onFetched: data => { pagination.total = data.total; } }); async function getDataByPage(page: number = 1) { if (page !== pagination.currentPage) { pagination.currentPage = 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 { ...result, getDataByPage, pagination, mobilePagination }; } export function useTableOperate( data: Ref, idKey: keyof TableData, getData: () => Promise ) { const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean(); const operateType = shallowRef('add'); function handleAdd() { operateType.value = 'add'; openDrawer(); } /** the editing row data */ const editingData = shallowRef(null); function handleEdit(id: TableData[keyof TableData]) { operateType.value = 'edit'; const findItem = data.value.find(item => item[idKey] === id) || null; editingData.value = jsonClone(findItem); openDrawer(); } /** the checked row keys of table */ const checkedRowKeys = shallowRef([]); /** the hook after the batch delete operation is completed */ async function onBatchDeleted() { window.$message?.success($t('common.deleteSuccess')); checkedRowKeys.value = []; await getData(); } /** the hook after the delete operation is completed */ async function onDeleted() { window.$message?.success($t('common.deleteSuccess')); await getData(); } return { drawerVisible, openDrawer, closeDrawer, operateType, handleAdd, editingData, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted }; } export function defaultTransform( response: FlatResponseData> ): PaginationData { 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>( cols: Column[], getColumnVisible?: (column: Column) => boolean ) { const checks: TableColumnCheck[] = []; cols.forEach(column => { if (column.type === 'selection') { checks.push({ prop: SELECTION_KEY, label: $t('common.check'), checked: true, visible: getColumnVisible?.(column) ?? false }); } else if (column.type === 'expand') { checks.push({ prop: EXPAND_KEY, label: $t('common.expandColumn'), checked: true, visible: getColumnVisible?.(column) ?? false }); } else if (column.type === 'index') { checks.push({ prop: INDEX_KEY, label: $t('common.index'), checked: true, visible: getColumnVisible?.(column) ?? false }); } else { checks.push({ prop: column.prop as string, label: column.label as string, checked: true, visible: getColumnVisible?.(column) ?? true }); } }); return checks; } function getColumns>(cols: Column[], checks: TableColumnCheck[]) { const columnMap = new Map(); cols.forEach(column => { if (column.type === 'selection') { columnMap.set(SELECTION_KEY, column); } else if (column.type === 'expand') { columnMap.set(EXPAND_KEY, column); } else if (column.type === 'index') { columnMap.set(INDEX_KEY, column); } else { columnMap.set(column.prop as string, column); } }); const filteredColumns = checks.filter(item => item.checked).map(check => columnMap.get(check.prop) as Column); return filteredColumns; }