import React, { Component } from 'react'; import { Checkbox } from 'antd'; import memoizeOne from 'memoize-one'; import { ScrollSync, Grid, AutoSizer } from 'react-virtualized'; import 'react-virtualized/styles.css'; import _ from 'lodash'; import styles from './Table.less'; import { TableProps, TableState } from './interface'; import Column from './Column'; import Cell from './Cell'; import extendIcon from '../assets/extend.png'; import IconFont from './base/extra/iconFont'; import scrollbarSize from 'dom-helpers/scrollbarSize'; export default class AirTable extends Component { private indexCount: any; config: any; memoizeLeftCount: Function; memoizeData: Function; static getDerivedStateFromProps(nextProps: TableProps, prevState: TableState) { const { prevProps } = prevState; let nextState: TableState = { ...prevState, prevProps: nextProps, }; if (JSON.stringify(prevProps.dataSource) !== JSON.stringify(nextProps.dataSource)) { nextState.dataSource = nextProps.dataSource; } if (JSON.stringify(prevProps.columns) !== JSON.stringify(nextProps.columns)) { nextState.columns = nextProps.columns; } if (JSON.stringify(prevProps.checked) !== JSON.stringify(nextProps.checked)) { nextState.checked = nextProps.checked; } nextState.tableHeight = document.body.clientHeight; nextState.tableWidth = document.body.clientWidth; return nextState; } constructor(props: TableProps) { super(props); const { columns, dataSource, tableId, checked = [] } = props; this.state = { columns, dataSource, checked, tableId, prevProps: props, tableWidth: 0, tableHeight: 0, }; this.config = { overscanColumnCount: 5, overscanRowCount: 5, columnWidth: 200, rowHeight: 40, }; this.memoizeLeftCount = memoizeOne(this.getLeftCount); this.memoizeData = memoizeOne(this.filterData); this.indexCount = {}; } componentDidMount() { window.addEventListener('resize', this.resize, false); } //重置窗口高度 resize = () => { this.setState({ tableHeight: document.body.clientHeight, tableWidth: document.body.clientWidth, }); }; //获取左侧固定列数量 getLeftCount = (columns: []) => { let len = 0; columns.map((item: any) => { if (item.fixed) { len += 1; } }); return len; }; //表头隐藏时过滤数据源 filterData = (columns: [], dataSource: []) => { if (columns.length === 0 || dataSource.length === 0) { return []; } let cloneData = _.cloneDeep(dataSource); let i = 0; this.indexCount = {}; cloneData.map((item1: any) => { const itemData = item1; itemData.rowData = itemData.rowData.filter((item2: any) => columns.some((temp: any) => temp.columnName === item2.colName), ); if (itemData.groupId) { if (this.indexCount[itemData.groupId]) { this.indexCount[itemData.groupId].push(itemData.id); } else { i += 1; this.indexCount[itemData.groupId] = [itemData.id]; } itemData.index = i; } return itemData; }); return cloneData; }; // 复选框选择 changeCheckbox = (record: any, flag: boolean) => { const { changeChecked, hasGroup } = this.props; const { checked, dataSource } = this.state; let temp = (checked && checked.slice()) || []; if (flag) { if (hasGroup) { const selected = dataSource.filter((item: any) => { return item.groupId === record.groupId; }); temp = temp.concat(selected); } else { temp.push(record); } } else { if (hasGroup) { temp = temp.filter((item: any) => { return item.groupId !== record.groupId; }); } const index = temp.findIndex((item: any) => { return item.id === record.id; }); temp.splice(index, 1); } if (typeof changeChecked === 'function') { changeChecked({ checked: temp }); } }; // 检测当前分组是否全部选中 getCheckedAll = () => { const { checked, dataSource } = this.state; const temp = (checked && checked.slice()) || []; const result = temp.filter((v: any) => { return dataSource.some((item: any) => { return item.id === v.id; }); }); return result.length === dataSource.length && result.length !== 0; }; // 全选/反选 changeCheckboxAll = () => { const { checked, dataSource } = this.state; const { changeChecked } = this.props; const temp = (checked && checked.slice()) || []; const flag = this.getCheckedAll(); let result: any[] = []; if (flag) { result = temp.concat(dataSource).filter((v: any) => { return ( temp.some((item: any) => { return item.id === v.id; }) && !dataSource.some((item: any) => { return item.id === v.id; }) ); }); } else { result = temp.concat( dataSource.filter((v: any) => { return !temp.some((item: any) => { return item.id === v.id; }); }), ); } if (typeof changeChecked === 'function') { changeChecked({ checked: result }); } }; // 下拉加载 onScrollRow = ({ clientHeight, scrollTop }: any) => { const height = clientHeight + scrollTop || 0; const { dataSource = [] } = this.state; const { rowHeight } = this.config; const { onScroll } = this.props; const offset = 100; if (dataSource.length === 0) return; if (height + offset >= dataSource.length * rowHeight && typeof onScroll === 'function') { onScroll(); } }; //渲染表头 renderHeaderCell = ({ columnIndex, key, rowIndex, style }: any) => { const { columns } = this.state; if (columns.length === 0) return null; const { columnType, columnChsName, width, columnAttrObj } = columns[columnIndex] || {}; if (columnIndex === 0) { return (
); } return (
); }; // 渲染首列复选框 renderCheckbox = (record: any, rowIndex: number) => { const { checked } = this.state; const { hasGroup } = this.props; const flag = checked && checked.some((item: any) => { return item.id === record.id; }); let checkbox = flag ? ( ) : (
{record.index || rowIndex + 1}
); if (hasGroup) { if ( this.indexCount[record.groupId][Math.floor((this.indexCount[record.groupId].length - 1) / 2)] !== record.id ) { // 填充透明的复选框使样式保持一致 checkbox = ; } } return checkbox; }; renderBodyRow = (data: any[], { columnIndex, key, rowIndex, style }: any) => { const { rowClassName, rowStyle, rowBorder = false } = this.props; const rowClass = typeof rowClassName === 'function' ? rowClassName({ index: rowIndex }) : rowClassName; const rowStyleObject = typeof rowStyle === 'function' ? rowStyle({ index: rowIndex }) : rowStyle; // 默认边框覆盖掉传过来的样式 const rowBorderStyle: any = { borderLeft: 'none', borderRight: '1px solid #e8e8e8', borderTop: 'none', borderBottom: '1px solid #e8e8e8', }; if (rowIndex === 0) { rowBorderStyle.borderTop = '1px solid #e8e8e8'; } if (columnIndex === 0) { rowBorderStyle.borderLeft = '1px solid #e8e8e8'; } // border需要单独设置 if (typeof rowBorder === 'string') { rowBorderStyle.borderTop = rowBorder; rowBorderStyle.borderBottom = rowBorder; if (columnIndex === 0) { rowBorderStyle.borderLeft = rowBorder; } if (columnIndex === data.length - 1) { rowBorderStyle.borderRight = rowBorder; } } let className = styles.bodyRow; if (rowClass) { className = `${className} ${rowClass}`; } return (
{this.renderBodyCellContainer(data, { columnIndex, key, rowIndex, style })}
); }; renderBodyCellContainer = (data: any[], { columnIndex, key, rowIndex, style }: any) => { const { columns } = this.state; const columnConfig = columns[columnIndex]; const { cellClassName, cellStyle } = columnConfig; const cellClass = typeof cellClassName === 'function' ? cellClassName({ index: columnIndex }) : cellClassName; const cellStyleObject = typeof cellStyle === 'function' ? cellStyle({ index: columnIndex }) : cellStyle; let className = styles.bodyCell; if (cellClass) { className = `${className} ${cellClass}`; } return (
{this.renderBodyCell(data, { columnIndex, key, rowIndex, style })}
); }; //渲染数据 renderBodyCell = (data: any[], { columnIndex, key, rowIndex }: any) => { const { columns, dataSource } = this.state; const { updateData, hasGroup, showForm = () => {}, delData, getData, extraMenu, noAdd, noEdit, noDel, tableId, flush, showHistory = () => {}, } = this.props; if (columns.length === 0 || data.length === 0) { return; } const filterRowData: any = data[rowIndex]; // 过滤掉隐藏项并追加行号的行数据,只在渲染逻辑中使用 const rowData: any = dataSource[rowIndex]; // 原始行数据,真实使用的数据 const columnConfig: any = columns[columnIndex]; // 列属性 const columnData: any = rowData.rowData[columnIndex]; // 列数据 const cellData: any = columnData.cellValueList; // 列数据 let className = styles.indexCell; if (columnIndex === 0) { return (
{this.renderCheckbox(filterRowData, rowIndex)}
edit
0 ? styles.firstCell : styles.empty} columns={columns} columnConfig={columnConfig} rowData={rowData} cellData={cellData} updateData={updateData} showForm={showForm} delData={delData} hasGroup={hasGroup} getData={getData} extraMenu={extraMenu} noAdd={noAdd} noEdit={noEdit} noDel={noDel} tableId={tableId} flush={flush} /> {(tableId === 1 || tableId === 14) && (
{filterRowData.groupHistoryCount && filterRowData.groupHistoryCount > 1 && ( {`(${ filterRowData.groupHistoryCount > 99 ? '99+' : filterRowData.groupHistoryCount })`} )}
)}
{(tableId === 1 || tableId === 14) && (
)}
); } return (
); }; render() { const { columns, dataSource, checked, tableHeight, tableWidth } = this.state; const { changeChecked, operateConfig = {}, hasGroup } = this.props; const { groupConfig } = operateConfig; const { overscanColumnCount, overscanRowCount, columnWidth, rowHeight } = this.config; let totalHeight = tableHeight; let colWidth = columnWidth; const columnCount = columns.length; const rowCount = dataSource.length; if (rowCount > 0 && rowCount * rowHeight < tableHeight) { // 表格行数少于一屏高度时,高度增加横向滚动条高度 totalHeight = rowCount * rowHeight + scrollbarSize(); } if (columnCount > 0 && columnCount * columnWidth < tableWidth) { // 表格宽度不足一屏时,按屏幕宽度平分每列宽度 colWidth = Math.floor(tableWidth / columnCount); } //有隐藏列时,修正数据与列头不匹配问题 const data = this.memoizeData(columns, dataSource); //左侧固定列数量 const leftCount = this.memoizeLeftCount(columns); return ( {({ onScroll, scrollLeft, scrollTop }: any) => { return (
{ //左侧表头
} { //左侧固定列
}
{({ width }: any) => { // 如果列太少不满一屏时,列头宽度按列计算 let headerWidth = width; if (rowCount * rowHeight > tableHeight) { // 表格行数大于一屏的高度时出现竖向滚动条,此时宽度减出滚动条宽度 headerWidth -= scrollbarSize(); } return (
{ //右侧表头
} { //右侧内容
) => { onScroll(...arg); this.onScrollRow(arg[0]); }} cellRenderer={this.renderBodyRow.bind(this, data)} columns={columns} checked={checked} hasGroup={hasGroup} />
}
); }}
); }}
); } }