import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue'; import type { Ref } from 'vue'; import type { PaginationProps } from 'naive-ui'; import { jsonClone } from '@sa/utils'; import { useBoolean, useHookTable } from '@sa/hooks'; import { useAppStore } from '@/store/modules/app'; import { $t } from '@/locales'; type TableData = NaiveUI.TableData; type GetTableData = NaiveUI.GetTableData; type TableColumn = NaiveUI.TableColumn; export function useTable(config: NaiveUI.NaiveTableConfig) { const scope = effectScope(); const appStore = useAppStore(); const isMobile = computed(() => appStore.isMobile); const { apiFn, apiParams, immediate } = config; const showTotal = config.showTotal || true; const SELECTION_KEY = '__selection__'; const EXPAND_KEY = '__expand__'; const { loading, empty, data, columns, columnChecks, reloadColumns, getData, searchParams, updateSearchParams, resetSearchParams: resetSearchParams0 } = useHookTable, TableColumn>>>({ apiFn, apiParams, searchParams: config.searchParams, columns: config.columns, transformer: res => { const { data: records = [], page: 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, index) => { 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 as string, 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>>(); 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); } if (isMobile.value && column.fixed) { column.fixed = undefined; } }); const filteredColumns = checks .filter(item => item.checked) .map(check => columnMap.get(check.key) as TableColumn>); 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, pageSizes: [10, 15, 20, 25, 30], onUpdatePage: async (page: number) => { pagination.page = page; updateSearchParams({ page, size: pagination.pageSize! }); getData(); }, onUpdatePageSize: async (pageSize: number) => { pagination.pageSize = pageSize; pagination.page = 1; updateSearchParams({ page: 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) { 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({ page: pageNum, size: pagination.pageSize! }); await getData(); } scope.run(() => { watch( () => appStore.locale, () => { reloadColumns(); } ); }); onScopeDispose(() => { scope.stop(); }); const resetSearchParams = () => { resetSearchParams0(); getData(); }; return { loading, empty, data, columns, columnChecks, reloadColumns, pagination, mobilePagination, updatePagination, getData, getDataByPage, searchParams, updateSearchParams, resetSearchParams }; } export function useTableOperate(data: Ref, getData: () => Promise) { const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean(); const operateType = ref('add'); function handleAdd() { operateType.value = 'add'; openDrawer(); } /** the editing row data */ const editingData: Ref = ref(null); function handleEdit(id: T['id']) { operateType.value = 'edit'; const findItem = data.value.find(item => item.id === id) || null; editingData.value = jsonClone(findItem); openDrawer(); } function handleCopy(id: T['id']) { operateType.value = 'add'; const findItem = data.value.find(item => item.id === id) || null; editingData.value = jsonClone(findItem); delete editingData.value?.id; openDrawer(); } /** the checked row keys of table */ const checkedRowKeys = ref([]); /** 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, handleCopy, checkedRowKeys, onBatchDeleted, onDeleted }; } function isTableColumnHasKey(column: TableColumn): column is NaiveUI.TableColumnWithKey { return Boolean((column as NaiveUI.TableColumnWithKey).key); }