UNPKG

bee-table

Version:
1,432 lines (1,322 loc) 52.2 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classes from 'component-classes'; import TableRow from './TableRow'; import TableHeader from './TableHeader'; import { measureScrollbar, debounce, warningOnce ,getMaxColChildrenLength} from './lib/utils'; import shallowequal from 'shallowequal'; import addEventListener from 'tinper-bee-core/lib/addEventListener'; import ColumnManager from './ColumnManager'; import createStore from './createStore'; import Loading from 'bee-loading'; import Icon from 'bee-icon'; import { Event,EventUtil,closest} from "./lib/utils"; import i18n from "./lib/i18n"; import { getComponentLocale } from "bee-locale/build/tool"; const propTypes = { data: PropTypes.array, expandIconAsCell: PropTypes.bool, defaultExpandAllRows: PropTypes.bool, expandedRowKeys: PropTypes.array, defaultExpandedRowKeys: PropTypes.array, useFixedHeader: PropTypes.bool, columns: PropTypes.array, clsPrefix: PropTypes.string, bodyStyle: PropTypes.object, style: PropTypes.object, //特殊的渲染规则的key值 rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), rowClassName: PropTypes.func, expandedRowClassName: PropTypes.func, childrenColumnName: PropTypes.string, onExpand: PropTypes.func, onRowHover:PropTypes.func, onExpandedRowsChange: PropTypes.func, indentSize: PropTypes.number, onRowClick: PropTypes.func, onRowDoubleClick: PropTypes.func, expandIconColumnIndex: PropTypes.number, //是否显示表头 showHeader: PropTypes.bool, title: PropTypes.func, footer: PropTypes.func, emptyText: PropTypes.func, scroll: PropTypes.object, rowRef: PropTypes.func, getBodyWrapper: PropTypes.func, children: PropTypes.node, draggable: PropTypes.bool, minColumnWidth: PropTypes.number, filterable: PropTypes.bool, filterDelay: PropTypes.number, onFilterChange: PropTypes.func, onFilterClear: PropTypes.func, syncHover: PropTypes.bool, tabIndex:PropTypes.string, hoverContent:PropTypes.func, size: PropTypes.oneOf(['sm', 'md', 'lg']), rowDraggAble: PropTypes.bool, onDropRow: PropTypes.func, onDragRowStart: PropTypes.func, bodyDisplayInRow: PropTypes.bool, // 表格内容超出列宽度时进行换行 or 以...形式展现 headerDisplayInRow: PropTypes.bool, // 表头内容超出列宽度时进行换行 or 以...形式展现 showRowNum: PropTypes.object, // 表格是否自动生成序号,格式为{base:number || 0,defaultKey:string || '_index',defaultName:string || '序号'} }; const defaultProps = { data: [], useFixedHeader: false, expandIconAsCell: false, defaultExpandAllRows: false, defaultExpandedRowKeys: [], rowKey: 'key', rowClassName: () => '', expandedRowClassName: () => '', onExpand() { }, onExpandedRowsChange() { }, onRowClick() { }, onRowDoubleClick() { }, clsPrefix: 'u-table', bodyStyle: {}, style: {}, childrenColumnName: 'children', indentSize: 15, expandIconColumnIndex: 0, showHeader: true, scroll: {}, rowRef: () => null, getBodyWrapper: body => body, // emptyText: () => <div><Icon type="uf-nodata" className="table-nodata"></Icon><span>{locale["no_data"]}</span></div>, columns:[], minColumnWidth: 80, locale:{}, syncHover: true, setRowHeight:()=>{}, setRowParentIndex:()=>{}, tabIndex:'0', heightConsistent:false, size: 'md', rowDraggAble:false, onDropRow: ()=>{}, onDragRowStart: ()=>{}, bodyDisplayInRow: true, headerDisplayInRow: true, showRowNum: false, }; class Table extends Component { constructor(props) { super(props); let expandedRowKeys = []; let rows = [...props.data]; this.columnManager = new ColumnManager(props.columns, props.children, props.originWidth, props.rowDraggAble, props.showRowNum); // 加入props.showRowNum参数 this.store = createStore({ currentHoverKey: null }); this.firstDid = true; if (props.defaultExpandAllRows) { for (let i = 0; i < rows.length; i++) { const row = rows[i]; expandedRowKeys.push(this.getRowKey(row, i)); rows = rows.concat(row[props.childrenColumnName] || []); } } else { expandedRowKeys = props.expandedRowKeys || props.defaultExpandedRowKeys; } this.state = { expandedRowKeys, data: props.data, currentHoverKey: null, scrollPosition: 'left', fixedColumnsHeadRowsHeight: [], fixedColumnsBodyRowsHeight: [], } this.onExpandedRowsChange = this.onExpandedRowsChange.bind(this); this.onExpanded = this.onExpanded.bind(this); this.onRowDestroy = this.onRowDestroy.bind(this); this.getRowKey = this.getRowKey.bind(this); this.getExpandedRows = this.getExpandedRows.bind(this); this.getHeader = this.getHeader.bind(this); this.getHeaderRows = this.getHeaderRows.bind(this); this.getExpandedRow = this.getExpandedRow.bind(this); this.getRowsByData = this.getRowsByData.bind(this); this.getRows = this.getRows.bind(this); this.getColGroup = this.getColGroup.bind(this); this.getLeftFixedTable = this.getLeftFixedTable.bind(this); this.getRightFixedTable = this.getRightFixedTable.bind(this); this.getTable = this.getTable.bind(this); this.getTitle = this.getTitle.bind(this); this.getFooter = this.getFooter.bind(this); this.getEmptyText = this.getEmptyText.bind(this); this.getHeaderRowStyle = this.getHeaderRowStyle.bind(this); this.syncFixedTableRowHeight = this.syncFixedTableRowHeight.bind(this); this.resetScrollX = this.resetScrollX.bind(this); this.findExpandedRow = this.findExpandedRow.bind(this); this.isRowExpanded = this.isRowExpanded.bind(this); this.detectScrollTarget = this.detectScrollTarget.bind(this); this.handleBodyScroll = this.handleBodyScroll.bind(this); this.handleRowHover = this.handleRowHover.bind(this); this.computeTableWidth = this.computeTableWidth.bind(this); this.onBodyMouseLeave = this.onBodyMouseLeave.bind(this); this.tableUid = null; this.contentTable = null; this.leftColumnsLength //左侧固定列的长度 this.centerColumnsLength //非固定列的长度 } componentWillMount() { this.centerColumnsLength = this.columnManager.centerColumns().length this.leftColumnsLength = this.columnManager.leftColumns().length } componentDidMount() { this.getTableUID(); EventUtil.addHandler(this.contentTable,'keydown',this.onKeyDown); EventUtil.addHandler(this.contentTable,'focus',this.onFocus); setTimeout(this.resetScrollX, 300); //含有纵向滚动条 // if(this.props.scroll.y){ this.scrollbarWidth = measureScrollbar(); // } //后续也放在recevice里面 if (!this.props.originWidth) { this.computeTableWidth(); } if (this.columnManager.isAnyColumnsFixed()) { this.syncFixedTableRowHeight(); this.resizeEvent = addEventListener( window, 'resize', this.resize ); } } componentWillReceiveProps(nextProps) { if ('data' in nextProps) { this.setState({ data: nextProps.data, }); } if ('expandedRowKeys' in nextProps) { this.setState({ expandedRowKeys: nextProps.expandedRowKeys, }); } if (nextProps.columns && nextProps.columns !== this.props.columns) { this.columnManager.reset(nextProps.columns, null, this.props.showRowNum); // 加入this.props.showRowNum参数 if(nextProps.columns.length !== this.props.columns.length && this.refs && this.bodyTable){ this.scrollTop = this.bodyTable.scrollTop; } } else if (nextProps.children !== this.props.children) { this.columnManager.reset(null, nextProps.children,this.porps.showRowNum); // 加入this.props.showRowNum参数 } //适配lazyload if(nextProps.scrollTop > -1){ // this.bodyTable.scrollTop = nextProps.scrollTop; this.scrollTop = nextProps.scrollTop; } if (!nextProps.originWidth) { this.computeTableWidth(); this.firstDid = true;//避免重复update } if(nextProps.resetScroll){ this.resetScrollX(); } // fix:模态框中使用table,计算的滚动条宽度为0的bug // fix:表格首次渲染时 display:none,再显示时,未重新计算,导致表行出现错位的bug if(this.scrollbarWidth<=0 && this.props.scroll.y){ this.scrollbarWidth = measureScrollbar(); } // console.log('this.scrollTop**********',this.scrollTop); } componentDidUpdate(prevProps) { if (this.columnManager.isAnyColumnsFixed()) { this.syncFixedTableRowHeight(); } //适应模态框中表格、以及父容器宽度变化的情况 if (typeof (this.props.scroll.x) !== 'number' && this.contentTable.getBoundingClientRect().width !== this.contentDomWidth && this.firstDid) { this.computeTableWidth(); this.firstDid = false;//避免重复update } if(this.scrollTop > -1){ this.refs.fixedColumnsBodyLeft && ( this.refs.fixedColumnsBodyLeft.scrollTop = this.scrollTop); this.refs.fixedColumnsBodyRight && ( this.refs.fixedColumnsBodyRight.scrollTop = this.scrollTop); this.bodyTable.scrollTop = this.scrollTop; this.scrollTop = -1; } if (prevProps.data.length === 0 || this.props.data.length === 0 ) { this.resetScrollX(); } // 是否传入 scroll中的y属性,如果传入判断是否是整数,如果是则进行比较 。bodyTable 的clientHeight进行判断 this.isShowScrollY(); } componentWillUnmount() { // 移除绑定事件,避免内存泄漏 this.contentTable = null; EventUtil.removeHandler(this.contentTable,'keydown',this.onKeyDown); EventUtil.removeHandler(this.contentTable,'focus',this.onFocus); if (this.resizeEvent) { this.resizeEvent.remove(); } } resize = ()=>{ debounce(this.syncFixedTableRowHeight, 150); this.computeTableWidth(); let renderFlag = this.state.renderFlag; this.setState({ renderFlag: !renderFlag }); } getTableUID =()=>{ let uid = "_table_uid_"+new Date().getTime(); this.tableUid = uid; let div = document.createElement("div"); // div.className = "u-table-drag-hidden-cont"; div.className = "u-table-drag-hidden-cont"; div.id = uid; this.contentTable.appendChild(div); } computeTableWidth() { //如果用户传了scroll.x按用户传的为主 let setWidthParam = this.props.scroll.x if (typeof (setWidthParam) == 'number') { let numSetWidthParam = parseInt(setWidthParam); this.contentWidth = numSetWidthParam; } else { // this.preContentDomWidth = this.contentDomWidth; //计算总表格宽度、根据表格宽度和各列的宽度和比较,重置最后一列 this.contentDomWidth = this.contentTable.getBoundingClientRect().width//表格容器宽度 this.contentWidth = this.contentDomWidth;//默认与容器宽度一样 } const computeObj = this.columnManager.getColumnWidth(this.contentWidth); let lastShowIndex = computeObj.lastShowIndex; this.computeWidth = computeObj.computeWidth; this.domWidthDiff = this.contentDomWidth - this.computeWidth; if (typeof (setWidthParam) == 'string' && setWidthParam.indexOf('%')) { this.contentWidth = this.contentWidth * parseInt(setWidthParam) / 100; this.domWidthDiff = this.contentDomWidth - this.contentWidth; } if (this.computeWidth < this.contentWidth) { let contentWidthDiff = this.scrollbarWidth?this.contentWidth - this.computeWidth-this.scrollbarWidth:this.contentWidth - this.computeWidth; //bordered的表格需要减去边框的差值1 if(this.props.bordered){ contentWidthDiff = contentWidthDiff-1; } this.setState({ contentWidthDiff, lastShowIndex }); } else { this.contentWidth = this.computeWidth; this.setState({ contentWidthDiff: 0, lastShowIndex });//重新渲染,为了显示滚动条 } } //根据内容动态的判断是否显示纵向滚动条 isShowScrollY(){ const props = this.props; const y = props.scroll && props.scroll.y; if(y){ const bodyH = this.bodyTable.clientHeight; const bodyContentH = this.bodyTable.querySelector('table').clientHeight; const rightBodyTable = this.refs.fixedColumnsBodyRight; // const leftBodyTable = this.refs.fixedColumnsBodyLeft; const overflowy = bodyContentH <= bodyH ? 'auto':'scroll'; this.bodyTable.style.overflowY = overflowy; this.headTable.style.overflowY = overflowy; rightBodyTable && (rightBodyTable.style.overflowY = overflowy); // 没有纵向滚动条时,表头横向滚动条根据内容动态显示 待验证 // if(overflowy == 'auto'){ // this.fixedHeadTable && (this.fixedHeadTable.style.overflowX = 'auto'); // rightBodyTable && (rightBodyTable.style.overflowX = 'auto'); // leftBodyTable && (leftBodyTable.style.overflowX = 'auto'); // } } } onExpandedRowsChange(expandedRowKeys) { if (!this.props.expandedRowKeys) { this.setState({ expandedRowKeys }); } this.props.onExpandedRowsChange(expandedRowKeys); } onExpanded(expanded, record, index, e) { if (e) { e.preventDefault(); e.stopPropagation(); } const info = this.findExpandedRow(record); if (typeof info !== 'undefined' && !expanded) { this.onRowDestroy(record, index); } else if (!info && expanded) { const expandedRows = this.getExpandedRows().concat(); expandedRows.push(this.getRowKey(record, index)); this.onExpandedRowsChange(expandedRows); } this.props.onExpand(expanded, record,index); } onRowDestroy(record, rowIndex) { const expandedRows = this.getExpandedRows().concat(); const rowKey = this.getRowKey(record, rowIndex); let index = -1; expandedRows.forEach((r, i) => { if (r === rowKey) { index = i; } }); if (index !== -1) { expandedRows.splice(index, 1); } // if(this.currentHoverKey == rowKey && this.hoverDom){ this.hoverDom.style.display = 'none'; } this.onExpandedRowsChange(expandedRows); } getRowKey(record, index) { const rowKey = this.props.rowKey; const key = (typeof rowKey === 'function') ? rowKey(record, index) : record[rowKey]; warningOnce( key !== undefined, 'Each record in table should have a unique `key` prop,' + 'or set `rowKey` to an unique primary key.' ); return key; } getExpandedRows() { return this.props.expandedRowKeys || this.state.expandedRowKeys; } getHeader(columns, fixed, leftFixedWidth, rightFixedWidth) { const { filterDelay, onFilterChange, onFilterClear, filterable, showHeader, expandIconAsCell, clsPrefix, onDragStart, onDragEnter, onDragOver, onDrop,onDragEnd, draggable, onMouseDown, onMouseMove, onMouseUp, dragborder, onThMouseMove, dragborderKey, minColumnWidth, headerHeight,afterDragColWidth,headerScroll ,bordered,onDropBorder,onDraggingBorder} = this.props; const rows = this.getHeaderRows(columns); if (expandIconAsCell && fixed !== 'right') { rows[0].unshift({ key: 'u-table-expandIconAsCell', className: `${clsPrefix}-expand-icon-th`, title: '', rowSpan: rows.length, }); } const trStyle = headerHeight&&!fixed ? { height: headerHeight } : (fixed ? this.getHeaderRowStyle(columns, rows) : null); let drop = draggable ? { onDragStart, onDragOver, onDrop,onDragEnd, onDragEnter, draggable } : {}; let dragBorder = dragborder ? { onMouseDown, onMouseMove, onMouseUp, dragborder, onThMouseMove, dragborderKey,onDropBorder,onDraggingBorder } : {}; let contentWidthDiff = 0; //非固定表格,宽度不够时自动扩充 if (!fixed) { contentWidthDiff = this.state.contentWidthDiff } return showHeader ? ( <TableHeader {...drop} {...dragBorder} locale={this.props.locale} minColumnWidth={minColumnWidth} contentWidthDiff={contentWidthDiff} contentWidth={this.contentWidth} lastShowIndex={this.state.lastShowIndex} clsPrefix={clsPrefix} rows={rows} contentTable={this.contentTable} rowStyle={trStyle} fixed={fixed} filterable={filterable} onFilterChange={onFilterChange} onFilterClear={onFilterClear} filterDelay={filterDelay} afterDragColWidth = {afterDragColWidth} contentDomWidth={this.contentDomWidth} scrollbarWidth = {this.scrollbarWidth} headerScroll = {headerScroll} bordered = {bordered} leftFixedWidth = {leftFixedWidth} rightFixedWidth = {rightFixedWidth} /> ) : null; } getHeaderRows(columns, currentRow = 0, rows) { let { contentWidthDiff = 0, lastShowIndex = -1 } = this.state; let filterCol = []; rows = rows || []; rows[currentRow] = rows[currentRow] || []; columns.forEach((column,i) => { if (column.rowSpan && rows.length < column.rowSpan) { while (rows.length < column.rowSpan) { rows.push([]); } } let width = column.width; if (typeof (width) == 'string' && width.indexOf('%') > -1 && this.contentWidth) { width = parseInt(this.contentWidth * parseInt(width) / 100); } else if (width) { width = parseInt(width); } if (lastShowIndex == i && width) { width = width + contentWidthDiff; } const cell = { key: column.key, className: column.className || '', children: column.title, drgHover: column.drgHover, fixed: column.fixed, width: width, dataindex:column.dataIndex, textAlign:column.textAlign, titleAlign: column.titleAlign, // 标题水平对齐方式 required: column.required, // 标题是否展示必填标志 }; if (column.onHeadCellClick) { cell.onClick = column.onHeadCellClick; } if (column.children) { this.getHeaderRows(column.children, currentRow + 1, rows); } if ('colSpan' in column) { cell.colSpan = column.colSpan; } if ('rowSpan' in column) { cell.rowSpan = column.rowSpan; } if (cell.colSpan !== 0) { rows[currentRow].push(cell); } //判断是否启用过滤 if (this.props.filterable) { //组装Filter需要的Col filterCol.push({ key: column.key, children: "过滤渲染", width: column.width, filtertype: column.filterType,//下拉的类型 包括['text','dropdown','date','daterange','number'] dataindex: column.dataIndex,//field datasource: this.props.data,//需要单独拿到数据处理 format: column.format,//设置日期的格式 filterdropdown: column.filterDropdown,//是否显示 show hide filterdropdownauto: column.filterDropdownAuto,//是否自定义数据 filterdropdowndata: column.filterDropdownData,//自定义数据格式 filterdropdownfocus: column.filterDropdownFocus,//焦点触发函数回调 filterdropdowntype: column.filterDropdownType,//下拉的类型分为 String,Number 默认是String filterdropdownincludekeys: column.filterDropdownIncludeKeys,//下拉条件按照指定的keys去显示 filterinputnumberoptions: column.filterInputNumberOptions//设置数值框内的详细属性 }); } }); if (this.props.filterable) { rows.push(filterCol); } return rows.filter(row => row.length > 0); } getExpandedRow(key, content, visible, className, fixed) { const { clsPrefix, expandIconAsCell } = this.props; let colCount; if (fixed === 'left') { colCount = this.columnManager.leftLeafColumns().length; } else if (fixed === 'right') { colCount = this.columnManager.rightLeafColumns().length; } else { // colCount = this.columnManager.leafColumns().length; colCount = this.columnManager.centerColumns().length; //计算非固定列的个数,fix: 嵌套表格场景,右侧列断开的问题 } function contentContainer() { if (content && content.props && content.props.style) { return ( <div style={{ height: content.props.style.height }}></div> ) } else { return ' ' } } const columns = [{ key: 'extra-row', render: () => ({ props: { colSpan: colCount, }, children: !fixed ? content : contentContainer(), }), }]; if (expandIconAsCell && fixed !== 'right') { columns.unshift({ key: 'expand-icon-placeholder', render: () => null, }); } return ( <TableRow columns={columns} visible={visible} className={className} key={`${key}-extra-row`} clsPrefix={`${clsPrefix}-expanded-row`} indent={1} expandable={false} store={this.store} dragborderKey={this.props.dragborderKey} rowDraggAble={this.props.rowDraggAble} onDragRow={this.onDragRow} onDragRowStart={this.onDragRowStart} /> ); } /** * 行拖拽开始时触发 * @param currentKey 当前拖拽目标的key */ onDragRowStart = (currentKey) => { let {data} = this.state,currentIndex,record; data.forEach((da,i)=>{ // tr 的唯一标识通过 data.key 或 rowKey 两种方式传进来 let trKey = da.key ? da.key : this.getRowKey(da, i); if(trKey == currentKey){ currentIndex = i; record = da; } }); this.props.onDragRowStart && this.props.onDragRowStart(record,currentIndex); } /** * 行拖拽结束时触发 * @param currentKey 当前拖拽目标的key * @param targetKey 拖拽结束时,目标位置的key */ onDragRow = (currentKey,targetKey)=>{ let {data} = this.state,currentIndex,targetIndex,record; data.forEach((da,i)=>{ // tr 的唯一标识通过 data.key 或 rowKey 两种方式传进来 let trKey = da.key ? da.key : this.getRowKey(da, i); if(trKey == currentKey){ currentIndex = i; record = da; } if(trKey == targetKey){ targetIndex = i; } }); data = this.swapArray(data,currentIndex,targetIndex); this.props.onDropRow && this.props.onDropRow(data,record); this.setState({ data, }); } /** * 数组元素交换位置 * @param {array} arr 数组 * @param {number} index1 添加项目的位置 * @param {number} index2 删除项目的位置 */ swapArray = (arr, index1, index2) => { var value1 = arr[index1] arr.splice(index1,1) if(index1<index2){ console.log('向下拖') arr.splice(index2,0,value1) }else { console.log('向上拖') arr.splice(index2+1,0,value1) } return arr; } /** * * * @param {*} data * @param {*} visible * @param {*} indent 层级 * @param {*} columns * @param {*} fixed * @param {number} [rootIndex=-1] 祖级节点 * @returns * @memberof Table */ getRowsByData(data, visible, indent, columns, fixed,rootIndex=-1) { const props = this.props; const childrenColumnName = props.childrenColumnName; const expandedRowRender = props.expandedRowRender; const expandRowByClick = props.expandRowByClick; const { fixedColumnsBodyRowsHeight } = this.state; let rst = []; let height; const rowClassName = props.rowClassName; const rowRef = props.rowRef; const expandedRowClassName = props.expandedRowClassName; const needIndentSpaced = props.data.some(record => record[childrenColumnName]); const onRowClick = props.onRowClick; const onRowDoubleClick = props.onRowDoubleClick; const expandIconAsCell = fixed !== 'right' ? props.expandIconAsCell : false; const expandIconColumnIndex = props.expandIconColumnIndex if(props.lazyLoad && props.lazyLoad.preHeight && indent == 0){ rst.push( <TableRow height={props.lazyLoad.preHeight} columns={[]} className='' key={'table_row_first'} store={this.store} visible = {true}/> ) } const lazyCurrentIndex = props.lazyLoad && props.lazyLoad.startIndex ?props.lazyLoad.startIndex :0; const lazyParentIndex = props.lazyLoad && props.lazyLoad.startParentIndex ?props.lazyLoad.startParentIndex :0; const lazyEndIndex = props.lazyLoad && props.lazyLoad.endIndex ?props.lazyLoad.endIndex :-1; for (let i = 0; i < data.length; i++) { let isHiddenExpandIcon; if ( props.showRowNum ){ switch(props.showRowNum.type){ case 'number':{ data[i][props.showRowNum.key || '_index'] = (props.showRowNum.base || 0) + 1; break; } case 'ascii': { data[i][props.showRowNum.key || '_index'] = String.fromCharCode(i + (props.showRowNum.base || '0').charCodeAt()); break; } default: { data[i][props.showRowNum.key || '_index'] = (props.showRowNum.base || 0) + 1; break; } } } const record = data[i]; const key = this.getRowKey(record, i); const childrenColumn = record[childrenColumnName]; const isRowExpanded = this.isRowExpanded(record, i); let expandedRowContent; let expandedContentHeight = 0; //fixedIndex一般是跟index是一个值的,只有是树结构时,会讲子节点的值也累计上 let fixedIndex = i; //判断是否是tree结构 if (this.treeType) { fixedIndex = this.treeRowIndex; } if (expandedRowRender && isRowExpanded) { expandedRowContent = expandedRowRender(record, fixedIndex+lazyCurrentIndex, indent); expandedContentHeight = parseInt(expandedRowContent.props && expandedRowContent.props.style && expandedRowContent.props.style.height?expandedRowContent.props.style.height:0); } //只有当使用expandedRowRender参数的时候才去识别isHiddenExpandIcon(隐藏行展开的icon) if (expandedRowRender && typeof props.haveExpandIcon == 'function') { isHiddenExpandIcon = props.haveExpandIcon(record, i); } const onHoverProps = {}; onHoverProps.onHover = this.handleRowHover; if (props.height) { height = props.height } else if(fixed || props.heightConsistent) { height = fixedColumnsBodyRowsHeight[fixedIndex]; } let leafColumns; if (fixed === 'left') { leafColumns = this.columnManager.leftLeafColumns(); } else if (fixed === 'right') { leafColumns = this.columnManager.rightLeafColumns(); } else { leafColumns = this.columnManager.leafColumns(); } let className = rowClassName(record, fixedIndex+lazyCurrentIndex, indent); //合计代码如果是最后一行并且有合计功能时,最后一行为合计列 if(i == data.length -1 && props.showSum){ className = className + ' sumrow'; } let paramRootIndex = rootIndex; //小于0说明为第一层节点,她的子孙节点要保存自己的根节点 if(paramRootIndex<0){ paramRootIndex = i+lazyParentIndex; } let index = i; if(rootIndex ==-1){ index = i+lazyParentIndex } rst.push( <TableRow indent={indent} indentSize={props.indentSize} needIndentSpaced={needIndentSpaced} className={`${className} ${this.props.rowDraggAble?' row-dragg-able ':''}`} record={record} expandIconAsCell={expandIconAsCell} onDestroy={this.onRowDestroy} index={index} visible={visible} expandRowByClick={expandRowByClick} onExpand={this.onExpanded} expandable={childrenColumn || expandedRowRender} expanded={isRowExpanded} clsPrefix={`${props.clsPrefix}-row`} childrenColumnName={childrenColumnName} columns={leafColumns} expandIconColumnIndex={expandIconColumnIndex} onRowClick={onRowClick} onRowDoubleClick={onRowDoubleClick} height={height} isHiddenExpandIcon={isHiddenExpandIcon} {...onHoverProps} key={"table_row_"+key+"_"+index} hoverKey={key} ref={rowRef} store={this.store} fixed={fixed} expandedContentHeight={expandedContentHeight} setRowHeight={props.setRowHeight} setRowParentIndex={props.setRowParentIndex} treeType={childrenColumn||this.treeType?true:false} fixedIndex={fixedIndex+lazyCurrentIndex} rootIndex = {rootIndex} syncHover = {props.syncHover} bodyDisplayInRow = {props.bodyDisplayInRow} rowDraggAble={this.props.rowDraggAble} onDragRow={this.onDragRow} onDragRowStart={this.onDragRowStart} contentTable={this.contentTable} tableUid = {this.tableUid} expandedIcon={props.expandedIcon} collapsedIcon={props.collapsedIcon} lazyStartIndex = {lazyCurrentIndex} lazyEndIndex = {lazyEndIndex} centerColumnsLength={this.centerColumnsLength} leftColumnsLength={this.leftColumnsLength} /> ); this.treeRowIndex++; const subVisible = visible && isRowExpanded; if (expandedRowContent && isRowExpanded) { rst.push(this.getExpandedRow( key, expandedRowContent, subVisible, expandedRowClassName(record, i, indent), fixed )); } if (childrenColumn) { this.treeType = true;//证明是tree表形式visible = {true} rst = rst.concat(this.getRowsByData( childrenColumn, subVisible, indent + 1, columns, fixed,paramRootIndex )); } } if(props.lazyLoad && props.lazyLoad.sufHeight && indent == 0){ rst.push( <TableRow height={props.lazyLoad.sufHeight} key={'table_row_end'} columns={[]} className='' store={this.store} visible = {true}/> ) } return rst; } getRows(columns, fixed) { //统计index,只有含有树表结构才有用,因为树表结构时,固定列的索引取值有问题 this.treeRowIndex = 0; let rs = this.getRowsByData(this.state.data, true, 0, columns, fixed); return rs; } getColGroup(columns, fixed) { let cols = []; let self = this; let { contentWidthDiff = 0, lastShowIndex = 0 } = this.state; if (this.props.expandIconAsCell && fixed !== 'right') { cols.push( <col className={`${this.props.clsPrefix}-expand-icon-col`} key="u-table-expand-icon-col" /> ); } let leafColumns; if (fixed === 'left') { contentWidthDiff = 0; leafColumns = this.columnManager.leftLeafColumns(); } else if (fixed === 'right') { contentWidthDiff = 0; leafColumns = this.columnManager.rightLeafColumns(); } else { leafColumns = this.columnManager.leafColumns(); } cols = cols.concat(leafColumns.map((c, i, arr) => { let fixedClass =''; let width = c.width; if (typeof (width) == 'string' && width.indexOf('%') > -1 && self.contentWidth) { width = parseInt(self.contentWidth * parseInt(width) / 100); } else if (width) { width = parseInt(width); } if (lastShowIndex == i && width) { width = width + contentWidthDiff; } if (!fixed && c.fixed) { fixedClass = ` ${this.props.clsPrefix}-row-fixed-columns-in-body`; } return <col key={c.key} style={{ width: width, minWidth: c.width }} className={fixedClass}/>; })); return <colgroup id="bee-table-colgroup">{cols}</colgroup>; } renderDragHideTable = () => { const { columns, dragborder, dragborderKey } = this.props; if (!dragborder) return null; let sum = 0; return (<div id={`u-table-drag-hide-table-${dragborderKey}`} className={`${this.props.clsPrefix}-hiden-drag`} > { columns.map((da, i) => { sum += da.width ? da.width : 0; return (<div className={`${this.props.clsPrefix}-hiden-drag-li`} key={da + "_hiden_" + i} style={{ left: sum + "px" }}></div>); }) } </div>); } getLeftFixedTable() { return this.getTable({ columns: this.columnManager.leftColumns(), fixed: 'left', }); } getRightFixedTable() { return this.getTable({ columns: this.columnManager.rightColumns(), fixed: 'right', }); } getTable(options = {}) { const { columns, fixed } = options; const { clsPrefix, scroll = {}, getBodyWrapper, footerScroll,headerScroll,hideHeaderScroll = false,expandIconAsCell } = this.props; let { useFixedHeader,data } = this.props; const bodyStyle = { ...this.props.bodyStyle }; // 这里为什么不写在上面? const headStyle = {}; const innerBodyStyle = {}; const leftFixedWidth = this.columnManager.getLeftColumnsWidth(this.contentWidth); const rightFixedWidth = this.columnManager.getRightColumnsWidth(this.contentWidth); let tableClassName = ''; //表格元素的宽度大于容器的宽度也显示滚动条 if (scroll.x || fixed || this.contentDomWidth < this.contentWidth) { tableClassName = `${clsPrefix}-fixed`; //没有数据并且含有顶部菜单时 if(this.props.data.length == 0 && this.props.headerScroll ){ bodyStyle.overflowX = 'hidden'; } if (!footerScroll) { bodyStyle.overflowX = bodyStyle.overflowX || 'auto'; } } if (scroll.y) { // maxHeight will make fixed-Table scrolling not working // so we only set maxHeight to body-Table here if (fixed) { // bodyStyle.height = bodyStyle.height || scroll.y; innerBodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y; innerBodyStyle.overflowY = bodyStyle.overflowY || 'scroll'; } else { bodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y; } bodyStyle.overflowY = bodyStyle.overflowY || 'scroll'; useFixedHeader = true; // Add negative margin bottom for scroll bar overflow bug const scrollbarWidth = this.scrollbarWidth; if (scrollbarWidth >= 0) { (fixed ? bodyStyle : headStyle).paddingBottom = '0px'; //显示表头滚动条 if(headerScroll){ if(fixed){ if(this.domWidthDiff <= 0){ headStyle.marginBottom = `${scrollbarWidth}px`; bodyStyle.marginBottom = `-${scrollbarWidth}px`; }else{ innerBodyStyle.overflowX = 'auto'; } }else{ //内容少,不用显示滚动条 if(this.domWidthDiff > 0){ headStyle.overflowX = 'hidden'; } headStyle.marginBottom = `0px`; } }else{ if(fixed){ if(this.domWidthDiff > 0){ headStyle.overflow = 'hidden'; innerBodyStyle.overflowX = 'auto'; //兼容expand场景、子表格含有固定列的场景 }else{ bodyStyle.marginBottom = `-${scrollbarWidth}px`; } }else{ // 没有数据时,表头滚动条隐藏问题 if(data.length == 0 && this.domWidthDiff < 0){ headStyle.marginBottom = '0px'; }else{ headStyle.marginBottom = `-${scrollbarWidth}px`; } } } } } if(data.length == 0 && hideHeaderScroll){ //支持 NCC 需求:表格无数据时,去掉表头滚动条 (https://github.com/iuap-design/tinper-bee/issues/207) headStyle.marginBottom = `-${this.scrollbarWidth}px`; } const renderTable = (hasHead = true, hasBody = true) => { const tableStyle = {}; if (!fixed && scroll.x) { // not set width, then use content fixed width if (scroll.x === true) { tableStyle.tableLayout = 'fixed'; } else { tableStyle.width = this.contentWidth - this.columnManager.getLeftColumnsWidth(this.contentWidth) - this.columnManager.getRightColumnsWidth(this.contentWidth); } } // 自动出现滚动条 if ( !fixed && this.contentDomWidth < this.contentWidth) { tableStyle.width = this.contentWidth - this.columnManager.getLeftColumnsWidth(this.contentWidth) - this.columnManager.getRightColumnsWidth(this.contentWidth); } const tableBody = hasBody ? getBodyWrapper( <tbody className={`${clsPrefix}-tbody`} onMouseLeave={this.onBodyMouseLeave}> {this.getRows(columns, fixed)} </tbody> ) : null; let _drag_class = this.props.dragborder ? "table-drag-bordered" : "" return ( <table className={` ${tableClassName} table-bordered ${_drag_class} `} style={tableStyle} > {/* {this.props.dragborder?null:this.getColGroup(columns, fixed)} */} {this.getColGroup(columns, fixed)} {hasHead ? this.getHeader(columns, fixed, leftFixedWidth, rightFixedWidth) : null} {tableBody} </table> ); }; let headTable; if (useFixedHeader) { headTable = ( <div className={`${clsPrefix}-header`} ref={(el)=>{fixed ? this.fixedHeadTable=el : this.headTable=el}} style={headStyle} onMouseOver={this.detectScrollTarget} onTouchStart={this.detectScrollTarget} onScroll={this.handleBodyScroll} > {renderTable(true, false)} </div> ); } let BodyTable = ( <div className={`${clsPrefix}-body`} style={bodyStyle} ref={(el)=>{this.bodyTable = el}} onMouseOver={this.detectScrollTarget} onTouchStart={this.detectScrollTarget} onScroll={this.handleBodyScroll} onMouseLeave={this.onBodyMouseLeave} > {this.renderDragHideTable()} {renderTable(!useFixedHeader)} </div> ); if (fixed && columns.length) { let refName; if (columns[0].fixed === 'left' || columns[0].fixed === true) { refName = 'fixedColumnsBodyLeft'; } else if (columns[0].fixed === 'right') { refName = 'fixedColumnsBodyRight'; } delete bodyStyle.overflowX; delete bodyStyle.overflowY; BodyTable = ( <div className={`${clsPrefix}-body-outer`} style={{ ...bodyStyle }} > <div style={{...innerBodyStyle}} className={`${clsPrefix}-body-inner`} ref={refName} onMouseOver={this.detectScrollTarget} onTouchStart={this.detectScrollTarget} onScroll={this.handleBodyScroll} > {renderTable(!useFixedHeader)} {/* <div className="scroll-dom" style={{height:`${this.scrollbarWidth}px`}}></div> */} </div> </div> ); } // const leftFixedWidth = this.columnManager.getLeftColumnsWidth(this.contentWidth); // const rightFixedWidth = this.columnManager.getRightColumnsWidth(this.contentWidth); let expandIconWidth = expandIconAsCell ? 33 : 0; let parStyle = {} if(!fixed){ parStyle = {'marginLeft':leftFixedWidth + expandIconWidth,'marginRight':rightFixedWidth} } return <div style={parStyle}>{headTable}{BodyTable}</div>; } getTitle() { const { title, clsPrefix } = this.props; return title ? ( <div className={`${clsPrefix}-title`}> {title(this.state.data)} </div> ) : null; } getFooter() { const { footer, clsPrefix } = this.props; return footer ? ( <div className={`${clsPrefix}-footer`}> {footer(this.state.data)} </div> ) : null; } getEmptyText() { const { emptyText : defaultEmptyText, clsPrefix, data } = this.props; let locale = getComponentLocale(this.props, this.context, 'Table', () => i18n); let emptyText = defaultEmptyText !== undefined ? defaultEmptyText : () => <div><Icon type="uf-nodata" className="table-nodata"></Icon><span>{locale["no_data"]}</span></div>; return !data.length ? ( <div className={`${clsPrefix}-placeholder`}> {emptyText()} </div> ) : null; } getHeaderRowStyle(columns, rows) { const { fixedColumnsHeadRowsHeight } = this.state; const headerHeight = fixedColumnsHeadRowsHeight[0]; if (headerHeight && columns) { if (headerHeight === 'auto') { return { height: 'auto' }; } return { height: headerHeight / rows.length }; } return null; } syncFixedTableRowHeight() { //this.props.height、headerHeight分别为用户传入的行高和表头高度,如果有值,所有行的高度都是固定的,主要为了避免在千行数据中有固定列时获取行高度有问题 const { clsPrefix, height, headerHeight,columns,heightConsistent } = this.props; const headRows = this.headTable ? this.headTable.querySelectorAll('thead') : this.bodyTable.querySelectorAll('thead'); const bodyRows = this.bodyTable.querySelectorAll(`.${clsPrefix}-row`) || []; const leftBodyRows = this.refs.fixedColumnsBodyLeft && this.refs.fixedColumnsBodyLeft.querySelectorAll(`.${clsPrefix}-row`) || []; const rightBodyRows = this.refs.fixedColumnsBodyRight && this.refs.fixedColumnsBodyRight.querySelectorAll(`.${clsPrefix}-row`) || []; const fixedColumnsHeadRowsHeight = [].map.call( headRows, row =>{ let height = headerHeight; if(headerHeight){ height = (getMaxColChildrenLength(columns)+1)*headerHeight; } return headerHeight ? height : (row.getBoundingClientRect().height || 'auto')} ); const fixedColumnsBodyRowsHeight = [].map.call( bodyRows, (row,index) =>{ let rsHeight = height; if(rsHeight){ return rsHeight; }else{ // 为了提高性能,默认获取主表的高度,但是有的场景中固定列的高度比主表的高度高,所以提供此属性,会统计所有列的高度取最大的,设置 if(heightConsistent){ let leftHeight,rightHeight,currentHeight,maxHeight; leftHeight = leftBodyRows[index]?leftBodyRows[index].getBoundingClientRect().height:0; rightHeight = rightBodyRows[index]?rightBodyRows[index].getBoundingClientRect().height:0; currentHeight = row.getBoundingClientRect().height maxHeight = Math.max(leftHeight,rightHeight,currentHeight); return maxHeight || 'auto' }else{ return row.getBoundingClientRect().height || 'auto' } } } ); if (shallowequal(this.state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) && shallowequal(this.state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)) { return; } this.setState({ fixedColumnsHeadRowsHeight, fixedColumnsBodyRowsHeight, }); } resetScrollX() { if (this.headTable) { this.headTable.scrollLeft = 0; } if (this.bodyTable) { this.bodyTable.scrollLeft = 0; } } findExpandedRow(record, index) { const rows = this.getExpandedRows().filter(i => i === this.getRowKey(record, index)); return rows[0]; } isRowExpanded(record, index) { return typeof this.findExpandedRow(record, index) !== 'undefined'; } onBodyMouseLeave(e){ this.hideHoverDom(e); } detectScrollTarget(e) { if (this.scrollTarget !== e.currentTarget) { this.scrollTarget = e.currentTarget; } } hideHoverDom(e){ if(this.hoverDom){ this.hoverDom.style.display = 'none'; } } handleBodyScroll(e) { const headTable = this.headTable; const { scroll = {},clsPrefix,handleScrollY, handleScrollX} = this.props; const {fixedColumnsBodyLeft, fixedColumnsBodyRight } = this.refs; // Prevent scrollTop setter trigger onScroll event // http://stackoverflow.com/q/1386696 if (e.currentTarget !== e.target) { return; } if (e.target.scrollLeft !== this.lastScrollLeft) { let position = ''; if (e.target === this.bodyTable && headTable) { headTable.scrollLeft = e.target.scrollLeft; } else if (e.target === headTable && this.bodyTable) { this.bodyTable.scrollLeft = e.target.scrollLeft; } if (e.target.scrollLeft === 0) { position='left'; } else if (e.target.scrollLeft + 1 >= e.target.children[0].getBoundingClientRect().width - e.target.getBoundingClientRect().width) { position='right'; } else if (this.state.scrollPosition !== 'middle') { position='middle'; } if(position){ classes(this.contentTable) .remove(new RegExp(`^${clsPrefix}-scroll-position-.+$`)) .add(`${clsPrefix}-scroll-position-${position}`); } if(handleScrollX){ debounce( handleScrollX(e.target.scrollLeft,this.treeType), 300) } } // console.log('lastScrollTop--'+this.lastScrollTop+'--eventScrollTop--'+ e.target.scrollTop); if (scroll.y && this.lastScrollTop != e.target.scrollTop && e.target !== headTable) { if (fixedColumnsBodyLeft && e.target !== fixedColumnsBodyLeft) { fixedColumnsBodyLeft.scrollTop = e.target.scrollTop; } if (fixedColumnsBodyRight && e.target !== fixedColumnsBodyRight) { fixedColumnsBodyRight.scrollTop = e.target.scrollTop; } if (this.bodyTable && e.target !== this.bodyTable) { this.bodyTable.scrollTop = e.target.scrollTop; } if(this.hoverDom){ this.hoverDom.style.display = 'none' } this.lastScrollTop = e.target.scrollTop; if(handleScrollY){ debounce( handleScrollY(this.lastScrollTop,this.treeType), 300) } } // Remember last scrollLeft for scroll direction detecting. this.lastScrollLeft = e.target.scrollLeft; } handleRowHover(isHover, key,event,currentIndex) { //增加新的API,设置是否同步Hover状态,提高性能,避免无关的渲染 let { syncHover,onRowHover,data } = this.props; const record = data[currentIndex]; // 固定列、或者含有hoverdom时情况下同步hover状态 if(this.columnManager.isAnyColumnsFixed() && syncHover ){ this.hoverKey = key; this.store.setState({ currentHoverKey: isHover ? key : null, }); } if(this.hoverDom){ if(isHover){ this.currentHoverKey = key; const td = closest(event.target,'td'); if(td){ const scrollTop = this.lastScrollTop ?this.lastScrollTop:0 let top = td.offsetTop - scrollTop; if(this.headTable){ top = top + this.headTable.clientHeight; } this.hoverDom.style.top = top + 'px'; this.hoverDom.style.height = td.offsetHeight + 'px'; this.hoverDom.style.lineHeight = td.offsetHeight + 'px'; this.hoverDom.style.display = 'block'; } } } onRowHover && onRowHover(currentIndex,record); } onRowHoverMouseEnter = () =>{ this.store.setState({ currentHoverKey: this.currentHoverKey, }); this.hoverDom.style.display = 'block'; } onRowHoverMouseLeave = () =>{ } onFocus=(e)=>{ this.props.onKeyTab&&this.props.onKeyTab(); } onKeyDown=(e)=>{ let event = Event.getEvent(e); // event.preventDefault?event.preventDefault():event.returnValue = false; if(event.keyCode === 38){//up event.preventDefault&&event.preventDefault(); this.props.onKeyUp&&this.props.onKeyUp(); }else if(event.keyCode === 40){//down event.preventDefault&&event.preventDefault(); this.props.onKeyDown&&this.props.onKeyDown(); } this.props.onTableKeyDown&&this.props.onTableKeyDown(); } render() { const props = this.props; const clsPrefix = props.clsPrefix; const hasFixedLeft = this.columnManager.isAnyColumnsLeftFixed(); let className = props.clsPrefix; if (props.className) { className += ` ${props.className}`; } if (props.useFixedHeader || (props.scroll && props.scroll.y)) { className += ` ${clsPrefix}-fixed-header`; } if (!props.showHeader) { className += ` ${clsPrefix}-hide-header`; } if (props.bordered) { className += ` ${clsPrefix}-bordered`; } className += ` ${clsPrefix}-scroll-position-${this.state.scrollPosition}`; //如果传入height说明是固定高度 if(props.height){ className += ' fixed-height'; } if(props.bodyDisplayInRow){ className += ' body-dispaly-in-row' } if(props.headerDisplayInRow){ className += ' header-dispaly-in-row' } const isTableScroll = this.columnManager.isAnyColumnsFixed() || props.scroll.x || props.scroll.y; let loading = props.loading; if (typeof loading === 'boolean') { loading = { show: loading, }; } if (props.size) { className += ` ${clsPrefix}-${props.size}`; } if(hasFixedLeft){ className += ` has-fixed-left`; } return ( <div className={className} style={props.style} ref={el => this.contentTable = el} tabIndex={props.focusable && (props.tabIndex?props.tabIndex:'0')} > {this.getTitle()} <div className={`${clsPrefix}-content`}> <div className={isTableScroll ? `${clsPrefix}-scroll` : ''} > {this.getTable({ columns: this.columnManager.groupedColumns() })} {this.getEmptyText()} {this.getFooter()} </div> {hasFixedLeft && <div className={`${clsPrefix}-fixed-left`}> {this.getLeftFixedTable()} </div>} {this.columnManager.isAnyColumnsRightFixed() && <div className={`${clsPrefix}-fixed-right`}> {this.getRightFixedTable()} </div>} </div> <Loading container={this} {...loading} /> { props.hoverContent && <div className="u-row-hover" onMouseEnter={this.onRowHoverMouseEnter} onMouseLeave={this.onRowHoverMouseLeave} ref={el=> this.hoverDom = el }>{props.hoverContent()}</div>} </div> ); } }; Table.propTypes = propTypes; Table.defaultProps = defaultProps; Table.contextTypes = { beeLoc