UNPKG

56.5 kBJavaScriptView Raw
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import classes from 'component-classes';
4import TableRow from './TableRow';
5import TableHeader from './TableHeader';
6import { measureScrollbar, debounce, warningOnce ,getMaxColChildrenLength} from './lib/utils';
7import shallowequal from 'shallowequal';
8import addEventListener from 'tinper-bee-core/lib/addEventListener';
9import ColumnManager from './ColumnManager';
10import createStore from './createStore';
11import Loading from 'bee-loading';
12import Icon from 'bee-icon';
13import { Event,EventUtil,closest} from "./lib/utils";
14import i18n from "./lib/i18n";
15import { getComponentLocale } from "bee-locale/build/tool";
16
17const propTypes = {
18 data: PropTypes.array,
19 expandIconAsCell: PropTypes.bool,
20 defaultExpandAllRows: PropTypes.bool,
21 expandedRowKeys: PropTypes.array,
22 defaultExpandedRowKeys: PropTypes.array,
23 useFixedHeader: PropTypes.bool,
24 columns: PropTypes.array,
25 clsPrefix: PropTypes.string,
26 bodyStyle: PropTypes.object,
27 style: PropTypes.object,
28 //特殊的渲染规则的key值
29 rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
30 rowClassName: PropTypes.func,
31 //column的主键,和 column.key 作用相同
32 columnKey: PropTypes.string,
33 expandedRowClassName: PropTypes.func,
34 childrenColumnName: PropTypes.string,
35 onExpand: PropTypes.func,
36 onRowHover:PropTypes.func,
37 onExpandedRowsChange: PropTypes.func,
38 indentSize: PropTypes.number,
39 onRowClick: PropTypes.func,
40 onRowDoubleClick: PropTypes.func,
41 expandIconColumnIndex: PropTypes.number,
42 //是否显示表头
43 showHeader: PropTypes.bool,
44 title: PropTypes.func,
45 footer: PropTypes.func,
46 emptyText: PropTypes.func,
47 scroll: PropTypes.object,
48 rowRef: PropTypes.func,
49 getBodyWrapper: PropTypes.func,
50 children: PropTypes.node,
51 draggable: PropTypes.bool,
52 minColumnWidth: PropTypes.number,
53 filterable: PropTypes.bool,
54 filterDelay: PropTypes.number,
55 onFilterChange: PropTypes.func,
56 onFilterClear: PropTypes.func,
57 syncHover: PropTypes.bool,
58 tabIndex:PropTypes.string,
59 hoverContent:PropTypes.func,
60 size: PropTypes.oneOf(['sm', 'md', 'lg']),
61 rowDraggAble: PropTypes.bool,
62 hideDragHandle: PropTypes.bool, // 隐藏行拖拽把手
63 onDropRow: PropTypes.func,
64 onDragRowStart: PropTypes.func,
65 onBodyScroll: PropTypes.func,
66 bodyDisplayInRow: PropTypes.bool, // 表格内容超出列宽度时进行换行 or 以...形式展现
67 headerDisplayInRow: PropTypes.bool, // 表头内容超出列宽度时进行换行 or 以...形式展现
68 showRowNum: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), // 表格是否自动生成序号,格式为{base:number || 0,defaultKey:string || '_index',defaultName:string || '序号'}
69 onPaste:PropTypes.func,
70 onBodyMouseLeave: PropTypes.func
71};
72
73const defaultProps = {
74 data: [],
75 useFixedHeader: false,
76 expandIconAsCell: false,
77 defaultExpandAllRows: false,
78 defaultExpandedRowKeys: [],
79 columnKey: 'key',
80 rowKey: 'key',
81 rowClassName: () => '',
82 expandedRowClassName: () => '',
83 onExpand() { },
84 onExpandedRowsChange() { },
85 onRowClick() { },
86 // onRowDoubleClick() { },
87 clsPrefix: 'u-table',
88 bodyStyle: {},
89 style: {},
90 childrenColumnName: 'children',
91 indentSize: 15,
92 expandIconColumnIndex: 0,
93 showHeader: true,
94 scroll: {},
95 rowRef: () => null,
96 getBodyWrapper: body => body,
97 // emptyText: () => <div><Icon type="uf-nodata" className="table-nodata"></Icon><span>{locale["no_data"]}</span></div>,
98 columns:[],
99 minColumnWidth: 80,
100 locale:{},
101 syncHover: true,
102 // setRowHeight:()=>{},
103 setRowParentIndex:()=>{},
104 tabIndex:'0',
105 heightConsistent:false,
106 size: 'md',
107 rowDraggAble:false,
108 hideDragHandle:false,
109 onDropRow: ()=>{},
110 onDragRowStart: ()=>{},
111 onBodyScroll: ()=>{},
112 bodyDisplayInRow: true,
113 headerDisplayInRow: true,
114 showRowNum: false,
115 onPaste:()=>{}
116};
117
118const expandIconCellWidth = Number(43);
119
120class Table extends Component {
121 constructor(props) {
122 super(props);
123 let expandedRowKeys = [];
124 let rows = [...props.data];
125 const showDragHandle = !props.hideDragHandle && props.rowDraggAble;
126 this.columnManager = new ColumnManager(props.columns, props.children, props.originWidth, showDragHandle, props.showRowNum); // 加入props.showRowNum参数
127 this.store = createStore({ currentHoverKey: null });
128 this.firstDid = true;
129 if (props.defaultExpandAllRows) {
130 for (let i = 0; i < rows.length; i++) {
131 const row = rows[i];
132 expandedRowKeys.push(this.getRowKey(row, i));
133 rows = rows.concat(row[props.childrenColumnName] || []);
134 }
135 } else {
136 expandedRowKeys = props.expandedRowKeys || props.defaultExpandedRowKeys;
137 }
138
139 this.state = {
140 expandedRowKeys,
141 data: props.data,
142 currentHoverKey: null,
143 scrollPosition: 'left',
144 fixedColumnsHeadRowsHeight: [],
145 fixedColumnsBodyRowsHeight: [],
146 fixedColumnsExpandedRowsHeight: {}, //扩展行的高度
147 }
148
149 this.onExpandedRowsChange = this.onExpandedRowsChange.bind(this);
150 this.onExpanded = this.onExpanded.bind(this);
151 this.onRowDestroy = this.onRowDestroy.bind(this);
152 this.getRowKey = this.getRowKey.bind(this);
153 this.getExpandedRows = this.getExpandedRows.bind(this);
154 this.getHeader = this.getHeader.bind(this);
155 this.getHeaderRows = this.getHeaderRows.bind(this);
156 this.getExpandedRow = this.getExpandedRow.bind(this);
157 this.getRowsByData = this.getRowsByData.bind(this);
158 this.getRows = this.getRows.bind(this);
159 this.getColGroup = this.getColGroup.bind(this);
160 this.getLeftFixedTable = this.getLeftFixedTable.bind(this);
161 this.getRightFixedTable = this.getRightFixedTable.bind(this);
162 this.getTable = this.getTable.bind(this);
163 this.getTitle = this.getTitle.bind(this);
164 this.getFooter = this.getFooter.bind(this);
165 this.getEmptyText = this.getEmptyText.bind(this);
166 this.getHeaderRowStyle = this.getHeaderRowStyle.bind(this);
167 this.syncFixedTableRowHeight = this.syncFixedTableRowHeight.bind(this);
168 this.resetScrollX = this.resetScrollX.bind(this);
169 this.findExpandedRow = this.findExpandedRow.bind(this);
170 this.isRowExpanded = this.isRowExpanded.bind(this);
171 this.detectScrollTarget = this.detectScrollTarget.bind(this);
172 this.handleBodyScroll = this.handleBodyScroll.bind(this);
173 this.handleRowHover = this.handleRowHover.bind(this);
174 this.computeTableWidth = this.computeTableWidth.bind(this);
175 this.onBodyMouseLeave = this.onBodyMouseLeave.bind(this);
176 this.tableUid = null;
177 this.contentTable = null;
178 this.leftColumnsLength //左侧固定列的长度
179 this.centerColumnsLength //非固定列的长度
180 this.columnsChildrenList = [];//复杂表头、所有叶子节点
181 }
182 componentWillMount() {
183 this.centerColumnsLength = this.columnManager.centerColumns().length
184 this.leftColumnsLength = this.columnManager.leftColumns().length
185 }
186
187 componentDidMount() {
188 this.getTableUID();
189 EventUtil.addHandler(this.contentTable,'keydown',this.onKeyDown);
190 EventUtil.addHandler(this.contentTable,'focus',this.onFocus);
191 setTimeout(this.resetScrollX, 300);
192 //含有纵向滚动条
193 // if(this.props.scroll.y){
194 this.scrollbarWidth = measureScrollbar();
195 // }
196 //后续也放在recevice里面
197 if (!this.props.originWidth) {
198 this.computeTableWidth();
199 }
200 if (this.columnManager.isAnyColumnsFixed()) {
201 this.syncFixedTableRowHeight();
202 this.resizeEvent = addEventListener(
203 window, 'resize', this.resize
204 );
205 }
206
207 }
208
209 componentWillReceiveProps(nextProps) {
210 let { hideDragHandle, rowDraggAble, showRowNum } = this.props;
211 if ('data' in nextProps) {
212 this.setState({
213 data: nextProps.data,
214 });
215 }
216 if ('expandedRowKeys' in nextProps) {
217 this.setState({
218 expandedRowKeys: nextProps.expandedRowKeys,
219 });
220 }
221 if (nextProps.columns && nextProps.columns !== this.props.columns) {
222 this.columnManager.reset(nextProps.columns, null, showRowNum, !hideDragHandle && rowDraggAble); // 加入this.props.showRowNum参数
223 if(nextProps.columns.length !== this.props.columns.length && this.refs && this.bodyTable){
224 this.scrollTop = this.bodyTable.scrollTop;
225 }
226 } else if (nextProps.children !== this.props.children) {
227 this.columnManager.reset(null, nextProps.children, showRowNum, !hideDragHandle && rowDraggAble); // 加入this.props.showRowNum参数
228 }
229 //适配lazyload
230 if(nextProps.scrollTop > -1){
231 // this.bodyTable.scrollTop = nextProps.scrollTop;
232 this.scrollTop = nextProps.scrollTop;
233 }
234 // fix:模态框中使用table,计算的滚动条宽度为0的bug
235 // fix:表格首次渲染时 display:none,再显示时,未重新计算,导致表行出现错位的bug
236 if(this.scrollbarWidth<=0 && this.props.scroll.y){
237 this.scrollbarWidth = measureScrollbar();
238 }
239 if (!nextProps.originWidth) {
240 this.computeTableWidth();
241 this.firstDid = true;//避免重复update
242 }
243 if(nextProps.resetScroll){
244 this.resetScrollX();
245 }
246
247 // console.log('this.scrollTop**********',this.scrollTop);
248
249 }
250
251 componentDidUpdate(prevProps, prevState) {
252 // todo: IE 大数据渲染,行高不固定,且设置了 heightConsistent={true} 时,滚动加载操作会导致 ie11 浏览器崩溃
253 // https://github.com/tinper-bee/bee-table/commit/bd2092cdbaad236ff89477304e58dea93325bf09
254 if(this.columnManager.isAnyColumnsFixed()) {
255 this.syncFixedTableRowHeight();
256 }
257
258 //适应模态框中表格、以及父容器宽度变化的情况
259 if (typeof (this.props.scroll.x) !== 'number' && this.contentTable.getBoundingClientRect().width !== this.contentDomWidth && this.firstDid) {
260 this.computeTableWidth();
261 this.firstDid = false;//避免重复update
262 }
263 if(this.scrollTop > -1){
264 this.refs.fixedColumnsBodyLeft && ( this.refs.fixedColumnsBodyLeft.scrollTop = this.scrollTop);
265 this.refs.fixedColumnsBodyRight && ( this.refs.fixedColumnsBodyRight.scrollTop = this.scrollTop);
266 this.bodyTable.scrollTop = this.scrollTop;
267 this.scrollTop = -1;
268 }
269 // 当表格没有数据时,重置滚动条位置,造成grid里面的表头列无法操作
270 // if (prevProps.data.length === 0 || this.props.data.length === 0 ) {
271 // this.resetScrollX();
272 // }
273
274 // 是否传入 scroll中的y属性,如果传入判断是否是整数,如果是则进行比较 。bodyTable 的clientHeight进行判断
275 this.isShowScrollY();
276 }
277
278 componentWillUnmount() {
279 // 移除绑定事件,避免内存泄漏
280 this.contentTable = null;
281 EventUtil.removeHandler(this.contentTable,'keydown',this.onKeyDown);
282 EventUtil.removeHandler(this.contentTable,'focus',this.onFocus);
283 if (this.resizeEvent) {
284 this.resizeEvent.remove();
285 }
286 }
287
288 resize = ()=>{
289 debounce(this.syncFixedTableRowHeight, 150);
290 this.computeTableWidth();
291 let renderFlag = this.state.renderFlag;
292 this.setState({
293 renderFlag: !renderFlag
294 });
295 }
296
297 getTableUID =()=>{
298 let uid = "_table_uid_"+new Date().getTime();
299 this.tableUid = uid;
300 let div = document.createElement("div");
301 // div.className = "u-table-drag-hidden-cont";
302 div.className = "u-table-drag-hidden-cont";
303 div.id = uid;
304 this.contentTable.appendChild(div);
305 }
306
307 computeTableWidth() {
308 let {expandIconAsCell} = this.props;
309 //如果用户传了scroll.x按用户传的为主
310 let setWidthParam = this.props.scroll.x
311
312 if (typeof (setWidthParam) == 'number') {
313 let numSetWidthParam = parseInt(setWidthParam);
314 this.contentWidth = numSetWidthParam;
315 } else {
316 // this.preContentDomWidth = this.contentDomWidth;
317 //计算总表格宽度、根据表格宽度和各列的宽度和比较,重置最后一列
318 this.contentDomWidth = this.contentTable.getBoundingClientRect().width//表格容器宽度
319
320 this.contentWidth = this.contentDomWidth;//默认与容器宽度一样
321
322 }
323 const computeObj = this.columnManager.getColumnWidth(this.contentWidth);
324 const expandColWidth = expandIconAsCell ? expandIconCellWidth : 0;
325 let lastShowIndex = computeObj.lastShowIndex;
326 this.computeWidth = computeObj.computeWidth + expandColWidth;
327
328 this.domWidthDiff = this.contentDomWidth - this.computeWidth;
329 if (typeof (setWidthParam) == 'string' && setWidthParam.indexOf('%')) {
330 this.contentWidth = this.contentWidth * parseInt(setWidthParam) / 100;
331 this.domWidthDiff = this.contentDomWidth - this.contentWidth;
332 }
333
334 if (this.computeWidth < this.contentWidth) {
335 let contentWidthDiff = this.scrollbarWidth?this.contentWidth - this.computeWidth-this.scrollbarWidth:this.contentWidth - this.computeWidth;
336 //bordered的表格需要减去边框的差值1
337 if(this.props.bordered){
338 contentWidthDiff = contentWidthDiff-1;
339 }
340 this.setState({ contentWidthDiff, lastShowIndex });
341 } else {
342 this.contentWidth = this.computeWidth;
343 this.setState({ contentWidthDiff: 0, lastShowIndex });//重新渲染,为了显示滚动条
344 }
345 }
346 //根据内容动态的判断是否显示纵向滚动条
347 isShowScrollY(){
348 const props = this.props;
349 const y = props.scroll && props.scroll.y;
350 if(y){
351 const bodyH = this.bodyTable.clientHeight;
352 const bodyContentH = this.bodyTable.querySelector('table').clientHeight;
353 const rightBodyTable = this.refs.fixedColumnsBodyRight;
354 // const leftBodyTable = this.refs.fixedColumnsBodyLeft;
355 const overflowy = bodyContentH <= bodyH ? 'auto':'scroll';
356 this.bodyTable.style.overflowY = overflowy;
357
358 this.headTable.style.overflowY = overflowy;
359 rightBodyTable && (rightBodyTable.style.overflowY = overflowy);
360 // 没有纵向滚动条时,表头横向滚动条根据内容动态显示 待验证
361 // if(overflowy == 'auto'){
362 // this.fixedHeadTable && (this.fixedHeadTable.style.overflowX = 'auto');
363 // rightBodyTable && (rightBodyTable.style.overflowX = 'auto');
364 // leftBodyTable && (leftBodyTable.style.overflowX = 'auto');
365 // }
366
367
368 }
369 }
370 onExpandedRowsChange(expandedRowKeys) {
371 if (!this.props.expandedRowKeys) {
372 this.setState({ expandedRowKeys });
373 }
374 this.props.onExpandedRowsChange(expandedRowKeys);
375 }
376
377 onExpanded(expanded, record, index, e) {
378 if (e) {
379 e.preventDefault();
380 e.stopPropagation();
381 }
382 const info = this.findExpandedRow(record);
383 if (typeof info !== 'undefined' && !expanded) {
384 this.onRowDestroy(record, index, true);
385 } else if (!info && expanded) {
386 const expandedRows = this.getExpandedRows().concat();
387 expandedRows.push(this.getRowKey(record, index));
388 this.onExpandedRowsChange(expandedRows);
389 }
390 this.props.onExpand(expanded, record,index);
391 }
392
393 onRowDestroy(record, rowIndex, isExpandOperation) {
394 const expandedRows = this.getExpandedRows().concat();
395 const rowKey = this.getRowKey(record, rowIndex);
396 let index = -1;
397 expandedRows.forEach((r, i) => {
398 if (r === rowKey) {
399 index = i;
400 }
401 });
402 if (index !== -1) {
403 expandedRows.splice(index, 1);
404 }
405 //
406 if(this.currentHoverKey == rowKey && this.hoverDom){
407 this.hoverDom.style.display = 'none';
408 }
409 // todo:如果是TableRow组件卸载触发的该方法,需要加判断,解决懒加载时,持续触发onExpandedRowsChange的问题
410 if(isExpandOperation){
411 this.onExpandedRowsChange(expandedRows);
412 } else {
413 const info = this.findExpandedRow(record);
414 if(typeof info === 'undefined'){
415 this.onExpandedRowsChange(expandedRows);
416 }
417 }
418 }
419
420 getRowKey(record, index) {
421 const rowKey = this.props.rowKey;
422 const key = (typeof rowKey === 'function') ?
423 rowKey(record, index) : record[rowKey];
424 warningOnce(
425 key !== undefined,
426 'Each record in table should have a unique `key` prop,' +
427 'or set `rowKey` to an unique primary key.'
428 );
429 return key;
430
431
432 }
433
434 getExpandedRows() {
435 return this.props.expandedRowKeys || this.state.expandedRowKeys;
436 }
437
438 getHeader(columns, fixed, leftFixedWidth, rightFixedWidth) {
439 const { lastShowIndex } = this.state;
440 const { filterDelay, onFilterChange, onFilterClear, filterable, showHeader, expandIconAsCell, clsPrefix, onDragStart, onDragEnter, onDragOver, onDrop,onDragEnd, draggable,
441 onMouseDown, onMouseMove, onMouseUp, dragborder, onThMouseMove, dragborderKey, minColumnWidth, headerHeight,afterDragColWidth,headerScroll ,bordered,onDropBorder,onDraggingBorder, bodyDisplayInRow, headerEventNoStop, onCopy} = this.props;
442 this.columnsChildrenList = []; //复杂表头拖拽,重新render表头前,将其置空
443 const rows = this.getHeaderRows(columns);
444 if (expandIconAsCell && fixed !== 'right') {
445 rows[0].unshift({
446 key: 'u-table-expandIconAsCell',
447 className: `${clsPrefix}-expand-icon-th`,
448 title: '',
449 rowSpan: rows.length,
450 width: expandIconCellWidth
451 });
452 this.columnsChildrenList.unshift({
453 className: "u-table-expand-icon-column",
454 key: "expand-icon"
455 })
456 }
457 const trStyle = headerHeight&&!fixed ? { height: headerHeight } : (fixed ? this.getHeaderRowStyle(columns, rows) : null);
458 let drop = draggable ? { onDragStart, onDragOver, onDrop,onDragEnd, onDragEnter, draggable } : {};
459 let dragBorder = dragborder ? { onMouseDown, onMouseMove, onMouseUp, dragborder, onThMouseMove, dragborderKey,onDropBorder,onDraggingBorder } : {};
460 let contentWidthDiff = 0;
461 //非固定表格,宽度不够时自动扩充
462 if (!fixed) {
463 contentWidthDiff = this.state.contentWidthDiff
464 }
465 return showHeader ? (
466 <TableHeader
467 {...drop}
468 {...dragBorder}
469 columnsChildrenList={this.columnsChildrenList}
470 locale={this.props.locale}
471 minColumnWidth={minColumnWidth}
472 contentWidthDiff={contentWidthDiff}
473 contentWidth={this.contentWidth}
474 lastShowIndex={expandIconAsCell ? parseInt(lastShowIndex) + 1 : lastShowIndex}
475 clsPrefix={clsPrefix}
476 rows={rows}
477 contentTable={this.contentTable}
478 rowStyle={trStyle}
479 fixed={fixed}
480 filterable={filterable}
481 onFilterChange={onFilterChange}
482 onFilterClear={onFilterClear}
483 filterDelay={filterDelay}
484 afterDragColWidth = {afterDragColWidth}
485 contentDomWidth={this.contentDomWidth}
486 scrollbarWidth = {this.scrollbarWidth}
487 headerScroll = {headerScroll}
488 bordered = {bordered}
489 leftFixedWidth = {leftFixedWidth}
490 rightFixedWidth = {rightFixedWidth}
491 bodyDisplayInRow = {bodyDisplayInRow}
492 eventNoStop = {headerEventNoStop}
493 onCopy = {onCopy}
494 />
495 ) : null;
496 }
497
498 getHeaderRows(columns, currentRow = 0, rows) {
499 const { columnKey } = this.props;
500 let { contentWidthDiff = 0, lastShowIndex = -1 } = this.state;
501 let filterCol = [];
502 rows = rows || [];
503 rows[currentRow] = rows[currentRow] || [];
504
505 columns.forEach((column,i) => {
506 if (!column.key) {
507 column.key = column[columnKey];
508 }
509 if (column.rowSpan && rows.length < column.rowSpan) {
510 while (rows.length < column.rowSpan) {
511 rows.push([]);
512 }
513 }
514 let width = column.width;
515 if (typeof (width) == 'string' && width.indexOf('%') > -1 && this.contentWidth) {
516 width = parseInt(this.contentWidth * parseInt(width) / 100);
517 } else if (width) {
518 width = parseInt(width);
519 }
520 if (!column.fixed && lastShowIndex == i && width) {
521 width = width + contentWidthDiff;
522 }
523 const cell = {
524 key: column.key,
525 className: column.className || '',
526 children: column.title,
527 drgHover: column.drgHover,
528 fixed: column.fixed,
529 width: width,
530 dataindex:column.dataIndex,
531 textAlign:column.textAlign,
532 titleAlign: column.titleAlign, // 标题水平对齐方式
533 required: column.required, // 标题是否展示必填标志
534 };
535 if (column.onHeadCellClick) {
536 cell.onClick = column.onHeadCellClick;
537 }
538 if (column.children) {
539 this.getHeaderRows(column.children, currentRow + 1, rows);
540 } else {
541 this.columnsChildrenList.push(column); //复杂表头拖拽,所有叶子节点
542 }
543 if ('colSpan' in column) {
544 cell.colSpan = column.colSpan;
545 }
546 if ('rowSpan' in column) {
547 cell.rowSpan = column.rowSpan;
548 }
549 if (cell.colSpan !== 0) {
550 rows[currentRow].push(cell);
551 }
552 //判断是否启用过滤
553 if (this.props.filterable) {
554 //组装Filter需要的Col
555 filterCol.push({
556 key: column.key,
557 children: "过滤渲染",
558 width: column.width,
559 filtertype: column.filterType,//下拉的类型 包括['text','dropdown','date','daterange','number']
560 dataindex: column.dataIndex,//field
561 datasource: this.props.data,//需要单独拿到数据处理
562 format: column.format,//设置日期的格式
563 filterdropdown: column.filterDropdown,//是否显示 show hide
564 filterdropdownauto: column.filterDropdownAuto,//是否自定义数据
565 filterdropdowndata: column.filterDropdownData,//自定义数据格式
566 filterdropdownfocus: column.filterDropdownFocus,//焦点触发函数回调
567 filterdropdowntype: column.filterDropdownType,//下拉的类型分为 String,Number 默认是String
568 filterdropdownincludekeys: column.filterDropdownIncludeKeys,//下拉条件按照指定的keys去显示
569 filterinputnumberoptions: column.filterInputNumberOptions//设置数值框内的详细属性
570 });
571 }
572 });
573 if (this.props.filterable) {
574 rows.push(filterCol);
575 }
576 return rows.filter(row => row.length > 0);
577 }
578
579 getExpandedRow(key, content, visible, className, fixed) {
580 const { clsPrefix, expandIconAsCell,onPaste, getCellClassName } = this.props;
581 let colCount;
582 if (fixed === 'left') {
583 colCount = this.columnManager.leftLeafColumns().length;
584 } else if (fixed === 'right') {
585 colCount = this.columnManager.rightLeafColumns().length;
586 } else {
587 colCount = this.columnManager.centerColumns().length; //计算非固定列的个数,fix: 嵌套表格场景,右侧列断开的问题
588 }
589
590 let expandedRowHeight = this.state.fixedColumnsExpandedRowsHeight[key] || 'auto';
591 function contentContainer() {
592 if (content && content.props && content.props.style) {
593 return (
594 <div style={{ height: content.props.style.height }}></div>
595 )
596 } else {
597 return ' '
598 }
599 }
600
601 const columns = [{
602 key: 'extra-row',
603 render: () => ({
604 props: {
605 colSpan: colCount,
606 },
607 children: !fixed ? content : contentContainer(),
608 }),
609 }];
610 if (expandIconAsCell && fixed !== 'right') {
611 columns.unshift({
612 key: 'expand-icon-placeholder',
613 render: () => null,
614 });
615 }
616 return (
617 <TableRow
618 onPaste={onPaste}
619 columns={columns}
620 visible={visible}
621 className={className}
622 key={`${key}-extra-row`}
623 clsPrefix={`${clsPrefix}-expanded-row`}
624 indent={1}
625 expandable={false}
626 store={this.store}
627 dragborderKey={this.props.dragborderKey}
628 rowDraggAble={this.props.rowDraggAble}
629 useDragHandle={this.props.useDragHandle}
630 onDragRow={this.onDragRow}
631 onDragRowStart={this.onDragRowStart}
632 height={expandedRowHeight}
633 getCellClassName = {getCellClassName}
634 />
635 );
636 }
637
638 /**
639 * 行拖拽开始时触发
640 * @param currentKey 当前拖拽目标的key
641 */
642 onDragRowStart = (currentKey) => {
643 let {data} = this.state,currentIndex,record;
644 data.forEach((da,i)=>{
645 // tr 的唯一标识通过 data.key 或 rowKey 两种方式传进来
646 let trKey = da.key ? da.key : this.getRowKey(da, i);
647 if(trKey == currentKey){
648 currentIndex = i;
649 record = da;
650 }
651 });
652 this.props.onDragRowStart && this.props.onDragRowStart(record,currentIndex);
653 }
654
655 /**
656 * 行拖拽结束时触发
657 * @param currentKey 当前拖拽目标的key
658 * @param targetKey 拖拽结束时,目标位置的key
659 */
660 onDragRow = (currentKey,targetKey)=>{
661 let {data} = this.state,currentIndex,targetIndex,record;
662 data.forEach((da,i)=>{
663 // tr 的唯一标识通过 data.key 或 rowKey 两种方式传进来
664 let trKey = da.key ? da.key : this.getRowKey(da, i);
665 if(trKey == currentKey){
666 currentIndex = i;
667 record = da;
668 }
669 if(trKey == targetKey){
670 targetIndex = i;
671 }
672 });
673 if(currentIndex > -1) {
674 data = this.swapArray(data,currentIndex,targetIndex);
675 this.props.onDropRow && this.props.onDropRow(data,record,targetIndex);
676 this.setState({
677 data,
678 });
679 } else {
680 this.props.onDropRow && this.props.onDropRow(data,record,targetIndex);
681 }
682 }
683 /**
684 * 数组元素交换位置
685 * @param {array} arr 数组
686 * @param {number} index1 添加项目的位置
687 * @param {number} index2 删除项目的位置
688 */
689 swapArray = (arr, index1, index2) => {
690 var value1 = arr[index1]
691 arr.splice(index1,1)
692 if(index1<index2){
693 arr.splice(index2,0,value1)
694
695 }else {
696 arr.splice(index2+1,0,value1)
697 }
698
699 return arr;
700 }
701
702 /**
703 *
704 *
705 * @param {*} data
706 * @param {*} visible
707 * @param {*} indent 层级
708 * @param {*} columns
709 * @param {*} fixed
710 * @param {number} [rootIndex=-1] 祖级节点
711 * @returns
712 * @memberof Table
713 */
714 getRowsByData(data, visible, indent, columns, fixed,rootIndex=-1) {
715 const props = this.props;
716 const childrenColumnName = props.childrenColumnName;
717 const expandedRowRender = props.expandedRowRender;
718 const expandRowByClick = props.expandRowByClick;
719 const onPaste = props.onPaste;
720 const { fixedColumnsBodyRowsHeight } = this.state;
721 let rst = [];
722 let height;
723 const rowClassName = props.rowClassName;
724 const rowRef = props.rowRef;
725 const expandedRowClassName = props.expandedRowClassName;
726 const needIndentSpaced = props.data.some(record => record[childrenColumnName]);
727 const onRowClick = props.onRowClick;
728 const onRowDoubleClick = props.onRowDoubleClick;
729
730 const expandIconAsCell = fixed !== 'right' ? props.expandIconAsCell : false;
731 const expandIconColumnIndex = props.expandIconColumnIndex
732 if(props.lazyLoad && props.lazyLoad.preHeight && indent == 0){
733 rst.push(
734 <TableRow onPaste={onPaste} height={props.lazyLoad.preHeight} columns={[]} className='' key={'table_row_first'} store={this.store} visible = {true}/>
735 )
736 }
737 const lazyCurrentIndex = props.lazyLoad && props.lazyLoad.startIndex ?props.lazyLoad.startIndex :0;
738 const lazyParentIndex = props.lazyLoad && props.lazyLoad.startParentIndex ?props.lazyLoad.startParentIndex :0;
739 const lazyEndIndex = props.lazyLoad && props.lazyLoad.endIndex ?props.lazyLoad.endIndex :-1;
740 for (let i = 0; i < data.length; i++) {
741 let isHiddenExpandIcon;
742 const record = data[i];
743 const key = this.getRowKey(record, i);
744 // 兼容 NCC 以前的业务逻辑,支持外部通过 record 中的 isleaf 字段,判断是否为叶子节点
745 record['_isLeaf'] = typeof record['isleaf'] === 'boolean' ? record['isleaf'] : record['_isLeaf'];
746 // _isLeaf 字段是在 bigData 里添加的,只有层级树大数据场景需要该字段
747 // _isLeaf 有三种取值情况:true / false / null。(Table内部字段)
748 const _isLeaf = typeof record['_isLeaf'] === 'boolean' ? record['_isLeaf'] : null;
749 const childrenColumn = _isLeaf ? false : record[childrenColumnName];
750 const isRowExpanded = this.isRowExpanded(record, i);
751 let expandedRowContent;
752 let expandedContentHeight = 0;
753 //fixedIndex一般是跟index是一个值的,只有是树结构时,会讲子节点的值也累计上
754 let fixedIndex = i;
755 //判断是否是tree结构
756 if (this.treeType) {
757 fixedIndex = this.treeRowIndex;
758 }
759 if (expandedRowRender && isRowExpanded) {
760 expandedRowContent = expandedRowRender(record, fixedIndex+lazyCurrentIndex, indent);
761 expandedContentHeight = parseInt(expandedRowContent.props && expandedRowContent.props.style && expandedRowContent.props.style.height?expandedRowContent.props.style.height:0);
762 }
763 //只有当使用expandedRowRender参数的时候才去识别isHiddenExpandIcon(隐藏行展开的icon)
764 if (expandedRowRender && typeof props.haveExpandIcon == 'function') {
765 isHiddenExpandIcon = props.haveExpandIcon(record, i);
766 }
767
768
769 const onHoverProps = {};
770
771 onHoverProps.onHover = this.handleRowHover;
772
773
774 if (props.bodyDisplayInRow && props.height) {
775 height = props.height
776 } else if(fixed || props.heightConsistent) {
777 height = fixedColumnsBodyRowsHeight[fixedIndex];
778 }
779
780 let leafColumns;
781 if (fixed === 'left') {
782 leafColumns = this.columnManager.leftLeafColumns();
783 } else if (fixed === 'right') {
784 leafColumns = this.columnManager.rightLeafColumns();
785 } else {
786 leafColumns = this.columnManager.leafColumns();
787 }
788 let className = rowClassName(record, fixedIndex+lazyCurrentIndex, indent);
789
790 //合计代码如果是最后一行并且有合计功能时,最后一行为合计列
791 if(i == data.length -1 && props.showSum){
792 className = className + ' sumrow';
793 }
794
795 let paramRootIndex = rootIndex;
796 //小于0说明为第一层节点,她的子孙节点要保存自己的根节点
797 if(paramRootIndex<0){
798 paramRootIndex = i+lazyParentIndex;
799 }
800 let index = i;
801 if(rootIndex ==-1){
802 index = i+lazyParentIndex
803 }
804 rst.push(
805 <TableRow
806 onPaste={onPaste}
807 indent={indent}
808 indentSize={props.indentSize}
809 needIndentSpaced={needIndentSpaced}
810 className={`${className}`}
811 record={record}
812 expandIconAsCell={expandIconAsCell}
813 onDestroy={this.onRowDestroy}
814 index={index}
815 visible={visible}
816 expandRowByClick={expandRowByClick}
817 onExpand={this.onExpanded}
818 expandable={expandedRowRender || ((childrenColumn && childrenColumn.length > 0) ? true : _isLeaf === false)}
819 expanded={isRowExpanded}
820 clsPrefix={`${props.clsPrefix}-row`}
821 childrenColumnName={childrenColumnName}
822 columns={leafColumns}
823 expandIconColumnIndex={expandIconColumnIndex}
824 onRowClick={onRowClick}
825 onRowDoubleClick={onRowDoubleClick}
826 height={height}
827 isHiddenExpandIcon={isHiddenExpandIcon}
828 {...onHoverProps}
829 key={"table_row_"+key+"_"+index}
830 hoverKey={key}
831 ref={rowRef}
832 store={this.store}
833 fixed={fixed}
834 expandedContentHeight={expandedContentHeight}
835 setRowHeight={props.setRowHeight}
836 setRowParentIndex={props.setRowParentIndex}
837 treeType={childrenColumn||this.treeType?true:false}
838 fixedIndex={fixedIndex+lazyCurrentIndex}
839 rootIndex = {rootIndex}
840 syncHover = {props.syncHover}
841 bodyDisplayInRow = {props.bodyDisplayInRow}
842 rowDraggAble={props.rowDraggAble}
843 useDragHandle={props.useDragHandle}
844 onDragRow={this.onDragRow}
845 onDragRowStart={this.onDragRowStart}
846 contentTable={this.contentTable}
847 tableUid = {this.tableUid}
848 expandedIcon={props.expandedIcon}
849 collapsedIcon={props.collapsedIcon}
850 lazyStartIndex = {lazyCurrentIndex}
851 lazyEndIndex = {lazyEndIndex}
852 centerColumnsLength={this.centerColumnsLength}
853 leftColumnsLength={this.leftColumnsLength}
854 expandIconCellWidth={expandIconCellWidth}
855 getCellClassName = {props.getCellClassName}
856 />
857 );
858 this.treeRowIndex++;
859 const subVisible = visible && isRowExpanded;
860
861 if (expandedRowContent && isRowExpanded) {
862 rst.push(this.getExpandedRow(
863 key, expandedRowContent, subVisible, expandedRowClassName(record, i, indent), fixed
864 ));
865 }
866 if (childrenColumn) {
867 this.isTreeType = true; //增加该标志位,为了兼容老版本,不修改以前的 `this.treeType` 的相关逻辑
868 this.treeType = true;//证明是tree表形式visible = {true}
869 rst = rst.concat(this.getRowsByData(
870 childrenColumn, subVisible, indent + 1, columns, fixed,paramRootIndex
871 ));
872 }
873 }
874
875 if(props.lazyLoad && props.lazyLoad.sufHeight && indent == 0){
876 rst.push(
877 <TableRow onPaste={onPaste} height={props.lazyLoad.sufHeight} key={'table_row_end'} columns={[]} className='' store={this.store} visible = {true}/>
878 )
879 }
880 if (!this.isTreeType) {
881 this.treeType = false;
882 }
883 return rst;
884 }
885
886 getRows(columns, fixed) {
887 //统计index,只有含有树表结构才有用,因为树表结构时,固定列的索引取值有问题
888 this.treeRowIndex = 0;
889 //每次遍历 data 前,将this.isTreeType置为 false,若遍历完 data,此变量仍为 false,说明是普通表格
890 this.isTreeType = false;
891 let rs = this.getRowsByData(this.state.data, true, 0, columns, fixed);
892 return rs;
893 }
894
895 getColGroup(columns, fixed) {
896 let cols = [];
897 let self = this;
898
899 let { contentWidthDiff = 0, lastShowIndex = 0 } = this.state;
900 if (this.props.expandIconAsCell && fixed !== 'right') {
901 cols.push(
902 <col
903 className={`${this.props.clsPrefix}-expand-icon-col`}
904 key="u-table-expand-icon-col"
905 />
906 );
907 }
908 let leafColumns;
909 if (fixed === 'left') {
910 contentWidthDiff = 0;
911 leafColumns = this.columnManager.leftLeafColumns();
912 } else if (fixed === 'right') {
913 contentWidthDiff = 0;
914 leafColumns = this.columnManager.rightLeafColumns();
915 } else {
916 leafColumns = this.columnManager.leafColumns();
917 }
918 cols = cols.concat(leafColumns.map((c, i, arr) => {
919 let fixedClass ='';
920 let width = c.width;
921 if (typeof (width) == 'string' && width.indexOf('%') > -1 && self.contentWidth) {
922 width = parseInt(self.contentWidth * parseInt(width) / 100);
923 } else if (width) {
924 width = parseInt(width);
925 }
926 if (lastShowIndex == i && width) {
927 width = width + contentWidthDiff;
928 }
929 if (!fixed && c.fixed) {
930 fixedClass = ` ${this.props.clsPrefix}-row-fixed-columns-in-body`;
931 }
932 return <col key={c.key} style={{ width: width, minWidth: c.width }} className={fixedClass}/>;
933 }));
934 return <colgroup id="bee-table-colgroup">{cols}</colgroup>;
935 }
936
937 renderDragHideTable = () => {
938 const { columns, dragborder, dragborderKey } = this.props;
939 if (!dragborder) return null;
940 let sum = 0;
941 return (<div id={`u-table-drag-hide-table-${dragborderKey}`} className={`${this.props.clsPrefix}-hiden-drag`} >
942 {
943 columns.map((da, i) => {
944 sum += da.width ? da.width : 0;
945 return (<div className={`${this.props.clsPrefix}-hiden-drag-li`} key={da + "_hiden_" + i} style={{ left: sum + "px" }}></div>);
946 })
947 }
948 </div>);
949 }
950
951 getLeftFixedTable() {
952 return this.getTable({
953 columns: this.columnManager.leftColumns(),
954 fixed: 'left',
955 });
956 }
957
958 getRightFixedTable() {
959 return this.getTable({
960 columns: this.columnManager.rightColumns(),
961 fixed: 'right',
962 });
963 }
964
965 getTable(options = {}) {
966 const { columns, fixed } = options;
967 const { clsPrefix, scroll = {}, getBodyWrapper, footerScroll,headerScroll,hideHeaderScroll = false,expandIconAsCell } = this.props;
968 let { useFixedHeader,data } = this.props;
969 const bodyStyle = { ...this.props.bodyStyle }; // 这里为什么不写在上面?
970 const headStyle = {};
971 const innerBodyStyle = {};
972 const leftFixedWidth = this.columnManager.getLeftColumnsWidth(this.contentWidth);
973 const rightFixedWidth = this.columnManager.getRightColumnsWidth(this.contentWidth);
974
975 let tableClassName = '';
976 //表格元素的宽度大于容器的宽度也显示滚动条
977 if (scroll.x || fixed || this.contentDomWidth < this.contentWidth) {
978 tableClassName = `${clsPrefix}-fixed`;
979 //没有数据并且含有顶部菜单时
980 if(this.props.data.length == 0 && this.props.headerScroll ){
981 bodyStyle.overflowX = 'hidden';
982 }
983 if (!footerScroll) {
984 bodyStyle.overflowX = bodyStyle.overflowX || 'auto';
985 }
986 }
987
988 if (scroll.y) {
989 // maxHeight will make fixed-Table scrolling not working
990 // so we only set maxHeight to body-Table here
991 if (fixed) {
992 // bodyStyle.height = bodyStyle.height || scroll.y;
993 innerBodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y;
994 innerBodyStyle.overflowY = bodyStyle.overflowY || 'scroll';
995 } else {
996 bodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y;
997 }
998 bodyStyle.overflowY = bodyStyle.overflowY || 'scroll';
999 useFixedHeader = true;
1000
1001 // Add negative margin bottom for scroll bar overflow bug
1002 const scrollbarWidth = this.scrollbarWidth;
1003 if (scrollbarWidth >= 0) {
1004 (fixed ? bodyStyle : headStyle).paddingBottom = '0px';
1005 //显示表头滚动条
1006 if(headerScroll){
1007 if(fixed){
1008
1009 if(this.domWidthDiff <= 0){
1010 headStyle.marginBottom = `${scrollbarWidth}px`;
1011 bodyStyle.marginBottom = `-${scrollbarWidth}px`;
1012 }else{
1013 innerBodyStyle.overflowX = 'auto';
1014 }
1015 }else{
1016 //内容少,不用显示滚动条
1017 if(this.domWidthDiff > 0){
1018 headStyle.overflowX = 'hidden';
1019 }
1020 headStyle.marginBottom = `0px`;
1021 }
1022 }else{
1023 if(fixed){
1024 if(this.domWidthDiff > 0){
1025 headStyle.overflow = 'hidden';
1026 innerBodyStyle.overflowX = 'auto'; //兼容expand场景、子表格含有固定列的场景
1027 }else{
1028 bodyStyle.marginBottom = `-${scrollbarWidth}px`;
1029 }
1030
1031 }else{
1032 // 没有数据时,表头滚动条隐藏问题
1033 if(data.length == 0 && this.domWidthDiff < 0){
1034 headStyle.marginBottom = '0px';
1035 }else{
1036 headStyle.marginBottom = `-${scrollbarWidth}px`;
1037 }
1038
1039 }
1040
1041 }
1042 }
1043 }
1044
1045 if(data.length == 0 && hideHeaderScroll){
1046 //支持 NCC 需求:表格无数据时,去掉表头滚动条 (https://github.com/iuap-design/tinper-bee/issues/207)
1047 headStyle.marginBottom = `-${this.scrollbarWidth}px`;
1048 }
1049
1050 const renderTable = (hasHead = true, hasBody = true) => {
1051 const tableStyle = {};
1052 if (!fixed && scroll.x) {
1053 // not set width, then use content fixed width
1054 if (scroll.x === true) {
1055 tableStyle.tableLayout = 'fixed';
1056 } else {
1057 tableStyle.width = this.contentWidth - this.columnManager.getLeftColumnsWidth(this.contentWidth) - this.columnManager.getRightColumnsWidth(this.contentWidth);
1058 }
1059 }
1060 // 自动出现滚动条
1061 if ( !fixed && this.contentDomWidth < this.contentWidth) {
1062 tableStyle.width = this.contentWidth - this.columnManager.getLeftColumnsWidth(this.contentWidth) - this.columnManager.getRightColumnsWidth(this.contentWidth);
1063 }
1064 const tableBody = hasBody ? getBodyWrapper(
1065 <tbody className={`${clsPrefix}-tbody`} onMouseLeave={this.onBodyMouseLeave}>
1066 {this.getRows(columns, fixed)}
1067 </tbody>
1068 ) : null;
1069 let _drag_class = this.props.dragborder ? "table-drag-bordered" : ""
1070 return (
1071 <table className={` ${tableClassName} table-bordered ${_drag_class} `} style={tableStyle} >
1072 {/* {this.props.dragborder?null:this.getColGroup(columns, fixed)} */}
1073 {this.getColGroup(columns, fixed)}
1074 {hasHead ? this.getHeader(columns, fixed, leftFixedWidth, rightFixedWidth) : null}
1075 {tableBody}
1076 </table>
1077 );
1078 };
1079
1080 let headTable;
1081
1082 if (useFixedHeader) {
1083 headTable = (
1084 <div
1085 className={`${clsPrefix}-header`}
1086 ref={(el)=>{fixed ? this.fixedHeadTable=el : this.headTable=el}}
1087 style={headStyle}
1088 onMouseOver={this.detectScrollTarget}
1089 onTouchStart={this.detectScrollTarget}
1090 onScroll={this.handleBodyScroll}
1091 >
1092 {renderTable(true, false)}
1093 </div>
1094 );
1095 }
1096 let BodyTable = (
1097 <div
1098 className={`${clsPrefix}-body`}
1099 style={bodyStyle}
1100 ref={(el)=>{this.bodyTable = el}}
1101 onMouseOver={this.detectScrollTarget}
1102 onTouchStart={this.detectScrollTarget}
1103 onScroll={this.handleBodyScroll}
1104 onMouseLeave={this.onBodyMouseLeave}
1105 >
1106 {this.renderDragHideTable()}
1107 {renderTable(!useFixedHeader)}
1108 </div>
1109 );
1110
1111 if (fixed && columns.length) {
1112 let refName;
1113 if (columns[0].fixed === 'left' || columns[0].fixed === true) {
1114 refName = 'fixedColumnsBodyLeft';
1115 } else if (columns[0].fixed === 'right') {
1116 refName = 'fixedColumnsBodyRight';
1117 }
1118 delete bodyStyle.overflowX;
1119 delete bodyStyle.overflowY;
1120 BodyTable = (
1121 <div
1122 className={`${clsPrefix}-body-outer`}
1123 style={{ ...bodyStyle }}
1124 >
1125 <div
1126 style={{...innerBodyStyle}}
1127 className={`${clsPrefix}-body-inner`}
1128 ref={refName}
1129 onMouseOver={this.detectScrollTarget}
1130 onTouchStart={this.detectScrollTarget}
1131 onScroll={this.handleBodyScroll}
1132 >
1133 {renderTable(!useFixedHeader)}
1134 {/* <div className="scroll-dom" style={{height:`${this.scrollbarWidth}px`}}></div> */}
1135 </div>
1136 </div>
1137 );
1138 }
1139 // const leftFixedWidth = this.columnManager.getLeftColumnsWidth(this.contentWidth);
1140 // const rightFixedWidth = this.columnManager.getRightColumnsWidth(this.contentWidth);
1141 let expandIconWidth = expandIconAsCell ? 32 : 0;
1142 let parStyle = {}
1143 if(!fixed){
1144 parStyle = {'marginLeft':leftFixedWidth + expandIconWidth,'marginRight':rightFixedWidth}
1145 }
1146 return <div style={parStyle}>{headTable}{BodyTable}</div>;
1147 }
1148
1149 getTitle() {
1150 const { title, clsPrefix } = this.props;
1151 return title ? (
1152 <div className={`${clsPrefix}-title`}>
1153 {title(this.state.data)}
1154 </div>
1155 ) : null;
1156 }
1157
1158 getFooter() {
1159 const { footer, clsPrefix } = this.props;
1160 return footer ? (
1161 <div className={`${clsPrefix}-footer`}>
1162 {footer(this.state.data)}
1163 </div>
1164 ) : null;
1165 }
1166
1167 getEmptyText() {
1168 const { emptyText : defaultEmptyText, clsPrefix, data } = this.props;
1169 let locale = getComponentLocale(this.props, this.context, 'Table', () => i18n);
1170 let emptyText = defaultEmptyText !== undefined ? defaultEmptyText : () => <div><Icon type="uf-nodata" className="table-nodata"></Icon><span>{locale["no_data"]}</span></div>;
1171
1172 return !data.length ? (
1173 <div className={`${clsPrefix}-placeholder`}>
1174 {emptyText()}
1175 </div>
1176 ) : null;
1177 }
1178
1179 getHeaderRowStyle(columns, rows) {
1180 const { fixedColumnsHeadRowsHeight } = this.state;
1181 const headerHeight = fixedColumnsHeadRowsHeight[0];
1182
1183 if (headerHeight && columns) {
1184 if (headerHeight === 'auto') {
1185 return { height: 'auto' };
1186 }
1187 return { height: headerHeight / rows.length };
1188 }
1189 return null;
1190 }
1191 getStyle(obj,attr){
1192 if(obj.currentStyle){
1193 return obj.currentStyle[attr];
1194 }
1195 else{
1196 return document.defaultView.getComputedStyle(obj,null)[attr];
1197 }
1198 }
1199 getTdPadding=(td)=>{
1200 let tdPaddingTop = this.getStyle(td,'paddingTop'),tdPaddingBottom = this.getStyle(td,'paddingBottom'),
1201 tdBorderTop = this.getStyle(td,'borderTopWidth'),tdBorderBottom = this.getStyle(td,'borderBottomWidth');
1202 return Number(tdPaddingTop.replace('px',''))+Number(tdPaddingBottom.replace('px',''))+Number(tdBorderTop.replace('px',''))+Number(tdBorderBottom.replace('px',''))
1203
1204 }
1205
1206 syncFixedTableRowHeight() {
1207 //this.props.height、headerHeight分别为用户传入的行高和表头高度,如果有值,所有行的高度都是固定的,主要为了避免在千行数据中有固定列时获取行高度有问题
1208 const { clsPrefix, height, headerHeight,columns,heightConsistent, bodyDisplayInRow } = this.props;
1209 const headRows = this.headTable ?
1210 this.headTable.querySelectorAll('thead') :
1211 this.bodyTable.querySelectorAll('thead');
1212 const expandedRows = this.bodyTable.querySelectorAll(`.${clsPrefix}-expanded-row`) || [];
1213 const bodyRows = this.bodyTable.querySelectorAll(`.${clsPrefix}-row`) || [];
1214 const leftBodyRows = this.refs.fixedColumnsBodyLeft && this.refs.fixedColumnsBodyLeft.querySelectorAll(`.${clsPrefix}-row`) || [];
1215 const rightBodyRows = this.refs.fixedColumnsBodyRight && this.refs.fixedColumnsBodyRight.querySelectorAll(`.${clsPrefix}-row`) || [];
1216 const fixedColumnsHeadRowsHeight = [].map.call(
1217 headRows, row =>{
1218 let height = headerHeight;
1219 if(headerHeight){
1220 height = (getMaxColChildrenLength(columns)+1)*headerHeight;
1221 }
1222 return headerHeight ? height : (row.getBoundingClientRect().height || 'auto')}
1223 );
1224 const fixedColumnsBodyRowsHeight = [].map.call(
1225 bodyRows, (row,index) =>{
1226 let rsHeight = height;
1227 if(bodyDisplayInRow && rsHeight){
1228 return rsHeight;
1229 }else{
1230 // 为了提高性能,默认获取主表的高度,但是有的场景中固定列的高度比主表的高度高,所以提供此属性,会统计所有列的高度取最大的,设置
1231 // 内容折行显示,并又设置了 height 的情况下,也要获取主表高度
1232 if(heightConsistent || (!bodyDisplayInRow && rsHeight)){
1233 let leftHeight,rightHeight,currentHeight,maxHeight;
1234 leftHeight = leftBodyRows[index]?leftBodyRows[index].getBoundingClientRect().height:0;
1235 rightHeight = rightBodyRows[index]?rightBodyRows[index].getBoundingClientRect().height:0;
1236 currentHeight = row.getBoundingClientRect().height
1237 maxHeight = Math.max(leftHeight,rightHeight,currentHeight);
1238 return maxHeight || 'auto'
1239 }else{
1240 return row.getBoundingClientRect().height || 'auto'
1241 }
1242 }
1243 }
1244 );
1245 const fixedColumnsExpandedRowsHeight = {};
1246 // expandedRows为NodeList Array.prototype.forEach ie 下报错 对象不支持 “forEach” 方法
1247 expandedRows.length > 0 && Array.prototype.forEach.call(expandedRows,row => {
1248 let parentRowKey = row && row.previousSibling && row.previousSibling.getAttribute("data-row-key"),
1249 height = row && row.getBoundingClientRect().height || 'auto';
1250 try {//子表数据减少时,动态计算高度
1251 let td = row.querySelector('td');
1252 let tdPadding = this.getTdPadding(td);
1253 let trueheight = row.querySelectorAll('.u-table')[0].getBoundingClientRect().height;
1254 height = trueheight+tdPadding;
1255 } catch (error) {
1256
1257 }
1258 fixedColumnsExpandedRowsHeight[parentRowKey] = height;
1259 })
1260 if (shallowequal(this.state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) &&
1261 shallowequal(this.state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight) &&
1262 shallowequal(this.state.fixedColumnsExpandedRowsHeight, fixedColumnsExpandedRowsHeight)) {
1263 return;
1264 }
1265 this.setState({
1266 fixedColumnsHeadRowsHeight,
1267 fixedColumnsBodyRowsHeight,
1268 fixedColumnsExpandedRowsHeight,
1269 });
1270 }
1271
1272 resetScrollX() {
1273 if (this.headTable) {
1274 this.headTable.scrollLeft = 0;
1275 }
1276 if (this.bodyTable) {
1277 this.bodyTable.scrollLeft = 0;
1278 }
1279 }
1280
1281 findExpandedRow(record, index) {
1282 const rows = this.getExpandedRows().filter(i => i === this.getRowKey(record, index));
1283 return rows[0];
1284 }
1285
1286 isRowExpanded(record, index) {
1287 return typeof this.findExpandedRow(record, index) !== 'undefined';
1288 }
1289 onBodyMouseLeave(e){
1290 this.hideHoverDom(e);
1291 const {onBodyMouseLeave} = this.props;
1292 if(onBodyMouseLeave) {
1293 onBodyMouseLeave()
1294 }
1295 }
1296
1297 detectScrollTarget(e) {
1298 if (this.scrollTarget !== e.currentTarget) {
1299 this.scrollTarget = e.currentTarget;
1300 }
1301 }
1302
1303 hideHoverDom(e){
1304 if(this.hoverDom){
1305 this.hoverDom.style.display = 'none';
1306 }
1307 }
1308
1309
1310 handleBodyScroll(e) {
1311 const headTable = this.headTable;
1312 const { scroll = {},clsPrefix,handleScrollY, handleScrollX, onBodyScroll} = this.props;
1313 const {fixedColumnsBodyLeft, fixedColumnsBodyRight } = this.refs;
1314 // Prevent scrollTop setter trigger onScroll event
1315 // http://stackoverflow.com/q/1386696
1316 if (e.currentTarget !== e.target) {
1317 return;
1318 }
1319 if (e.target.scrollLeft !== this.lastScrollLeft) {
1320 let position = '';
1321 if (e.target === this.bodyTable && headTable) {
1322 headTable.scrollLeft = e.target.scrollLeft;
1323 } else if (e.target === headTable && this.bodyTable) {
1324 this.bodyTable.scrollLeft = e.target.scrollLeft;
1325 }
1326 if (e.target.scrollLeft === 0) {
1327 position='left';
1328 } else if (e.target.scrollLeft + 1 >=
1329 e.target.children[0].getBoundingClientRect().width -
1330 e.target.getBoundingClientRect().width) {
1331 position='right';
1332 } else if (this.state.scrollPosition !== 'middle') {
1333 position='middle';
1334 }
1335 if(position){
1336 classes(this.contentTable)
1337 .remove(new RegExp(`^${clsPrefix}-scroll-position-.+$`))
1338 .add(`${clsPrefix}-scroll-position-${position}`);
1339 }
1340 if(handleScrollX){
1341 debounce(
1342 handleScrollX(e.target.scrollLeft,this.treeType),
1343 300)
1344 }
1345 }
1346 // console.log('lastScrollTop--'+this.lastScrollTop+'--eventScrollTop--'+ e.target.scrollTop);
1347 if (scroll.y && this.lastScrollTop != e.target.scrollTop && e.target !== headTable) {
1348 if (fixedColumnsBodyLeft && e.target !== fixedColumnsBodyLeft) {
1349 fixedColumnsBodyLeft.scrollTop = e.target.scrollTop;
1350 }
1351 if (fixedColumnsBodyRight && e.target !== fixedColumnsBodyRight) {
1352 fixedColumnsBodyRight.scrollTop = e.target.scrollTop;
1353 }
1354 if (this.bodyTable && e.target !== this.bodyTable) {
1355 this.bodyTable.scrollTop = e.target.scrollTop;
1356 }
1357 if(this.hoverDom){
1358 this.hoverDom.style.display = 'none'
1359 }
1360 this.lastScrollTop = e.target.scrollTop;
1361 if(handleScrollY){
1362 debounce(
1363 handleScrollY(this.lastScrollTop,this.treeType,onBodyScroll),
1364 300)
1365 }else{
1366 //滚动回调
1367 onBodyScroll(this.lastScrollTop)
1368 }
1369
1370 }
1371
1372 // Remember last scrollLeft for scroll direction detecting.
1373 this.lastScrollLeft = e.target.scrollLeft;
1374 }
1375
1376 handleRowHover(isHover, key,event,currentIndex, propsRecord) {
1377 //增加新的API,设置是否同步Hover状态,提高性能,避免无关的渲染
1378 let { syncHover,onRowHover,data } = this.props;
1379 //fix:树形表,onRowHover返回参数异常
1380 let { isTreeType } = this;
1381 const record = isTreeType ? propsRecord : data[currentIndex];
1382 // 固定列、或者含有hoverdom时情况下同步hover状态
1383 if(this.columnManager.isAnyColumnsFixed() && syncHover ){
1384 this.hoverKey = key;
1385 this.store.setState({
1386 currentHoverKey: isHover ? key : null,
1387 });
1388 }
1389 if(this.hoverDom){
1390 if(isHover){
1391 this.currentHoverKey = key;
1392 const td = closest(event.target,'td');
1393 if(td){
1394 const scrollTop = this.lastScrollTop ?this.lastScrollTop:0
1395 let top = td.offsetTop - scrollTop;
1396 if(this.headTable){
1397 top = top + this.headTable.clientHeight;
1398 }
1399 this.hoverDom.style.top = top + 'px';
1400 this.hoverDom.style.height = td.offsetHeight + 'px';
1401 this.hoverDom.style.lineHeight = td.offsetHeight + 'px';
1402 this.hoverDom.style.display = 'block';
1403 }
1404 this.setState({
1405 currentHoverIndex: currentIndex,
1406 currentHoverRecord: record
1407 })
1408 }
1409 }
1410
1411 onRowHover && onRowHover(currentIndex,record);
1412
1413 }
1414
1415 onRowHoverMouseEnter = () =>{
1416
1417 this.store.setState({
1418 currentHoverKey: this.currentHoverKey,
1419 });
1420 this.hoverDom.style.display = 'block';
1421
1422 }
1423 onRowHoverMouseLeave = () =>{
1424
1425 }
1426 onFocus=(e)=>{
1427 this.props.onKeyTab&&this.props.onKeyTab();
1428 }
1429
1430 onKeyDown=(e)=>{
1431 let event = Event.getEvent(e);
1432 // event.preventDefault?event.preventDefault():event.returnValue = false;
1433 if(event.keyCode === 38){//up
1434 event.preventDefault&&event.preventDefault();
1435 this.props.onKeyUp&&this.props.onKeyUp();
1436 }else if(event.keyCode === 40){//down
1437 event.preventDefault&&event.preventDefault();
1438 this.props.onKeyDown&&this.props.onKeyDown();
1439 }
1440 this.props.onTableKeyDown&&this.props.onTableKeyDown();
1441 }
1442
1443 render() {
1444 const { currentHoverRecord, currentHoverIndex } = this.state;
1445 const props = this.props;
1446 const clsPrefix = props.clsPrefix;
1447 const hasFixedLeft = this.columnManager.isAnyColumnsLeftFixed();
1448 let className = props.clsPrefix;
1449 if (props.className) {
1450 className += ` ${props.className}`;
1451 }
1452 if (props.useFixedHeader || (props.scroll && props.scroll.y)) {
1453 className += ` ${clsPrefix}-fixed-header`;
1454 }
1455 if (!props.showHeader) {
1456 className += ` ${clsPrefix}-hide-header`;
1457 }
1458 if (props.bordered) {
1459 className += ` ${clsPrefix}-bordered`;
1460 }
1461 if (props.onCopy) {
1462 className += ` copy`;
1463 }
1464 className += ` ${clsPrefix}-scroll-position-${this.state.scrollPosition}`;
1465 //如果传入height说明是固定高度
1466 //内容过多折行显示时,height 属性会失效,为了避免产生错行
1467 if(props.bodyDisplayInRow && props.height){
1468 className += ' fixed-height';
1469 }
1470 if(props.bodyDisplayInRow){
1471 className += ' body-dispaly-in-row'
1472 }
1473 if(props.headerDisplayInRow){
1474 className += ' header-dispaly-in-row'
1475 }
1476 const isTableScroll = this.columnManager.isAnyColumnsFixed() ||
1477 props.scroll.x ||
1478 props.scroll.y;
1479 let loading = props.loading;
1480 if (typeof loading === 'boolean') {
1481 loading = {
1482 show: loading,
1483 };
1484 }
1485 if (props.size) {
1486 className += ` ${clsPrefix}-${props.size}`;
1487 }
1488 if(hasFixedLeft){
1489 className += ` has-fixed-left`;
1490 }
1491
1492 return (
1493 <div className={className} style={props.style} ref={el => this.contentTable = el}
1494 tabIndex={props.focusable && (props.tabIndex?props.tabIndex:'0')} >
1495 {this.getTitle()}
1496 <div className={`${clsPrefix}-content`}>
1497
1498 <div className={isTableScroll ? `${clsPrefix}-scroll` : ''} >
1499 {this.getTable({ columns: this.columnManager.groupedColumns() })}
1500 {this.getEmptyText()}
1501 {this.getFooter()}
1502 </div>
1503
1504 {hasFixedLeft &&
1505 <div className={`${clsPrefix}-fixed-left`}>
1506 {this.getLeftFixedTable()}
1507 </div>}
1508 {this.columnManager.isAnyColumnsRightFixed() &&
1509 <div className={`${clsPrefix}-fixed-right`}>
1510 {this.getRightFixedTable()}
1511 </div>}
1512 </div>
1513 <Loading
1514 container={this}
1515 {...loading} />
1516 { props.hoverContent && <div className="u-row-hover"
1517 onMouseEnter={this.onRowHoverMouseEnter} onMouseLeave={this.onRowHoverMouseLeave} ref={el=> this.hoverDom = el }>{props.hoverContent(currentHoverRecord, currentHoverIndex)}</div>}
1518 </div>
1519 );
1520 }
1521};
1522
1523Table.propTypes = propTypes;
1524Table.defaultProps = defaultProps;
1525Table.contextTypes = {
1526 beeLocale: PropTypes.object
1527};
1528
1529export default Table;