first commit

This commit is contained in:
2026-09-21 19:21:35 +08:00
commit 6c944c4ffd
2733 changed files with 296956 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
import Table from './src/Table.vue'
import { ElTable } from 'element-plus'
import { TableColumn, TableSetProps } from './src/types'
export type {
TableColumn,
TableSlotDefault,
Pagination,
TableSetProps,
TableProps
} from './src/types'
export interface TableExpose {
setProps: (props: Recordable) => void
setColumn: (columnProps: TableSetProps[]) => void
addColumn: (column: TableColumn, index?: number) => void
delColumn: (field: string) => void
elTableRef: ComponentRef<typeof ElTable>
}
export { Table }
@@ -0,0 +1,587 @@
<script lang="tsx">
import {
ElTable,
ElTableColumn,
ElPagination,
ComponentSize,
ElTooltipProps,
ElImage,
ElEmpty,
ElCard
} from 'element-plus'
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { setIndex } from './helper'
import type { TableProps, TableColumn, Pagination, TableSetProps } from './types'
import { set, get } from 'lodash-es'
import { CSSProperties } from 'vue'
import { getSlot } from '@/utils/tsxHelper'
import TableActions from './components/TableActions.vue'
import { createVideoViewer } from '@/components/VideoPlayer'
import { Icon } from '@/components/Icon'
import { BaseButton } from '@/components/Button'
export default defineComponent({
name: 'Table',
props: {
pageSize: propTypes.number.def(10),
currentPage: propTypes.number.def(1),
// 是否展示表格的工具栏
showAction: propTypes.bool.def(false),
// 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
showOverflowTooltip: propTypes.bool.def(true),
// 表头
columns: {
type: Array as PropType<TableColumn[]>,
default: () => []
},
// 是否展示分页
pagination: {
type: Object as PropType<Pagination>,
default: (): Pagination | undefined => undefined
},
// 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
reserveSelection: propTypes.bool.def(false),
// 加载状态
loading: propTypes.bool.def(false),
// 是否叠加索引
reserveIndex: propTypes.bool.def(false),
// 对齐方式
align: propTypes.string
.validate((v: string) => ['left', 'center', 'right'].includes(v))
.def('left'),
// 表头对齐方式
headerAlign: propTypes.string
.validate((v: string) => ['left', 'center', 'right'].includes(v))
.def('left'),
data: {
type: Array as PropType<Recordable[]>,
default: () => []
},
// 图片自动预览字段数组
imagePreview: {
type: Array as PropType<string[]>,
default: () => []
},
// 视频自动预览字段数组
videoPreview: {
type: Array as PropType<string[]>,
default: () => []
},
height: propTypes.oneOfType([Number, String]),
maxHeight: propTypes.oneOfType([Number, String]),
stripe: propTypes.bool.def(false),
border: propTypes.bool.def(true),
size: {
type: String as PropType<ComponentSize>,
validator: (v: ComponentSize) => ['default', 'small', 'large'].includes(v)
},
fit: propTypes.bool.def(true),
showHeader: propTypes.bool.def(true),
highlightCurrentRow: propTypes.bool.def(false),
currentRowKey: propTypes.oneOfType([Number, String]),
// row-class-name, 类型为 (row: Recordable, rowIndex: number) => string | string
rowClassName: {
type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
default: ''
},
rowStyle: {
type: [Function, Object] as PropType<
(row: Recordable, rowIndex: number) => Recordable | CSSProperties
>,
default: undefined
},
cellClassName: {
type: [Function, String] as PropType<
(row: Recordable, column: any, rowIndex: number) => string | string
>,
default: ''
},
cellStyle: {
type: [Function, Object] as PropType<
(row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
>,
default: undefined
},
headerRowClassName: {
type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
default: ''
},
headerRowStyle: {
type: [Function, Object] as PropType<
(row: Recordable, rowIndex: number) => Recordable | CSSProperties
>,
default: undefined
},
headerCellClassName: {
type: [Function, String] as PropType<
(row: Recordable, column: any, rowIndex: number) => string | string
>,
default: ''
},
headerCellStyle: {
type: [Function, Object] as PropType<
(row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
>,
default: undefined
},
rowKey: propTypes.string.def('id'),
emptyText: propTypes.string.def('No Data'),
defaultExpandAll: propTypes.bool.def(false),
expandRowKeys: {
type: Array as PropType<string[]>,
default: undefined
},
defaultSort: {
type: Object as PropType<{ prop: string; order: string }>,
default: () => ({})
},
tooltipEffect: {
type: String as PropType<'dark' | 'light'>,
default: 'dark'
},
tooltipOptions: {
type: Object as PropType<
Pick<
ElTooltipProps,
| 'effect'
| 'enterable'
| 'hideAfter'
| 'offset'
| 'placement'
| 'popperClass'
| 'popperOptions'
| 'showAfter'
| 'showArrow'
>
>,
default: () => ({
enterable: true,
placement: 'top',
showArrow: true,
hideAfter: 200,
popperOptions: { strategy: 'fixed' }
})
},
showSummary: propTypes.bool.def(false),
sumText: propTypes.string.def('Sum'),
summaryMethod: {
type: Function as PropType<(param: { columns: any[]; data: any[] }) => any[]>,
default: undefined
},
spanMethod: {
type: Function as PropType<
(param: { row: any; column: any; rowIndex: number; columnIndex: number }) => any[]
>,
default: undefined
},
selectOnIndeterminate: propTypes.bool.def(true),
indent: propTypes.number.def(16),
lazy: propTypes.bool.def(false),
load: {
type: Function as PropType<(row: Recordable, treeNode: any, resolve: Function) => void>,
default: undefined
},
treeProps: {
type: Object as PropType<{ hasChildren?: string; children?: string; label?: string }>,
default: () => ({ hasChildren: 'hasChildren', children: 'children', label: 'label' })
},
tableLayout: {
type: String as PropType<'auto' | 'fixed'>,
default: 'fixed'
},
scrollbarAlwaysOn: propTypes.bool.def(false),
flexible: propTypes.bool.def(false),
// 自定义内容
customContent: propTypes.bool.def(false),
cardBodyStyle: {
type: Object as PropType<CSSProperties>,
default: () => ({})
},
cardBodyClass: {
type: String as PropType<string>,
default: ''
},
cardWrapStyle: {
type: Object as PropType<CSSProperties>,
default: () => ({})
},
cardWrapClass: {
type: String as PropType<string>,
default: ''
}
},
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
setup(props, { attrs, emit, slots, expose }) {
const elTableRef = ref<ComponentRef<typeof ElTable>>()
// 注册
onMounted(() => {
const tableRef = unref(elTableRef)
emit('register', tableRef?.$parent, elTableRef)
})
const pageSizeRef = ref(props.pageSize)
const currentPageRef = ref(props.currentPage)
// useTable传入的props
const outsideProps = ref<TableProps>({})
const mergeProps = ref<TableProps>({})
const getProps = computed(() => {
const propsObj = { ...props }
Object.assign(propsObj, unref(mergeProps))
return propsObj
})
const setProps = (props: TableProps = {}) => {
mergeProps.value = Object.assign(unref(mergeProps), props)
outsideProps.value = { ...props } as any
}
const setColumn = (columnProps: TableSetProps[], columnsChildren?: TableColumn[]) => {
const { columns } = unref(getProps)
for (const v of columnsChildren || columns) {
for (const item of columnProps) {
if (v.field === item.field) {
set(v, item.path, item.value)
} else if (v.children?.length) {
setColumn(columnProps, v.children)
}
}
}
}
const addColumn = (column: TableColumn, index?: number) => {
const { columns } = unref(getProps)
if (index !== void 0) {
columns.splice(index, 0, column)
} else {
columns.push(column)
}
}
const delColumn = (field: string) => {
const { columns } = unref(getProps)
const index = columns.findIndex((item) => item.field === field)
if (index > -1) {
columns.splice(index, 1)
}
}
const refresh = () => {
emit('refresh')
}
const changSize = (size: ComponentSize) => {
setProps({ size })
}
const confirmSetColumn = (columns: TableColumn[]) => {
setProps({ columns })
}
expose({
setProps,
setColumn,
delColumn,
addColumn,
elTableRef
})
const pagination = computed(() => {
return Object.assign(
{
small: false,
background: false,
pagerCount: 7,
layout: 'sizes, prev, pager, next, jumper, ->, total',
pageSizes: [10, 20, 30, 40, 50, 100],
disabled: false,
hideOnSinglePage: false,
total: 10
},
unref(getProps).pagination
)
})
watch(
() => unref(getProps).pageSize,
(val: number) => {
pageSizeRef.value = val
}
)
watch(
() => unref(getProps).currentPage,
(val: number) => {
currentPageRef.value = val
}
)
watch(
() => pageSizeRef.value,
(val: number) => {
emit('update:pageSize', val)
}
)
watch(
() => currentPageRef.value,
(val: number) => {
emit('update:currentPage', val)
}
)
const getBindValue = computed(() => {
const bindValue: Recordable = { ...attrs, ...unref(getProps) }
delete bindValue.columns
delete bindValue.data
delete bindValue.align
return bindValue
})
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
const { align, headerAlign, showOverflowTooltip, imagePreview, videoPreview } =
unref(getProps)
return columnsChildren.map((v) => {
if (v.hidden) return null
const props = { ...v } as any
if (props.children) delete props.children
const children = v.children
const slots = {
default: (...args: any[]) => {
const data = args[0]
let isPreview = false
isPreview =
imagePreview.some((item) => (item as string) === v.field) ||
videoPreview.some((item) => (item as string) === v.field)
return children && children.length
? renderTreeTableColumn(children)
: props?.slots?.default
? props.slots.default(...args)
: v?.formatter
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
: isPreview
? renderPreview(get(data.row, v.field), v.field)
: get(data.row, v.field)
}
}
if (props?.slots?.header) {
slots['header'] = (...args: any[]) => props.slots.header(...args)
}
return (
<ElTableColumn
showOverflowTooltip={showOverflowTooltip}
align={align}
headerAlign={headerAlign}
{...props}
prop={v.field}
>
{slots}
</ElTableColumn>
)
})
}
const renderPreview = (url: string, field: string) => {
const { imagePreview, videoPreview } = unref(getProps)
return (
<div class="flex items-center">
{imagePreview.includes(field) ? (
<ElImage
src={url}
fit="cover"
class="w-[100%]"
lazy
preview-src-list={[url]}
preview-teleported
/>
) : videoPreview.includes(field) ? (
<BaseButton
type="primary"
icon={<Icon icon="vi-ep:video-play" />}
onClick={() => {
createVideoViewer({
url
})
}}
>
预览
</BaseButton>
) : null}
</div>
)
}
const renderTableColumn = (columnsChildren?: TableColumn[]) => {
const {
columns,
reserveIndex,
pageSize,
currentPage,
align,
headerAlign,
showOverflowTooltip,
reserveSelection,
imagePreview,
videoPreview
} = unref(getProps)
return (columnsChildren || columns).map((v) => {
if (v.hidden) return null
if (v.type === 'index') {
return (
<ElTableColumn
type="index"
index={
v.index ? v.index : (index) => setIndex(reserveIndex, index, pageSize, currentPage)
}
align={v.align || align}
headerAlign={v.headerAlign || headerAlign}
label={v.label}
fixed={v.fixed}
width="65px"
></ElTableColumn>
)
} else if (v.type === 'selection') {
return (
<ElTableColumn
type="selection"
reserveSelection={reserveSelection}
align={align}
headerAlign={headerAlign}
selectable={v.selectable}
width="50"
></ElTableColumn>
)
} else {
const props = { ...v } as any
if (props.children) delete props.children
const children = v.children
const slots = {
default: (...args: any[]) => {
const data = args[0]
let isPreview = false
isPreview =
imagePreview.some((item) => (item as string) === v.field) ||
videoPreview.some((item) => (item as string) === v.field)
return children && children.length
? renderTreeTableColumn(children)
: props?.slots?.default
? props.slots.default(...args)
: v?.formatter
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
: isPreview
? renderPreview(get(data.row, v.field), v.field)
: get(data.row, v.field)
}
}
if (props?.slots?.header) {
slots['header'] = (...args: any[]) => props.slots.header(...args)
}
return (
<ElTableColumn
showOverflowTooltip={showOverflowTooltip}
align={align}
headerAlign={headerAlign}
{...props}
prop={v.field}
>
{slots}
</ElTableColumn>
)
}
})
}
return () => {
const tableSlots = {}
if (getSlot(slots, 'empty')) {
tableSlots['empty'] = (...args: any[]) => getSlot(slots, 'empty', args)
}
if (getSlot(slots, 'append')) {
tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
}
return (
<div v-loading={unref(getProps).loading}>
{unref(getProps).customContent ? (
<div class="flex flex-wrap">
{unref(getProps)?.data?.length ? (
unref(getProps)?.data.map((item) => {
const cardSlots = {
default: () => {
return getSlot(slots, 'content', item)
}
}
if (getSlot(slots, 'content-header')) {
cardSlots['header'] = () => {
return getSlot(slots, 'content-header', item)
}
}
if (getSlot(slots, 'content-footer')) {
cardSlots['footer'] = () => {
return getSlot(slots, 'content-footer', item)
}
}
return (
<ElCard
shadow="hover"
class={unref(getProps).cardWrapClass}
style={unref(getProps).cardWrapStyle}
bodyClass={unref(getProps).cardBodyClass}
bodyStyle={unref(getProps).cardBodyStyle}
>
{cardSlots}
</ElCard>
)
})
) : (
<div class="flex flex-1 justify-center">
<ElEmpty description="暂无数据" />
</div>
)}
</div>
) : (
<>
{unref(getProps).showAction && !unref(getProps).customContent ? (
<TableActions
columns={unref(getProps).columns}
onChangSize={changSize}
onRefresh={refresh}
onConfirm={confirmSetColumn}
/>
) : null}
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
{{
default: () => renderTableColumn(),
...tableSlots
}}
</ElTable>
</>
)}
{unref(getProps).pagination ? (
<ElPagination
v-model:pageSize={pageSizeRef.value}
v-model:currentPage={currentPageRef.value}
class="mt-10px"
{...unref(pagination)}
></ElPagination>
) : undefined}
</div>
)
}
}
})
</script>
@@ -0,0 +1,166 @@
<script setup lang="ts">
import {
ElDrawer,
ElCheckbox,
ElCheckboxGroup,
ElText,
ElRadioButton,
ElRadioGroup
} from 'element-plus'
import { TableColumn } from '../types'
import { PropType, ref, watch, unref } from 'vue'
import { cloneDeep } from 'lodash-es'
import { DEFAULT_FILTER_COLUMN } from '@/constants'
import { VueDraggable } from 'vue-draggable-plus'
const modelValue = defineModel<boolean>()
const props = defineProps({
columns: {
type: Array as PropType<TableColumn[]>,
default: () => []
}
})
const emit = defineEmits(['confirm'])
const oldColumns = ref<TableColumn[]>()
const settingColumns = ref<TableColumn[]>()
// 存储不要的列
const hiddenColumns = ref<TableColumn[]>([])
const defaultCheckColumns = ref<string[]>([])
const checkColumns = ref<string[]>([])
const checkAll = ref(false)
const isIndeterminate = ref(true)
const handleCheckAllChange = (val: boolean) => {
checkColumns.value = val ? unref(defaultCheckColumns) : []
isIndeterminate.value = false
}
const handleCheckedColumnsChange = (value: string[]) => {
const checkedCount = value.length
checkAll.value = checkedCount === unref(defaultCheckColumns)?.length
isIndeterminate.value = checkedCount > 0 && checkedCount < unref(defaultCheckColumns)?.length
}
const confirm = () => {
const newColumns = cloneDeep(unref(settingColumns))?.map((item) => {
const fixed = unref(settingColumns)?.find((col) => col.field === item.field)?.fixed
item.hidden = !unref(checkColumns)?.includes(item.field)
item.fixed = fixed ? fixed : false
return item
})
emit('confirm', [...unref(hiddenColumns), ...(newColumns || [])])
modelValue.value = false
}
const restore = () => {
initColumns([...unref(hiddenColumns), ...(unref(oldColumns) || [])], true)
}
const initColumns = (columns: TableColumn[], isReStore = false) => {
const newColumns = columns?.filter((item) => {
if (!isReStore) {
item.fixed = item.fixed !== void 0 ? item.fixed : false
}
return (item.type && !DEFAULT_FILTER_COLUMN.includes(item.type)) || !item.type
})
if (!unref(oldColumns)?.length) {
oldColumns.value = cloneDeep(newColumns)
}
settingColumns.value = cloneDeep(newColumns)
hiddenColumns.value = cloneDeep(
columns?.filter((item) => item.type && DEFAULT_FILTER_COLUMN.includes(item.type))
)
defaultCheckColumns.value = unref(settingColumns)?.map((item) => item.field) || []
checkColumns.value =
unref(settingColumns)
?.filter((item) => !item.hidden)
?.map((item) => item.field) || []
if (unref(checkColumns)?.length === unref(defaultCheckColumns)?.length) {
checkAll.value = true
isIndeterminate.value = false
}
}
watch(
() => props.columns,
(columns) => {
initColumns(columns)
},
{
immediate: true,
deep: true
}
)
</script>
<template>
<ElDrawer v-model="modelValue" title="列设置" size="350px">
<div>
<div class="flex items-center justify-between">
<div class="flex items-center justify-between">
<ElCheckbox
v-model="checkAll"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
/>
<ElText class="ml-8px!">{{ checkColumns.length }} / {{ settingColumns?.length }}</ElText>
</div>
<ElText>固定 / 排序</ElText>
</div>
<div v-if="settingColumns?.length">
<VueDraggable
v-model="settingColumns"
target=".el-checkbox-group"
handle=".handle"
:animation="150"
>
<ElCheckboxGroup
ref="draggableWrap"
v-model="checkColumns"
@change="handleCheckedColumnsChange"
>
<div
v-for="item in settingColumns"
:key="item.field"
class="flex items-center justify-between mt-12px"
>
<ElCheckbox :value="item.field">
{{ item.label }}
</ElCheckbox>
<div class="flex items-center">
<ElRadioGroup size="small" v-model="item.fixed">
<ElRadioButton value="left">
<Icon icon="vi-ep:arrow-left" />
</ElRadioButton>
<ElRadioButton :value="false">
<Icon icon="vi-ep:close" />
</ElRadioButton>
<ElRadioButton value="right">
<Icon icon="vi-ep:arrow-right" />
</ElRadioButton>
</ElRadioGroup>
<div class="ml-12px cursor-move handle"><Icon icon="vi-ep:rank" /></div>
</div>
</div>
</ElCheckboxGroup>
</VueDraggable>
</div>
</div>
<template #footer>
<div>
<BaseButton @click="restore">还原</BaseButton>
<BaseButton type="primary" @click="confirm">确定</BaseButton>
</div>
</template>
</ElDrawer>
</template>
@@ -0,0 +1,105 @@
<script lang="tsx">
import { defineComponent, unref, computed, PropType, ref } from 'vue'
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ComponentSize } from 'element-plus'
import { Icon } from '@/components/Icon'
import { useI18n } from '@/hooks/web/useI18n'
import { useAppStore } from '@/store/modules/app'
import { TableColumn } from '../types'
import ColumnSetting from './ColumnSetting.vue'
export default defineComponent({
name: 'TableActions',
components: {
ColumnSetting
},
props: {
columns: {
type: Array as PropType<TableColumn[]>,
default: () => []
}
},
emits: ['refresh', 'changSize', 'confirm'],
setup(props, { emit }) {
const appStore = useAppStore()
const { t } = useI18n()
const sizeMap = computed(() => appStore.sizeMap)
const showSetting = ref(false)
const refresh = () => {
emit('refresh')
}
const changSize = (size: ComponentSize) => {
emit('changSize', size)
}
const confirm = (columns: TableColumn[]) => {
emit('confirm', columns)
}
const showColumnSetting = () => {
showSetting.value = true
}
return () => (
<>
<div class="text-right h-28px flex items-center justify-end">
<div title="刷新" class="w-30px h-20px flex items-center justify-end" onClick={refresh}>
<Icon
icon="vi-ant-design:sync-outlined"
class="cursor-pointer"
hover-color="var(--el-color-primary)"
/>
</div>
<ElDropdown trigger="click" onCommand={changSize}>
{{
default: () => {
return (
<div title="尺寸" class="w-30px h-20px flex items-center justify-end">
<Icon
icon="vi-ant-design:column-height-outlined"
class="cursor-pointer"
hover-color="var(--el-color-primary)"
/>
</div>
)
},
dropdown: () => {
return (
<ElDropdownMenu>
{{
default: () => {
return unref(sizeMap).map((v) => {
return (
<ElDropdownItem key={v} command={v}>
{t(`size.${v}`)}
</ElDropdownItem>
)
})
}
}}
</ElDropdownMenu>
)
}
}}
</ElDropdown>
<div
title="列设置"
class="w-30px h-20px flex items-center justify-end"
onClick={showColumnSetting}
>
<Icon
icon="vi-ant-design:setting-outlined"
class="cursor-pointer"
hover-color="var(--el-color-primary)"
/>
</div>
</div>
<ColumnSetting v-model={showSetting.value} columns={props.columns} onConfirm={confirm} />
</>
)
}
})
</script>
@@ -0,0 +1,8 @@
export const setIndex = (reserveIndex: boolean, index: number, size: number, current: number) => {
const newIndex = index + 1
if (reserveIndex) {
return size * (current - 1) + newIndex
} else {
return newIndex
}
}
@@ -0,0 +1,98 @@
import { TableProps as ElTableProps } from 'element-plus'
export interface TableColumn {
field: string
label?: string
type?: string
/**
* 是否隐藏
*/
hidden?: boolean
children?: TableColumn[]
slots?: {
default?: (...args: any[]) => JSX.Element | JSX.Element[] | null
header?: (...args: any[]) => JSX.Element | null
}
index?: number | ((index: number) => number)
columnKey?: string
width?: string | number
minWidth?: string | number
fixed?: boolean | 'left' | 'right'
renderHeader?: (...args: any[]) => JSX.Element | null
// sortable?: boolean
sortMethod?: (...args: any[]) => number
sortBy?: string | string[] | ((...args: any[]) => string | string[])
sortOrders?: (string | null)[]
resizable?: boolean
formatter?: (...args: any[]) => any
showOverflowTooltip?: boolean
align?: 'left' | 'center' | 'right'
headerAlign?: 'left' | 'center' | 'right'
className?: string
labelClassName?: string
selectable?: (...args: any[]) => boolean
reserveSelection?: boolean
filters?: Array<{ text: string; value: string }>
filterPlacement?: string
filterMultiple?: boolean
filterMethod?: (...args: any[]) => boolean
filteredValue?: string[]
[key: string]: any
}
export interface TableSlotDefault {
row: Recordable
column: TableColumn
$index: number
[key: string]: any
}
export interface Pagination {
small?: boolean
background?: boolean
pageSize?: number
defaultPageSize?: number
total?: number
pageCount?: number
pagerCount?: number
currentPage?: number
defaultCurrentPage?: number
layout?: string
pageSizes?: number[]
popperClass?: string
prevText?: string
nextText?: string
disabled?: boolean
hideOnSinglePage?: boolean
}
export interface TableSetProps {
field: string
path: string
value: any
}
export interface TableProps extends Omit<Partial<ElTableProps<any[]>>, 'data'> {
pageSize?: number
currentPage?: number
showAction?: boolean
// 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
showOverflowTooltip?: boolean
// 表头
columns?: TableColumn[]
// 是否展示分页
pagination?: Pagination | undefined
// 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
reserveSelection?: boolean
// 加载状态
loading?: boolean
// 是否叠加索引
reserveIndex?: boolean
// 对齐方式
align?: 'left' | 'center' | 'right'
// 表头对齐方式
headerAlign?: 'left' | 'center' | 'right'
imagePreview?: string[]
videoPreview?: string[]
sortable?: boolean
data?: Recordable
}