UNPKG

react-virtualized-talbe

Version:

react virtualized table

1,096 lines (915 loc) 36.5 kB
import React, { Component } from 'react'; import cn from 'classnames'; import _ from 'lodash'; import PropTypes from 'prop-types'; import { Checkbox, Button, Pagination, Dropdown } from 'antd'; import Loading from '../Loading'; import EmptyHolder from '../EmptyHolder'; import Sorter from '../Sorter'; import leftArrow from './images/left-arrow.svg'; import rightArrow from './images/right-arrow.svg'; import moreIcon from './images/more-icon.svg'; import scrollbarSize from 'dom-helpers/util/scrollbarSize'; import { AutoSizer, ScrollSync, Grid, } from 'react-virtualized'; import 'react-virtualized/styles.css'; import { localClass, pagerContainer } from './VTable.scss'; let blinkInterval = null; /*** 固定列必须带有宽度 ***/ class VirtualizedTable extends Component { static propTypes = { bordered: PropTypes.bool, //table风格 columns: PropTypes.array, // 列配置 sortBy: PropTypes.string, // 列配置 sortOrder: PropTypes.oneOf(['ascend', 'descend', '']), // 列配置 dataSource: PropTypes.array, // 数据源 rowKey: PropTypes.func, //设置rowkey loading: PropTypes.bool, // 加载装 'pending', 'done', 'error' height: PropTypes.number, // 设置控件高度 rowSelection:PropTypes.object, // 是否支持选中 pagination:PropTypes.oneOfType([ PropTypes.bool, PropTypes.object, ]), //分页参数 emptyInfo:PropTypes.string, //空白占位控件参数 hasButton:PropTypes.bool, //空白占位控件参数 buttonInfo:PropTypes.object, //空白占位控件参数 fixedRowCount: PropTypes.number, // 固定行数 headerHoverEnable: PropTypes.bool, // header是否支持hover选中列 onHeaderCellClick: PropTypes.func, // 头部点击事件 onBodyCellClick: PropTypes.func, // cell点击事件 onBodyCellDoubleClick: PropTypes.func, // cell双击事件 onRowClick: PropTypes.func, // 行点击事件 onRowDoubleClick: PropTypes.func, // 行双击事件 onCell: PropTypes.func, // 给body cell添加props onHeaderCell: PropTypes.func, // 给Header cell添加props } static defaultProps = { } constructor(props) { super(props); let defaultColumnWidth = 250; let columns = props.columns || []; if (props.rowSelection) { this.addCheckboxColumn(columns); } columns = this.addNavColumns(columns); this.state = { columns, height: _.get(props, 'height') || 0, overscanColumnCount: 2, overscanRowCount: 3, rowHeight: 40, fixedRowCount: props.fixedRowCount || 0, defaultColumnWidth, }; this.centerHeaderRef = React.createRef(); this.centerBodyRef = React.createRef(); this.rootRef = React.createRef(); this._renderBodyCell = this._renderBodyCell.bind(this); this._renderHeaderCell = this._renderHeaderCell.bind(this); this._renderLeftSideCell = this._renderLeftSideCell.bind(this); this._getColunmWidth = this._getColunmWidth.bind(this); } rowKey = (record) => { const { rowKey } = this.props; if (_.isFunction(rowKey)) { return rowKey(record); } return record.id; } addCheckboxColumn = (columns) => { if (columns[0] && columns[0].__columnType !== 'rowSelect') { columns.unshift({ key: '', fixed: 'left', width: 40, __columnType: 'rowSelect', renderHeader: () => { const { rowSelection: { selectedRowKeys, getCheckboxProps } = {}, dataSource } = this.props; const checked = !_.isEmpty(selectedRowKeys) && selectedRowKeys.length === dataSource.length; let restProps = {}; if (_.isFunction(getCheckboxProps)) { restProps = getCheckboxProps({}); } return ( <Checkbox checked={checked} {...restProps} onChange={this.handleToggleSelectAll} indeterminate={!checked && !_.isEmpty(selectedRowKeys)} className='abc-chk-all'> </Checkbox> ) }, render: (text, record, index) => { const { rowSelection: { selectedRowKeys, getCheckboxProps } = {} } = this.props; let restProps = {}; if (_.isFunction(getCheckboxProps)) { restProps = getCheckboxProps(record); } return ( <Checkbox dataref={record} {...restProps} onChange={this.handleToggleSelect} checked={_.indexOf(selectedRowKeys, this.rowKey(record)) >= 0}> </Checkbox> ) } }) } return columns; } hasNav = () => { let hasNav = false const { columns, defaultColumnWidth } = this.state; const allWidth = _.sumBy(columns, each => each.width || defaultColumnWidth); if (this.centerBodyRef.current) { const ele = this.centerBodyRef.current._scrollingContainer; if (ele && ele.clientWidth < allWidth) { hasNav = true; } } return hasNav; } // 增加向左,向右的列 addNavColumns = (columns) => { let leftFixedColumns = _.filter(columns, each => each.fixed === 'left') || []; let rightFixedColumns = _.filter(columns, each => each.fixed === 'right') || []; let cColumns = _.filter(columns, each => each.fixed !== 'right' && each.fixed !== 'left') || []; if (!leftFixedColumns.find(each => each.__columnType === 'leftNav')) { leftFixedColumns.push({ key: 'leftNav', fixed: 'left', width: 20, __columnType: 'leftNav', renderHeader: () => { //如果没有出现水平滚动条 不渲染 if (!this.hasNav()) { return <div className='nav-wrapper-empty'></div>; } return ( <div className="nav-wrapper left-nav-wrapper" onClick={this.handleScrollLeft}> {/* <Icons type='left-arrow' /> */} <img src={leftArrow} alt='' style={{width:15}}/> </div> ) }, }) } if (!rightFixedColumns.find(each => each.__columnType === 'rightNav')) { rightFixedColumns.unshift({ key: 'rightNav', fixed: 'right', width: 20, __columnType: 'rightNav', renderHeader: () => { //如果没有出现水平滚动条 不渲染 if (!this.hasNav()) { return <div className='nav-wrapper-empty'></div>; } return ( <div className="nav-wrapper right-nav-wrapper" onClick={this.handleScrollRight}> {/* <Icons type='right-arrow' /> */} <img src={rightArrow} alt='' style={{width:15}} /> </div> ) }, }) } // if (!_.isEmpty(columns)) { if (!columns.find(each => !each.width)) { //所有的列都设置了宽度 则将非固定列的最后一个宽置空 _.last(cColumns).width = undefined; } } return leftFixedColumns.concat(cColumns, rightFixedColumns); } componentDidMount() { } componentDidUpdate(prevProps) { const { columns, loading, height } = this.props; if (loading !== prevProps.loading && !loading && this.centerBodyRef.current) { this.centerBodyRef.current.scrollToPosition({ scrollTop: 0 }); } if (prevProps.height && prevProps.height !== height) { this.setState({ height }) } if (columns !== prevProps.columns) { if (this.props.rowSelection) { this.addCheckboxColumn(columns); } this.setState({ columns: this.addNavColumns(columns) }, () => { this.reRenderColumns(); }); } } componentWillUnmount() { this.isUnmouting = true; } // 当列变化或者容器宽度变化时 动态计算列宽 reRenderColumns = () => { const { columns } = this.state; columns.forEach((each, index) => { if (this.centerHeaderRef.current) { this.centerHeaderRef.current.recomputeGridSize({ columnIndex: index }) this.centerBodyRef.current.recomputeGridSize({ columnIndex: index }); } }) } handleResize = () => { const { columns } = this.state; this.setState({ columns: this.addNavColumns(columns) }, () => { this.reRenderColumns(); }) } debouncedResize = _.debounce(this.handleResize, 200, { leading: true, }); // options:{rowIndex, key, columnIndex} handleHeaderCellClick = (col, options = {}, extraProps, ...restArgs) => { const { onHeaderCellClick } = this.props; if (_.isFunction(onHeaderCellClick)) { onHeaderCellClick(col, options, ...restArgs); } if (_.isFunction(extraProps.onClick)) { extraProps.onClick(col, options, ...restArgs); } } handleBodyCellMouseEnter = (row, options= {}, extraProps={}, ...restArgs) => { const { hoverRowIndex } = this.state; if (hoverRowIndex !== options.rowIndex) { this.setState({ hoverRowIndex: options.rowIndex, hoverColumnKey: null, }) this.reRenderColumns(); } if (_.isFunction(extraProps.onMouseEnter)) { extraProps.onMouseEnter(row, options, ...restArgs); } } // options:{rowIndex, key, columnIndex, column} handleBodyCellClick = (rowData, options={}, extraProps= {}, ...restArgs) => { const { onBodyCellClick, onRowClick } = this.props; const { selectedRowData = {} } = this.state; if (_.isFunction(onBodyCellClick)) { onBodyCellClick(rowData, options) } if (_.isFunction(extraProps.onClick)) { extraProps.onClick(rowData, options, ...restArgs); } // 点击rowSelection列 不触发rowClick事件 if (_.isFunction(onRowClick)) { if (options.column.__columnType !== 'rowSelect') { onRowClick(rowData, options) } } if (selectedRowData.rowIndex !== options.rowIndex) { this.setState({ selectedRowData: { rowData, ...options } }) this.reRenderColumns(); } } handleBodyCellDBClick = (rowData, options={}, extraProps, ...restArgs) => { const { onBodyCellDoubleClick, onRowDoubleClick } = this.props; const { selectedRowData = {} } = this.state; if (_.isFunction(onBodyCellDoubleClick)) { onBodyCellDoubleClick(rowData, options) } if (_.isFunction(extraProps.onDoubleClick)) { extraProps.onDoubleClick(rowData, options, ...restArgs); } // 点击rowSelection列 不触发rowClick事件 if (_.isFunction(onRowDoubleClick)) { if (options.column.__columnType !== 'rowSelect') { onRowDoubleClick(rowData, options) } } if (selectedRowData.rowIndex === options.rowIndex) { return; } this.setState({ selectedRowData: { rowData, ...options } }) this.reRenderColumns(); } handleHeaderCellMouseEnter = (col, extraProps, ...restArgs) => { if (_.isFunction(extraProps.onMouseEnter)) { extraProps.onMouseEnter(col, ...restArgs); } let xKey = col.key; if (!col || col.key === 'rightNav' || col.key === 'leftNav') { xKey = null; } this.setState({ hoverColumnKey: xKey, hoverRowIndex: null }) this.reRenderColumns(); } handleHeaderMouseLeave = () => { // const { headerHoverEnable } = this.props; // if (!headerHoverEnable) { // return; // } this.setState({ hoverColumnKey: null, hoverRowIndex: null //兼容fixedRowCount!==0 的情况 }) this.reRenderColumns(); } handleBodyMouseLeave = () => { this.setState({ hoverRowIndex: null }) this.reRenderColumns(); } handleScrollLeft = () => { const { leftFixedColumns } = this.resolveColumns(); const { columns, defaultColumnWidth } = this.state; const leftWidth = _.sumBy(leftFixedColumns, 'width'); if (!this.centerHeaderRef.current || !this.centerBodyRef.current || !this.rootRef.current) { return; } let nextLeft = 0; let sumWidth = 0; const { state: { scrollLeft } = {} } = this.centerHeaderRef.current; for (let i = 0; i < columns.length; i++) { let xCol = columns[i]; let width = xCol.width || defaultColumnWidth; sumWidth += width; // 找到当前滚动在最左侧的列 if (scrollLeft > sumWidth - leftWidth && scrollLeft <= sumWidth + width - leftWidth) { nextLeft = sumWidth - leftWidth; break; } } if (nextLeft < 0) { nextLeft = 0; } this.centerBodyRef.current.scrollToPosition({ scrollLeft: nextLeft }); } handleScrollRight = () => { const { columns, defaultColumnWidth } = this.state; const { dataSource = [] } = this.props; const { rightFixedColumns } = this.resolveColumns(); const rightWidth = _.sumBy(rightFixedColumns, each => each.width || defaultColumnWidth); if (!this.centerHeaderRef.current || !this.centerBodyRef.current || !this.rootRef.current) { return; } let scrollLeft = this.centerBodyRef.current.state.scrollLeft; let bodyContainer = _.get(this.centerBodyRef.current, '_scrollingContainer'); let headerContainer = _.get(this.centerHeaderRef.current, '_scrollingContainer'); let nextLeft = 0; let sumWidth = 0; let xIndex = -1; for (let i = 0; i < columns.length; i++) { let tWidth = columns[i].width || defaultColumnWidth; if (sumWidth + tWidth - scrollLeft >= this.allWidth - rightWidth) { xIndex = i; break; } sumWidth += tWidth; } if (xIndex === -1) { return ; } let left = sumWidth; let width = columns[xIndex].width || defaultColumnWidth; var p = this._getClientWidth() - width; nextLeft = left - p + rightWidth; let scrollWidth = bodyContainer.scrollWidth; let clientWidth = bodyContainer.clientWidth; if (_.isEmpty(dataSource)) { scrollWidth =_.sumBy(columns, each => each.width || defaultColumnWidth); clientWidth = headerContainer.clientWidth; if (scrollWidth < clientWidth) { scrollWidth = clientWidth; } } if (nextLeft > scrollWidth - clientWidth) { nextLeft = scrollWidth - clientWidth; } this.centerBodyRef.current.scrollToPosition({ scrollLeft: nextLeft }); } scrollToColumn = (col) => { let left = 0; const { columns, defaultColumnWidth } = this.state; const { leftFixedColumns } = this.resolveColumns(); const leftWidth = _.sumBy(leftFixedColumns, 'width'); const minWidth = _.sumBy(columns, each => each.width || defaultColumnWidth); const clientWidth = this._getClientWidth(); for (let i = 0; i < columns.length; i++) { let xCol = columns[i]; if (xCol.key === col.key) { break; } left += xCol.width; } this.setState({ blinkColumnKey: col.key, }, () => { if (blinkInterval) { window.clearTimeout(blinkInterval); } blinkInterval = window.setTimeout(() => { if (this.isUnmouting) { return; } this.setState({blinkColumnKey: null}) }, 1800) }) this.reRenderColumns(); // 如果没有滚动条 则不滚动 if (minWidth <= clientWidth) { return; } left = left - leftWidth; if (left >= minWidth - clientWidth) { left = minWidth - clientWidth; } else if (left < 0) { left = 0; } this.centerBodyRef.current.scrollToPosition({ scrollLeft: left }); } scrollToRow = (rowIndex) => { this.centerBodyRef.current.scrollToCell({ rowIndex }); } render() { const { height, overscanColumnCount, overscanRowCount, rowHeight, fixedRowCount } = this.state; const { dataSource, pagination, loading, emptyInfo, hasButton, buttonInfo, bordered } = this.props; let rowCount = dataSource.length; let headRowCount = 1 + fixedRowCount; const { columnCount, } = this.resolveColumns(); let bodyContainer = _.get(this.centerBodyRef.current, '_scrollingContainer'); let hasVerticalScrollBar = height < rowHeight * rowCount; if (bodyContainer) { hasVerticalScrollBar = bodyContainer.scrollHeight > bodyContainer.clientHeight; } return ( <React.Fragment> <ScrollSync className={localClass}> {({ clientHeight, clientWidth, onScroll, scrollHeight, scrollLeft, scrollTop, scrollWidth, }) => { return ( <React.Fragment> <div ref={this.rootRef} className={cn('GridRow', localClass, {bordered})}> <div className={'GridColumn'}> <AutoSizer onResize={this.debouncedResize} disableHeight> {({ width }) => { this.allWidth = width; return ( <div style={{ height: height + rowHeight }}> <div onMouseLeave={this.handleHeaderMouseLeave} style={{ height: rowHeight * headRowCount, width: this._getClientWidth(), }}> <Grid className={'HeaderGrid'} ref={this.centerHeaderRef} columnWidth={this._getColunmWidth} columnCount={columnCount} height={rowHeight * headRowCount} overscanColumnCount={overscanColumnCount} cellRenderer={this._renderHeaderCell} cellRangeRenderer={this._cellRangeRenderer.bind(this, 'header')} rowHeight={rowHeight} rowCount={headRowCount} scrollLeft={scrollLeft} width={this._getClientWidth()} /> </div> <div onMouseLeave={this.handleBodyMouseLeave} style={{ // height: height - fixedRowCount * rowHeight, height: this._getBodyHeight(height, fixedRowCount, rowHeight), width, }}> <Grid className={'BodyGrid'} ref={this.centerBodyRef} columnWidth={this._getColunmWidth} columnCount={columnCount} height={this._getBodyHeight(height, fixedRowCount, rowHeight)} cellRangeRenderer={this._cellRangeRenderer.bind(this, 'body')} overscanColumnCount={overscanColumnCount} overscanRowCount={overscanRowCount} cellRenderer={this._renderBodyCell} onScroll={onScroll} rowHeight={rowHeight} rowCount={this.getRowCount(rowCount, fixedRowCount)} width={hasVerticalScrollBar ? width : this._getClientWidth()} /> </div> </div> ) }} </AutoSizer> </div> { loading && <React.Fragment> <div className='loading-mask' style={{}}> </div> <Loading /> </React.Fragment> } { !loading && _.isEmpty(dataSource) && <EmptyHolder placeholder={emptyInfo || '暂无数据'} hasButton={hasButton} buttonInfo={buttonInfo} /> } </div> </React.Fragment> ); }} </ScrollSync> {!_.isEmpty(dataSource) && pagination && <div className={pagerContainer}> <Pagination style={{ float: 'right', padding: '15px 20px', fontSize: '12px' }} {...pagination} itemRender={(page, type, originalElement) => { if (type === "prev") { return <Button>上一页</Button>; } if (type === "next") { return <Button>下一页</Button>; } return originalElement; }} total={pagination.total} /> </div> } </React.Fragment> ); } _getBodyHeight = (height, fixedRowCount, rowHeight) => { // const { dataSource = [] } = this.props; // let currHeight = dataSource.length * rowHeight; // if (currHeight < height) { // height = currHeight; // } let p = height - fixedRowCount * rowHeight; return p > 0 ? p : 0; } resolveColumns = () => { const { columns } = this.state; let columnCount = columns.length; let leftFixedColumns = _.filter(columns, each => each.fixed === 'left') || []; let rightFixedColumns = _.filter(columns, each => each.fixed === 'right') || []; return { columnCount, leftFixedColumns, rightFixedColumns } } handleSorterClick = (col, evt) => { let { onSort, sortBy, sortOrder } = this.props; if(evt && _.isFunction(evt.preventDefault)) { evt.preventDefault(); evt.stopPropagation(); } if (sortBy === col.key) { sortOrder = sortOrder === 'descend' ? 'ascend' : 'descend'; } else { sortBy = col.key; sortOrder = 'descend'; } if (_.isFunction(onSort)) { onSort({sortBy, sortOrder}); } } _getClientWidth = () => { return this.allWidth - scrollbarSize(); } // 计算动态列宽 _getColunmWidth({ index }) { const { columns, defaultColumnWidth } = this.state; let rWidth = +_.get(columns[index], 'width'); if (rWidth) { return rWidth; } var xCount = _.countBy(columns, each => !each.width); // 没有设置width的列数量 var xW = _.sumBy(columns, 'width'); // 已经设置列宽的总宽度 var dWidth = this._getClientWidth() - xW; // 剩余的宽度 var temp = dWidth / xCount.true;// 平均宽度 return temp < defaultColumnWidth ? defaultColumnWidth : temp; } _renderBodyCell({ columnIndex, key, rowIndex, style}) { return this._renderLeftSideCell({ columnIndex, key, rowIndex, style }); } _renderHeaderCell({ columnIndex, key, rowIndex, style }) { return this._renderLeftHeaderCell({ columnIndex, key, rowIndex, style }); } // 最终执行head cell渲染的函数 _renderLeftHeaderCell = ({ columnIndex, key, style, rowIndex }) => { const { fixedRowCount, hoverColumnKey, blinkColumnKey } = this.state; if (rowIndex !== 0) { return this._renderLeftSideCell({ columnIndex, key, style, rowIndex: rowIndex - 1 - fixedRowCount }); // rowIndex 负补偿 } const { columns } = this.state; const { renderHeaderMoreOptions, onHeaderCell, sortOrder, sortBy } = this.props; const { columnCount, rightFixedColumns } = this.resolveColumns(); let xCol = columns[columnIndex] || {}; let extraProps = {}; if (_.isFunction(onHeaderCell)) { extraProps = onHeaderCell(xCol, {columnIndex, key, rowIndex}) || {}; } let { onMouseEnter, onClick, className, columnindex, key:x, style: extraStyle, ...restExtraProps } = extraProps; let menu = null; if (_.isFunction(renderHeaderMoreOptions)) { menu = renderHeaderMoreOptions(xCol); } return ( <div onMouseEnter={this.handleHeaderCellMouseEnter.bind(this, xCol, {columnIndex, key, rowIndex}, extraProps)} onClick={this.handleHeaderCellClick.bind(this, xCol, {columnIndex, key, rowIndex}, extraProps)} className={cn('headerCell', `headerCell-${columnIndex}`, className, { columnHover: xCol.key === hoverColumnKey, blinkCell: xCol.key === blinkColumnKey, fixed: xCol.fixed, lastNonFixed: columnCount - rightFixedColumns.length === columnIndex + 1 })} columnindex={columnIndex} key={key} style={{ ...style, textAlign: xCol.textAlign || 'left', ...extraStyle }} {...restExtraProps}> { _.isFunction(xCol.renderHeader) ? xCol.renderHeader(xCol.title, xCol, columnIndex) : ( <div className='header-cell-container'> <div className='header-title clearfix' title={xCol.title || ''}> <span className='header-title-text'> {this._renderHeaderTitle(xCol)} </span> { xCol.sorter && <Sorter onClick={this.handleSorterClick.bind(this, xCol)} className='header-sorter' sortOrder={ xCol.key === sortBy ? (sortOrder || 'descend') : '' } /> } </div> { !_.isEmpty(menu) && <div className='header-more' onClick={this._cancelEvent}> <Dropdown trigger={['hover']} overlay={menu} placement="bottomRight"> <div className='sort-wrapper'> <img src={moreIcon} alt='' style={{width: 10}} /> {/* <OnlineSvg type='icon-more'/> */} </div> </Dropdown> </div> } </div> ) } </div> ); } // 最终执行body cell渲染的函数 _renderLeftSideCell({ columnIndex, key, rowIndex, style }) { const { dataSource, bordered, rowClassName, onCell } = this.props; const { columns, fixedRowCount, hoverColumnKey, hoverRowIndex, blinkColumnKey, selectedRowData = {} } = this.state; const { columnCount, rightFixedColumns } = this.resolveColumns(); rowIndex = rowIndex + fixedRowCount; if (rowIndex <= 0) { rowIndex = 0; } const xRow = dataSource[rowIndex] || {}; const xCol = columns[columnIndex] || {}; const text = _.get(xRow, `${xCol.key}`); const rowClass = rowIndex % 2 === 0 ? 'oddRow' : 'evenRow' const classNames = cn(rowClass, 'cell', `cell-${rowIndex}`, `column-${columnIndex}`, { fixed: !!xCol.fixed, rowClassName, columnHover: xCol.key === hoverColumnKey, blinkCell: xCol.key === blinkColumnKey, rowActive: hoverRowIndex === rowIndex, cellBordered: bordered, cellLeftNav: xCol.key === 'leftNav', cellRightNav: xCol.key === 'rightNav', navCell: xCol.key === 'rightNav' || xCol.key === 'leftNav', rowSelected: selectedRowData.rowIndex === rowIndex, lastNonFixed: columnCount - rightFixedColumns.length === columnIndex + 1 }); if (xCol.__columnType === 'rowSelect') { style ={...style}; style.textAlign = 'center'; } let extraProps = {}; if (_.isFunction(onCell)) { extraProps = onCell(xRow, {columnIndex, key, rowIndex, column: xCol}) || {}; } let { onMouseEnter, onClick, onDoubleClick, className, key:x, style:extraStyle, ...restExtraProps } = extraProps; return ( <div className={classNames} key={key} onMouseEnter={this.handleBodyCellMouseEnter.bind(this, xRow, {columnIndex, key, rowIndex, column: xCol}, extraProps)} onClick={this.handleBodyCellClick.bind(this, xRow, {columnIndex, key, rowIndex, column: xCol}, extraProps)} onDoubleClick={this.handleBodyCellDBClick.bind(this, xRow, {columnIndex, key, rowIndex, column: xCol}, extraProps)} style={{ textAlign: xCol.textAlign || 'left', ...style, ...extraStyle }} {...restExtraProps}> <div className='cell-container'> {_.isFunction(xCol.render) ? xCol.render(text, xRow, columnIndex) : text} </div> </div> ); } _renderHeaderTitle = (xCol) => { if (_.isFunction(xCol.renderHeaderTitle)) { return xCol.renderHeaderTitle(xCol); } return xCol.title || xCol.key || '--'; } getRowCount = (rowCount, fixedRowCount) => { let temp = rowCount - fixedRowCount; return temp > 0 ? temp : 0; } //2种方式更新checkbox状态 一种是通过js控制每个控件的状态 另外一种是通过rerender重新刷新表控件 handleToggleSelectAll = (evt) => { let { dataSource, rowSelection: { selectedRowKeys = [], onChange } = {} } = this.props; if (_.isEmpty(dataSource)) { return; } if (selectedRowKeys.length === dataSource.length) { selectedRowKeys.length = 0; // 清空 } else { selectedRowKeys.length = 0; // 添加 dataSource.map(each => this.rowKey(each)).forEach(each => { selectedRowKeys.push(each); }) } if (_.isFunction(onChange)) { onChange(selectedRowKeys); } } handleToggleSelect = (evt) => { const dataref = evt.target.dataref || {}; const id = this.rowKey(dataref); let { rowSelection: { selectedRowKeys = [], onChange } = {} } = this.props; let index = _.findIndex(selectedRowKeys, each => each === id) if (index === -1) { selectedRowKeys.push(id); } else { selectedRowKeys.splice(index, 1); } if (_.isFunction(onChange)) { onChange(selectedRowKeys); } } _cancelEvent = (evt) => { if (evt && _.isFunction(evt.preventDefault)) { evt.preventDefault(); evt.stopPropagation(); } } _cellRangeRenderer = (placement, { cellCache, // Temporary cell cache used while scrolling cellRenderer, // Cell renderer prop supplied to Grid columnSizeAndPositionManager, // @see CellSizeAndPositionManager, columnStartIndex, // Index of first column (inclusive) to render columnStopIndex, // Index of last column (inclusive) to render horizontalOffsetAdjustment, // Horizontal pixel offset (required for scaling) isScrolling, // The Grid is currently being scrolled rowSizeAndPositionManager, // @see CellSizeAndPositionManager, rowStartIndex, // Index of first row (inclusive) to render rowStopIndex, // Index of last row (inclusive) to render scrollLeft, // Current horizontal scroll offset of Grid scrollTop, // Current vertical scroll offset of Grid styleCache, // Temporary style (size & position) cache used while scrolling verticalOffsetAdjustment, // Vertical pixel offset (required for scaling) }) => { const { columns, defaultColumnWidth } = this.state; const { dataSource } = this.props; const leftSideCells = []; const rightSideCells = []; const bodyCells = []; let leftSideColunms = _.filter(columns, col => col.fixed === 'left'); let rightSideColunms = _.filter(columns, col => col.fixed === 'right'); let leftSideWidth = _.sumBy(leftSideColunms, (col) => col.width || defaultColumnWidth); let rightSideWidth = _.sumBy(rightSideColunms, (col) => col.width || defaultColumnWidth); let scrollWidth = _.sumBy(columns, (col) => col.width || defaultColumnWidth); let clientWidth = this._getClientWidth();//headerContainer.clientWidth; // let scrollContainer = null; // if (placement === 'header') { // scrollContainer = _.get(this.centerBodyRef.current, '_scrollingContainer'); // } else { // scrollContainer = _.get(this.centerHeaderRef.current, '_scrollingContainer'); // } if (scrollWidth < clientWidth) { scrollWidth = clientWidth; } let deltaWidth = scrollWidth - clientWidth; if (deltaWidth < 0) { deltaWidth = 0; } if (scrollLeft > deltaWidth) { scrollLeft = deltaWidth; } for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) { let rowDatum = rowSizeAndPositionManager.getSizeAndPositionOfCell(rowIndex) for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { let columnDatum = columnSizeAndPositionManager.getSizeAndPositionOfCell(columnIndex) // 超出范围的列 需要重新计算 if ((columnIndex < columnStartIndex && !columns[columnIndex].fixed ) || (columnIndex > columnStopIndex && !columns[columnIndex].fixed )) { // 如果数据源不为空 则不渲染该列 if (!_.isEmpty(dataSource)) { continue; } // 超出视口范围的列 不渲染 if (columnDatum.offset - scrollLeft > this.allWidth || columnDatum.offset - scrollLeft + columnDatum.size < 0) { continue; } } let left = undefined; let fixed = _.get(columns[columnIndex], 'fixed'); let top = rowDatum.offset + verticalOffsetAdjustment let key = `${rowIndex}-${columnIndex}` let height = rowDatum.size let width = columnDatum.size let style = { height, width, left, top, position: 'absolute' } if (fixed === 'left') { style.left = columnDatum.offset + horizontalOffsetAdjustment; leftSideCells.push(cellRenderer({columnIndex, key, style, rowIndex})); } else if (fixed === 'right') { // 需要计算右边 style.left = columnDatum.offset + horizontalOffsetAdjustment - (scrollWidth - rightSideWidth); rightSideCells.push(cellRenderer({columnIndex, key, style, rowIndex})); } else { style.left = columnDatum.offset + horizontalOffsetAdjustment; bodyCells.push(cellRenderer({columnIndex, key, style, rowIndex})); } } } return [( <React.Fragment key='sticky-scroll-wrapper'> <div key='left-side-container' style={{width: leftSideWidth, height: '100%', position: 'sticky', float: 'left', display: 'inline-block', zIndex: 3, top:0, left: '0px'}}> <div style={{position: 'relative'}}> {leftSideCells} </div> </div> <div key='body-content-container' style={{width: clientWidth - (leftSideWidth + rightSideWidth), height: '100%', float: 'left',display: 'inline-block' }}> {bodyCells} </div> <div key='right-side-container' style={{width: rightSideWidth, height: '100%', position: 'sticky', float: 'left', display: 'inline-block', left: clientWidth - rightSideWidth}}> <div style={{position: 'relative'}}> {rightSideCells} </div> </div> </React.Fragment> )] } } export default VirtualizedTable;