UNPKG

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