import cns from 'classnames' import React, { Children, ComponentType, CSSProperties, isValidElement, ReactElement, ReactNode, TableHTMLAttributes, useContext, useMemo } from 'react' import { ComponentSize, useLangData, usePrefix } from '../ConfigProvider' import { Loading } from '../Loading' import { Empty } from '../Empty' import { concat, isDev } from '../utils' import { DataTableContextValue, dataTableCtx } from './context' import { useScrollBar, ScrollBarProvider } from './scroll-bar' import { HorizontalScrollProvider, useHorizontalScroll } from './horizontal-scroll' import { useColumnWidth, ColumnWidthProvider } from './column-width' import { SelectionHeadCell, SelectionBodyCell, SelectionProvider } from './selection' import { Pagination } from './pagination' import { HeadCell, BodyCell, MeasureCell } from './cell' import { DataTableColumnOptions, DataTableColumnOptionsWithAccessor, DataTableColumnOptionsWithoutAccessor, InternalColumnOptions, DataTablePaginationOptions, DataTableSelectionOptions, InternalRecord, InternalSelectionOptions } from './defs' export { DataTablePaginationAlign, DataTableColumnOptions, DataTableSortOrder, DataTableColumnSelectFilterValue } from './defs' export type DataTableProps = { /** 尺寸 */ size?: ComponentSize /** 列信息 */ columns?: Array> /** 数据列表 */ records: R[] /** 数据唯一标识对应的字段名 */ recordIdAccessor?: keyof R /** 行选择配置,开启行选择要求提供 `recordIdAccessor` */ selection?: DataTableSelectionOptions /** 分页配置 */ pagination?: DataTablePaginationOptions /** 是否加载中 */ loading?: boolean /** * 通过 `children` 传入 `` 也可以指定列的配置信息。 * 若指定了 prop `columns`,`children` 会被忽略。 * */ children?: ReactNode /** 自定义样式名 */ className?: string /** 自定义样式 */ style?: CSSProperties /** `table` 元素的 html 属性;当 `fixHead` 为 `true` 时, 会分别传给 head & body 对应的 `table` */ tableHtmlProps?: TableHTMLAttributes /** 空状态下展示的内容,建议用 `Empty` 组件 */ empty?: ReactNode /** * 边框配置 * + `full` - 全边框 * + `row` - 行与行之间才有边框 * + `none` - 无边框 */ border?: 'row' | 'full' | 'none' /** 是否有斑马纹;若有斑马纹则 `border` 总是会被视作 `none` */ zebra?: boolean /** * 是否开启“固定表头”;开启固定表头后, * + 表格整体会使用 div 元素进行渲染,head & body 被分别渲染为独立的 table * + table-layout 会被设置为 `fixed` */ fixHead?: boolean /** * 配置使用固定列时的表格内容的宽度,搭配 `Column` 的 prop `fixed` 一起使用;如果表格的宽度小于该宽度,则表格可以左右滑动,设置为 fixed 的列会在滑动到边缘时固定住。 * 内部通过 `position: sticky` 实现固定效果,因此不支持 IE11 */ fixColumn?: { contentWidth: string } /** 生成行样式名 */ getRowClassName?: (record: R, index: number) => string /** 行点击事件 */ onRowClick?: (record: R, index: number) => void } interface WithColumn { // eslint-disable-next-line @typescript-eslint/naming-convention Column(props: DataTableColumnOptionsWithAccessor): ReactElement | null // eslint-disable-next-line @typescript-eslint/naming-convention Column(props: DataTableColumnOptionsWithoutAccessor): ReactElement | null } /** * 用于方便地去定义含自定义 `Record` 类型的 `DataTable` / `DataTableColumn` 组件,使用姿势: * ```ts * const MyTable: DataTableType = DataTable * const MyTableColumn: DataTableColumnType = DataTableColumn * ``` * 在后续使用中 `MyTable` 等价于 `DataTable`,`MyTableColumn` 等价于 `DataTableColumn` */ export type DataTableType = ComponentType> & WithColumn export type DataTableColumnType = WithColumn['Column'] const noop = () => undefined export function DataTable({ columns: columnsFromProps, records: recordsFromProps, recordIdAccessor, selection: selectionFromProps, pagination, loading = false, children, className: classNameFromProps, style, tableHtmlProps, empty, size = 'default', border = 'row', zebra = false, fixHead = false, fixColumn, getRowClassName, onRowClick }: DataTableProps) { if (selectionFromProps != null && recordIdAccessor == null) throw new Error('`recordIdAccessor` is required while `selection` enabled.') const selection: InternalSelectionOptions | undefined = useMemo(() => ( selectionFromProps == null ? undefined : { type: 'multiple', onChange: noop, ...selectionFromProps } ), [selectionFromProps]) if (fixColumn != null) { const tableStyle = { width: fixColumn.contentWidth, minWidth: '100%', // 如果内容(table)宽度小于容器宽度,拉伸内容以进行平铺展示 ...tableHtmlProps?.style } tableHtmlProps = { ...tableHtmlProps, style: tableStyle } } if (zebra) border = 'none' const baseCls = usePrefix('data-table') const columns = useMemo( () => getInternalColumns(columnsFromProps, children, selection), [columnsFromProps, children, selection] ) const records = useMemo( () => getInternalRecords(recordsFromProps, recordIdAccessor), [recordsFromProps, recordIdAccessor] ) const ctxValue = useMemo( () => ({ baseCls, size, fixHead, columns, records, empty, getRowClassName, onRowClick }), [baseCls, size, fixHead, columns, records, empty, getRowClassName, onRowClick] ) const className = cns( baseCls, concat(baseCls, 'border', border), zebra && concat(baseCls, 'zebra'), concat(baseCls, 'size', size), fixHead && concat(baseCls, 'fix-head'), fixColumn && concat(baseCls, 'fix-column'), classNameFromProps ) return ( ) } // eslint-disable-next-line max-len export function DataTableColumn(props: DataTableColumnOptionsWithAccessor): ReactElement | null export function DataTableColumn(props: DataTableColumnOptionsWithoutAccessor): ReactElement | null export function DataTableColumn() { return null } DataTable.Column = DataTableColumn function getInternalColumns( columnsFromProps: Array> | undefined, children: ReactNode | undefined, selection: InternalSelectionOptions | undefined ): InternalColumnOptions[] { const columns = columnsFromProps ?? children2ColumnOptions(children) const internalColumns: InternalColumnOptions[] = columns.map((column, i) => ({ ...column, internalId: column.id ?? (column as any).accessor ?? `__data_table_index_${i}__`, render: (column.render ?? renderValue) as any // 这里类型有细节问题,不过不影响逻辑正确性 })) if (selection != null) { const selectionColumn: InternalColumnOptions = { internalId: '__data_table_selection_column__', title: , width: '54px', // 54: 14 宽度 + 20 左右 padding fixed: selection.fixed, // eslint-disable-next-line react/display-name render: (_, record, index: number) => (