bee-table/src/Table.js

1281 lines
43 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 './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 "./utils";
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,
};
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: () => <Icon type="uf-nodata" className="table-nodata"></Icon>,
columns:[],
minColumnWidth: 80,
locale:{},
syncHover: true,
setRowHeight:()=>{},
setRowParentIndex:()=>{},
tabIndex:'0',
heightConsistent:false,
size: 'md',
rowDraggAble: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);
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);
}
componentDidMount() {
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);
if(nextProps.columns.length !== this.props.columns.length && this.refs && this.refs.bodyTable){
this.scrollTop = this.refs.bodyTable.scrollTop;
}
} else if (nextProps.children !== this.props.children) {
this.columnManager.reset(null, nextProps.children);
}
//适配lazyload
if(nextProps.scrollTop > -1){
// this.refs.bodyTable.scrollTop = nextProps.scrollTop;
this.scrollTop = nextProps.scrollTop;
}
if (!nextProps.originWidth) {
this.computeTableWidth();
this.firstDid = true;//避免重复update
}
if(nextProps.resetScroll){
this.resetScrollX();
}
// 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.refs.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() {
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
});
}
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.refs.bodyTable.clientHeight;
const bodyContentH = this.refs.bodyTable.querySelector('table').clientHeight;
const rightBodyTable = this.refs.fixedColumnsBodyRight;
const overflowy = bodyContentH <= bodyH ? 'auto':'scroll';
this.refs.bodyTable.style.overflowY = overflowy;
this.refs.headTable.style.overflowY = overflowy;
rightBodyTable && (rightBodyTable.style.overflowY = overflowy);
}
}
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) {
const { filterDelay, onFilterChange, onFilterClear, filterable, showHeader, expandIconAsCell, clsPrefix, onDragStart, onDragEnter, onDragOver, onDrop, draggable,
onMouseDown, onMouseMove, onMouseUp, dragborder, onThMouseMove, dragborderKey, minColumnWidth, headerHeight,afterDragColWidth,headerScroll ,bordered,onDropBorder} = 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, onDragEnter, draggable } : {};
let dragBorder = dragborder ? { onMouseDown, onMouseMove, onMouseUp, dragborder, onThMouseMove, dragborderKey,onDropBorder } : {};
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}
/>
) : 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
};
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;
}
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}
/>
);
}
onDragRow = (currentIndex,targetIndex)=>{
let {data} = this.state,
currentObj = data[currentIndex],
targetObj = data[targetIndex];
console.log(currentIndex+" ----------onRowDragEnd-------- "+targetIndex);
data.splice(targetIndex, 0, data.splice(currentIndex, 1).shift());
console.log(" _data---- ",data);
this.setState({
data: data,
});
}
/**
*
*
* @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 = fixed !== 'right' ? props.expandIconColumnIndex : -1;
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;
for (let i = 0; i < data.length; i++) {
let isHiddenExpandIcon;
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}
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}
contentTable={this.contentTable}
/>
);
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 } = this.props;
let { useFixedHeader } = this.props;
const bodyStyle = { ...this.props.bodyStyle };
const headStyle = {};
const innerBodyStyle = {};
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{
headStyle.marginBottom = `-${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 id="bee-table-uid" 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) : null}
{tableBody}
</table>
);
};
let headTable;
if (useFixedHeader) {
headTable = (
<div
className={`${clsPrefix}-header`}
ref={fixed ? null : 'headTable'}
style={headStyle}
onMouseOver={this.detectScrollTarget}
onTouchStart={this.detectScrollTarget}
onScroll={this.handleBodyScroll}
>
{renderTable(true, false)}
</div>
);
}
let BodyTable = (
<div
className={`${clsPrefix}-body`}
style={bodyStyle}
ref="bodyTable"
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 parStyle = {}
if(!fixed){
parStyle = {'marginLeft':leftFixedWidth,'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, clsPrefix, data } = this.props;
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.refs.headTable ?
this.refs.headTable.querySelectorAll('thead') :
this.refs.bodyTable.querySelectorAll('thead');
const bodyRows = this.refs.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.refs.headTable) {
this.refs.headTable.scrollLeft = 0;
}
if (this.refs.bodyTable) {
this.refs.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 { scroll = {},clsPrefix,handleScrollY, handleScrollX} = this.props;
const { headTable, bodyTable, 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 === bodyTable && headTable) {
headTable.scrollLeft = e.target.scrollLeft;
} else if (e.target === headTable && bodyTable) {
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 (bodyTable && e.target !== bodyTable) {
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.refs.headTable){
top = top + this.refs.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.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;
export default Table;