/* eslint-disable max-len */ /* eslint-disable jsx-a11y/interactive-supports-focus */ /* eslint-disable no-underscore-dangle */ /** * Table 组件将不会修改成 FunctionComponent, * 因为考虑到之前在使用时存在不少通过 实例.table 的方法直接访问 元素的用法。 */ import * as React from "react" import { VariableSizeList } from "react-window" import PropTypes from "prop-types" import shallowEqual from "shallowequal" import addEventListener from "rc-util/lib/Dom/addEventListener" import { debounce } from "debounce" import classNames from "classnames" import omit from "../_util/omit" import Column, { IColumnProps } from "./Column" import Affix from "../affix" import Checkbox from "../checkbox" import Icon from "../icon" import Popover, { IPopoverProps } from "../popover" import Radio from "../radio" import ResizeObserver from "../resize-observer" import TableSort from "./TableSort" import TableFilter from "./TableFilter" import ColumnManager from "./ColumnManager" import "./style" interface IBaseObject { [key: string]: any } interface IFixedColumnsInfo { columnsLength: number width: number dataIndex: string fixed: "left" | "right" index: number isLastLeft?: boolean isFirstRight?: boolean offset?: number } /* eslint-disable no-nested-ternary */ const noop = () => {} const prefix = "adui-table" const TD_MIN_WIDTH = 100 const SELECT_TD_WIDTH = 50 const SELECT_AND_EXPAND_TD_WIDTH = 80 const TD_HEIGHT = { large: 60, medium: 50, mini: 40, small: 40, } const HORIZONTAL_ALIGN = { center: "center" as "center", left: "flex-start" as "flex-start", right: "flex-end" as "flex-end", } const VERTICAL_ALIGN = { bottom: "flex-end" as "flex-end", center: "center" as "center", top: "flex-start" as "flex-start", } const isFixedLeft = (col: IBaseObject): boolean => col.fixed === "left" || col.fixed === true const isFixedRight = (col: IBaseObject): boolean => col.fixed === "right" export interface ITableProps { [key: string]: any /** * 统一地控制列水平靠齐方向 */ align?: null | "left" | "center" | "right" /** * 子节点 */ children?: any /** * 以数组的方式传入 Columns,如果传入了此 Prop,则 Table 会忽略 children */ columns?: IColumnProps[] | null /** * 附加类名 */ className?: string /** * 控制整个 Table 的 Columns 能否调整宽度 */ columnsResizable?: boolean | null /** * 数据源 */ dataSource?: T[] /** * 【展开行】默认展开的行的 key,请在传入前进行合法校验,因为 Table 对此 Prop 没有过多校验 * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ defaultExpandedRowKeys?: Array | null /** * 【选择行】默认选中的行的 key,请在传入前进行合法校验,因为 Table 对此 Prop 没有过多校验 * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ defaultSelectedRowKeys?: Array | null /** * 空数据源时显示的内容 */ emptyText?: React.ReactNode /** * 【展开行】是否显示展开 icon,如果为 false,意味着 * 1. 使用 expandOnRowClick 或者 * 2. 需要在组件外自行调用组件方法完成展开功能(this.table.handleExpand(row.key))。 */ expandIconVisible?: boolean /** * 【展开行】是否在点击整行时触发展开收起 * 此 prop 会在 .tr 上加入 onClick 事件, * 如果不希望单元格内 onClick 触发事件,请在那些事件中 stopPropagation。 */ expandOnRowClick?: boolean /** * 【展开行】从外部控制,展开的行的 key,请在传入前进行合法校验,因为 Table 对此 Prop 没有过多校验 * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ expandedRowKeys?: Array | null /** * 【展开行】展开行的内容 */ expandedRowRender?: (row: T, rowIndex: number) => React.ReactNode /** * 设置每个单元格的类名:(row, col, rowIndex, colIndex) => (string) */ getCellClassName?: ( row: T, col: IBaseObject, rowIndex: number, colIndex: number ) => string /** * 设置每个单元格上的由组件规定的 prop, * 如 rowSpan colSpan,(row, col, rowIndex, colIndex) => ({}) */ getCellProps?: ( row: T, col: IBaseObject, rowIndex: number, colIndex: number ) => IBaseObject | void /** * 设置每个单元格上的 style,(row, col, rowIndex, colIndex) => ({}) */ getCellStyle?: ( row: T, col: IBaseObject, rowIndex: number, colIndex: number ) => React.CSSProperties | void /** * 设置 thead 的类名:() => (string) */ getHeadClassName?: () => string /** * 设置 thead 上的 style,() => ({}) */ getHeadStyle?: () => React.CSSProperties | void /** * 设置每个 TH 的类名:(col, colIndex) => (string) */ getHeadCellClassName?: (col: IBaseObject, colIndex: number) => string /** * 设置每个 TH 上的 style,(col, colIndex) => ({}) */ getHeadCellStyle?: ( col: IBaseObject, colIndex: number ) => React.CSSProperties | void /** * 设置每行的类名:(row, rowIndex) => (string) */ getRowClassName?: (row: T, rowIndex: number) => string /** * 设置每行上的由组件规定的 prop,其余的则会透传给行元素 * 如 popover,(row, col, rowIndex, colIndex) => ({}) */ getRowProps?: (row: T, rowIndex: number) => { popover?: IPopoverProps } /** * 设置每行的 style,(row, rowIndex) => ({}) */ getRowStyle?: (row: T, rowIndex: number) => React.CSSProperties | void /** * 【选择行】设置每个 Checkbox/Radio 上的 prop (row, rowIndex) => ({}); * 可以在这里回传 disabled: true, 控制该行不能选择;请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ getSelectProps?: (row: T, rowIndex: number) => IBaseObject /** * adui@2.15.0 是否在整体宽度有剩余的情况下填充 */ grow?: boolean /** * 是否需要表头固定到页面上 */ headerAffixed?: boolean /** * 距离窗口顶部达到指定偏移量后触发表头固定 */ headerAffixedOffsetTop?: number /** * 参考 的 getTarget Prop */ headerAffixGetTarget?: (() => HTMLElement) | null /** * 横向滚动条(如果有)在 headerAffixed 开启后,距离容器底部到达指定偏移量后触发固定行为 */ scrollXAffixedOffsetBottom?: number /** * 加重头部,开启之后表头将会加粗 + 背景 */ headerEmphasized?: boolean /** * 指定高度以实现表格内滚动,此 prop 与 prop headerAffixed 互斥。 */ height?: number | null /** * 是否加载中 */ loading?: boolean /** * 【展开行】展开每行时的 handler,Table 也以此 prop 作为开启展开功能的判断; * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ onExpandChange?: ((keys: Array) => void) | null /** * 每行的 click handler,(row, i, e) => {} */ onRowClick?: | ((row: T, rowIndex: number, e?: React.MouseEvent) => void) | null /** * 每行的 mouseEnter handler,(row, i, e) => {} */ onRowMouseEnter?: | ((row: T, rowIndex: number, e?: React.MouseEvent) => void) | null /** * 每行的 mouseLeave handler,(row, i, e) => {} */ onRowMouseLeave?: | ((row: T, rowIndex: number, e?: React.MouseEvent) => void) | null /** * 【选择行】选择每行时的 handler,Table 也以此 prop 作为开启选择功能的判断; * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 * * adui@2.35.0 新增第二个参数 options,此参数是可选的 —— 全选时为 undefined。其中,selected 表示当前行操作是选中还是取消选中,selectedRows 表示当前已选中的行,currentRowKey 表示当前操作行的 key,currentRow 表示当前操作行的数据。 */ onSelectChange?: | (( keys: Array, options?: { selected: boolean selectedRows: T[] currentRowKey: React.ReactText currentRow: T } ) => void) | null /** * Table 滚动时的 handler */ onScroll?: ((e: React.UIEvent) => void) | null /** * 【选择行】多 / 单选;请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ selectMultiple?: boolean /** * 【选择行】是否在点击整行时触发选择。 * 此 prop 会在 .tr 上加入 onClick 事件, * 如果不希望单元格内 onClick 触发事件,请在那些事件中 stopPropagation。 */ selectOnRowClick?: boolean /** * 【选择行】从外部控制,选中的行的 key,请在传入前进行合法校验, * 因为 Table 对此 Prop 没有过多校验; * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ selectedRowKeys?: Array | null /** * 设置尺寸,注意:尺寸仅控制行与列间距,与字体大小不绑定。 */ size?: "mini" | "small" | "medium" | "large" /** * 统一地控制列垂直靠齐方向 */ verticalAlign?: "top" | "center" | "bottom" /** * 虚拟列表的 Props,请参考 https://react-window.vercel.app/#/examples/list/variable-size */ virtualListProps?: IBaseObject /** * 虚拟列表的样式,请在这里指定宽度,默认将会有 500 px 宽度 */ virtualListStyle?: React.CSSProperties /** * 是否开启虚拟滚动,此 Prop 请配合 height Prop 同时使用,用于处理大数据量下的虚拟内滚动; * 此功能的实际用例较少,如果有任何问题请及时反馈 */ virtualScroll?: boolean } export interface ITableState { affixedTop: boolean affixedBottom: boolean children: React.ReactNode combinedCellsInfo: IBaseObject[] expandedRowKeys: Array fixedColumnsInfos: Array mainTableStyle: React.CSSProperties isMainTableOverflowX: boolean isMainTableOverflowY: boolean isResizerShow: boolean currentlyResizing: IBaseObject resized: IBaseObject[] selectedRowKeys: Array } /** * 表格是一种格式化信息的展示形式。通常服务于大量数据浏览、管理场景。 */ class Table extends React.Component< ITableProps, ITableState > { public static Column: typeof Column = Column public static propTypes = { /** * 统一地控制列水平靠齐方向 */ align: PropTypes.oneOf([null, "left", "center", "right"]), /** * 子节点 */ children: PropTypes.node, /** * 附加类名 */ className: PropTypes.string, /** * 以数组的方式传入 Columns,如果传入了此 Prop,则 Table 会忽略 children */ columns: PropTypes.array, /** * 控制整个 Table 的 Columns 能否调整宽度 */ columnsResizable: PropTypes.bool, /** * 数据源 */ dataSource: PropTypes.array, /** * 【展开行】默认展开的行的 key,请在传入前进行合法校验,因为 Table 对此 Prop 没有过多校验 * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ defaultExpandedRowKeys: PropTypes.array, /** * 【选择行】默认选中的行的 key,请在传入前进行合法校验,因为 Table 对此 Prop 没有过多校验 * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ defaultSelectedRowKeys: PropTypes.array, /** * 空数据源时显示的内容 */ emptyText: PropTypes.node, /** * 【展开行】是否显示展开 icon,如果为 false,意味着 * 1. 使用 expandOnRowClick 或者 * 2. 需要在组件外自行调用组件方法完成展开功能(this.table.handleExpand(row.key))。 */ expandIconVisible: PropTypes.bool, /** * 【展开行】是否在点击整行时触发展开收起 * 此 prop 会在 .tr 上加入 onClick 事件, * 如果不希望单元格内 onClick 触发事件,请在那些事件中 stopPropagation。 */ expandOnRowClick: PropTypes.bool, /** * 【展开行】从外部控制,展开的行的 key,请在传入前进行合法校验,因为 Table 对此 Prop 没有过多校验 * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ expandedRowKeys: PropTypes.array, /** * 【展开行】展开行的内容 */ expandedRowRender: PropTypes.func, /** * 设置每个单元格的类名:(row, col, rowIndex, colIndex) => (string) */ getCellClassName: PropTypes.func, /** * 设置每个单元格上的由组件规定的 prop, * 如 rowSpan colSpan,(row, col, rowIndex, colIndex) => ({}) */ getCellProps: PropTypes.func, /** * 设置每个单元格上的 style,(row, col, rowIndex, colIndex) => ({}) */ getCellStyle: PropTypes.func, /** * 设置 thead 的类名:position => (string) */ getHeadClassName: PropTypes.func, /** * 设置 thead 上的 style,position => ({}) */ getHeadStyle: PropTypes.func, /** * 设置每个 TH 的类名:(col, colIndex) => (string) */ getHeadCellClassName: PropTypes.func, /** * 设置每个 TH 上的 style,(col, colIndex) => ({}) */ getHeadCellStyle: PropTypes.func, /** * 设置每行的类名:(row, rowIndex) => (string) */ getRowClassName: PropTypes.func, /** * 设置每行上的由组件规定的 prop,其余的则会透传给行元素 * 如 popover,(row, col, rowIndex, colIndex) => ({}) */ getRowProps: PropTypes.func, /** * 设置每行的 style,(row, rowIndex) => ({}) */ getRowStyle: PropTypes.func, /** * 【选择行】设置每个 Checkbox/Radio 上的 prop (row, rowIndex) => ({}); * 可以在这里回传 disabled: true, 控制该行不能选择;请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ getSelectProps: PropTypes.func, /** * adui@2.15.0 是否在整体宽度有剩余的情况下填充 */ grow: PropTypes.bool, /** * 是否需要表头固定到页面上 */ headerAffixed: PropTypes.bool, /** * 距离窗口顶部达到指定偏移量后触发表头固定 */ headerAffixedOffsetTop: PropTypes.number, /** * 横向滚动条(如果有)在 headerAffixed 开启后,距离容器底部到达指定偏移量后触发固定行为 */ scrollXAffixedOffsetBottom: PropTypes.number, /** * 参考 的 getTarget Prop */ headerAffixGetTarget: PropTypes.func, /** * 加重头部,开启之后表头将会加粗 + 背景 */ headerEmphasized: PropTypes.bool, /** * 指定高度以实现表格内滚动,此 prop 与 prop headerAffixed 互斥。 */ height: (props: ITableProps) => { const { headerAffixed, height } = props if (height) { if (typeof height !== "number") { return new Error( "Invalid prop `height` supplied to `Table`, expected `number`." ) } if (headerAffixed && !!height) { return new Error( "Table.js 不允许同时使用 prop `headerAffixed` 与 `height`,不认可将这两者混用的需求场景。" ) } } return null }, /** * 是否加载中 */ loading: PropTypes.bool, /** * 【展开行】展开每行时的 handler,Table 也以此 prop 作为开启展开功能的判断; * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ onExpandChange: PropTypes.func, /** * 每行的 click handler,(row, i, e) => {} */ onRowClick: PropTypes.func, /** * 每行的 mouseEnter handler,(row, i, e) => {} */ onRowMouseEnter: PropTypes.func, /** * 每行的 mouseLeave handler,(row, i, e) => {} */ onRowMouseLeave: PropTypes.func, /** * 【选择行】选择每行时的 handler,Table 也以此 prop 作为开启选择功能的判断; * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 * * adui@2.35.0 新增第二个参数 options,此参数是可选的 —— 全选时为 undefined。其中,selected 表示当前行操作是选中还是取消选中,selectedRows 表示当前已选中的行,currentRowKey 表示当前操作行的 key,currentRow 表示当前操作行的数据。 */ onSelectChange: PropTypes.func, /** * Table 滚动时的 handler */ onScroll: PropTypes.func, /** * 【选择行】多 / 单选;请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ selectMultiple: PropTypes.bool, /** * 【选择行】是否在点击整行时触发选择。 * 此 prop 会在 .tr 上加入 onClick 事件, * 如果不希望单元格内 onClick 触发事件,请在那些事件中 stopPropagation。 */ selectOnRowClick: PropTypes.bool, /** * 【选择行】从外部控制,选中的行的 key,请在传入前进行合法校验, * 因为 Table 对此 Prop 没有过多校验; * 请确保 dataSource 的对象含有属性 key,及其唯一的值。 */ selectedRowKeys: PropTypes.array, /** * 设置尺寸,注意:尺寸仅控制行与列间距,与字体大小不绑定。 */ size: PropTypes.oneOf(["mini", "small", "medium", "large"]), /** * 统一地控制列垂直靠齐方向 */ verticalAlign: PropTypes.oneOf([null, "top", "center", "bottom"]), /** * 虚拟列表的 Props,请参考 https://react-window.vercel.app/#/examples/list/variable-size */ virtualListProps: PropTypes.object, /** * 虚拟列表的样式,请在这里指定宽度,默认将会有 500 px 宽度 */ virtualListStyle: PropTypes.object, /** * 是否开启虚拟滚动,此 Prop 请配合 height Prop 同时使用,用于处理大数据量下的虚拟内滚动; * 此功能的实际用例较少,如果有任何问题请及时反馈 */ virtualScroll: PropTypes.bool, } public static defaultProps: ITableProps = { align: null, children: "", className: undefined, columns: null, columnsResizable: null, dataSource: [], defaultExpandedRowKeys: null, defaultSelectedRowKeys: null, emptyText: "暂无数据", expandIconVisible: true, expandOnRowClick: false, expandedRowKeys: null, expandedRowRender: () => "", getCellClassName: () => "", getCellProps: noop, getCellStyle: noop, getHeadClassName: () => "", getHeadStyle: noop, getHeadCellClassName: () => "", getHeadCellStyle: noop, getRowClassName: () => "", getRowProps: () => ({}), getRowStyle: noop, getSelectProps: () => ({}), grow: undefined, headerAffixed: false, headerAffixedOffsetTop: 0, headerAffixGetTarget: null, scrollXAffixedOffsetBottom: 0, headerEmphasized: false, height: null, loading: false, onExpandChange: null, onRowClick: null, onRowMouseEnter: null, onRowMouseLeave: null, onSelectChange: null, onScroll: null, selectMultiple: true, selectOnRowClick: false, selectedRowKeys: null, size: "small", verticalAlign: undefined, virtualListProps: {}, virtualListStyle: {}, virtualScroll: false, } public static getDerivedStateFromProps = ({ children, columns, expandedRowKeys, selectedRowKeys, }: ITableProps) => { const newState: Partial = {} if (children !== null || columns !== null) { newState.children = columns || children } if (expandedRowKeys !== null) { newState.expandedRowKeys = expandedRowKeys } if (selectedRowKeys !== null) { newState.selectedRowKeys = selectedRowKeys } return Object.keys(newState).length > 0 ? newState : null } public combinedCellsInfo: IBaseObject[] = [] public affixHeader: Affix public affixScrollbar: Affix public mainTable: HTMLDivElement public mainTableBody: HTMLDivElement public mainThead: HTMLDivElement public wrapper: HTMLDivElement public virtualList: any private debouncedWindowResize: any private resizeEvent: any private columnManager: any private fixedColumnsInfosRef: IFixedColumnsInfo[] = [] constructor(props: ITableProps) { super(props) const { children, columns, defaultExpandedRowKeys, defaultSelectedRowKeys, expandedRowKeys: expandedRowKeysProp, selectedRowKeys: selectedRowKeysProp, } = props this.columnManager = new ColumnManager(columns || children) let selectedRowKeys: Array = [] if (selectedRowKeysProp) { selectedRowKeys = selectedRowKeysProp } else if (defaultSelectedRowKeys) { selectedRowKeys = defaultSelectedRowKeys } let expandedRowKeys: Array = [] if (expandedRowKeysProp) { expandedRowKeys = expandedRowKeysProp } else if (defaultExpandedRowKeys) { expandedRowKeys = defaultExpandedRowKeys } this.state = { affixedBottom: false, affixedTop: false, children: columns || children, combinedCellsInfo: [], currentlyResizing: {}, // 正在改变宽度的列的信息 expandedRowKeys, fixedColumnsInfos: [], isMainTableOverflowX: false, isMainTableOverflowY: false, isResizerShow: false, mainTableStyle: {}, resized: [], // 改变过宽度的所有列的数据 selectedRowKeys, } this.debouncedWindowResize = debounce(this.handleWindowResize, 100) } public shouldComponentUpdate = ( nextProps: ITableProps, nextState: ITableState ) => !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState) public componentDidMount = () => { const { columnManager, combinedCellsInfo, debouncedWindowResize, props } = this const { isAnyColumnsFixed } = columnManager const { headerAffixed, height, virtualScroll } = props // 固定表头需要监听 resize 事件 if (headerAffixed || height || isAnyColumnsFixed) { this.handleWindowResize() this.resizeEvent = addEventListener( window, "resize", debouncedWindowResize ) } if (combinedCellsInfo.length) { this.setState({ combinedCellsInfo }) } setTimeout(() => { const virtualWrapper = this.virtualList?._outerRef if (virtualScroll && virtualWrapper) { this.mainThead.style.paddingRight = "14px" virtualWrapper.addEventListener( "scroll", () => { this.mainThead.scrollLeft = virtualWrapper.scrollLeft }, true ) } }, 0) } public componentDidUpdate = ({ dataSource: dataSourceOld, getCellProps: getCellPropsOld, columns: columnsOld, }: ITableProps) => { const { dataSource, getCellProps, columns } = this.props /** * handleWindowResize 不应该只在 didmount 时执行 * didUpdate 时也需要 */ if (!shallowEqual(dataSourceOld, dataSource)) { setTimeout(this.handleWindowResize, 0) } if (!shallowEqual(columnsOld, columns)) { columns?.forEach(({ dataIndex, fixed, width }, index) => { if (typeof width === "number" && fixed) { this.handleThResize(columns.length, width, dataIndex, index, fixed) } }) } /** * 合并单元格需拿到真实的 dom 元素计算尺寸,因此这里需要 forceUpdate */ if (!shallowEqual(getCellPropsOld, getCellProps) && getCellProps) { this.forceUpdate() } } public componentWillUnmount = () => { if (this.resizeEvent) { this.resizeEvent.remove() } if (this.debouncedWindowResize) { this.debouncedWindowResize.clear() } } public resizeColumnStart = ( e: React.MouseEvent, col: IColumnProps ) => { e.stopPropagation() document.body.style.cursor = "col-resize" this.setState({ currentlyResizing: { col, dataIndex: col.dataIndex, parentWidth: e.currentTarget.parentElement && e.currentTarget.parentElement.parentElement && e.currentTarget.parentElement.parentElement.getBoundingClientRect() .width, startX: e.pageX, }, }) setTimeout(() => { window.addEventListener("mousemove", this.resizeColumnMoving) window.addEventListener("mouseup", this.resizeColumnEnd) window.addEventListener("mouseleave", this.resizeColumnEnd) }, 0) } public resizeColumnMoving = (e: MouseEvent) => { e.stopPropagation() const { resized, currentlyResizing } = this.state const { col, dataIndex, parentWidth, startX } = currentlyResizing const title = col.title || "" /** * 13 代表字号;用 em 的方式计算宽度本身不够合理,这里 + 2,不然在 Safari 下空间会不够。 */ let baseWidth = Math.ceil(title.length > 4 ? title.length / 2 : title.length) * 13 + 2 if (!baseWidth) { baseWidth = 100 } const newResized = resized.filter((o) => o.dataIndex !== dataIndex) const newWidth = Math.max( Math.ceil(parentWidth + e.pageX - startX), !!col.onSort || !!col.onFilter ? baseWidth + 36 : baseWidth + 16 ) newResized.push({ dataIndex, value: newWidth, }) this.setState({ resized: newResized, }) this.debouncedWindowResize() } public resizeColumnEnd = (e: MouseEvent) => { const { mainTable } = this document.body.style.cursor = "" e.stopPropagation() window.removeEventListener("mousemove", this.resizeColumnMoving) window.removeEventListener("mouseup", this.resizeColumnEnd) window.removeEventListener("mouseleave", this.resizeColumnEnd) this.setState({ currentlyResizing: {}, isMainTableOverflowX: mainTable && mainTable.scrollWidth > mainTable.offsetWidth, isMainTableOverflowY: mainTable && mainTable.scrollHeight > mainTable.offsetHeight, isResizerShow: false, }) } public handleWindowResize = () => { const { columnManager, mainTable, props } = this const { isAnyColumnsFixed } = columnManager const { headerAffixed, height } = props if ((headerAffixed || height || isAnyColumnsFixed) && mainTable) { const { mainTableStyle: oldStyle } = this.state const rect = mainTable.getBoundingClientRect() const mainTableStyle: Partial = {} mainTableStyle.left = rect.left mainTableStyle.width = rect.width if (!shallowEqual(mainTableStyle, oldStyle)) { this.setState({ mainTableStyle }) } this.setState({ isMainTableOverflowX: mainTable && mainTable.scrollWidth > mainTable.offsetWidth, isMainTableOverflowY: mainTable && mainTable.scrollHeight > mainTable.offsetHeight, }) } } public handleMainTableScroll = (e?: React.UIEvent) => { const { affixHeader, mainTable } = this const { onScroll } = this.props const { affixedTop } = this.state if (onScroll && e) { // e 可能是 thead const target = e.target as HTMLDivElement if (target.getAttribute("role") === "table") { onScroll(e) } } if (affixedTop && affixHeader && mainTable && affixHeader.fixedNode) { affixHeader.fixedNode.scrollLeft = mainTable.scrollLeft } // 固定头的情况,需要同步固定滚动条和主 Table 滚动条 this.syncTableScrollPosition(mainTable.scrollLeft) } public syncTableScrollPosition = (value: number) => { const { affixScrollbar, mainTable, mainThead } = this const { height } = this.props if (mainTable && affixScrollbar && affixScrollbar.fixedNode) { if (mainTable.scrollLeft !== value) { mainTable.scrollLeft = value } if (affixScrollbar.fixedNode.scrollLeft !== value) { affixScrollbar.fixedNode.scrollLeft = value } } // 如果设定了 prop height,并且存在横向滚动条,则要同步 thead 的滚动位置 if (height && mainThead) { mainThead.scrollLeft = value } } public handleRowClick = ( row: T, i: number, e: React.MouseEvent ) => { const { expandOnRowClick, getSelectProps, onRowClick, selectOnRowClick } = this.props const { selectedRowKeys } = this.state const { key } = row if ( selectOnRowClick && typeof key !== "undefined" && !( getSelectProps && getSelectProps(row, i) && getSelectProps(row, i).disabled ) ) { this.handleSelect(key, !selectedRowKeys.includes(key)) } if (expandOnRowClick) { this.handleExpand(key) } if (onRowClick) { onRowClick(row, i, e) } } public handleThResize = ( columnsLength: number, width: number, dataIndex: string, index: number, fixedPosition?: boolean | "left" | "right" ) => { const { getColumns } = this.columnManager let { fixedColumnsInfosRef: fixedColumnsInfos } = this const columns = getColumns() as IColumnProps[] /** * columns 变化后 fixedColumnsInfos 中可能存在已经移除的 column */ fixedColumnsInfos = fixedColumnsInfos.filter((o) => columns.find((p) => p.dataIndex === o.dataIndex && !!p.fixed) ) const i = fixedColumnsInfos.findIndex((o) => o.dataIndex === dataIndex) // fixed 可能是 Boolean const fixed = fixedPosition === "right" ? "right" : "left" if (i > -1) { if ( fixedColumnsInfos[i].width === width && fixedColumnsInfos[i].columnsLength === columnsLength && fixedColumnsInfos[i].index === index ) { return } fixedColumnsInfos.splice(i, 1) } fixedColumnsInfos.push({ width, dataIndex, index, fixed, columnsLength }) fixedColumnsInfos.sort((a, b) => a.index - b.index) const fixedColumnsInfosLeft = fixedColumnsInfos.filter( (o) => o.fixed === "left" ) const fixedColumnsInfosRight = fixedColumnsInfos .filter((o) => o.fixed === "right") .sort((a, b) => b.index - a.index) fixedColumnsInfos.forEach((col) => { let colIndex if (col.fixed === "left") { colIndex = fixedColumnsInfosLeft.findIndex( (o) => o.dataIndex === col.dataIndex ) if (colIndex === fixedColumnsInfosLeft.length - 1) { col.isLastLeft = true } else { col.isLastLeft = false } // 容错,不允许 slice(0, -1) 的危险操作 col.offset = colIndex === -1 ? 0 : fixedColumnsInfosLeft .slice(0, colIndex) .reduce((acc, cur) => acc + cur.width, 0) } else { colIndex = fixedColumnsInfosRight.findIndex( (o) => o.dataIndex === col.dataIndex ) if (colIndex === fixedColumnsInfosRight.length - 1) { col.isFirstRight = true } else { col.isFirstRight = false } // 容错,不允许 slice(0, -1) 的危险操作 col.offset = colIndex === -1 ? 0 : fixedColumnsInfosRight .slice(0, colIndex) .reduce((acc, cur) => acc + cur.width, 0) } }) this.fixedColumnsInfosRef = fixedColumnsInfos this.setState({ fixedColumnsInfos }) } public handleTheadMouseEnter = () => { const { isResizerShow } = this.state if (!isResizerShow) { this.setState({ isResizerShow: true, }) } } public handleTheadMouseLeave = () => { const { currentlyResizing } = this.state if (Object.keys(currentlyResizing).length === 0) { this.setState({ isResizerShow: false, }) } } /** * 展开行 */ public handleExpand = (key: React.ReactText) => { if (typeof key === "undefined") { return } const { expandedRowKeys: expandedRowKeysProp, onExpandChange } = this.props const { expandedRowKeys } = this.state const keys = [...expandedRowKeys] const index = keys.indexOf(key) if (index > -1) { keys.splice(index, 1) } else { keys.push(key) } if (expandedRowKeysProp === null) { this.setState({ expandedRowKeys: keys }) } if (onExpandChange) { onExpandChange(keys) } } /** * 选择行 */ public handleSelect = (key: React.ReactText, selected: boolean) => { const { onSelectChange, selectedRowKeys: selectedRowKeysProp, selectMultiple, dataSource, } = this.props const currentRow = dataSource?.find((o) => o.key === key) if (typeof key === "undefined" || !dataSource?.length || !currentRow) { return } const { selectedRowKeys } = this.state let keys = [...selectedRowKeys] if (selectMultiple) { if (selected) { keys.push(key) } else { keys.splice(selectedRowKeys.indexOf(key), 1) } } else { keys = [key] } if (selectedRowKeysProp === null) { this.setState({ selectedRowKeys: keys, }) } if (onSelectChange) { onSelectChange(keys, { selected: !selectMultiple || selected, currentRowKey: key, selectedRows: dataSource.filter((o) => keys.includes(o.key)), currentRow, }) } } /** * 选择行全选操作 */ public handleSelectAll = (checked: boolean) => { const { onSelectChange, selectedRowKeys: selectedRowKeysProp } = this.props const { selectedRowKeys } = this.state const keys = [...selectedRowKeys] const availableKeys = this.getAvailableRowsKeys() if (checked) { availableKeys.forEach((key) => { if (!selectedRowKeys.includes(key)) { keys.push(key) } }) } else { // 不能直接清空数组,因为可能传入了与当前 dataSource 无关的 keys; // 也不能直接 splice,因为要检查是否 getSelectProps.disabled。 selectedRowKeys.forEach((key) => { if (availableKeys.includes(key)) { keys.splice(keys.indexOf(key), 1) } }) } if (selectedRowKeysProp === null) { this.setState({ selectedRowKeys: keys, }) } if (onSelectChange) { onSelectChange(keys) } } /** * 判断是否已选中,all or one */ public hasSelected = (mode: "all" | "one") => { const keys = this.getAvailableRowsKeys() const { dataSource } = this.props const { selectedRowKeys } = this.state if ( dataSource && dataSource.length && keys.length && (mode === "all" ? keys.every((key) => selectedRowKeys.includes(key)) : keys.some((key) => selectedRowKeys.includes(key))) ) { return true } return false } public getAvailableRowsKeys = () => { const { dataSource, getSelectProps } = this.props const keys: Array = [] if (!dataSource) { return [] } dataSource.forEach((row, i) => { const { key } = row if ( typeof key !== "undefined" && !( getSelectProps && getSelectProps(row, i) && getSelectProps(row, i).disabled ) ) { keys.push(key) } }) return keys } public generateTable = () => { const { dataSource, expandIconVisible, expandedRowRender, expandOnRowClick, getHeadClassName, getHeadStyle, getRowClassName, getRowProps, getRowStyle, getSelectProps, grow, headerAffixed, headerAffixedOffsetTop, headerAffixGetTarget, scrollXAffixedOffsetBottom, height, onExpandChange, onRowMouseEnter, onRowMouseLeave, onSelectChange, selectMultiple, selectOnRowClick, size, verticalAlign, virtualListProps, virtualListStyle, virtualScroll, } = this.props const { affixedTop, affixedBottom, expandedRowKeys, fixedColumnsInfos, isMainTableOverflowX, isMainTableOverflowY, mainTableStyle, resized, selectedRowKeys, } = this.state const { getColumns, getGroupColumnsDepth, isAnyColumnsLeftFixed } = this.columnManager const columns = getColumns() as IColumnProps[] const theadStyle: React.CSSProperties = { height: `${TD_HEIGHT[size || "small"] * getGroupColumnsDepth()}px`, ...((getHeadStyle && getHeadStyle()) || {}), } /** * 展开行和选择行时,thead和每行的第一列前都要加入一列 * 此列也会影响 sticky 的偏移位置 */ const selectAndExpand = !!onSelectChange && !!onExpandChange const theadPlaceholderVisible = expandIconVisible && (!!onSelectChange || !!onExpandChange) const thead = (
{theadPlaceholderVisible && (
{selectMultiple && !!onSelectChange && (() => { const hasSelectedAll = this.hasSelected("all") const hasSelectedOne = this.hasSelected("one") return (
) })()}
)} {columns.map((col, index) => { const { align, dataIndex, filters, fixed, grow: colGrow, onFilter, onSort, sortOrder, title, width, } = col const isGrow = colGrow !== undefined ? colGrow : grow const resizedCol = resized.find((o) => o.dataIndex === dataIndex) const clickable = !!onSort || (sortOrder !== null && sortOrder !== undefined) || !!onFilter || !!filters const sortableAndFilterable = (!!onSort || (sortOrder !== null && sortOrder !== undefined)) && (!!onFilter || !!filters) let minWidth = 0 if (typeof title === "string") { minWidth = Math.ceil(title.length > 4 ? title.length / 2 : title.length) * 13 + 16 if (clickable) { minWidth += 20 } } let flexValue if (typeof width === "number") { flexValue = `${width} 0 auto` } else if (typeof width === "string") { if (width.length - width.lastIndexOf("px") === 2) { flexValue = `${width.slice(0, -2)} 0 auto` } else { flexValue = `0 0 ${width}` } } else { flexValue = `${Math.max(TD_MIN_WIDTH, minWidth)} 0 auto` } /** * th left 值:取得 resize-observer 存储的该列的偏移量,加上如果有选择行、展开行时的 SELECT_TD_WIDTH —— 仅对 left 有效 * th right 值:取得 resize-observer 存储的该列的偏移量,加上如果内滚动时的滚动条宽度(14) —— 仅对 right 有效 */ return ( { this.handleThResize( columns.length, widthResize, dataIndex, index, fixed ) }} disabled={!fixed} key={`${dataIndex || index}_${columns.length}`} >
o.dataIndex === dataIndex )?.isLastLeft, [`${prefix}-th_rightFirst`]: fixedColumnsInfos.find( (o) => o.dataIndex === dataIndex )?.isFirstRight, })} key={dataIndex ? `${dataIndex}_${index}` : index} style={{ left: isFixedLeft(col) ? (fixedColumnsInfos.find((o) => o.dataIndex === dataIndex) ?.offset || 0) + (theadPlaceholderVisible ? selectAndExpand ? SELECT_AND_EXPAND_TD_WIDTH : SELECT_TD_WIDTH : 0) : undefined, right: isFixedRight(col) ? (fixedColumnsInfos.find((o) => o.dataIndex === dataIndex) ?.offset || 0) + (isMainTableOverflowY && height ? 14 : 0) : undefined, flex: resizedCol ? `${resizedCol.value} 0 auto` : flexValue, maxWidth: isGrow ? undefined : resizedCol ? resizedCol.value : width || "", textAlign: align || undefined, width: resizedCol ? resizedCol.value : width || Math.max(TD_MIN_WIDTH, minWidth), }} data-column={index} data-column-key={dataIndex ? `${dataIndex}_${index}` : index} > {this.generateThCell(col, { index, columns })} {col.children && (
{col.children.map( (childCol: IColumnProps, childColIndex: number) => (
{this.generateThCell(childCol)} {childCol.children && (
{childCol.children.map( ( grandCol: IColumnProps, grandColIndex: number ) => (
{this.generateThCell(grandCol)}
) )}
)}
) )}
)}
) })}
) const generateTrs = ( row: T, rowIndex: number, style?: React.CSSProperties ) => { const { key } = row const colArray: any = [] const selectPropsGetted = getSelectProps && getSelectProps(row, rowIndex) const allSelectProps = { checked: selectedRowKeys.includes(key), className: `${prefix}-selectComponent`, onChange: (checked: boolean) => this.handleSelect(key, checked), onClick: (e: React.MouseEvent) => e.stopPropagation(), ...(selectPropsGetted || {}), } const selectProps = omit(allSelectProps, ["popoverProps"]) const selectContent = selectMultiple ? ( ) : ( ) const expandContent = (
this.handleExpand(key)} role="none" >
) let selectCell = onExpandChange ? expandContent : selectContent if (selectAndExpand) { selectCell = (
{selectContent} {expandContent}
) } const generateTr = (columnsParam: any[], parentIndex?: number) => { columnsParam.forEach((col, colIndexParam) => { /** * 如果前面 的 col 有 children,则要把 children.length 额外加到 colIndex */ let colIndex = colIndexParam if (parentIndex) { for (let i = 0; i < parentIndex; i += 1) { const childrenCount = columns[i].children?.length || 1 if (childrenCount) { colIndex += childrenCount } } } if (!col.children) { colArray.push(this.generateTbodyCell(row, col, rowIndex, colIndex)) } else { generateTr(col.children, colIndexParam) } }) } generateTr(columns) let tr = (
onRowMouseEnter(row, rowIndex, e) : undefined } onMouseLeave={ onRowMouseLeave ? (e) => onRowMouseLeave(row, rowIndex, e) : undefined } onClick={(e) => this.handleRowClick(row, rowIndex, e)} onKeyDown={noop} style={{ ...((getRowStyle && getRowStyle(row, rowIndex)) || {}), ...(style || {}), }} {...omit((getRowProps && getRowProps(row, rowIndex)) || {}, [ "popover", ])} > {theadPlaceholderVisible && (
{selectPropsGetted?.popoverProps?.popup ? ( {selectCell} ) : ( selectCell )}
)} {colArray}
) if (getRowProps) { const { popover } = getRowProps(row, rowIndex) if (popover?.popup) { tr = {tr} } } if (onExpandChange) { return ( {tr} {expandedRowKeys.includes(key) ? (
{!!expandedRowRender && expandedRowRender(row, rowIndex)}
) : null}
) } return tr } return [ headerAffixed ? ( this.wrapper} getTarget={headerAffixGetTarget} width={mainTableStyle.width} onChange={(bool) => { this.setState({ affixedTop: bool }, () => { // 固定的那一刻也需要立即重新定位。 // 重新定位、包括下面的 className, style 只在主表格上发生,因为固定列的 Affix 行为很单纯。 if (bool) { this.handleWindowResize() this.handleMainTableScroll() } }) }} className={affixedTop ? `${prefix}-affix` : ""} style={{ display: "flex", zIndex: 2, alignItems: "baseline", flexDirection: "column", ...(affixedTop ? mainTableStyle : {}), }} ref={this.saveRef("affixHeader")} > {thead} ) : ( thead ),
{!virtualScroll && dataSource && !!dataSource.length && dataSource.map((row, rowIndex) => { return generateTrs(row, rowIndex) })} {virtualScroll && dataSource && !!dataSource.length && ( 42} className={`${prefix}-virtual-wrapper`} style={{ ...virtualListStyle, }} ref={(o: any) => { this.virtualList = o }} {...virtualListProps} onScroll={(options: { scrollDirection: "forward" | "backward" scrollOffset: number }) => { if (virtualListProps?.onScroll) { virtualListProps.onScroll(options) } }} > {({ index, style, }: { index: number style: React.CSSProperties }) => generateTrs(dataSource[index], index, style)} )}
, isMainTableOverflowX && headerAffixed && ( this.mainTableBody} getTarget={headerAffixGetTarget} // @ts-ignore ref={this.saveRef("affixScrollbar")} className={classNames(`${prefix}-affixScrollbar`, { [`${prefix}-affixScrollbar_show`]: affixedBottom, })} style={{ zIndex: 2, ...mainTableStyle, }} onScroll={(e: React.SyntheticEvent) => this.syncTableScrollPosition(e.currentTarget.scrollLeft) } onChange={(bool) => this.setState({ affixedBottom: bool })} >
), ] } public generateThCell = (col: IColumnProps, options?: IBaseObject) => { const { align, columnsResizable, getHeadCellClassName: getHeadCellClassNameProp, getHeadCellStyle: getHeadCellStyleProp, } = this.props const { resizable: colResizable, getHeadCellClassName, getHeadCellStyle, onSort, sortOrder, onFilter, filters, } = col const clickable = !!onSort || (sortOrder !== null && sortOrder !== undefined) || !!onFilter || !!filters const sortableAndFilterable = (!!onSort || (sortOrder !== null && sortOrder !== undefined)) && (!!onFilter || !!filters) let resizable = true if (typeof colResizable === "boolean") { resizable = colResizable } else if (typeof columnsResizable === "boolean") { resizable = columnsResizable } const { currentlyResizing, isResizerShow } = this.state const isLastTh = options && options.index === options.columns.length - 1 const thCell = (
{(!clickable || sortableAndFilterable) && col.title} {(!!col.onSort || (col.sortOrder !== null && col.sortOrder !== undefined)) && ( (col.onSort ? col.onSort(order) : {})} sortOrder={col.sortOrder} title={col.title} /> )} {(!!col.onFilter || !!col.filters) && ( { if (col.onFilter) { col.onFilter(filter) } }} onFilterVisibleChange={(visible) => { if (col.onFilterVisibleChange) { col.onFilterVisibleChange(visible) } }} title={col.title} /> )} {resizable && !isLastTh && (
this.resizeColumnStart(e, col)} role="none" /> )}
) if (col.popover) { return ( {thCell} ) } return thCell } public generateTbodyCell = ( row: T, cell: IColumnProps, rowIndex: number, cellIndex: number ) => { const { align: alignProp, expandIconVisible, getCellClassName: getCellClassNameProp, getCellProps: getCellPropsProp, getCellStyle: getCellStyleProp, grow, onExpandChange, onSelectChange, verticalAlign: verticalAlignProp, } = this.props const selectAndExpand = !!onSelectChange && !!onExpandChange const theadPlaceholderVisible = expandIconVisible && (!!onSelectChange || !!onExpandChange) const { currentlyResizing, fixedColumnsInfos, resized } = this.state const { align, dataIndex, filters, grow: colGrow, onFilter, onSort, render, sortOrder, title, verticalAlign, width, getCellClassName, getCellProps, getCellStyle, } = cell const isGrow = colGrow !== undefined ? colGrow : grow const clickable = !!onSort || (sortOrder !== null && sortOrder !== undefined) || !!onFilter || !!filters let minWidth = 0 if (typeof title === "string") { minWidth = Math.ceil(title.length > 4 ? title.length / 2 : title.length) * 13 + 16 if (clickable) { minWidth += 20 } } let flexValue if (typeof width === "number") { flexValue = `${width} 0 auto` } else if (typeof width === "string") { if (width.length - width.lastIndexOf("px") === 2) { flexValue = `${width.slice(0, -2)} 0 auto` } else { flexValue = `0 0 ${width}` } } else { flexValue = `${Math.max(TD_MIN_WIDTH, minWidth)} 0 auto` } const resizedCol = resized.find((o) => o.dataIndex === dataIndex) const cellClassName = (getCellClassName && getCellClassName(row, rowIndex)) || (getCellClassNameProp && getCellClassNameProp(row, cell, rowIndex, cellIndex)) const cellProps = (getCellProps && getCellProps(row, rowIndex)) || (getCellPropsProp && getCellPropsProp(row, cell, rowIndex, cellIndex)) || {} const cellStyle = (getCellStyle && getCellStyle(row, rowIndex)) || (getCellStyleProp && getCellStyleProp(row, cell, rowIndex, cellIndex)) || {} const { rowSpan, colSpan, ...otherCellProps } = cellProps if (rowSpan || colSpan) { this.combinedCellsInfo.push({ cell, cellIndex, colSpan, row, rowIndex, rowSpan, }) } return (
o.dataIndex === dataIndex )?.isLastLeft, [`${prefix}-td_rightFirst`]: fixedColumnsInfos.find( (o) => o.dataIndex === dataIndex )?.isFirstRight, [`${prefix}-td_combined`]: rowSpan || colSpan, })} key={dataIndex ? `${dataIndex}_${cellIndex}` : cellIndex} style={{ left: isFixedLeft(cell) ? (fixedColumnsInfos.find((o) => o.dataIndex === dataIndex) ?.offset || 0) + (theadPlaceholderVisible ? selectAndExpand ? SELECT_AND_EXPAND_TD_WIDTH : SELECT_TD_WIDTH : 0) : undefined, right: isFixedRight(cell) ? fixedColumnsInfos.find((o) => o.dataIndex === dataIndex)?.offset : undefined, flex: resizedCol ? `${resizedCol.value} 0 auto` : flexValue, width: resizedCol ? resizedCol.value : width || Math.max(TD_MIN_WIDTH, minWidth), maxWidth: isGrow ? undefined : resizedCol ? resizedCol.value : width || "", }} role="cell" data-row={rowIndex} data-column={cellIndex} {...otherCellProps} >
{render ? render(row, cell, rowIndex, cellIndex) : row[dataIndex]} {(rowSpan || colSpan) && (
{render ? render(row, cell, rowIndex, cellIndex) : row[dataIndex]}
)}
) } public getCombinedCellStyle = ( _: IBaseObject, __: IColumnProps, rowIndex: number, colIndex: number, rowSpan: number, colSpan: number ) => { const getTableCell = (rowIndexParam: number, colIndexParam: number) => { if (this.mainTable) { const row = this.mainTable.querySelectorAll('[role="row"]')[rowIndexParam] if (row) { return row.children[colIndexParam] } } return null } const cell = getTableCell(rowIndex, colIndex) const style: React.CSSProperties = {} if (rowSpan < 2 || typeof rowSpan === "undefined") { // 最后 - 1 是为了不挡住 cell 的 box-shadow style.height = "calc(100% - 1px)" } else { const endCell = getTableCell(rowIndex + rowSpan - 1, colIndex) if (cell && endCell) { const cellRect = cell.getBoundingClientRect() const endCellRect = endCell.getBoundingClientRect() // 最后 - 1 是为了不挡住 cell 的 box-shadow style.height = `${ endCellRect.height + endCellRect.top - cellRect.top - 1 }px` } } if (colSpan < 2 || typeof colSpan === "undefined") { // 最后 - 1 是为了不挡住 cell 的 box-shadow style.width = "calc(100% - 1px)" } else { const endCell = getTableCell(rowIndex, colIndex + colSpan - 1) if (cell && endCell) { const cellRect = cell.getBoundingClientRect() const endCellRect = endCell.getBoundingClientRect() // 最后 - 1 是为了不挡住 cell 的 box-shadow style.width = `${ endCellRect.width + endCellRect.left - cellRect.left - 1 }px` } } return style } public saveRef = ( name?: | "affixHeader" | "mainTable" | "mainTableBody" | "mainThead" | "wrapper" ) => (node: any) => { if (name) { this[name] = node } } public render() { const { className, dataSource, emptyText, headerAffixed, headerAffixedOffsetTop, headerAffixGetTarget, headerEmphasized, height, loading, size, virtualScroll, ...otherProps } = this.props const restProps = omit(otherProps, [ "align", "children", "columns", "columnsResizable", "defaultExpandedRowKeys", "defaultSelectedRowKeys", "expandIconVisible", "expandOnRowClick", "expandedRowKeys", "expandedRowRender", "getCellClassName", "getCellProps", "getCellStyle", "getHeadClassName", "getHeadStyle", "getHeadCellClassName", "getHeadCellStyle", "getRowClassName", "getRowProps", "getRowStyle", "getSelectProps", "grow", "onExpandChange", "onSelectChange", "onScroll", "onRowClick", "onRowMouseEnter", "onRowMouseLeave", "selectMultiple", "selectedRowKeys", "selectOnRowClick", "scrollXAffixedOffsetBottom", "verticalAlign", "virtualListProps", "virtualListStyle", ]) const { children, isMainTableOverflowX, isMainTableOverflowY } = this.state if (children) { this.columnManager.reset(children) } const { getGroupColumnsDepth } = this.columnManager const empty = !(dataSource && dataSource.length) const classSet = classNames( className, `${prefix}-wrapper`, `${prefix}-${size}`, { [`${prefix}-headerEmphasized`]: headerEmphasized, [`${prefix}-empty`]: empty, [`${prefix}-loading`]: loading, [`${prefix}-overflow-x`]: isMainTableOverflowX, [`${prefix}-overflow-y`]: isMainTableOverflowY, [`${prefix}-virtualScroll`]: virtualScroll, } ) return (
{loading && (headerAffixed ? ( this.wrapper} getTarget={headerAffixGetTarget} className={`${prefix}-progress`} > ) : (
))}
{this.generateTable()} {empty && (
{emptyText}
)}
) } } export default Table