import React, { Component } from 'react'; import classNames from 'classnames'; import memoizeOne from 'memoize-one'; import { Empty, Spin } from 'antd'; import { ScrollSync, Grid, AutoSizer } from 'react-virtualized'; import { Resizable, ResizableBox } from 'react-resizable'; import _ from 'lodash'; import scrollbarSize from 'dom-helpers/scrollbarSize'; import styles from './Table.less'; import { TableProps, TableState, ColumnProps, RowProps } from './interface'; import Column from './Column'; import Cell from './Cell'; import LeftDragFixed from './DragFixed'; import RightDragFixed from './RightDragFixed'; import DragSorted from './DragSorted'; import { getMergeClassName, getMergeStyle } from '../utils/utils'; import { Consumer } from './context'; const borderStyle = '1px solid #ececec'; export default class AirTable extends Component { config: any; grid1: any; grid2: any; grid3: any; grid4: any; grid5: any; grid6: any; tableContainer: any; widthHandleWrapper: any; widthHandle: any; memoizeData: Function; memoizeLeftColumns: Function; memoizeRightColumns: Function; memoizeColumns: Function; memoizeLeftWidth: Function; memoizeTotalWidth: Function; static getDerivedStateFromProps(nextProps: TableProps, prevState: TableState) { const { columns, dataSource } = prevState; const nextState: TableState = { ...prevState, }; if (JSON.stringify(dataSource) !== JSON.stringify(nextProps.dataSource)) { nextState.dataSource = nextProps.dataSource; } if (JSON.stringify(columns) !== JSON.stringify(nextProps.columns)) { nextState.columns = nextProps.columns; } return nextState; } constructor(props: TableProps) { super(props); const { columns, dataSource, width = 0, height = 0, rowHeight, headerHeight, columnWidth } = props; this.state = { columns, dataSource, tableWidth: width, tableHeight: height, }; this.config = { overscanColumnCount: 5, overscanRowCount: 5, columnWidth: columnWidth || 150, rowHeight: rowHeight || 80, headerHeight: headerHeight || 60, }; this.memoizeLeftColumns = memoizeOne(this.getLeftColumns); this.memoizeRightColumns = memoizeOne(this.getRightColumns); this.memoizeColumns = memoizeOne(this.getShowColumns); this.memoizeLeftWidth = memoizeOne(this.getLeftWidth); this.memoizeTotalWidth = memoizeOne(this.getTotalWidth); this.memoizeData = memoizeOne(this.filterData); } // 获取左侧固定列数量 getLeftColumns = (columns: ColumnProps[]) => { return columns.filter((item) => { return !!item.showStatus && item.fixed && item.fixed !== 'right'; }); }; // 获取右侧固定列数量 getRightColumns = (columns: ColumnProps[]) => { return columns.filter((item) => { return !!item.showStatus && item.fixed === 'right'; }); }; // 获取表格显示列 getShowColumns = (columns: ColumnProps[]) => { return columns.filter((item) => { return !!item.showStatus && (!item.hideScope || item.hideScope.indexOf('LIST') === -1); }); }; // 获取左侧固定列总宽度 getLeftWidth = (leftColumns: ColumnProps[], columns: ColumnProps[]) => { const { tableWidth = 0 } = this.state; const { totalWidth, configWidth, defaultWidthLen } = this.memoizeTotalWidth(columns); const { columnWidth } = this.config; let colWidth = columnWidth; if (totalWidth < tableWidth) { colWidth = _.floor((tableWidth - configWidth) / defaultWidthLen); } let total = 0; leftColumns.map((item) => { total += item.width || colWidth; }); return total; }; // 获取右侧列总宽度 getTotalWidth = (columns: ColumnProps[]) => { const { columnWidth } = this.config; let configWidth = 0; let configWidthLen = 0; let defaultWidth = 0; let defaultWidthLen = 0; columns.map((item: any) => { if (item.width) { configWidth += item.width; configWidthLen += 1; } else { defaultWidth += columnWidth; defaultWidthLen += 1; } }); const totalWidth = configWidth + defaultWidth; return { configWidth, defaultWidth, totalWidth, configWidthLen, defaultWidthLen, }; }; // 获取表格显示数据 filterData = (columns: ColumnProps[], dataSource: RowProps[]) => { if (columns.length === 0 || dataSource.length === 0) { return []; } const cloneData = _.cloneDeep(dataSource); cloneData.map((item: any) => { item.rowData = item.rowData.filter((item2: any) => { return columns.some((temp: any) => { return temp.columnName === item2.colName; }); }); return item; }); return cloneData; }; // 获取每列的宽度 getColumnWidth = ( { columns, showColumns }: { columns: ColumnProps[]; showColumns: ColumnProps[] }, { index }: { index: number }, ) => { const { tableWidth = 0 } = this.state; const { totalWidth, configWidth, defaultWidthLen } = this.memoizeTotalWidth(columns); const { columnWidth } = this.config; let colWidth = columnWidth; if (totalWidth < tableWidth) { colWidth = _.floor((tableWidth - configWidth) / defaultWidthLen); } const columnObj = showColumns[index]; if (columnObj && columnObj.width) { return columnObj.width; } return colWidth; }; onResize = ({ width, height }: { width: number; height: number }) => { this.setState( { tableWidth: width, tableHeight: height, }, () => { this.grid1 && this.grid1.recomputeGridSize(); this.grid2 && this.grid2.recomputeGridSize(); this.grid3 && this.grid3.recomputeGridSize(); this.grid4 && this.grid4.recomputeGridSize(); this.grid5 && this.grid5.recomputeGridSize(); this.grid6 && this.grid6.recomputeGridSize(); }, ); }; // 下拉加载 onScroll = ({ clientHeight, scrollHeight, scrollTop }: any) => { const height = clientHeight + scrollTop || 0; const { onScroll, distanceToLoad = 0 } = this.props; if (scrollTop > 0 && height >= scrollHeight - distanceToLoad && typeof onScroll === 'function') { onScroll(); } }; // 列伸缩回调 onResizeWidth = (e) => { const originLeft = (this.tableContainer && this.tableContainer.getBoundingClientRect().x) || 0; if (this.widthHandle) { this.widthHandle.style.left = `${e.x - originLeft}px`; } }; // 列伸缩开始 onResizeWidthStart = (e) => { const originLeft = (this.tableContainer && this.tableContainer.getBoundingClientRect().x) || 0; if (this.widthHandleWrapper && this.widthHandle) { this.widthHandle.style.left = `${e.x - originLeft}px`; this.widthHandleWrapper.style.display = 'block'; } }; // 列伸缩结束 onResizeWidthStop = (columnName: string, e, { size }) => { const { columns } = this.state; const newColumns: any = []; columns.map((item) => { if (item.columnName === columnName) { item.width = size.width; } newColumns.push(item); }); if (this.widthHandleWrapper) { this.widthHandleWrapper.style.display = 'none'; } this.setState( { columns: newColumns, }, () => { this.grid1 && this.grid1.recomputeGridSize(); this.grid2 && this.grid2.recomputeGridSize(); this.grid3 && this.grid3.recomputeGridSize(); this.grid4 && this.grid4.recomputeGridSize(); this.grid5 && this.grid5.recomputeGridSize(); this.grid6 && this.grid6.recomputeGridSize(); }, ); }; // 拖拽自定义固定列前置动作 onResizeStartLeftDragFixed = () => { // 拖动开始,将表格滚动回最左端 if (this.grid4) { this.grid4.scrollToPosition({ scrollLeft: 0 }); } }; // 拖拽自定义固定列后置动作 onResizeStopLeftDragFixed = ({ columns }) => { this.setState( { columns, }, () => { this.grid1 && this.grid1.recomputeGridSize(); this.grid2 && this.grid2.recomputeGridSize(); }, ); }; // 拖拽自定义固定列前置动作 onResizeStartRightDragFixed = () => { // 拖动开始,将表格滚动回最右端 const { columns } = this.state; const showColumns = this.memoizeColumns(columns); if (this.grid4) { this.grid4.scrollToCell({ columnIndex: showColumns.length - 1 }); } }; // 拖拽滚动表格 onScrollHor = (direction: string, step: number) => { // 拖动超过视图范围,将表格左右滚动 if (this.grid4) { if (direction === 'right') { if (this.grid4.state.scrollLeft >= this.grid4.getTotalColumnsWidth() - this.state.tableWidth - 100) { console.log('已经到最右侧了'); return; } this.grid4.scrollToPosition({ scrollLeft: this.grid4.state.scrollLeft + step }); } else { if (this.grid4.state.scrollLeft <= 0) { console.log('已经到最左侧了'); return; } this.grid4.scrollToPosition({ scrollLeft: this.grid4.state.scrollLeft - step }); } } }; // 拖拽排序回调 onDragSorted = (dragColumn: any, dropColumn: any) => { const { onDragSorted } = this.props; const { columns } = this.state; const newColumns: any[] = []; columns.map((item) => { if (dragColumn.orderNo > dropColumn.orderNo) { if (item.orderNo >= dropColumn.orderNo && item.orderNo <= dragColumn.orderNo) { if (item.columnName === dragColumn.columnName) { newColumns.push({ ...item, orderNo: dropColumn.orderNo, }); } else { newColumns.push({ ...item, orderNo: item.orderNo + 1, }); } } else { newColumns.push(item); } } else { if (item.columnName === dragColumn.columnName) { newColumns.push({ ...item, orderNo: dropColumn.orderNo - 1, }); } else if (item.orderNo >= dragColumn.orderNo && item.orderNo <= dropColumn.orderNo) { newColumns.push({ ...item, orderNo: item.orderNo - 1, }); } else { newColumns.push(item); } } }); newColumns.sort((a, b) => { return a.orderNo - b.orderNo; }); if (typeof onDragSorted === 'function') { onDragSorted(newColumns); } }; // 渲染表头 renderHeaderCell = ( { showColumns, position }: { showColumns: ColumnProps[]; position?: string }, { columnIndex, key, style }: any, ) => { if (showColumns.length === 0) return null; const { sortConfig, showIndex, rowSelection, dataSource, onDragSorted } = this.props; const { columnType = 1, columnName, columnChsName = '', columnAttrObj = {}, sortFlag, questionText, icon, requiredFlag, orderNo, } = showColumns[columnIndex]; return ( { e.stopPropagation(); }} /> } minConstraints={[100, 100]} onResize={this.onResizeWidth} onResizeStart={this.onResizeWidthStart} onResizeStop={this.onResizeWidthStop.bind(this, columnName)} draggableOpts={{ enableUserSelectHack: false }} > ); }; // 渲染数据 renderBodyCell = ( { showColumns, showData, position = 'center', }: { showColumns: ColumnProps[]; showData: RowProps; position?: string }, { columnIndex, key, rowIndex, style }: any, ) => { const { columns, dataSource, tableHeight = 0 } = this.state; const { headerHeight, rowHeight } = this.config; const { emitChangeCell, paginationConfig, showIndex, showExpand, emptyPlaceholder, cellEditable, rowSelection, contentMenu, rowClassName, rowStyle, tableId, } = this.props; if (showColumns.length === 0 || showData.length === 0) { return; } const record: any = dataSource[rowIndex]; // 行数据 const rowData: any = showData[rowIndex]; // 行数据 const columnConfig: any = showColumns[columnIndex]; // 列属性 // const columnData: any = rowData.rowData[columnIndex]; // 列数据 const columnData: any = rowData.rowData.find((temp) => { return temp.colName === columnConfig.columnName; }); // 列数据 const cellData: any = columnData && columnData.cellValueList; // 列数据 const { cellClassName, cellStyle, bordered = false } = columnConfig; const rowClassNameStr = getMergeClassName(styles.bodyRow, rowClassName, { rowIndex }); const rowStyleObj = getMergeStyle({}, rowStyle, { rowIndex }); // 默认边框覆盖掉传过来的样式 const cellBorderStyle: any = { borderLeft: 'none', borderRight: 'none', borderTop: 'none', borderBottom: borderStyle, }; if (bordered) { cellBorderStyle.borderRight = borderStyle; if (columnIndex === 0) { cellBorderStyle.borderLeft = borderStyle; } } const cellClassNameStr = getMergeClassName(styles.bodyCell, cellClassName, { columnIndex, columnConfig, columnData, cellData, }); const cellStyleObj = getMergeStyle({ ...style, ...cellBorderStyle }, cellStyle, { columnIndex, columnConfig, columnData, cellData, }); let maxPopHeight = (tableHeight - headerHeight - rowHeight - 10) / 2; if (maxPopHeight > 250) { maxPopHeight = 250; } return ( ); }; render() { const { loading, noDataPlaceholder, loadComp, canFixed, tableId } = this.props; const { columns, dataSource, tableWidth = 0, tableHeight = 0 } = this.state; const { overscanColumnCount, overscanRowCount, rowHeight, headerHeight, columnWidth } = this.config; const scrollbarWidth = scrollbarSize() || 0; const showColumns = this.memoizeColumns(columns); const leftColumns = this.memoizeLeftColumns(columns); const rightColumns = this.memoizeRightColumns(columns); // 有隐藏列时,修正数据与列头不匹配问题 const showData = this.memoizeData(showColumns, dataSource); const leftData = this.memoizeData(leftColumns, dataSource); const rightData = this.memoizeData(rightColumns, dataSource); // 左侧固定列数量 const leftCount = leftColumns.length; const rightCount = rightColumns.length; const columnCount = showColumns.length; const rowCount = showData.length; const leftWidth = this.memoizeLeftWidth(leftColumns, showColumns); const rightWidth = this.memoizeLeftWidth(rightColumns, showColumns); let totalHeight: number = tableHeight - headerHeight; const { totalWidth } = this.memoizeTotalWidth(showColumns); let realWidth = tableWidth; let paddingRight = 0; if (rowCount > 0 && rowCount * rowHeight > totalHeight) { realWidth = tableWidth + scrollbarWidth; paddingRight = scrollbarWidth; } return ( {({ locale }) => ( {({ onScroll, scrollLeft, scrollTop }: any) => { return (
{ this.tableContainer = dom; }} > {totalWidth > tableWidth && leftCount > 0 && (
{ // 左侧表头
{ this.grid1 = dom; }} className={styles.headerGrid} columnWidth={this.getColumnWidth.bind(this, { columns: showColumns, showColumns: leftColumns, })} columnCount={leftCount} width={leftWidth} rowHeight={headerHeight} rowCount={1} height={headerHeight} cellRenderer={this.renderHeaderCell.bind(this, { showColumns: leftColumns, })} />
} { // 左侧固定列
{ this.grid2 = dom; }} className={styles.sideGrid} overscanRowCount={overscanRowCount} cellRenderer={this.renderBodyCell.bind(this, { showColumns: leftColumns, showData: leftData, position: 'left', })} columnWidth={this.getColumnWidth.bind(this, { columns: showColumns, showColumns: leftColumns, })} onScroll={(...arg: Array) => { onScroll({ scrollTop: arg[0].scrollTop }); this.onScroll(arg[0]); }} columnCount={leftCount} width={leftWidth + scrollbarWidth} rowHeight={rowHeight} rowCount={rowCount} height={totalHeight - scrollbarWidth} scrollTop={scrollTop} /> } )} {canFixed && ( )}
{({ width, height }: any) => { return (
{ // 中部表头
{ this.grid3 = dom; }} className={styles.headerGrid} overscanColumnCount={overscanColumnCount} columnWidth={this.getColumnWidth.bind(this, { columns: showColumns, showColumns, })} columnCount={columnCount} width={width} rowHeight={headerHeight} rowCount={1} height={headerHeight} scrollLeft={scrollLeft} cellRenderer={this.renderHeaderCell.bind(this, { showColumns, })} />
} { // 中部内容
{rowCount > 0 ? ( { this.grid4 = dom; }} className={styles.centerGrid} overscanColumnCount={overscanColumnCount} overscanRowCount={overscanRowCount} columnWidth={this.getColumnWidth.bind(this, { columns: showColumns, showColumns, })} columnCount={columnCount} width={realWidth} rowHeight={rowHeight} rowCount={rowCount} onScroll={(...arg: Array) => { onScroll(...arg); this.onScroll(arg[0]); }} scrollTop={scrollTop} height={totalHeight} cellRenderer={this.renderBodyCell.bind(this, { showColumns, showData, position: 'center', })} /> ) : ( columnCount > 0 && !loading && ( ) )} } ); }} {canFixed && ( )} {totalWidth > tableWidth && rightCount > 0 && (
{ // 右侧表头
{ this.grid5 = dom; }} className={styles.headerGrid} columnWidth={this.getColumnWidth.bind(this, { columns: showColumns, showColumns: rightColumns, })} columnCount={rightCount} width={rightWidth} rowHeight={headerHeight} rowCount={1} height={headerHeight} cellRenderer={this.renderHeaderCell.bind(this, { showColumns: rightColumns, position: 'right', })} />
} { // 右侧固定列
{ this.grid6 = dom; }} className={styles.sideGrid} overscanRowCount={overscanRowCount} cellRenderer={this.renderBodyCell.bind(this, { showColumns: rightColumns, showData: rightData, position: 'right', })} columnWidth={this.getColumnWidth.bind(this, { columns: showColumns, showColumns: rightColumns, })} onScroll={(...arg: Array) => { onScroll({ scrollTop: arg[0].scrollTop }); this.onScroll(arg[0]); }} columnCount={rightCount} width={rightWidth + scrollbarWidth} rowHeight={rowHeight} rowCount={rowCount} height={totalHeight - scrollbarWidth} scrollTop={scrollTop} /> } )}
{loading && (loadComp ? loadComp : )}
{ this.widthHandleWrapper = dom; }} >
{ this.widthHandle = dom; }} />
); }} )} ); } }