UNPKG

42.2 kBJavaScriptView Raw
1/* eslint no-console:0 */
2import React from 'react';
3import TreeNode from './TreeNode';
4import InfiniteScroll from './infiniteScroll';
5import classNames from 'classnames';
6import {
7 loopAllChildren,
8 isInclude,
9 getOffset,
10 filterParentPosition,
11 handleCheckState,
12 getCheck,
13 getStrictlyValue,
14 arraysEqual,
15 closest,
16 convertListToTree,
17} from './util';
18import PropTypes from 'prop-types';
19import { KeyCode } from 'tinper-bee-core';
20import CONFIG from './config';
21import createStore from './createStore';
22import omit from 'omit.js';
23
24function noop() {}
25
26class Tree extends React.Component {
27 constructor(props) {
28 super(props);
29 ['onKeyDown', 'onCheck',"onUlFocus","_focusDom","onUlMouseEnter","onUlMouseLeave"].forEach((m) => {
30 this[m] = this[m].bind(this);
31 });
32 this.contextmenuKeys = [];
33 this.checkedKeysChange = true;
34 this.selectKeyDomPos = '0-0';
35 this.state = {
36 expandedKeys: this.getDefaultExpandedKeys(props),
37 checkedKeys: this.getDefaultCheckedKeys(props),
38 selectedKeys: this.getDefaultSelectedKeys(props),
39 dragNodesKeys: '',
40 dragOverNodeKey: '',
41 dropNodeKey: '',
42 focusKey: '', //上下箭头选择树节点时,用于标识focus状态
43 treeData: [], //Tree结构数组(全量)
44 flatTreeData: [], //一维数组(全量)
45 prevProps: null
46 };
47 //默认显示20条,rowsInView根据定高算的。在非固定高下,这个只是一个大概的值。
48 this.rowsInView = CONFIG.defaultRowsInView;
49 //一次加载多少数据
50 this.loadCount = CONFIG.loadBuffer ? this.rowsInView + CONFIG.loadBuffer * 2 : 16;
51 this.flatTreeKeysMap = {}; //存储所有 key-value 的映射,方便获取各节点信息
52 this.startIndex = 0;
53 this.endIndex = this.startIndex + this.loadCount;
54 this.cacheTreeNodes = []; //缓存 treenode 节点数组
55 this.store = createStore({ rowHeight: 24 }); //rowHeight 树节点的高度,此变量在滚动加载场景很关键
56 }
57
58 componentDidMount() {
59 // 此处为了区分数据是不是异步渲染的,prevProps 作为标识
60 if(this.hasTreeNode()){
61 this.setState({
62 prevProps: this.props
63 })
64 }
65 // ncc制造,树参照包含下级需求,checkStrictly 动态改变后,拿到组件内部属性 this.checkedKeys
66 if(this.props._getTreeObj){
67 this.props._getTreeObj(this);
68 }
69 this.calculateRowHeight();
70 }
71
72 // 判断初始化挂载时,有没有渲染树节点
73 hasTreeNode = () => {
74 const { children, treeData } = this.props;
75 let noTreeNode = children === null || typeof children === 'undefined' || (typeof children === 'object' && children.length === 0) || (typeof treeData === 'object' && treeData.length === 0);
76 return !noTreeNode;
77 }
78
79 componentWillMount() {
80 const { treeData,lazyLoad } = this.props;
81 let sliceTreeList = [];
82 //启用懒加载,把 Tree 结构拍平,为后续动态截取数据做准备
83 if(lazyLoad) {
84 let flatTreeData = this.deepTraversal(treeData);
85 flatTreeData.forEach((element) => {
86 if(sliceTreeList.length >= this.loadCount) return;
87 sliceTreeList.push(element);
88 });
89 this.handleTreeListChange(sliceTreeList);
90 this.setState({
91 flatTreeData
92 })
93 } else {
94 this.setState({
95 treeData
96 })
97 }
98 }
99
100 componentWillReceiveProps(nextProps) {
101 let flatTreeDataDone = false;//已经更新过flatTree
102 const {startIndex,endIndex,props,state} = this;
103 const {prevProps} = state;
104 const expandedKeys = this.getDefaultExpandedKeys(nextProps, true);
105 const checkedKeys = this.getDefaultCheckedKeys(nextProps, true);
106 const selectedKeys = this.getDefaultSelectedKeys(nextProps, true);
107 const st = {
108 prevProps:nextProps
109 };
110 // 用于记录这次data内容有没有变化
111 this.dataChange = false;
112 function needSync(name) {
113 return (!prevProps && name in nextProps) || (prevProps && prevProps[name] !== nextProps[name]);
114 }
115 // ================ expandedKeys =================
116 if (needSync('expandedKeys') || (prevProps && needSync('autoExpandParent')) || (prevProps && prevProps['expandedKeys'] !== expandedKeys)) {
117 st.expandedKeys = expandedKeys;
118 } else if ((!prevProps && props.defaultExpandAll) || (!prevProps && props.defaultExpandedKeys)) {
119 st.expandedKeys = this.getDefaultExpandedKeys(nextProps);
120 }
121 if(st.expandedKeys){
122 //缓存 expandedKeys
123 this.cacheExpandedKeys = new Set(expandedKeys);
124 if(nextProps.lazyLoad) {
125 let flatTreeData = this.deepTraversal(nextProps.treeData);
126 this.cacheExpandedKeys = null;
127 st.flatTreeData = flatTreeData;
128 let newTreeList = flatTreeData.slice(startIndex,endIndex);
129 this.handleTreeListChange(newTreeList, startIndex, endIndex);
130 flatTreeDataDone=true;
131 }
132 }
133
134 // ================ checkedKeys =================
135 if (checkedKeys) {
136 if (nextProps.checkedKeys === this.props.checkedKeys) {
137 this.checkedKeysChange = false;
138 } else {
139 this.checkedKeysChange = true;
140 }
141 st.checkedKeys = checkedKeys;
142 }
143
144 // ================ selectedKeys =================
145 if (selectedKeys) {
146 st.selectedKeys = selectedKeys;
147 }
148
149 // ================ treeData =================
150 if(nextProps.hasOwnProperty('treeData') && nextProps.treeData !== this.props.treeData){
151 this.dataChange = true;
152 //treeData更新时,需要重新处理一次数据
153 if(nextProps.lazyLoad) {
154 if(!flatTreeDataDone){
155 let flatTreeData = this.deepTraversal(nextProps.treeData);
156 st.flatTreeData = flatTreeData;
157 let newTreeList = flatTreeData.slice(startIndex,endIndex);
158 this.handleTreeListChange(newTreeList, startIndex, endIndex);
159 }
160
161 } else {
162 st.treeData = nextProps.treeData;
163 }
164 }
165
166 // ================ children =================
167 if(nextProps.children !== this.props.children){
168 this.dataChange = true;
169 }
170 this.setState(st);
171 }
172
173 componentDidUpdate(){
174 if(!this.hasCalculateRowHeight) {
175 this.calculateRowHeight();
176 }
177 }
178
179 calculateRowHeight = () => {
180 const { lazyLoad } = this.props;
181 // 启用懒加载,计算树节点真实高度
182 if(!lazyLoad) return;
183 const treenodes = this.tree.querySelectorAll('.u-tree-treenode-close')[0];
184 if(!treenodes) return;
185 this.hasCalculateRowHeight = true;
186 let rowHeight = treenodes.getBoundingClientRect().height;
187 this.store.setState({
188 rowHeight: rowHeight
189 });
190 }
191
192 onDragStart(e, treeNode) {
193 this.dragNode = treeNode;
194 this.dragNodesKeys = this.getDragNodes(treeNode);
195 const st = {
196 dragNodesKeys: this.dragNodesKeys,
197 };
198 const expandedKeys = this.getExpandedKeys(treeNode, false);
199 if (expandedKeys) {
200 // Controlled expand, save and then reset
201 this.getRawExpandedKeys();
202 st.expandedKeys = expandedKeys;
203 }
204 this.setState(st);
205 this.props.onDragStart({
206 event: e,
207 node: treeNode,
208 });
209 this._dropTrigger = false;
210 }
211
212 onDragEnterGap(e, treeNode) {
213 const offsetTop = (0, getOffset)(treeNode.selectHandle).top;
214 const offsetHeight = treeNode.selectHandle.offsetHeight;
215 const pageY = e.pageY;
216 const gapHeight = 2;
217 if (pageY > offsetTop + offsetHeight - gapHeight) {
218 this.dropPosition = 1;
219 return 1;
220 }
221 if (pageY < offsetTop + gapHeight) {
222 this.dropPosition = -1;
223 return -1;
224 }
225 this.dropPosition = 0;
226 return 0;
227 }
228
229 onDragEnter(e, treeNode) {
230 const enterGap = this.onDragEnterGap(e, treeNode);
231 if (this.dragNode.props.eventKey === treeNode.props.eventKey && enterGap === 0) {
232 this.setState({
233 dragOverNodeKey: '',
234 });
235 return;
236 }
237 const st = {
238 dragOverNodeKey: treeNode.props.eventKey,
239 };
240 const expandedKeys = this.getExpandedKeys(treeNode, true);
241 if (expandedKeys) {
242 this.getRawExpandedKeys();
243 st.expandedKeys = expandedKeys;
244 }
245 this.setState(st);
246 this.props.onDragEnter({
247 event: e,
248 node: treeNode,
249 expandedKeys: expandedKeys && [...expandedKeys] || [...this.state.expandedKeys],
250 });
251 }
252
253 onDragOver(e, treeNode) {
254 this.props.onDragOver({
255 event: e,
256 node: treeNode
257 });
258 }
259
260 onDragLeave(e, treeNode) {
261 this.props.onDragLeave({
262 event: e,
263 node: treeNode
264 });
265 }
266
267 onDrop(e, treeNode) {
268 const key = treeNode.props.eventKey;
269 this.setState({
270 dragOverNodeKey: '',
271 dropNodeKey: key,
272 });
273 if (this.dragNodesKeys.indexOf(key) > -1) {
274 if (console.warn) {
275 console.warn('can not drop to dragNode(include it\'s children node)');
276 }
277 return false;
278 }
279
280 const posArr = treeNode.props.pos.split('-');
281 const res = {
282 event: e,
283 node: treeNode,
284 dragNode: this.dragNode,
285 dragNodesKeys: [...this.dragNodesKeys],
286 dropPosition: this.dropPosition + Number(posArr[posArr.length - 1]),
287 };
288 if (this.dropPosition !== 0) {
289 res.dropToGap = true;
290 }
291 if ('expandedKeys' in this.props) {
292 res.rawExpandedKeys = [...this._rawExpandedKeys] || [...this.state.expandedKeys];
293 }
294 this.props.onDrop(res);
295 this._dropTrigger = true;
296 }
297
298 onDragEnd(e, treeNode) {
299 this.setState({
300 dragOverNodeKey: '',
301 });
302 this.props.onDragEnd({
303 event: e,
304 node: treeNode
305 });
306 }
307/**
308 *
309 *
310 * @param {*} treeNode 当前操作的节点
311 * @param {*} keyType 键盘事件通用的key类型 left 为收起,right为展开
312 * @returns
313 * @memberof Tree
314 */
315onExpand(treeNode,keyType) {
316 const { treeData,lazyLoad } = this.props;
317 let expanded = !treeNode.props.expanded;
318 const controlled = 'expandedKeys' in this.props;
319 const expandedKeys = [...this.state.expandedKeys];
320 const index = expandedKeys.indexOf(treeNode.props.eventKey);
321
322 if(keyType == 'left'){
323 expanded = false;
324 }else if(keyType == 'right'){
325 expanded = true;
326 }
327
328 if (expanded && index === -1) {
329 expandedKeys.push(treeNode.props.eventKey);
330 } else if (!expanded && index > -1) {
331 expandedKeys.splice(index, 1);
332 }
333 if (!controlled) {
334 this.setState({
335 expandedKeys
336 });
337 }
338 this.props.onExpand(expandedKeys, {
339 node: treeNode,
340 expanded
341 });
342
343 // after data loaded, need set new expandedKeys
344 if (expanded && this.props.loadData) {
345 return this.props.loadData(treeNode).then(() => {
346 if (!controlled) {
347 this.setState({
348 expandedKeys
349 });
350 }
351 });
352 }
353 //收起和展开时,缓存 expandedKeys
354 this.cacheExpandedKeys = new Set(expandedKeys);
355 //启用懒加载,把 Tree 结构拍平,为后续动态截取数据做准备
356 if(lazyLoad) {
357 let flatTreeData = this.deepTraversal(treeData);
358 this.cacheExpandedKeys = null;
359 this.setState({
360 flatTreeData
361 })
362 }
363 }
364
365 onCheck(treeNode) {
366 let checked = !treeNode.props.checked;
367 if (treeNode.props.halfChecked) {
368 checked = true;
369 }
370 const key = treeNode.props.eventKey;
371 let checkedKeys = [...this.state.checkedKeys];
372 const index = checkedKeys.indexOf(key);
373
374 const newSt = {
375 event: 'check',
376 node: treeNode,
377 checked,
378 };
379
380
381 if (this.props.checkStrictly) {
382 let rsCheckedKeys = [];
383 if (checked && index === -1) {
384 checkedKeys.push(key);
385 // rsCheckedKeys.push(key);//onCheck第一个参数的key不对
386 }
387 if (!checked && index > -1) {
388 checkedKeys.splice(index, 1);
389 }
390 this.treeNodesStates[treeNode.props.pos].checked = checked;
391 newSt.checkedNodes = [];
392 loopAllChildren(this.props.children, (item, ind, pos, keyOrPos) => {
393 if (checkedKeys.indexOf(keyOrPos) !== -1) {
394 newSt.checkedNodes.push(item);
395 rsCheckedKeys.push(keyOrPos);
396 }
397 });
398 if (!('checkedKeys' in this.props)) {
399 this.setState({
400 checkedKeys:rsCheckedKeys
401 });
402 }
403 const halfChecked = this.props.checkedKeys ? this.props.checkedKeys.halfChecked : [];
404 this.props.onCheck(getStrictlyValue(rsCheckedKeys, halfChecked), newSt);
405 } else {
406 if (checked && index === -1) {
407 this.treeNodesStates[treeNode.props.pos].checked = true;
408 const checkedPositions = [];
409 Object.keys(this.treeNodesStates).forEach(i => {
410 if (this.treeNodesStates[i].checked) {
411 checkedPositions.push(i);
412 }
413 });
414 handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true);
415 }
416 if (!checked) {
417 this.treeNodesStates[treeNode.props.pos].checked = false;
418 this.treeNodesStates[treeNode.props.pos].halfChecked = false;
419 handleCheckState(this.treeNodesStates, [treeNode.props.pos], false);
420 }
421 const checkKeys = getCheck(this.treeNodesStates);
422 newSt.checkedNodes = checkKeys.checkedNodes;
423 newSt.checkedNodesPositions = checkKeys.checkedNodesPositions;
424 newSt.halfCheckedKeys = checkKeys.halfCheckedKeys;
425 this.checkKeys = checkKeys;
426
427 this._checkedKeys = checkedKeys = checkKeys.checkedKeys;
428 if (!('checkedKeys' in this.props)) {
429 this.setState({
430 checkedKeys,
431 });
432 }
433 this.props.onCheck(checkedKeys, newSt);
434 }
435 }
436
437 onSelect(treeNode) {
438 const props = this.props;
439 const selectedKeys = [...this.state.selectedKeys];
440 const eventKey = treeNode.props.eventKey || treeNode.key;
441 const index = selectedKeys.indexOf(eventKey);
442 let selected;
443 //cancelUnSelect为true时第二次点击时不取消选中
444 if(props.cancelUnSelect){
445 if (index == -1) {
446 selected = true;
447 if (!props.multiple) {
448 selectedKeys.length = 0;
449 }
450 selectedKeys.push(eventKey);
451 }
452 }else{
453 if (index !== -1) {
454 selected = false;
455 selectedKeys.splice(index, 1);
456 } else {
457 selected = true;
458 if (!props.multiple) {
459 selectedKeys.length = 0;
460 }
461 selectedKeys.push(eventKey);
462 }
463 }
464
465 const selectedNodes = [];
466 if (selectedKeys.length) {
467 const treeNodes = this.props.children || treeNode.props.root.cacheTreeNodes
468 loopAllChildren(treeNodes, (item) => {
469 if (selectedKeys.indexOf(item.key) !== -1) {
470 selectedNodes.push(item);
471 }
472 });
473 }
474 const newSt = {
475 event: 'select',
476 node: treeNode,
477 selected,
478 selectedNodes,
479 };
480 if (!('selectedKeys' in this.props)) {
481 this.setState({
482 selectedKeys,
483 });
484 }
485 props.onSelect(selectedKeys, newSt);
486 }
487
488
489 onDoubleClick(treeNode){
490 const props = this.props;
491 const eventKey = treeNode.props.eventKey;
492 const newSt = {
493 event: 'dblclick',
494 node: treeNode
495 };
496 if(props.expandWhenDoubleClick) {
497 this.onExpand(treeNode);
498 }
499 props.onDoubleClick(eventKey,newSt);
500 }
501
502 onMouseEnter(e, treeNode) {
503 this.props.onMouseEnter({
504 event: e,
505 node: treeNode
506 });
507 }
508
509 onMouseLeave(e, treeNode) {
510 this.props.onMouseLeave({
511 event: e,
512 node: treeNode
513 });
514 }
515
516
517
518 onContextMenu(e, treeNode) {
519 const selectedKeys = [...this.state.selectedKeys];
520 const eventKey = treeNode.props.eventKey;
521 if (this.contextmenuKeys.indexOf(eventKey) === -1) {
522 this.contextmenuKeys.push(eventKey);
523 }
524 this.contextmenuKeys.forEach((key) => {
525 const index = selectedKeys.indexOf(key);
526 if (index !== -1) {
527 selectedKeys.splice(index, 1);
528 }
529 });
530 if (selectedKeys.indexOf(eventKey) === -1) {
531 selectedKeys.push(eventKey);
532 }
533 this.setState({
534 selectedKeys,
535 });
536 this.props.onRightClick({
537 event: e,
538 node: treeNode
539 });
540 }
541
542 getTreeNode(){
543 const props = this.props;
544
545 }
546
547 goDown(currentPos,currentIndex,e,treeNode){
548 const props = this.props;
549 const state = this.state;
550 let treeChildren = props.children ? props.children : this.cacheTreeNodes; //最终渲染在 Tree 标签中的子节点
551 const nextIndex = parseInt(currentIndex) + 1;
552
553 let nextPos,backNextPos;
554 let nextTreeNode,backNextTreeNode;
555 const backNextPosArr=[],backNextTreeNodeArr = [],tempBackNextPosArr=[];
556 //是否为展开的节点,如果展开获取第一个子节点的信息,如果没有取相邻节点,若也没有相邻节点则获取父节点的下一个节点
557 if(state.expandedKeys.indexOf(treeNode.props.eventKey)>-1){
558 nextPos = currentPos + '-0';
559 }else{
560 nextPos = currentPos.substr(0,currentPos.lastIndexOf('-')+1)+nextIndex;
561
562
563 }
564 //若向下的节点没有了,找到父级相邻节点
565 let tempPosArr = currentPos.split('-');
566 let tempPosArrLength = tempPosArr.length;
567 //将可能是下一个节点的的位置都备份一遍
568 while(tempPosArrLength>1){
569 backNextPos = tempPosArrLength>1 && tempPosArr.slice(0,tempPosArrLength-1).join('-')+'-' + (parseInt(tempPosArr[tempPosArrLength-1])+1)
570 tempBackNextPosArr.push(backNextPos);
571 tempPosArr = tempPosArr.slice(0,tempPosArrLength-1)
572 tempPosArrLength = tempPosArr.length;
573 }
574 //选中下一个相邻的节点
575 loopAllChildren(treeChildren,function(itemNode,index,pos,newKey){
576 if(pos == nextPos){
577 nextTreeNode = itemNode;
578 }
579 tempBackNextPosArr.forEach(item=>{
580 if(item && item == pos){
581 // backNextTreeNode = item;
582 backNextTreeNodeArr.push(itemNode);
583 backNextPosArr.push(pos);
584 }
585 })
586
587 })
588 //如果没有下一个节点,则获取父节点的下一个节点
589 if(!nextTreeNode){
590 for(let i=0;i<backNextTreeNodeArr.length;i++){
591 if(backNextTreeNodeArr[i]){
592 nextTreeNode = backNextTreeNodeArr[i];
593 nextPos = backNextPosArr[i];
594 break;
595 }
596 }
597
598
599 }
600
601 //查询的下一个节点不为空的话,则选中
602 if(nextTreeNode){
603 const queryInfo = `a[pos="${nextPos}"]`;
604 const parentEle = closest(e.target,".u-tree")
605 const focusEle = parentEle?parentEle.querySelector(queryInfo):null;
606 focusEle && focusEle.focus()
607 const eventKey = nextTreeNode.props.eventKey || nextTreeNode.key;
608 this.setState({
609 focusKey: eventKey
610 })
611 if(props.autoSelectWhenFocus){
612 this.onSelect(nextTreeNode);
613 }
614 }
615 }
616
617 goUp(currentPos,currentIndex,e,treeNode){
618 const props = this.props;
619 const state = this.state;
620 if(currentIndex == 0 && currentPos.length === 3){
621 return
622 }
623 // 向上键Up
624 const preIndex = parseInt(currentIndex) - 1;
625 let prePos;
626 if(preIndex>= 0){
627 prePos = currentPos.substr(0,currentPos.lastIndexOf('-')+1)+preIndex;
628 }else{
629 prePos = currentPos.substr(0,currentPos.lastIndexOf('-'));
630 }
631
632 let prevTreeNode,preElement;
633 //选中上一个相邻的节点
634 loopAllChildren(props.children,function(item,index,pos,newKey){
635 if(pos == prePos){
636 prevTreeNode = item;
637 }
638 })
639 //查询的上一个节点不为空的话,则选中
640 if(prevTreeNode){
641 if(preIndex >=0){
642 //如果上面的节点展开则默认选择最后一个子节点
643 if(state.expandedKeys.indexOf(prevTreeNode.key)>-1){
644 const preElementArr = e.target.parentElement.previousElementSibling.querySelectorAll('a');
645 preElement = preElementArr[preElementArr.length-1];
646 prePos = preElement.getAttribute('pos');
647 loopAllChildren(props.children,function(item,index,pos,newKey){
648 if(pos == prePos){
649 prevTreeNode = item;
650 }
651 })
652 }else{
653 //上一个节点没有展开
654 preElement = e.target.parentElement.previousElementSibling.querySelector('a')
655 }
656 }else{
657 // 不存在上一个节点时,选中它的父节点
658 preElement = e.target.parentElement.parentElement.parentElement.querySelector('a')
659 }
660
661
662 }
663 preElement && preElement.focus();
664 const eventKey = prevTreeNode.props.eventKey || prevTreeNode.key;
665 this.setState({
666 focusKey: eventKey
667 })
668 if(props.autoSelectWhenFocus){
669 this.onSelect(prevTreeNode);
670 }
671 }
672 // all keyboard events callbacks run from here at first
673 onKeyDown(e,treeNode) {
674 // e.stopPropagation();
675
676 const props = this.props;
677 const currentPos = treeNode.props.pos;
678 const selectable = treeNode.props.selectable;
679 const currentIndex = currentPos.substr(currentPos.lastIndexOf('-')+1);
680 //向下键down
681 if(e.keyCode == KeyCode.DOWN){
682 this.goDown(currentPos,currentIndex,e,treeNode);
683 }else if(e.keyCode == KeyCode.UP){
684 this.goUp(currentPos,currentIndex,e,treeNode);
685 }else if(e.keyCode == KeyCode.LEFT && !treeNode.props.isLeaf){
686 // 收起树节点
687 this.onExpand(treeNode,'left');
688 }else if (e.keyCode == KeyCode.RIGHT && !treeNode.props.isLeaf){
689 // 展开树节点
690 this.onExpand(treeNode,'right');
691 }else if (e.keyCode == KeyCode.SPACE){
692 this.onSelect(treeNode);
693 // 如果是多选tree则进行选中或者反选该节点
694 props.checkable && this.onCheck(treeNode);
695 }else if(e.keyCode == KeyCode.ENTER){
696 if(props.onDoubleClick) {
697 this.onDoubleClick(treeNode);
698 } else {
699 selectable && this.onSelect(treeNode);
700 props.checkable && this.onCheck(treeNode);
701 }
702 }
703 this.props.keyFun && this.props.keyFun(e,treeNode);
704 // e.preventDefault();
705
706 }
707
708 _focusDom(selectKeyDomPos,targetDom){
709 const queryInfo = `a[pos="${selectKeyDomPos}"]`;
710 const parentEle = closest(targetDom,".u-tree")
711 const focusEle = parentEle?parentEle.querySelector(queryInfo):null;
712 if(document.activeElement !== focusEle){
713 focusEle && focusEle.focus();
714 }
715 }
716
717 /**
718 * 此方法为了解决树快捷键,当有的元素隐藏,按tab键也要显示的问题
719 * @param {*} e
720 */
721 onUlFocus(e){
722 const targetDom = e.target;
723
724 // 如果当前tree节点不包括上一个焦点节点会触发此方法
725 if(this.tree == targetDom && !this.isIn && !this.tree.contains(e.relatedTarget)){
726 const {onFocus, children} = this.props;
727 const {selectedKeys=[]} = this.state;
728 let tabIndexKey = selectedKeys[0]
729 let isExist = false;
730 const treeNode = children&&children.length && children[0];
731 let eventKey = treeNode&&treeNode.props.eventKey || treeNode.key;
732 if((this.selectKeyDomExist && tabIndexKey) || !tabIndexKey){
733 isExist = true;
734 const queryInfo = `a[pos="${this.selectKeyDomPos}"]`;
735 const parentEle = closest(e.target,".u-tree")
736 const focusEle = parentEle?parentEle.querySelector(queryInfo):null;
737 focusEle && focusEle.focus();
738 // TAB键选中树后,默认聚焦在第一个(已选中)节点,并显示 focus 状态。
739 this.setState({
740 focusKey: tabIndexKey || eventKey
741 })
742 }
743 let onFocusRes = onFocus && onFocus(isExist);
744 if(onFocusRes instanceof Promise){
745 onFocusRes.then(()=>{
746 this._focusDom(this.selectKeyDomPos,targetDom);
747 });
748 }else{
749 this._focusDom(this.selectKeyDomPos,targetDom);
750 }
751 }
752 }
753
754
755 onUlMouseEnter(e){
756 this.isIn = true;
757 // console.log('onUlMouseEnter----isIn-----',this.isIn);
758 }
759 onUlMouseLeave(e){
760 this.isIn = false;
761 // console.log('onUlMouseLeave----isIn-----',this.isIn);
762
763 }
764
765 getFilterExpandedKeys(props, expandKeyProp, expandAll) {
766 const keys = props[expandKeyProp];
767 if (!expandAll && !props.autoExpandParent) {
768 return keys || [];
769 }
770 const expandedPositionArr = [];
771 if (props.autoExpandParent) {
772 loopAllChildren(props.children, (item, index, pos, newKey) => {
773 if (keys.indexOf(newKey) > -1) {
774 expandedPositionArr.push(pos);
775 }
776 });
777 }
778 const filterExpandedKeys = [];
779 loopAllChildren(props.children, (item, index, pos, newKey) => {
780 if (expandAll) {
781 filterExpandedKeys.push(newKey);
782 } else if (props.autoExpandParent) {
783 expandedPositionArr.forEach(p => {
784 if ((p.split('-').length > pos.split('-').length && isInclude(pos.split('-'), p.split('-')) || pos === p) && filterExpandedKeys.indexOf(newKey) === -1) {
785 filterExpandedKeys.push(newKey);
786 }
787 });
788 }
789 });
790 return filterExpandedKeys.length ? filterExpandedKeys : keys;
791 }
792
793 getDefaultExpandedKeys(props, willReceiveProps) {
794 let expandedKeys = willReceiveProps ? undefined :
795 this.getFilterExpandedKeys(props, 'defaultExpandedKeys',
796 props.defaultExpandedKeys.length ? false : props.defaultExpandAll);
797 if ('expandedKeys' in props) {
798 expandedKeys = (props.autoExpandParent ?
799 this.getFilterExpandedKeys(props, 'expandedKeys', false) :
800 props.expandedKeys) || [];
801 }
802 return expandedKeys;
803 }
804
805 getDefaultCheckedKeys(props, willReceiveProps) {
806 let checkedKeys = willReceiveProps ? undefined : props.defaultCheckedKeys;
807 if ('checkedKeys' in props) {
808 checkedKeys = props.checkedKeys || [];
809 if (props.checkStrictly) {
810 if (props.checkedKeys.checked) {
811 checkedKeys = props.checkedKeys.checked;
812 } else if (!Array.isArray(props.checkedKeys)) {
813 checkedKeys = [];
814 }
815 }
816 }
817 return checkedKeys;
818 }
819
820 getDefaultSelectedKeys(props, willReceiveProps) {
821 const getKeys = (keys) => {
822 if (props.multiple) {
823 return [...keys];
824 }
825 if (keys.length) {
826 return [keys[0]];
827 }
828 return keys;
829 };
830 let selectedKeys = willReceiveProps ? undefined : getKeys(props.defaultSelectedKeys);
831 if ('selectedKeys' in props) {
832 selectedKeys = getKeys(props.selectedKeys);
833 }
834 return selectedKeys;
835 }
836
837 getRawExpandedKeys() {
838 if (!this._rawExpandedKeys && ('expandedKeys' in this.props)) {
839 this._rawExpandedKeys = [...this.state.expandedKeys];
840 }
841 }
842
843 getOpenTransitionName() {
844 const props = this.props;
845 let transitionName = props.openTransitionName;
846 const animationName = props.openAnimation;
847 if (!transitionName && typeof animationName === 'string') {
848 transitionName = `${props.prefixCls}-open-${animationName}`;
849 }
850 return transitionName;
851 }
852
853 getDragNodes(treeNode) {
854 const dragNodesKeys = [];
855 const tPArr = treeNode.props.pos.split('-');
856 loopAllChildren(this.props.children, (item, index, pos, newKey) => {
857 const pArr = pos.split('-');
858 if (treeNode.props.pos === pos || tPArr.length < pArr.length && isInclude(tPArr, pArr)) {
859 dragNodesKeys.push(newKey);
860 }
861 });
862 return dragNodesKeys;
863 }
864
865 getExpandedKeys(treeNode, expand) {
866 const key = treeNode.props.eventKey;
867 const expandedKeys = this.state.expandedKeys;
868 const expandedIndex = expandedKeys.indexOf(key);
869 let exKeys;
870 if (expandedIndex > -1 && !expand) {
871 exKeys = [...expandedKeys];
872 exKeys.splice(expandedIndex, 1);
873 return exKeys;
874 }
875 if (expand && expandedKeys.indexOf(key) === -1) {
876 return expandedKeys.concat([key]);
877 }
878 }
879
880 filterTreeNode(treeNode) {
881 const filterTreeNode = this.props.filterTreeNode;
882 if (typeof filterTreeNode !== 'function' || treeNode.props.disabled) {
883 return false;
884 }
885 return filterTreeNode.call(this, treeNode);
886 }
887
888 /**
889 * 将截取后的 List 数组转换为 Tree 结构,并更新 state
890 */
891 handleTreeListChange = (treeList, startIndex, endIndex) => {
892 // 属性配置设置
893 let attr = {
894 id: 'key',
895 parendId: 'parentKey',
896 name: 'title',
897 rootId: null,
898 isLeaf: 'isLeaf'
899 };
900 let treeData = convertListToTree(treeList, attr, this.flatTreeKeysMap);
901
902 this.startIndex = typeof(startIndex) !== "undefined" ? startIndex : this.startIndex;
903 this.endIndex = typeof(endIndex) !== "undefined" ? endIndex : this.endIndex;
904 this.setState({
905 treeData : treeData
906 })
907 this.dataChange = true;
908 }
909
910 /**
911 * 深度遍历 treeData,把Tree数据拍平,变为一维数组
912 * @param {*} treeData
913 * @param {*} parentKey 标识父节点
914 * @param {*} isShown 该节点是否显示在页面中,当节点的父节点是展开状态 或 该节点是根节点时,该值为 true
915 */
916 deepTraversal = (treeData, parentKey=null, isShown) => {
917 let {expandedKeys} = this.state,
918 expandedKeysSet = this.cacheExpandedKeys ? this.cacheExpandedKeys : new Set(expandedKeys),
919 flatTreeData = [],
920 flatTreeKeysMap = this.flatTreeKeysMap, //存储所有 key-value 的映射,方便获取各节点信息
921 dataCopy = treeData;
922 if(Array.isArray(dataCopy)){
923 for (let i=0, l=dataCopy.length; i<l; i++) {
924 let { key, title, children, ...props } = dataCopy[i],
925 dataCopyI = new Object(),
926 isLeaf = children ? false : true;
927 //如果父节点是收起状态,则子节点的展开状态无意义。(一级节点或根节点直接判断自身状态即可)
928 let isExpanded = (parentKey === null || expandedKeysSet.has(parentKey)) ? expandedKeysSet.has(key) : false;
929 dataCopyI = Object.assign(dataCopyI,{
930 key,
931 title,
932 isExpanded,
933 parentKey : parentKey || null,
934 isShown,
935 isLeaf
936 },{...props});
937 //该节点的父节点是展开状态 或 该节点是根节点
938 if(isShown || parentKey === null){
939 flatTreeData.push(dataCopyI); // 取每项数据放入一个新数组
940 flatTreeKeysMap[key] = dataCopyI;
941 }
942 if (Array.isArray(children) && children.length > 0){
943 // 若存在children则递归调用,把数据拼接到新数组中,并且删除该children
944 flatTreeData = flatTreeData.concat(this.deepTraversal(children, key, isExpanded));
945 }
946 }
947 }
948 return flatTreeData;
949 }
950
951 /**
952 * 根据 treeData 渲染树节点
953 * @param data 树形结构的数组
954 * @param preHeight 前置占位高度
955 * @param sufHeight 后置占位高度
956 */
957 renderTreefromData = (data) => {
958 let {renderTitle,renderTreeNodes} = this.props;
959 if(renderTreeNodes) {
960 return renderTreeNodes(data);
961 }
962 const loop = data => data.map((item) => {
963 const { key, title, children, isLeaf , ...others } = item;
964 if (item.children) {
965 return (
966 <TreeNode {...others} key={key} title={renderTitle ? renderTitle(item) : key} isLeaf={isLeaf}>
967 {loop(item.children)}
968 </TreeNode>
969 );
970 }
971 return <TreeNode {...others} key={key} title={renderTitle ? renderTitle(item) : key} isLeaf={true}/>;
972 });
973 return loop(data);
974 }
975
976 /**
977 * @description 计算懒加载时的前置占位和后置占位
978 * @param start {Number} 开始截取数据的位置
979 * @param end {Number} 结束截取数据的位置
980 * @return sumHeight {Number} 空白占位的高度
981 */
982 getSumHeight = (start, end) => {
983 let sumHeight = 0;
984 if(start > end) {
985 return sumHeight;
986 }
987 let span = Math.abs(end - start);
988 if(span) {
989 sumHeight = span * this.store.getState().rowHeight;
990 }
991 return sumHeight;
992 }
993
994 renderTreeNode(child, index, level = 0) {
995 // fix: 懒加载场景,index 计算错误
996 const actualIndex = index + parseInt(this.startIndex);
997 const pos = `${level}-${actualIndex}`;
998 const key = child.key || pos;
999
1000 const state = this.state;
1001 const props = this.props;
1002 const {selectedKeys=[]} = this.state;
1003 let tabIndexKey = selectedKeys[0]
1004 if(tabIndexKey && key == tabIndexKey){
1005 this.selectKeyDomExist = true;
1006 this.selectKeyDomPos = pos;
1007 }
1008 // prefer to child's own selectable property if passed
1009 let selectable = props.selectable;
1010 if (child.props.hasOwnProperty('selectable')) {
1011 selectable = child.props.selectable;
1012 }
1013 let draggable = props.draggable;
1014 if(child.props.hasOwnProperty('draggable')){
1015 draggable = child.props.draggable;
1016 }
1017 let isLeaf = null;
1018 if(child.props.hasOwnProperty('isLeaf')){
1019 isLeaf = child.props.isLeaf;
1020 }
1021
1022 const cloneProps = {
1023 root: this,
1024 eventKey: key,
1025 pos,
1026 selectable,
1027 loadData: props.loadData,
1028 onMouseEnter: props.onMouseEnter,
1029 onMouseLeave: props.onMouseLeave,
1030 onRightClick: props.onRightClick,
1031 onDoubleClick:props.onDoubleClick,
1032 onKeyDown:props.onKeyDown,
1033 prefixCls: props.prefixCls,
1034 showLine: props.showLine,
1035 showIcon: props.showIcon,
1036 draggable,
1037 dragOver: state.dragOverNodeKey === key && this.dropPosition === 0,
1038 dragOverGapTop: state.dragOverNodeKey === key && this.dropPosition === -1,
1039 dragOverGapBottom: state.dragOverNodeKey === key && this.dropPosition === 1,
1040 _dropTrigger: this._dropTrigger,
1041 expanded: state.expandedKeys.indexOf(key) !== -1,
1042 selected: state.selectedKeys.indexOf(key) !== -1,
1043 focused: state.focusKey === key,
1044 openTransitionName: this.getOpenTransitionName(),
1045 openAnimation: props.openAnimation,
1046 filterTreeNode: this.filterTreeNode.bind(this),
1047 openIcon: props.openIcon,
1048 closeIcon: props.closeIcon,
1049 focusable:props.focusable,
1050 tabIndexKey: state.selectedKeys[0],
1051 tabIndexValue:props.tabIndexValue,
1052 ext:child.props.ext,
1053 mustExpandable:props.mustExpandable,
1054 isLeaf
1055 };
1056 if (props.checkable) {
1057 cloneProps.checkable = props.checkable;
1058 if (props.checkStrictly) {
1059 if (state.checkedKeys) {
1060 cloneProps.checked = state.checkedKeys.indexOf(key) !== -1 || false;
1061 }
1062 if (props.checkedKeys && props.checkedKeys.halfChecked) {
1063 cloneProps.halfChecked = props.checkedKeys.halfChecked.indexOf(key) !== -1 || false;
1064 } else {
1065 cloneProps.halfChecked = false;
1066 }
1067 } else {
1068 if (this.checkedKeys) {
1069 cloneProps.checked = this.checkedKeys.indexOf(key) !== -1 || false;
1070 }
1071 cloneProps.halfChecked = this.halfCheckedKeys.indexOf(key) !== -1;
1072 }
1073 }
1074 if (this.treeNodesStates && this.treeNodesStates[pos]) {
1075 Object.assign(cloneProps, this.treeNodesStates[pos].siblingPosition);
1076 }
1077 return React.cloneElement(child, cloneProps);
1078 }
1079
1080 render() {
1081 const props = this.props;
1082 const {
1083 showLine, prefixCls, className, focusable, checkable, loadData, checkStrictly, tabIndexValue, lazyLoad, getScrollContainer,
1084 defaultExpandedKeys, defaultSelectedKeys, defaultCheckedKeys, openAnimation, draggable,
1085 ...others
1086 } = this.props;
1087 const customProps = {...omit(others, [
1088 'showIcon',
1089 'cancelUnSelect',
1090 'onCheck',
1091 'selectable',
1092 'autoExpandParent',
1093 'defaultExpandAll',
1094 'onExpand',
1095 'autoSelectWhenFocus',
1096 'expandWhenDoubleClick',
1097 'expandedKeys',
1098 'keyFun',
1099 'openIcon',
1100 'closeIcon',
1101 'treeData',
1102 'checkedKeys',
1103 'selectedKeys',
1104 'renderTreeNodes',
1105 'mustExpandable',
1106 'onMouseEnter',
1107 'onMouseLeave',
1108 'onDoubleClick'
1109 ])}
1110 const { treeData,flatTreeData } = this.state;
1111 let { startIndex, endIndex } = this, //数据截取的开始位置和结束位置
1112 preHeight = 0, //前置占位高度
1113 sufHeight = 0, //后置占位高度
1114 treeNode = [], //根据传入的 treeData 生成的 treeNode 节点数组
1115 treeChildren = props.children; //最终渲染在 Tree 标签中的子节点
1116 if(lazyLoad){
1117 preHeight = this.getSumHeight(0, startIndex);
1118 sufHeight = this.getSumHeight(endIndex, flatTreeData.length);
1119 }
1120 if(!props.children && treeData) { //传入json数据
1121 treeNode = this.renderTreefromData(treeData);
1122 this.cacheTreeNodes = treeNode;
1123 treeChildren = treeNode;
1124 }
1125 let showLineCls = "";
1126 if (showLine) {
1127 showLineCls = `${prefixCls}-show-line`;
1128 }
1129 const domProps = {
1130 className: classNames(className, prefixCls, showLineCls),
1131 role: 'tree-node',
1132 };
1133
1134 if (focusable) {
1135 domProps.onFocus = this.onUlFocus;
1136 domProps.onMouseEnter = this.onUlMouseEnter;
1137 domProps.onMouseLeave = this.onUlMouseLeave;
1138 }
1139
1140 // if (props.focusable) {
1141 // // domProps.tabIndex = '0';//需求改成了默认选择第一个节点或者选中的节点
1142 // // domProps.onKeyDown = this.onKeyDown;//添加到具体的treeNode上了
1143 // }
1144 const getTreeNodesStates = () => {
1145 this.treeNodesStates = {};
1146 loopAllChildren(treeChildren, (item, index, pos, keyOrPos, siblingPosition) => {
1147 this.treeNodesStates[pos] = {
1148 siblingPosition,
1149 };
1150 }, undefined, startIndex);
1151 };
1152 if (showLine && !checkable ) {
1153 getTreeNodesStates();
1154 }
1155 if (checkable && (this.checkedKeysChange || loadData || this.dataChange)) {
1156 if (checkStrictly) {
1157 getTreeNodesStates();
1158 } else if (props._treeNodesStates) {
1159 this.treeNodesStates = props._treeNodesStates.treeNodesStates;
1160 this.halfCheckedKeys = props._treeNodesStates.halfCheckedKeys;
1161 this.checkedKeys = props._treeNodesStates.checkedKeys;
1162 } else {
1163 const checkedKeys = this.state.checkedKeys;
1164 let checkKeys;
1165 if (!loadData && this.checkKeys && this._checkedKeys &&
1166 arraysEqual(this._checkedKeys, checkedKeys) && !this.dataChange) {
1167 // if checkedKeys the same as _checkedKeys from onCheck, use _checkedKeys.
1168 checkKeys = this.checkKeys;
1169 } else {
1170 const checkedPositions = [];
1171 this.treeNodesStates = {};
1172 loopAllChildren(treeChildren, (item, index, pos, keyOrPos, siblingPosition) => {
1173 this.treeNodesStates[pos] = {
1174 node: item,
1175 key: keyOrPos,
1176 checked: false,
1177 halfChecked: false,
1178 siblingPosition,
1179 };
1180 if (checkedKeys.indexOf(keyOrPos) !== -1) {
1181 this.treeNodesStates[pos].checked = true;
1182 checkedPositions.push(pos);
1183 }
1184 }, undefined, startIndex);
1185 // if the parent node's key exists, it all children node will be checked
1186 handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true);
1187 checkKeys = getCheck(this.treeNodesStates);
1188 }
1189 this.halfCheckedKeys = checkKeys.halfCheckedKeys;
1190 this.checkedKeys = checkKeys.checkedKeys;
1191 }
1192 }
1193 this.selectKeyDomExist = false;
1194 return (
1195 lazyLoad ?
1196 <InfiniteScroll
1197 className="u-tree-infinite-scroll"
1198 treeList={flatTreeData}
1199 handleTreeListChange={this.handleTreeListChange}
1200 getScrollParent={getScrollContainer}
1201 store={this.store}
1202 >
1203 <ul {...domProps} unselectable="true" ref={(el)=>{this.tree = el}} tabIndex={focusable && tabIndexValue} {...customProps}>
1204 <li style={{height : preHeight}} className='u-treenode-start' key={'tree_node_start'}></li>
1205 { React.Children.map(treeChildren, this.renderTreeNode, this) }
1206 <li style={{height : sufHeight}} className='u-treenode-end' key={'tree_node_end'}></li>
1207 </ul>
1208 </InfiniteScroll>
1209 :
1210 <ul {...domProps} unselectable="true" ref={(el)=>{this.tree = el}} tabIndex={focusable && tabIndexValue} {...customProps}>
1211 { React.Children.map(treeChildren, this.renderTreeNode, this) }
1212 </ul>
1213 );
1214 }
1215}
1216
1217Tree.propTypes = {
1218 prefixCls: PropTypes.string,
1219 children: PropTypes.any,
1220 showLine: PropTypes.bool,
1221 showIcon: PropTypes.bool,
1222 selectable: PropTypes.bool,
1223 multiple: PropTypes.bool,
1224 checkable: PropTypes.oneOfType([
1225 PropTypes.bool,
1226 PropTypes.node,
1227 ]),
1228 _treeNodesStates: PropTypes.object,
1229 checkStrictly: PropTypes.bool,
1230 draggable: PropTypes.bool,
1231 autoExpandParent: PropTypes.bool,
1232 defaultExpandAll: PropTypes.bool,
1233 defaultExpandedKeys: PropTypes.arrayOf(PropTypes.string),
1234 expandedKeys: PropTypes.arrayOf(PropTypes.string),
1235 defaultCheckedKeys: PropTypes.arrayOf(PropTypes.string),
1236 checkedKeys: PropTypes.oneOfType([
1237 PropTypes.arrayOf(PropTypes.string),
1238 PropTypes.object,
1239 ]),
1240 defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
1241 selectedKeys: PropTypes.arrayOf(PropTypes.string),
1242 onExpand: PropTypes.func,
1243 onCheck: PropTypes.func,
1244 onSelect: PropTypes.func,
1245 loadData: PropTypes.func,
1246 onMouseEnter: PropTypes.func,
1247 onMouseLeave: PropTypes.func,
1248 onRightClick: PropTypes.func,
1249 onDragStart: PropTypes.func,
1250 onDragEnter: PropTypes.func,
1251 onDragOver: PropTypes.func,
1252 onDragLeave: PropTypes.func,
1253 onDrop: PropTypes.func,
1254 onDragEnd: PropTypes.func,
1255 filterTreeNode: PropTypes.func,
1256 openTransitionName: PropTypes.string,
1257 focusable: PropTypes.bool,
1258 openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
1259 lazyLoad: PropTypes.bool,
1260 treeData: PropTypes.array,
1261 renderTreeNodes: PropTypes.func,
1262 autoSelectWhenFocus: PropTypes.bool,
1263 getScrollContainer: PropTypes.func,
1264 expandWhenDoubleClick: PropTypes.bool
1265};
1266
1267Tree.defaultProps = {
1268 prefixCls: 'rc-tree',
1269 showLine: false,
1270 showIcon: true,
1271 selectable: true,
1272 multiple: false,
1273 checkable: false,
1274 checkStrictly: false,
1275 draggable: false,
1276 autoExpandParent: true,
1277 defaultExpandAll: false,
1278 defaultExpandedKeys: [],
1279 defaultCheckedKeys: [],
1280 defaultSelectedKeys: [],
1281 onExpand: noop,
1282 onCheck: noop,
1283 onSelect: noop,
1284 onDragStart: noop,
1285 onDragEnter: noop,
1286 onDragOver: noop,
1287 onDragLeave: noop,
1288 onDrop: noop,
1289 onDragEnd: noop,
1290 tabIndexValue:0,
1291 lazyLoad: false,
1292 autoSelectWhenFocus: false,
1293 getScrollContainer: noop,
1294 expandWhenDoubleClick: false
1295};
1296
1297export default Tree;
\No newline at end of file