import type { FlatResponseData } from '@sa/axios' import type { PaginationData, TableColumnCheck, UseTableOptions } from '@sa/hooks' import type { PaginationProps } from 'naive-ui' import type { Ref } from 'vue' import { useBoolean, useTable } from '@sa/hooks' import { jsonClone } from '@sa/utils' import { computed, effectScope, onScopeDispose, reactive, shallowRef, watch } from 'vue' import { $t } from '@/locales' import { useAppStore } from '@/store/modules/app' export type UseNaiveTableOptions = Omit< UseTableOptions, Pagination>, 'pagination' | 'getColumnChecks' | 'getColumns' > & { /** * 获取列的可见性 * * @param column * * @default true * * @returns 如果列可见返回 true,否则返回 false */ getColumnVisible?: (column: NaiveUI.TableColumn) => boolean } const SELECTION_KEY = '__selection__' const EXPAND_KEY = '__expand__' /** * NaiveUI 表格 * @param options 表格选项 */ export function useNaiveTable(options: UseNaiveTableOptions) { const scope = effectScope() // 表格作用域,用于管理表格的响应式数据 const appStore = useAppStore() const result = useTable, false>({ ...options, getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible), getColumns, }) // 计算表格的总宽度,用于水平滚动 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 UseNaivePaginatedTableOptions = UseNaiveTableOptions & { paginationProps?: Omit /** * 是否显示表格的总条数 * * @default true */ showTotal?: boolean onPaginationParamsChange?: (params: PaginationParams) => void | Promise } /** * NaiveUI 分页表格 * @param options 表格选项 */ export function useNaivePaginatedTable( options: UseNaivePaginatedTableOptions, ) { 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 // 针对移动端,如果系统不支持移动端,可以直接使用 `pagination` 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, true>({ ...options, pagination: true, getColumnChecks: cols => getColumnChecks(cols, options.getColumnVisible), getColumns, onFetched: (data) => { pagination.itemCount = data.total pagination.pageSize = data.pageSize }, }) 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 { ...result, getDataByPage, pagination, mobilePagination, } } /** * NaiveUI 表格操作 * @param data 表格数据 * @param idKey 表格数据的主键字段 * @param getData 获取表格数据的函数 */ export function useTableOperate( data: Ref, idKey: keyof TableData, getData: () => Promise, ): { drawerVisible: Ref /** 抽屉是否可见 */ openDrawer: () => void /** 打开抽屉 */ closeDrawer: () => void /** 关闭抽屉 */ operateType: Ref /** 操作类型 */ handleAdd: () => void /** 新增操作 */ editingData: Ref /** 编辑行数据 */ handleEdit: (id: TableData[keyof TableData]) => void /** 编辑操作 */ checkedRowKeys: Ref /** 表格的选中行 keys */ onBatchDeleted: () => Promise /** 批量删除操作完成后的钩子 */ onDeleted: () => Promise /** 删除操作完成后的钩子 */ } { const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean() const operateType = shallowRef('add') function handleAdd() { operateType.value = 'add' openDrawer() } /** 编辑行数据 */ 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() } /** 表格的选中行 keys */ const checkedRowKeys = shallowRef([]) /** 批量删除操作完成后的钩子 */ async function onBatchDeleted() { window.$message?.success($t('common.deleteSuccess')) checkedRowKeys.value = [] await getData() } /** 删除操作完成后的钩子 */ async function onDeleted() { window.$message?.success($t('common.deleteSuccess')) await getData() } return { drawerVisible, openDrawer, closeDrawer, operateType, handleAdd, editingData, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted, } } /** * 默认的表格数据转换函数 * * @param response 原始的 API 响应数据 * @returns 转换后的分页数据 */ export function defaultTransform( response: FlatResponseData>, ): PaginationData { const { data, error } = response if (!error) { const { data: list, currentPage: pageNum, pageSize: size, total = 0 } = data return { data: list, pageNum, 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 (isTableColumnHasKey(column)) { checks.push({ key: column.key as string, title: column.title!, checked: true, visible: getColumnVisible?.(column) ?? true, }) } else if (column.type === 'selection') { checks.push({ key: SELECTION_KEY, title: $t('common.check'), checked: true, visible: getColumnVisible?.(column) ?? false, }) } else if (column.type === 'expand') { checks.push({ key: EXPAND_KEY, title: $t('common.expandColumn'), checked: true, visible: getColumnVisible?.(column) ?? false, }) } }) return checks } function getColumns>(cols: Column[], checks: TableColumnCheck[]) { 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) } }) const filteredColumns = checks.filter(item => item.checked).map(check => columnMap.get(check.key) as Column) return filteredColumns } export function isTableColumnHasKey(column: NaiveUI.TableColumn): column is NaiveUI.TableColumnWithKey { return Boolean((column as NaiveUI.TableColumnWithKey).key) }