bee-table7/src/Table.js

1395 lines
47 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 './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
};
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: ()=>{}
};
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);
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;
}
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);
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);
}
//适配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) {
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,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, 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}
/>
) : 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;
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 = 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;
const lazyEndIndex = props.lazyLoad && props.lazyLoad.endIndex ?props.lazyLoad.endIndex :-1;
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} ${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}
/>
);
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 = {};
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) : 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.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 = {
beeLocale: PropTypes.object
};
export default Table;