Files
reader-star/apps/admin/src/hooks/common/table.ts

333 lines
8.2 KiB
TypeScript
Raw Normal View History

2026-01-16 13:06:44 +08:00
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<ResponseData, ApiData, Pagination extends boolean> = Omit<
UseTableOptions<ResponseData, ApiData, NaiveUI.TableColumn<ApiData>, Pagination>,
'pagination' | 'getColumnChecks' | 'getColumns'
> & {
/**
*
2026-01-16 13:06:44 +08:00
*
* @param column
*
* @default true
*
* @returns true false
2026-01-16 13:06:44 +08:00
*/
getColumnVisible?: (column: NaiveUI.TableColumn<ApiData>) => boolean
}
const SELECTION_KEY = '__selection__'
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,
})
// 计算表格的总宽度,用于水平滚动
2026-01-16 13:06:44 +08:00
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<PaginationProps, 'page' | 'pageSize'>
type UseNaivePaginatedTableOptions<ResponseData, ApiData> = UseNaiveTableOptions<ResponseData, ApiData, true> & {
paginationProps?: Omit<PaginationProps, 'page' | 'pageSize' | 'itemCount'>
/**
*
2026-01-16 13:06:44 +08:00
*
* @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
// 针对移动端,如果系统不支持移动端,可以直接使用 `pagination`
2026-01-16 13:06:44 +08:00
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
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,
}
}
export function useTableOperate<TableData>(
data: Ref<TableData[]>,
idKey: keyof TableData,
getData: () => Promise<void>,
): {
drawerVisible: Ref<boolean>
openDrawer: () => void
closeDrawer: () => void
operateType: Ref<NaiveUI.TableOperateType>
handleAdd: () => void
editingData: Ref<TableData | null>
handleEdit: (id: TableData[keyof TableData]) => void
checkedRowKeys: Ref<string[]>
onBatchDeleted: () => Promise<void>
onDeleted: () => Promise<void>
} {
2026-01-16 13:06:44 +08:00
const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean()
const operateType = shallowRef<NaiveUI.TableOperateType>('add')
function handleAdd() {
operateType.value = 'add'
openDrawer()
}
/** 编辑行数据 */
2026-01-16 13:06:44 +08:00
const editingData = shallowRef<TableData | null>(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 */
2026-01-16 13:06:44 +08:00
const checkedRowKeys = shallowRef<string[]>([])
/** 批量删除操作完成后的钩子 */
2026-01-16 13:06:44 +08:00
async function onBatchDeleted() {
window.$message?.success($t('common.deleteSuccess'))
checkedRowKeys.value = []
await getData()
}
/** 删除操作完成后的钩子 */
2026-01-16 13:06:44 +08:00
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
*/
2026-01-16 13:06:44 +08:00
export function defaultTransform<ApiData>(
response: FlatResponseData<any, Api.Common.PaginatingQueryRecord<ApiData>>,
): PaginationData<ApiData> {
const { data, error } = response
if (!error) {
const { list, currentPage: pageNum, pageSize: size, total } = data
2026-01-16 13:06:44 +08:00
return {
data: list,
pageNum,
2026-01-16 13:06:44 +08:00
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) => {
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<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)
}