import React , { useState , useEffect , useRef } from 'react' ; import { Tree as AntTree , Dropdown } from 'antd' ; import { uniqWith } from 'lodash' ; import RcTree from 'rc-tree' ; import { Key } from 'rc-tree/lib/interface' ; import { NodeDragEventParams } from 'rc-tree/lib/contextTypes' ; import { EventDataNode , TreeProps , DataNode } from 'antd/lib/tree' ; import { OverlayFunc } from '../interface' ; const getPopupContainer = ( container ) => { return container . parentNode ?. parentNode ?. parentNode ?. parentNode ?. parentElement ! ; } ; export interface TreeHandle { reload : ( treeNode ? : EventDataNode ) => Promise < void > ; scrollTo : ( key : string ) => void ; update : ( callback : ( dataNode : EventDataNode ) => void ) => void ; del : ( callback : ( dataNode : EventDataNode ) => boolean ) => void ; filter : ( callback : ( ) => Promise < EventDataNode [ ] > ) => void ; /**
     * 获取当前右键的上下文
     */ rightContext : ( ) => EventDataNode | null ; } interface CustomEventDataNode extends EventDataNode { parent ? : CustomEventDataNode ; } export interface Props extends Omit < TreeProps , 'loadData' | 'treeData' | 'loadedKeys' | 'defaultCheckedKeys' | 'defaultExpandAll' | 'defaultExpandedKeys' | 'defaultExpandParent' | 'defaultSelectedKeys' | 'filterAntTreeNode' | 'onDrop' > { loadData : ( treeNode : EventDataNode | null ) => Promise < DataNode [ ] > ; overlay ? : React . ReactElement | OverlayFunc ; tree ? : React . MutableRefObject < TreeHandle | null > ; enableDirectoryTree ? : boolean ; expandAll ? : boolean ; onDrop ? : ( info : NodeDragEventParams & { dragNode : EventDataNode ; dragNodesKeys : Key [ ] ; dropPosition : number ; dropToGap : boolean ; } ) => void | Promise < void > ; } const findTreeNode = ( treeNodes , callback ) => { treeNodes . some ( ele => { const result = callback ( ele ) ; if ( result ) return true ; if ( ele . children && ele . children . length > 0 ) { findTreeNode ( ele . children , callback ) ; } return false ; } ) ; } ; const update = ( nodes , overlay ? ) => { if ( nodes ) { nodes . forEach ( ( ele ) => { if ( overlay ) { ele . title = ( < Dropdown overlay = { overlay } trigger = { [ 'contextMenu' ] } getPopupContainer = { getPopupContainer } > < span > { ele . title } < /span> < /Dropdown>); } if ( ele . children ) { update ( ele . children as EventDataNode [ ] , overlay ) ; } } ) ; } } ; export const Tree = ( props ) => { const [ treeNodes , setTreeNodes ] = useState < CustomEventDataNode [ ] > ( [ ] ) ; const [ loadedKeys , setLoadedKeys ] = useState < ( string | number ) [ ] > ( [ ] ) ; const [ expandedKeys , setExpandedKeys ] = useState < ( string | number ) [ ] > ( props . expandedKeys || [ ] ) ; const [ selectedKeys , setSelectedKeys ] = useState < ( string | number ) [ ] > ( props . selectedKeys || [ ] ) ; const contextNode = useRef < EventDataNode | null > ( null ) ; const [ checkedKeys , setCheckedKeys ] = useState < Key [ ] | { checked : Key [ ] ; halfChecked : Key [ ] ; } > ( [ ] ) ; const treeRef = useRef < RcTree > ( null ) ; const reload = async ( ) => { const tempTreeNode = await props . loadData ( null ) ; if ( props . expandAll ) { setExpandedKeys ( expandedKeys . concat ( tempTreeNode . map ( ele => ele . key ) ) ) ; } const joinExpandedKeys = [ ] ; tempTreeNode . forEach ( ele => { if ( props . expandedKeys ?. includes ( ele . key ) ) { joinExpandedKeys . push ( ele . key ) ; } } ) ; update ( tempTreeNode , props . overlay ) ; setTreeNodes ( tempTreeNode as EventDataNode [ ] ) ; setExpandedKeys ( expandedKeys . concat ( joinExpandedKeys ) ) ; } ; const filter = ( callback ) => { const loopsDel = ( loopNodes ) : EventDataNode [ ] => { const resultNodes = [ ] ; loopNodes . forEach ( ele => { if ( callback ( ele ) ) return ; if ( ele . children && ele . children . length > 0 ) { const children = ele . children as EventDataNode [ ] ; resultNodes . push ( { ... ele , children : loopsDel ( children ) , } ) ; } else { resultNodes . push ( ele ) ; } } ) ; return resultNodes ; } ; return loopsDel ( treeNodes ) ; } ; if ( props . tree ) { const { tree } = props ; tree . current = { reload : async ( treeNode ? ) => { let currentNode ; const expandKey = [ ] ; if ( treeNode === undefined || treeNodes . some ( node => node . key === treeNode . key ) ) { reload ( ) ; setLoadedKeys ( [ ] ) ; setExpandedKeys ( [ ] ) ; return ; } await new Promise < void > ( resolve => { const findTreeNodeFun = ( ele ) => { const { children = [ ] } = ele ; const some = ( chil ) => { if ( chil . key === treeNode . key ) { currentNode = ele as CustomEventDataNode ; props . loadData ( ele as EventDataNode ) . then ( nodeData => { ele . children = nodeData . map ( node => { let { title } = node ; if ( props . overlay ) { title = ( < Dropdown overlay = { props . overlay } getPopupContainer = { getPopupContainer } trigger = { [ 'contextMenu' ] } > < span > { node . title } < /span> < /Dropdown>); } return { ... node , parent : ele , title , } ; } ) ; resolve ( ) ; } ) ; return true ; } return false ; } ; return children . some ( some ) ; } ; findTreeNode ( treeNodes , findTreeNodeFun ) ; } ) ; if ( currentNode ) { const loops = ( node ) => { if ( node . parent ) { loops ( node . parent ) ; } expandKey . push ( node . key ) ; } ; loops ( currentNode ) ; } setExpandedKeys ( expandKey ) ; setLoadedKeys ( [ ] ) ; setTreeNodes ( [ ... treeNodes ] ) ; } , scrollTo : ( key ) => { if ( treeRef . current ) { treeRef . current . scrollTo ( { key , } ) ; } } , filter : async ( callback ) => { const datas = await callback ( ) ; update ( datas , props . overlay ) ; const loadKeys = [ ] ; findTreeNode ( datas , ele => { loadKeys . push ( ele . key ) ; return false ; } ) ; setTreeNodes ( datas ) ; setLoadedKeys ( loadKeys ) ; } , update : callback => { findTreeNode ( treeNodes , treeNode => { callback ( treeNode as EventDataNode ) ; let { title } = treeNode ; if ( props . overlay ) { title = ( < Dropdown overlay = { props . overlay } trigger = { [ 'contextMenu' ] } getPopupContainer = { getPopupContainer } > < span > { treeNode . title } < /span> < /Dropdown>); } treeNode . title = title ; return false ; } ) ; setTreeNodes ( [ ... treeNodes ] ) ; } , del : ( callback ) => { setTreeNodes ( filter ( callback ) ) ; } , rightContext : ( ) => contextNode . current , } ; } useEffect ( ( ) => { reload ( ) ; } , [ ] ) ; useEffect ( ( ) => { setExpandedKeys ( props . expandedKeys as ( string | number ) [ ] ) ; } , [ props . expandedKeys ] ) ; useEffect ( ( ) => { setSelectedKeys ( props . selectedKeys || [ ] ) ; } , [ props . selectedKeys ] ) ; useEffect ( ( ) => { setCheckedKeys ( props . checkedKeys || [ ] ) ; } , [ props . checkedKeys ] ) ; const TreeNode = props . enableDirectoryTree ? AntTree . DirectoryTree : AntTree ; return ( < TreeNode ref = { treeRef } style = { props . style } titleRender = { props . titleRender } loadData = { async ( treeNode ) => { if ( treeNode ) { loadedKeys . push ( treeNode . key ) ; } const children = await props . loadData ( treeNode ) ; if ( props . expandAll ) { setExpandedKeys ( expandedKeys . concat ( children . map ( ele => ele . key ) ) ) ; } findTreeNode ( treeNodes , ele => { if ( ele . key === treeNode . key ) { ele . children = children . map ( chil => { let { title } = chil ; if ( props . overlay ) { title = ( < Dropdown overlay = { props . overlay } trigger = { [ 'contextMenu' ] } getPopupContainer = { getPopupContainer } > < span > { chil . title } < /span> < /Dropdown>); } return { ... chil , parent : ele , title , } ; } ) ; } return false ; } ) ; setTreeNodes ( [ ... treeNodes ] ) ; const joinExpandedKeys = [ ] ; children . forEach ( ele => { if ( props . expandedKeys ?. includes ( ele . key ) ) { joinExpandedKeys . push ( ele . key ) ; } } ) ; if ( treeNode ) { setExpandedKeys ( uniqWith ( expandedKeys . concat ( joinExpandedKeys ) . concat ( treeNode . key ) ) ) ; } else { setExpandedKeys ( expandedKeys . concat ( joinExpandedKeys ) ) ; } } } checkedKeys = { checkedKeys } expandedKeys = { expandedKeys } selectedKeys = { selectedKeys } loadedKeys = { loadedKeys } treeData = { treeNodes } checkStrictly = { props . checkStrictly } height = { props . height } autoExpandParent = { props . autoExpandParent } blockNode = { props . blockNode } checkable = { props . checkable } disabled = { props . disabled } draggable = { props . draggable } multiple = { props . multiple } selectable = { props . selectable } showIcon = { props . showIcon } switcherIcon = { props . switcherIcon } showLine = { props . showLine } onSelect = { ( keys , info ) => { setSelectedKeys ( keys ) ; if ( props . onSelect ) { props . onSelect ( keys , info ) ; } } } onCheck = { ( checked , info ) => { setCheckedKeys ( checked ) ; props . onCheck ?. ( checked , info ) ; } } onExpand = { ( keys , info ) => { setExpandedKeys ( keys ) ; if ( props . onExpand ) { props . onExpand ( keys , info ) ; } } } onActiveChange = { props . onActiveChange } onBlur = { props . onBlur } onLoad = { props . onLoad } onClick = { props . onClick } onDoubleClick = { props . onDoubleClick } onRightClick = { info => { contextNode . current = info . node ; if ( props . onRightClick ) { props . onRightClick ( info ) ; } } } onFocus = { props . onFocus } onKeyDown = { props . onKeyDown } onMouseEnter = { props . onMouseEnter } onMouseLeave = { props . onMouseLeave } onDragStart = { props . onDragStart } onDragOver = { props . onDragOver } onDragEnter = { props . onDragEnter } onDragLeave = { props . onDragLeave } onDragEnd = { props . onDragEnd } onDrop = { info => { const { node , dragNode , dropPosition , dropToGap , } = info ; const loops = ( loopsTreeNodes ) : DataNode [ ] => { const result = [ ] ; loopsTreeNodes . forEach ( ( ele ) => { const treeNode = { ... ele } ; if ( ele . children ) { treeNode . children = loops ( ele . children ) ; } if ( treeNode . key === node . key ) { if ( dropToGap ) { result . push ( treeNode ) ; result . push ( dragNode ) ; } else if ( ! dropToGap && dropPosition === 0 ) { treeNode . children ?. splice ( 0 , 0 , dragNode ) ; result . push ( treeNode ) ; } else if ( treeNode . children ) { treeNode . children . push ( dragNode ) ; result . push ( treeNode ) ; } else { treeNode . children = [ dragNode ] ; result . push ( treeNode ) ; } } else if ( treeNode . key !== dragNode . key ) { result . push ( treeNode ) ; } } ) ; return result ; } ; let preventDefault = false ; const commit = ( ) => { if ( preventDefault ) return ; const data = loops ( treeNodes ) ; setTreeNodes ( data as EventDataNode [ ] ) ; } ; if ( props . onDrop ) { const onDropResult = props . onDrop ( { ... info , event : { ... info . event , preventDefault : ( ) => { preventDefault = true ; } } } ) ; if ( onDropResult && onDropResult . then ) { onDropResult . then ( ( ) => { commit ( ) ; } ) ; } } else { commit ( ) ; } } } icon = { props . icon } / > ) ; } ;