import React, { useEffect, useState, useMemo, useCallback, useRef, } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' import { Table, Form, Pagination, Empty } from 'antd' import { TableProps, TableEventListeners, TableRowSelection, ColumnProps } from 'antd/lib/table' import { TooltipPlacement } from 'antd/lib/tooltip' import { PaginationConfig as AntPaginationConfig } from 'antd/lib/pagination' import { compose } from 'recompose' import _ from 'lodash' import warning from '@util/warning' import { renderColumnItem, useRowSelection, getStorageWidth, switchIndex, usePagination, setMainTableBorder, diffList, setStyle, computeIndexAndRowKey, getComputedColIndex, computeIndex, getVirtualList, getPureRecord, cloneDatasource, getStyleText, originKey } from './_utils' import Header from '@header' import { EditStatus } from '@data-cell' import TableComponent from './tableRef' import BodyCell from './bodycell' import BodyRow from './bodyrow' import BodyWrapper from './bodywrapper' import HeaderCell from './headercell' import HeaderRow from './headerrow' import { DataContext, TableContext, TableBodyWrapperContext } from './context' import { tableWrapperRef, scrollIntoViewWithRowKey } from './compose' const defaultKey = "key" type Direction = 'row' | 'row-reverse' const defaultProps = { bordered: true, columns: [], dataSource: [], pagination: null, resizable: true, headerMarginBottom: 10, rowSelection: null, isZebra: true, // 斑马纹 editable: EditStatus.CANCEL, wrap: false, footerDirection: 'row' as Direction, orderList: [], light: false, cellPadding: 4, headerProps: {}, emptyDescription: '暂无数据', withIndex: -1, onSave: (list: Array, diffData: Array>) => { }, } export type Order = { fieldName: string, orderType: string } export type Record = { children?: Record[], isDeleted?: boolean, [key: string]: any } export type RowKey = (record: T, index?: number) => string export type ProtoExtends = U & { [K in Exclude]?: NonNullable } export type PartRequired = Required> & Partial> // 重写RowSelection类型 export type RowSelection = ProtoExtends, { selectedRowKeys: string[], onSelect?: (rows: any, selected: boolean) => void, preventDefault?: boolean, clickable?: boolean, showFooterSelection?: boolean }> export type EditRender = (value: string, record: T, rowIndex: number) => React.ReactElement export type EditConfig = { render: EditRender, showDirt?: boolean, editValue?: string | ((record: T, rowIndex: number, dataIndex: string) => string), clickable?: boolean | ((record: T, rowIndex: number, dataIndex: string) => boolean) } // 重写column类型 export interface GColumnProps extends ColumnProps { showFilter?: boolean, codeData?: string, editConfig?: EditConfig, showTip?: boolean, placement?: TooltipPlacement, expandColumn?: boolean, // 是否是展开列 } // editaction export type EditActions = ([list, Fn]: [Array, React.Dispatch>>], keys: Array) => React.ReactElement // ondragend export type OnDragEnd = (list: Array) => void // tail export type Tail = (currentPageData: Array) => React.ReactNode export type PaginationConfig = AntPaginationConfig | null | false export interface RowHeight { (index: number, Record: T): number | string, } export interface VirtualScroll { threshold?: number, // 单页显示的行数 rowHeight?: number | string, //| RowHeight, center?: boolean } const defaultVirtualScrollConfig: VirtualScroll = { threshold: 20, rowHeight: 24, center: true, } // 定义GantTableProps基础类型 interface Props { cellPadding: React.ReactText, dataSource: T[], columns: GColumnProps[], editActions: EditActions, emptyDescription: React.ReactNode, flex?: boolean, footerDirection: Direction, headerLeft: React.ReactElement, headerMarginBottom: number, headerRight: React.ReactNode, onDragEnd: OnDragEnd, orderList: Array, pagination: PaginationConfig, resizable: boolean, rowSelection: RowSelection, scrollKey: string, spacing: React.ReactText, tableKey: string, tail: Tail, title: React.ReactNode, onScroll: Function, // deprecated // resizeCell: boolean, virtualScroll: VirtualScroll | true, } // 定义GantTableProps继承TableProps type GantTableProps = ProtoExtends, Props> // 定义GantTableProps默认参数 type GantTableDefaultProps = Partial // 包含默认值属性,对外暴露的Props类型 export type GantTableListOuterProps = PartRequired, GantTableProps>, 'columns' | 'dataSource'> type GantTableListProps = GantTableListOuterProps & { tableWraper: HTMLDivElement }; // 滚动元素类型 export type ScrollElement = HTMLElement & { scrollTopBackUp: number, scrollloaded: boolean } const GantTableList = function GantTableList(props: GantTableListProps, ref: React.Ref) { const { pagination, rowSelection, rowKey, dataSource, isZebra, editable, onRow: originOnRow, bordered: customBorderd, scroll, wrap, footerDirection, flex, orderList, light, spacing, cellPadding, onSave, expandedRowKeys, onExpand, onExpandedRowsChange, headerRight, editActions, tableWraper, withIndex, virtualScroll: virtualScrollConfig, resizable: resizeCell, onScroll, defaultExpandAllRows } = props /* =======================warning======================= */ if (process.env.NODE_ENV !== "production") { warning( !("flex" in props), `GantTableList \`flex\` is deprecated, please do not use` ) warning( !("resizeCell" in props), `GantTableList \`resizeCell\` is deprecated, please use \`resizable\` instead` ) } const [tableKey] = useState(() => { // 用于缓存列宽度 if (props.tableKey) { return props.tableKey } if (props.columns && props.columns.length) { const str = props.columns.map(item => item.dataIndex + item.title).join('') return window.escape(str).replace(/%u/g, '') } return Math.random().toString(32).slice(2) }) const computedRowKey: RowKey = useCallback( (record, index) => { if (!record) return index const recordKey = typeof rowKey === 'function' ? rowKey(record, index) : record[rowKey || defaultKey]; return recordKey === undefined ? index : recordKey }, [rowKey] ) const scrollY = useMemo(() => _.get(scroll, 'y') as string | number, [scroll]) // 有子节点禁用排序功能 //#region // 是否是树形结构 const isTree: boolean = useMemo(() => dataSource.some(item => _.get(item, 'children.length')), [dataSource]) // 树状结构或者编辑状态下 禁止拖拽 const sortable = useMemo(() => isTree ? false : !!props.onDragEnd, [isTree, props.onDragEnd]) const [lock, setLock] = useState(true) // 控制onSave回调 const isEdit = useMemo(() => editable === EditStatus.EDIT, [editable]) // level-1层数据复制dataSource,防止数据污染 const dataList = useMemo(() => _.cloneDeep(dataSource), [dataSource]) // level-1层数据,编辑时数据 const [cacheDataList, setCacheDataList] = useState([]) useEffect(() => { // edit状态下打开锁,其他状态不变 if (isEdit) { setLock(false) } setCacheDataList(list => { if (isEdit) { return cloneDatasource(dataList) } return [] }) }, [dataList, isEdit]) //#endregion // 处理表格columns,可能有嵌套头部 //#region const [columns, setColumns] = useState(props.columns); useEffect(() => { setColumns(props.columns) }, [props.columns]) // 展开的expandedRowKeys const [expandRowKeys, setexpandRowKeys] = useState([]) useEffect(() => { if (Array.isArray(expandedRowKeys)) { setexpandRowKeys(expandedRowKeys) } }, [expandedRowKeys]) // 是否触发虚拟滚动 const virtualScroll = useMemo(() => !!(scrollY && virtualScrollConfig), [scrollY, virtualScrollConfig]) const virtualScrollConfigInner = useMemo>(() => (virtualScroll ? virtualScrollConfig === true ? defaultVirtualScrollConfig : { ...defaultVirtualScrollConfig, ...virtualScrollConfig } : {} as VirtualScroll), [virtualScroll, virtualScrollConfig]) // 虚拟滚动的条数 const thresholdInner = useMemo(() => _.get(virtualScrollConfigInner, 'threshold', defaultVirtualScrollConfig.threshold), [virtualScrollConfigInner]) /** * level-2层数据 * 根据是否编辑获取数据列表 * 同时添加g-index序号,获取所有可展开rowKey * 编辑状态下会有__origin属性 */ const [dataListWithIndex, expandableRowKeys] = useMemo(() => { const list = isEdit ? cacheDataList : dataList; return computeIndexAndRowKey(list, computedRowKey) }, [isEdit, cacheDataList, dataList, computedRowKey]) useEffect(() => { // 默认打开所有子菜单 if (defaultExpandAllRows && _.isUndefined(expandedRowKeys)) { setexpandRowKeys(expandableRowKeys) } }, []) /** * 虚拟滚动的相关数据 */ const [outlineNum, setOutLineNum] = useState(0) /** * level-2层数据 * 总数据、实际要渲染的rowkeys,用这个数据计算实际高度 * 通过复制dataListWithIndex数据计算,因为在虚拟滚动下要刨除掉children,但是不能影响源数据 */ const [renderListAll, renderRowKeys, tilingListAll] = useMemo(() => { if (dataListWithIndex.length === 0) return [[], [], []] return computeIndex(dataListWithIndex, expandRowKeys, virtualScroll) }, [dataListWithIndex, expandRowKeys]) // 单元格padding和border高度 const padddingBorder = useMemo(() => 2 * parseInt(cellPadding as string) + 1, [cellPadding]) // dom高度 const originRowHeight = useMemo(() => { let height = parseInt(virtualScrollConfigInner.rowHeight as string) if (rowSelection) { height = Math.max(height, 20 + padddingBorder) } if (isTree) { return Math.max(height, 18 + padddingBorder) } return height || 0 }, [virtualScrollConfigInner, isTree, padddingBorder, rowSelection]) // 行高 // 对单元格中的选择框和树状结构的展开按钮有影响 const originLineHeight = useMemo(() => { if (virtualScroll && virtualScrollConfigInner.center) { return (originRowHeight - padddingBorder) + 'px' } }, [virtualScrollConfigInner, padddingBorder, virtualScroll]) // 计算滚动比例 const rate = 1 // const rate = useMemo(() => Math.ceil(Math.chain(renderRowKeys.length).multiply(originRowHeight).divide(3e+7).done()), [renderRowKeys, originRowHeight]) // 逻辑上的行高,包括border const rowHeight = useMemo(() => originRowHeight / rate + 1, [originRowHeight, rate]) const mainHeight = useMemo(() => renderRowKeys.length * rowHeight, [renderRowKeys, rowHeight]) // 最终渲染的数据 const renderList = useMemo(() => { let list = renderListAll if (virtualScroll) { list = getVirtualList(outlineNum, thresholdInner, renderRowKeys, tilingListAll) } return list }, [virtualScroll, outlineNum, renderRowKeys, tilingListAll]) //#endredion const minHeight = useMemo(() => renderList.length > 0 ? scrollY : undefined, [scrollY, renderList]) const storageWidth = useMemo(() => getStorageWidth(tableKey)['table'] || _.get(scroll, 'x') || '100%', [tableKey]) const headerFixed = useMemo(() => !_.isUndefined(scrollY), [scrollY]) const bordered = useMemo(() => light ? false : customBorderd, [light, customBorderd]) // 当展开项发生变化的时候主动触发table的更新,去重新计算滚动条 const [emitReCompute, setEmitReCompute] = useState(0) // 业务层修改editable状态的时候,计算修改前后数据的 useEffect(() => { if (editable === EditStatus.SAVE && !lock) { // 保存之后,锁上保证下次更新不会再次进入 setLock(true) console.time('计算diff') const diffData = diffList(dataList, cacheDataList, isTree) console.timeEnd('计算diff') onSave(_.cloneDeep(cacheDataList), diffData) } }, [editable, dataList, cacheDataList, isTree, lock]) //行选择 //#region const [computedRowSelection, setselectedRowKeys, footerselection] = useRowSelection(rowSelection, dataListWithIndex, bordered) const computedPagination = usePagination(pagination, computedRowSelection, dataSource.length) const footerCallback = useCallback((currentPageData) => { return ( <> {currentPageData.length ? footerselection : null}
{typeof props.tail === 'function' && props.tail(currentPageData)}
{computedPagination && }
) }, [props.tail, computedPagination, footerselection, footerDirection]) const footer = useMemo(() => { if (!(dataSource.length && footerselection) && !props.tail && !computedPagination) return null return footerCallback }, [props.tail, dataSource, computedPagination, footerselection]) //#endregion // 滚动加载 //#region const onscroll = useCallback(_.debounce((e) => { // 编辑状态下不触发whell事件 if (!onScroll || isEdit) return if (e.type === 'wheel') { // 向下滚动,视图上移 if (e.deltaY > 0) { onScroll() } // 向上滚动,视图下移 else { } } else { const bodyTable: ScrollElement = e.target if (bodyTable.scrollTop > bodyTable.scrollTopBackUp) { // 向下滚动 const scrollAvailable = bodyTable.scrollHeight - bodyTable.clientHeight const lef = scrollAvailable - bodyTable.scrollTop if (lef <= bodyTable.scrollHeight * 0.01) { // 滚动到临界点 if (!bodyTable.scrollloaded) { bodyTable.scrollloaded = true onScroll() bodyTable.scrollloaded = false } } else { // scrollloaded控制在一定距离内不会重复调用 bodyTable.scrollloaded = false } } bodyTable.scrollTopBackUp = bodyTable.scrollTop } e.preventDefault() }, 50), [onScroll, isEdit]) const [tableGroup] = useState(new Map()) /** * 计算虚拟滚动误差 * 平均每滚动多少条要修正误差 */ const scrollError = useMemo(() => { if (scrollY) { // 最后一屏之前渲染的条数 const leave = renderRowKeys.length - thresholdInner // 最大滚动高度 const maxScroll = mainHeight - parseInt(scrollY as string) // 最多滚动多少条 const maxScrollLength = Math.floor(maxScroll / rowHeight) // 偏差条数 const error = leave - maxScrollLength if (error > 0) { return Math.floor(maxScrollLength / error) } } return 0 }, [mainHeight, scrollY, renderRowKeys, thresholdInner, rowHeight]) /** * 虚拟滚动 */ const onVScroll = useCallback((e) => { const { scrollTop } = e.currentTarget const outTopLinevir = Math.floor(scrollTop / rowHeight); // 用于计算table的位置 // 校正移动的条数,修正设置的值,防止在rate不为1的情况下,出现数据遗漏的问题 const outTopLine = outTopLinevir + (scrollError > 0 ? Math.floor(outTopLinevir / scrollError) : 0) // 设置数据 setOutLineNum(outTopLine) // 实际渲染的高度 // td有个border-bottom,要加1 const domHeight = thresholdInner * rowHeight * rate const outTopHeight = outTopLinevir * rowHeight let top = Math.max(0, Math.min(mainHeight - domHeight, outTopHeight)) tableGroup.forEach(table => setStyle(table, `transform: translate(0, ${top}px)`)) // const table = tableGroup.get('bodyTable') // setStyle(table, `transform: translate(0, ${top}px)`) // e.preventDefault() }, [thresholdInner, rowHeight, renderRowKeys, rate, mainHeight, tableGroup, scrollError]) // 绑定滚动事件 const bindScroll = useCallback( () => { // if (tableWraper && _.isEmpty(computedPagination)) { if (tableWraper) { const bodyTable: ScrollElement = tableWraper.querySelector('.ant-table-body'); bodyTable.scrollTopBackUp = bodyTable.scrollTop || 0 bodyTable.addEventListener('wheel', onscroll, false) bodyTable.addEventListener('scroll', onscroll, false) if (virtualScroll) { bodyTable.addEventListener('wheel', onVScroll, false) bodyTable.addEventListener('scroll', onVScroll, false) } } }, [tableWraper, onscroll, computedPagination, virtualScroll, onVScroll], ) // 移除滚动事件 const removeScroll = useCallback( () => { if (tableWraper) { const bodyTable = tableWraper.querySelector('.ant-table-body'); bodyTable.removeEventListener('wheel', onscroll, false) bodyTable.removeEventListener('scroll', onscroll, false) if (virtualScroll) { bodyTable.removeEventListener('wheel', onVScroll, false) bodyTable.removeEventListener('scroll', onVScroll, false) } } }, [tableWraper, onscroll, virtualScroll, onVScroll], ) useEffect(() => { bindScroll() return () => { removeScroll() } }, [tableWraper, bindScroll, removeScroll]) // table header const onHeaderCell = useCallback( (col, { hasFixed, hasChildren, index, originOnHeaderCell }) => { const { key, dataIndex, fixed, align, children, } = col // 明亮模式下或者是固定列以及有嵌套表头不允许resize const resizable = !(light || fixed || hasChildren) type resizeCellProps = Partial<{ orderType: string, key: string, flex: boolean, fixed: boolean, hasFixed: boolean, tableKey: string, dataIndex: string, headerFixed: boolean, resizable: boolean, }> interface cellProps extends resizeCellProps { style: React.CSSProperties } let headerCellProps: cellProps = { style: { width: col.width, maxWidth: col.width } // 防止折行模式下,被内容撑出 } const ordered = orderList.find(order => dataIndex === order.fieldName) if (ordered) { headerCellProps.orderType = ordered.orderType } if (resizeCell) headerCellProps = ({ ...headerCellProps, key, flex, fixed, hasFixed, // 有固定的列 tableKey, dataIndex, headerFixed, // 表格头固定 // 当前列是否可以被缩放,弹性缩放下,最后一列不允许缩放 resizable: flex ? resizable && index !== length - 1 : resizable, }) if (typeof originOnHeaderCell === 'function') return { ...headerCellProps, ...originOnHeaderCell(col) } return headerCellProps }, [light, cellPadding, tableKey, orderList, resizeCell, flex, headerFixed], ) const onCell = useCallback( (col, record, rowIndex, { hasFixed, originOnCell }) => { const { dataIndex, editConfig, fixed } = col const cellEditable = _.isPlainObject(editConfig) && !_.isEmpty(editConfig) // 修正rowIndex值 const computedRowIndex = rowIndex + outlineNum // 虚拟滚动或者带有固定列的表格不换行 // 根据是否有固定列,以及是否是虚拟滚动来控制文本是否折行 const cWrap = (virtualScroll || hasFixed) ? false : wrap const style: React.CSSProperties = { width: col.width } if (!cWrap) { // 防止折行模式下,被内容撑出 // 如果有maxWidth,会有出现缩小单元格时内容在单元格外的异常 style.maxWidth = col.width } let defaultCellProps = { style, wrap: cWrap, light, record: { ...record }, sortable, rowIndex: computedRowIndex, dataIndex, cellPadding, editConfig: {}, } if (cellEditable) defaultCellProps.editConfig = editConfig if (originOnCell) { return { ...defaultCellProps, ...originOnCell(record, computedRowIndex) } } return defaultCellProps }, [wrap, light, sortable, tableKey, cellPadding, headerFixed, virtualScroll, outlineNum], ) /** * columns API * @param editConfig object 编辑对象 * showDirt boolean 是否显示脏标记 * render function 编辑时的渲染函数 * editValue string|function 编辑组件的值 */ const convertColumns = useCallback( (cols, nest = false): GColumnProps[] => { const hasFixed = cols.some(col => col.fixed) const computedCols = cols.map(({ width, onHeaderCell: originOnHeaderCell, onCell: originOnCell, render: originRender, ...col }, index) => { // 添加自定义render col.render = (...args) => renderColumnItem({ ...col, render: originRender }, ...args as [string, object, number]) const hasChildren = _.get(col, 'children.length') if (hasChildren) { // 嵌套表头不允许resize col.children = convertColumns(col.children, true) } col.width = getStorageWidth(tableKey)[col.dataIndex] || width col.onHeaderCell = col => onHeaderCell(col, { hasFixed, hasChildren, index, originOnHeaderCell }) col.onCell = (record, rowIndex) => onCell(col, record, rowIndex + outlineNum, { hasFixed, originOnCell }) return col }) if (withIndex >= 0 && !nest) { const index = { dataIndex: 'g-index', title: '序号', width: 40, } return [...computedCols.slice(0, withIndex), index, ...computedCols.slice(withIndex)] } return computedCols }, [onHeaderCell, onCell, tableKey, withIndex, outlineNum] ) //#endregion // 处理表格行删除线 const onRow = useCallback( (record, index) => { const pureRecord = getPureRecord(record) type OptialProps = Partial<{ onClick: (e: React.MouseEvent) => void }> interface onRowProps extends OptialProps { isDeleted: boolean, rowIndex: number, sortable: boolean, originRecord?: T } const rowIndex = index + outlineNum const defaultRowProps: onRowProps = { isDeleted: record.isDeleted, rowIndex, sortable } if (isEdit) { defaultRowProps.originRecord = record[originKey] } let originListener: TableEventListeners = {} if (originOnRow) { originListener = originOnRow(pureRecord, rowIndex) } if (_.get(rowSelection, 'clickable')) { const getCheckBoxProps = _.get(rowSelection, 'getCheckboxProps') let checkable = true if (getCheckBoxProps && typeof getCheckBoxProps === 'function') { const boxProps = getCheckBoxProps(pureRecord) checkable = !_.get(boxProps, 'disable') } if (checkable) { defaultRowProps.onClick = e => { setselectedRowKeys(record) if (typeof originListener.onClick === 'function') { originListener.onClick(e) } } } } if (originOnRow) { return { ...originListener, ...defaultRowProps } } return defaultRowProps }, [sortable, setselectedRowKeys, rowSelection, outlineNum] ) // 表格中所有使用的组件 // 控制什么时候显示可编辑组件 //#region const onDragEnd = useCallback( (result) => { if (!result.destination) return // 要减掉第一行,所以需要要减1 let sourceIndex = result.source.index; let destinationIndex = result.destination.index; const list = switchIndex(dataSource, sourceIndex, destinationIndex) sortable && props.onDragEnd(list) }, [dataSource, sortable] ) const components = useMemo( () => ( { table: TableComponent, header: { // wrapper: HeaderWrapper, row: HeaderRow, cell: HeaderCell, }, body: { wrapper: (sortable ? BodyWrapper : 'tbody') as React.ReactType, row: BodyRow, // 添加删除线支持 cell: BodyCell, }, } ), [resizeCell, BodyWrapper, sortable, HeaderCell] ); //#endregion const tableColumns = useMemo(() => convertColumns(columns), [columns, convertColumns, orderList]) // dataIndex的索引 const computedColIndex = useMemo(() => getComputedColIndex(tableColumns), [tableColumns]) const expandIconColumnIndex = useMemo(() => { let index = 0 index = Math.max(tableColumns.findIndex(item => item.expandColumn), 0) return computedRowSelection ? index + 1 : index }, [tableColumns, computedRowSelection]) // 缺省显示 const emptyText = useMemo(() => { return (
) }, [scrollY, props.emptyDescription]) // 展开变化,触发重新计算滚动相关数据 const expandedRowsChange = useCallback( (expandedRowKeys: string[] | number[]) => { setEmitReCompute(e => e + 1) }, [], ) const expand = useCallback( (expanded, record) => { let rowkey = expandRowKeys const key = record["g-row-key"]; if (expanded) { rowkey = [...expandRowKeys, key] } else { // row was collapse const expandedRowIndex = expandRowKeys.indexOf(key); if (expandedRowIndex !== -1) rowkey = [...expandRowKeys.slice(0, expandedRowIndex), ...expandRowKeys.slice(expandedRowIndex + 1)] } setexpandRowKeys(rowkey) if (onExpandedRowsChange) { onExpandedRowsChange(rowkey) } if (onExpand) { const pureRecord = getPureRecord(record) onExpand(expanded, pureRecord) } }, [onExpandedRowsChange, onExpand, expandRowKeys], ) // 劫持headerRight let headerRightElement = useMemo(() => { let actions = null if (isEdit && typeof editActions === 'function') { const keys = computedRowSelection ? (computedRowSelection.selectedRowKeys || []) : [] actions = editActions([cacheDataList, setCacheDataList], keys) } return ( <> {actions} {headerRight} ) }, [isEdit, editActions, cacheDataList, headerRight, computedRowSelection]) const onResize = useCallback( () => { setEmitReCompute(e => e + 1) }, [], ) const dataContextValue = useMemo(() => ({ isTree, cellPadding, dataSource: cacheDataList, setDataSource: setCacheDataList, computedRowKey, editable, // 用于控制脏标记的显示,如果是save就会清除掉脏标记 computedColIndex, computedRowSelection, originRowHeight, originLineHeight, }), [isTree, cellPadding, cacheDataList, editable, computedColIndex, computedRowSelection, originRowHeight, originLineHeight]) const tableContextValue = useMemo(() => ({ light, spacing, dataSource, // 滚动加载的时候触发更新header的overflow emitReCompute, // 展开的时候触发更新header的overflow headerFixed, tableColumns, onResize, virtualScroll, mainHeight, tableGroup, outlineNum, thresholdInner, renderRowKeys, storageWidth, scrollY, }), [light, spacing, dataSource, emitReCompute, tableColumns, headerFixed, onResize, virtualScroll, mainHeight, tableGroup, outlineNum, thresholdInner, renderRowKeys, storageWidth, scrollY]) const bodyWrapperContext = useMemo(() => ({ onDragEnd }), [onDragEnd]) const style = useMemo(() => { const s = { ...(props.style || {}) } s['--padding'] = getStyleText(cellPadding) s['--lineHeight'] = getStyleText(originLineHeight) return s }, [props.style, cellPadding, originLineHeight]) const getPrefixCls = (cls) => 'gant-' + cls; const renderTable = () => { const { pagination, title = '', className, headerLeft, headerMarginBottom, bodyStyle, scroll = {}, headerProps, locale = {}, ...tableProps } = props; const tablePrefixCls = getPrefixCls('table'); const reizetablePrefixCls = getPrefixCls('table-resizable'); const sortablePrefixCls = getPrefixCls('table-sortable'); const zebraPrefixCls = getPrefixCls('table-zebra'); const lightPrefixCls = getPrefixCls('table-light'); return ( <> {(title || headerRightElement || headerLeft) && (
)} } expandIconColumnIndex={expandIconColumnIndex} style={style} /> ) } return (
{renderTable()}
) } const GTable = compose( tableWrapperRef, scrollIntoViewWithRowKey, Form.create(), React.forwardRef, )(GantTableList) GTable.propTypes = { columns: PropTypes.array.isRequired, resizable: PropTypes.bool, headerLeft: PropTypes.element, headerRight: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), headerMarginBottom: PropTypes.number, isZebra: PropTypes.bool, tableKey: PropTypes.string, editable: PropTypes.oneOf([EditStatus.EDIT, EditStatus.CANCEL, EditStatus.SAVE]), editActions: PropTypes.func, // 行拖拽 onDragEnd: PropTypes.func, // 编辑表格保存 onSave: PropTypes.func, wrap: PropTypes.bool, tail: PropTypes.func, // 无限滚动` onScroll: PropTypes.func, light: PropTypes.bool, // 明亮模式开关 spacing: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]), headerProps: PropTypes.object, withIndex: PropTypes.number } GTable.defaultProps = defaultProps export default class GantTable extends React.Component>{ render() { return () } }