Browse Source

feat: add generic support for Table

main
刘凯 1 year ago
parent
commit
68955d3a0b
  1. 18
      src/components/Table/src/BasicTable.vue
  2. 19
      src/components/Table/src/hooks/useDataSource.ts
  3. 277
      src/components/Table/src/props.ts
  4. 10
      src/components/Table/src/types/table.ts

18
src/components/Table/src/BasicTable.vue

@ -1,9 +1,9 @@
<!-- eslint-disable no-useless-call --> <!-- eslint-disable no-useless-call -->
<script lang="ts" setup> <script lang="ts" setup generic="T extends Recordable = Recordable">
import { computed, inject, ref, toRaw, unref, useAttrs, useSlots, watchEffect } from 'vue' import { computed, inject, ref, toRaw, unref, useAttrs, useSlots, watchEffect } from 'vue'
import { Table } from 'ant-design-vue' import { Table } from 'ant-design-vue'
import { omit } from 'lodash-es' import { omit } from 'lodash-es'
import type { BasicTableProps, ColumnChangeParam, InnerHandlers, SizeType, TableActionType } from './types/table' import type { BasicTableProps, ColumnChangeParam, InnerHandlers, SizeType, SlotBodyCellProps, TableActionType } from './types/table'
import HeaderCell from './components/HeaderCell.vue' import HeaderCell from './components/HeaderCell.vue'
import { usePagination } from './hooks/usePagination' import { usePagination } from './hooks/usePagination'
@ -20,7 +20,7 @@ import { useTableExpand } from './hooks/useTableExpand'
import { createTableContext } from './hooks/useTableContext' import { createTableContext } from './hooks/useTableContext'
import { useTableFooter } from './hooks/useTableFooter' import { useTableFooter } from './hooks/useTableFooter'
import { useTableForm } from './hooks/useTableForm' import { useTableForm } from './hooks/useTableForm'
import { basicProps } from './props' import { defineTableProps } from './props'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { PageWrapperFixedHeightKey } from '@/enums/pageEnum' import { PageWrapperFixedHeightKey } from '@/enums/pageEnum'
@ -30,7 +30,7 @@ import { warn } from '@/utils/log'
defineOptions({ name: 'BasicTable' }) defineOptions({ name: 'BasicTable' })
const props = defineProps(basicProps) const props = defineProps(defineTableProps<T>())
const emit = defineEmits([ const emit = defineEmits([
'fetch-success', 'fetch-success',
@ -109,7 +109,7 @@ const {
reload, reload,
getAutoCreateKey, getAutoCreateKey,
updateTableData, updateTableData,
} = useDataSource( } = useDataSource<T>(
getProps, getProps,
{ {
tableData, tableData,
@ -239,7 +239,7 @@ function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props } innerPropsRef.value = { ...unref(innerPropsRef), ...props }
} }
const tableAction: TableActionType = { const tableAction: TableActionType<T> = {
reload: async params => void reload(params), reload: async params => void reload(params),
getSelectRows, getSelectRows,
setSelectedRows, setSelectedRows,
@ -312,7 +312,8 @@ emit('register', tableAction, formActions)
@resize-column="setColumnWidth" @resize-column="setColumnWidth"
> >
<template v-for="item in Object.keys($slots)" #[item]="data" :key="item"> <template v-for="item in Object.keys($slots)" #[item]="data" :key="item">
<slot :name="item" v-bind="data || {}" /> <!-- eslint-disable-next-line vue/no-extra-parens -->
<slot :name="item" v-bind="((data || {}) as SlotBodyCellProps<T>)" />
</template> </template>
<template #headerCell="{ column }"> <template #headerCell="{ column }">
<slot name="headerCell" v-bind="{ column }"> <slot name="headerCell" v-bind="{ column }">
@ -321,7 +322,8 @@ emit('register', tableAction, formActions)
</template> </template>
<!-- 增加对antdv3.x兼容 --> <!-- 增加对antdv3.x兼容 -->
<template #bodyCell="data"> <template #bodyCell="data">
<slot name="bodyCell" v-bind="data || {}" /> <!-- eslint-disable-next-line vue/no-extra-parens -->
<slot name="bodyCell" v-bind="((data || {}) as SlotBodyCellProps<T>)" />
</template> </template>
<!-- <template #[`header-${column.dataIndex}`] v-for="(column, index) in columns" :key="index"> --> <!-- <template #[`header-${column.dataIndex}`] v-for="(column, index) in columns" :key="index"> -->
<!-- <HeaderCell :column="column" /> --> <!-- <HeaderCell :column="column" /> -->

19
src/components/Table/src/hooks/useDataSource.ts

@ -5,6 +5,7 @@ import { cloneDeep, get, merge } from 'lodash-es'
import type { PaginationProps } from '../types/pagination' import type { PaginationProps } from '../types/pagination'
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table' import type { BasicTableProps, FetchParams, SorterResult } from '../types/table'
import { FETCH_SETTING, PAGE_SIZE, ROW_KEY } from '../const' import { FETCH_SETTING, PAGE_SIZE, ROW_KEY } from '../const'
import type { EditRecordRow } from '../components/editable'
import { isBoolean, isFunction, isObject } from '@/utils/is' import { isBoolean, isFunction, isObject } from '@/utils/is'
import { buildUUID } from '@/utils/uuid' import { buildUUID } from '@/utils/uuid'
@ -21,7 +22,7 @@ interface SearchState {
sortInfo: Recordable sortInfo: Recordable
filterInfo: Record<string, string[]> filterInfo: Record<string, string[]>
} }
export function useDataSource( export function useDataSource<T extends Recordable = Recordable>(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
{ {
getPaginationInfo, getPaginationInfo,
@ -128,18 +129,18 @@ export function useDataSource(
return unref(dataSourceRef) return unref(dataSourceRef)
}) })
async function updateTableData(index: number, key: string, value: any) { async function updateTableData<K extends keyof T>(index: number, key: K, value: T[K]): Promise<EditRecordRow<T>> {
const record = dataSourceRef.value[index] const record = dataSourceRef.value[index]
if (record) if (record)
dataSourceRef.value[index][key] = value (dataSourceRef as any).value[index][key] = value
return dataSourceRef.value[index] return dataSourceRef.value[index] as any
} }
function updateTableDataRecord( function updateTableDataRecord(
rowKey: string | number, rowKey: string | number,
record: Recordable, record: T,
): Recordable | undefined { ): EditRecordRow<T> | undefined {
const row = findTableDataRecord(rowKey) const row = findTableDataRecord(rowKey)
if (row) { if (row) {
@ -200,14 +201,14 @@ export function useDataSource(
} }
function insertTableDataRecord( function insertTableDataRecord(
record: Recordable | Recordable[], record: T | T[],
index?: number, index?: number,
): Recordable[] | undefined { ): EditRecordRow<T>[] | undefined {
// if (!dataSourceRef.value || dataSourceRef.value.length == 0) return; // if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
index = index ?? dataSourceRef.value?.length index = index ?? dataSourceRef.value?.length
const _record = isObject(record) ? [record as Recordable] : (record as Recordable[]) const _record = isObject(record) ? [record as Recordable] : (record as Recordable[])
unref(dataSourceRef).splice(index, 0, ..._record) unref(dataSourceRef).splice(index, 0, ..._record)
return unref(dataSourceRef) return unref(dataSourceRef) as any
} }
function findTableDataRecord(rowKey: string | number) { function findTableDataRecord(rowKey: string | number) {

277
src/components/Table/src/props.ts

@ -10,142 +10,151 @@ import type {
TableSetting, TableSetting,
} from './types/table' } from './types/table'
import { DEFAULT_FILTER_FN, DEFAULT_SIZE, DEFAULT_SORT_FN, FETCH_SETTING } from './const' import { DEFAULT_FILTER_FN, DEFAULT_SIZE, DEFAULT_SORT_FN, FETCH_SETTING } from './const'
import type { EditRecordRow } from './components/editable'
import type { FormProps } from '@/components/Form' import type { FormProps } from '@/components/Form'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
export const basicProps = { export function defineTableProps<T>() {
clickToRowSelect: { type: Boolean, default: true }, return {
isTreeTable: Boolean, clickToRowSelect: { type: Boolean, default: true },
tableSetting: propTypes.shape<TableSetting>({}), isTreeTable: Boolean,
inset: Boolean, tableSetting: propTypes.shape<TableSetting>({}),
sortFn: { inset: Boolean,
type: Function as PropType<(sortInfo: SorterResult) => any>, sortFn: {
default: DEFAULT_SORT_FN, type: Function as PropType<(sortInfo: SorterResult) => any>,
}, default: DEFAULT_SORT_FN,
filterFn: { },
type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>, filterFn: {
default: DEFAULT_FILTER_FN, type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
}, default: DEFAULT_FILTER_FN,
showTableSetting: Boolean, },
autoCreateKey: { type: Boolean, default: true }, showTableSetting: Boolean,
striped: { type: Boolean, default: true }, autoCreateKey: { type: Boolean, default: true },
showSummary: Boolean, striped: { type: Boolean, default: true },
summaryFunc: { showSummary: Boolean,
type: [Function, Array] as PropType<(...arg: any[]) => any[]>, summaryFunc: {
default: null, type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
}, default: null,
summaryData: { },
type: Array as PropType<Recordable[]>, summaryData: {
default: null, type: Array as PropType<Recordable[]>,
}, default: null,
indentSize: propTypes.number.def(24), },
canColDrag: { type: Boolean, default: true }, indentSize: propTypes.number.def(24),
api: { canColDrag: { type: Boolean, default: true },
type: Function as PropType<(...arg: any[]) => Promise<any>>, api: {
default: null, type: Function as PropType<
}, (...arg: any[]) => Promise<
beforeFetch: { | T[]
type: Function as PropType<Fn>, | { list: T[], pageNo?: number, pageSize?: number, total?: number }
default: null, >
}, >,
afterFetch: { default: null,
type: Function as PropType<Fn>, },
default: null, beforeFetch: {
}, type: Function as PropType<Fn>,
handleSearchInfoFn: { default: null,
type: Function as PropType<Fn>, },
default: null, afterFetch: {
}, type: Function as PropType<Fn>,
fetchSetting: { default: null,
type: Object as PropType<FetchSetting>, },
default: () => { handleSearchInfoFn: {
return FETCH_SETTING type: Function as PropType<Fn>,
}, default: null,
}, },
// 立即请求接口 fetchSetting: {
immediate: { type: Boolean, default: true }, type: Object as PropType<FetchSetting>,
emptyDataIsShowTable: { type: Boolean, default: true }, default: () => {
// 额外的请求参数 return FETCH_SETTING
searchInfo: { },
type: Object as PropType<Recordable>, },
default: null, // 立即请求接口
}, immediate: { type: Boolean, default: true },
// 默认的排序参数 emptyDataIsShowTable: { type: Boolean, default: true },
defSort: { // 额外的请求参数
type: Object as PropType<Recordable>, searchInfo: {
default: null, type: Object as PropType<Recordable>,
}, default: null,
// 使用搜索表单 },
useSearchForm: propTypes.bool, // 默认的排序参数
// 表单配置 defSort: {
formConfig: { type: Object as PropType<Recordable>,
type: Object as PropType<Partial<FormProps>>, default: null,
default: null, },
}, // 使用搜索表单
columns: { useSearchForm: propTypes.bool,
type: Array as PropType<BasicColumn[]>, // 表单配置
default: () => [], formConfig: {
}, type: Object as PropType<Partial<FormProps>>,
showIndexColumn: { type: Boolean, default: true }, default: null,
indexColumnProps: { },
type: Object as PropType<BasicColumn>, columns: {
default: null, type: Array as PropType<BasicColumn[]>,
}, default: () => [],
actionColumn: { },
type: Object as PropType<BasicColumn>, showIndexColumn: { type: Boolean, default: true },
default: null, indexColumnProps: {
}, type: Object as PropType<BasicColumn>,
ellipsis: { type: Boolean, default: true }, default: null,
isCanResizeParent: { type: Boolean, default: false }, },
canResize: { type: Boolean, default: true }, actionColumn: {
clearSelectOnPageChange: propTypes.bool, type: Object as PropType<BasicColumn>,
resizeHeightOffset: propTypes.number.def(0), default: null,
rowSelection: { },
type: Object as PropType<TableRowSelection | null>, ellipsis: { type: Boolean, default: true },
default: null, isCanResizeParent: { type: Boolean, default: false },
}, canResize: { type: Boolean, default: true },
title: { clearSelectOnPageChange: propTypes.bool,
type: [String, Function] as PropType<string | ((data: Recordable) => string)>, resizeHeightOffset: propTypes.number.def(0),
default: null, rowSelection: {
}, type: Object as PropType<TableRowSelection<EditRecordRow<T>> | null>,
titleHelpMessage: { default: null,
type: [String, Array] as PropType<string | string[]>, },
}, title: {
maxHeight: propTypes.number, type: [String, Function] as PropType<string | ((data: Recordable) => string)>,
dataSource: { default: null,
type: Array as PropType<Recordable[]>, },
default: null, titleHelpMessage: {
}, type: [String, Array] as PropType<string | string[]>,
rowKey: { },
type: [String, Function] as PropType<string | ((record: Recordable) => string)>, maxHeight: propTypes.number,
default: '', dataSource: {
}, type: Array as PropType<T[]>,
bordered: propTypes.bool, default: null,
pagination: { },
type: [Object, Boolean] as PropType<PaginationProps | boolean>, rowKey: {
default: null, // eslint-disable-next-line ts/ban-types
}, type: [String, Function] as PropType<keyof T | ((record: T) => keyof T) | (string & {})>,
loading: propTypes.bool, default: '',
rowClassName: { },
type: Function as PropType<(record: TableCustomRecord<any>, index: number) => string>, bordered: propTypes.bool,
}, pagination: {
scroll: { type: [Object, Boolean] as PropType<PaginationProps | boolean>,
type: Object as PropType<{ x: number | string | true, y: number | string }>, default: null,
default: null, },
}, loading: propTypes.bool,
beforeEditSubmit: { rowClassName: {
type: Function as PropType< type: Function as PropType<(record: TableCustomRecord<any>, index: number) => string>,
(data: { },
record: Recordable scroll: {
index: number type: Object as PropType<{ x: number | string | true, y: number | string }>,
key: string | number default: null,
value: any },
}) => Promise<any> beforeEditSubmit: {
>, type: Function as PropType<
}, (data: {
size: { record: T
type: String as PropType<SizeType>, index: number
default: DEFAULT_SIZE, key: string | number
}, value: any
}) => Promise<any>
>,
},
size: {
type: String as PropType<SizeType>,
default: DEFAULT_SIZE,
},
}
} }

10
src/components/Table/src/types/table.ts

@ -1,6 +1,7 @@
import type { VNodeChild } from 'vue' import type { VNodeChild } from 'vue'
import type { TableRowSelection as ITableRowSelection, Key } from 'ant-design-vue/lib/table/interface' import type { TableRowSelection as ITableRowSelection, Key } from 'ant-design-vue/lib/table/interface'
import type { ColumnProps } from 'ant-design-vue/lib/table' import type { ColumnProps } from 'ant-design-vue/lib/table'
import type { Table } from 'ant-design-vue'
import type { EditRecordRow } from '../components/editable' import type { EditRecordRow } from '../components/editable'
import type { PaginationProps } from './pagination' import type { PaginationProps } from './pagination'
@ -210,7 +211,8 @@ export interface BasicTableProps<T = Recordable<any>> {
// 在分页改变的时候清空选项 // 在分页改变的时候清空选项
clearSelectOnPageChange?: boolean clearSelectOnPageChange?: boolean
// //
rowKey?: keyof T | ((record: T) => keyof T) // eslint-disable-next-line ts/ban-types
rowKey?: keyof T | ((record: T) => keyof T) | (string & {})
// 数据 // 数据
dataSource?: T[] dataSource?: T[]
// 标题右侧提示 // 标题右侧提示
@ -382,7 +384,7 @@ export interface BasicTableProps<T = Recordable<any>> {
* The cell will not submit data while callback return false * The cell will not submit data while callback return false
*/ */
beforeEditSubmit?: (data: { beforeEditSubmit?: (data: {
record: Recordable record: T
index: number index: number
key: string | number key: string | number
value: any value: any
@ -500,3 +502,7 @@ export interface ColumnChangeParam {
export interface InnerHandlers { export interface InnerHandlers {
onColumnsChange: (data: ColumnChangeParam[]) => void onColumnsChange: (data: ColumnChangeParam[]) => void
} }
export type SlotBodyCellProps<T> =
& Omit<Parameters<Required<InstanceType<typeof Table>['$slots']>['bodyCell']>[0], 'record'>
& { record: EditRecordRow<T> }