import { AbstractTreeNode , isLeafNode } from 'ali-react-table' ; import { arrayUtils } from './select-utils' ; class Wrapper < N extends AbstractTreeNode > { constructor ( input ? ) { Object . assign ( this , input ) ; } /** 是否为根节点 */ root ? : boolean ; /** 子节点数组 */ children ? : Wrapper < N > [ ] ; /** 父节点 (parent wrapper node) */ parent ? : Wrapper < N > ; /** 指向对应的普通节点 */ node : N ; /** 是否与其父节点脱离关联 */ detached ? : boolean ; /** 是否「选中」 */ checked ? : boolean ; /** 该节点是否「恰好选中」 (即节点的值是否在 value 数组中） */ exactChecked ? : boolean ; /** 父节点是否被选中 */ parentChecked ? : boolean ; /** 是否有任意子孙节点被选中 */ anyDescendentsChecked ? : boolean ; /** 该节点的所有子孙节点是否均为选中 */ allChildrenChecked ? : boolean ; } export type TreeCheckedStrategy = 'all' | 'parent' | 'child' ; export interface TreeDataHelperOptions < N extends AbstractTreeNode > { tree : N [ ] ; getNodeValue ( node ) : string ; value : string [ ] ; checkedStrategy : TreeCheckedStrategy ; isDetached ? ( node ) : boolean ; } export default class TreeDataHelper < N extends AbstractTreeNode > { private readonly opts : TreeDataHelperOptions < N > ; private readonly valueSet : ReadonlySet < string > ; private wrapperMap : Map < string , Wrapper < N > > ; private rootWrapper : Wrapper < N > ; constructor ( opts ) { this . opts = opts ; this . valueSet = new Set ( opts . value ) ; this . initWrapperTree ( ) ; } private get value ( ) { return this . opts . value ; } private isDetached = ( node ) => { return this . opts . isDetached ?. ( node ) ?? false ; } ; private initWrapperTree ( ) { const valueSet = new Set ( this . value ) ; this . rootWrapper = new Wrapper < N > ( { root : true , children : [ ] } ) ; this . wrapperMap = new Map < string , Wrapper < N > > ( ) ; const getNodeValue = this . opts . getNodeValue ; const { isDetached , wrapperMap } = this ; dfs ( this . rootWrapper , this . opts . tree , false ) ; function dfs ( parentWrapper , nodes , inheritParentChecked ) { parentWrapper . allChildrenChecked = true ; if ( process . env . NODE_ENV !== 'production' ) { if ( nodes . length > 0 && nodes . every ( isDetached ) ) { console . warn ( 'TreeDataHelper 检测到部分节点的下所有子节点均为 detached 状态，这将导致该节点变为「无效节点」' , parentWrapper . node ) ; } } for ( const node of nodes ) { const detached = isDetached ( node ) ; const exactChecked = valueSet . has ( getNodeValue ( node ) ) ; if ( exactChecked && ! detached ) { parentWrapper . anyDescendentsChecked = true ; } const parentChecked = ! detached && inheritParentChecked ; const checked = exactChecked || parentChecked ; const wrapper = new Wrapper < N > ( { parent : parentWrapper , node , checked , exactChecked , parentChecked , anyDescendentsChecked : checked , detached , } ) ; wrapperMap . set ( getNodeValue ( node ) , wrapper ) ; parentWrapper . children . push ( wrapper ) ; if ( ! isLeafNode ( node ) ) { wrapper . children = [ ] ; dfs ( wrapper , node . children as N [ ] , checked ) ; if ( wrapper . anyDescendentsChecked && ! detached ) { parentWrapper . anyDescendentsChecked = true ; } if ( wrapper . allChildrenChecked ) { wrapper . checked = true ; for ( const child of wrapper . children ) { if ( ! child . detached ) { child . parentChecked = true ; } } } } if ( ! wrapper . checked && ! detached ) { parentWrapper . allChildrenChecked = false ; } } } } public isIndeterminate ( nodeValue ) { const wrapper = this . wrapperMap . get ( nodeValue ) ; return ! wrapper . checked && wrapper . anyDescendentsChecked ; } public isChecked ( nodeValue ) { const wrapper = this . wrapperMap . get ( nodeValue ) ; return wrapper . checked ; } public getValueAfterCheck ( nodeValue ) { if ( this . isChecked ( nodeValue ) ) { return this . getCleanValue ( ) ; } const nextValue = arrayUtils . merge ( this . value , [ nodeValue ] ) ; return new TreeDataHelper ( { ... this . opts , value : nextValue } ) . getCleanValue ( ) ; } public getValueAfterUncheck ( nodeValue ) { if ( ! this . isChecked ( nodeValue ) ) { return this . getCleanValue ( ) ; } const wrapper = this . wrapperMap . get ( nodeValue ) ; const { getNodeValue } = this . opts ; const appendArray = getAppendArray ( wrapper ) ; const removeSet = getRemoveSet ( wrapper ) ; const nextValue = arrayUtils . diff ( this . value . concat ( appendArray ) , removeSet ) ; return new TreeDataHelper ( { ... this . opts , value : nextValue } ) . getCleanValue ( ) ; function getAppendArray ( startWrapper ) { const result = [ ] ; let current = startWrapper ; while ( current . parentChecked && ! current . detached ) { for ( const sibling of current . parent . children ) { if ( sibling === current || sibling . exactChecked || sibling . detached ) { continue ; } result . push ( getNodeValue ( sibling . node ) ) ; } current = current . parent ; } return result ; } function getRemoveSet ( startWrapper ) { const result = new Set < string > ( ) ; let wrapper = startWrapper ; while ( true ) { result . add ( getNodeValue ( wrapper . node ) ) ; if ( wrapper . detached || ! wrapper . parentChecked ) { break ; } wrapper = wrapper . parent ; } function dfs ( wrappers ) { if ( wrappers == null ) { return ; } for ( const wrapper of wrappers ) { if ( wrapper . detached || ! wrapper . checked ) { continue ; } result . add ( getNodeValue ( wrapper . node ) ) ; if ( ! isLeafNode ( wrapper ) && wrapper . anyDescendentsChecked ) { dfs ( wrapper . children ) ; } } } dfs ( startWrapper . children ) ; return result ; } } public getValueAfterToggle ( nodeValue ) { if ( this . isChecked ( nodeValue ) ) { return this . getValueAfterUncheck ( nodeValue ) ; } else { return this . getValueAfterCheck ( nodeValue ) ; } } public getNode ( nodeValue ) { return this . wrapperMap . get ( nodeValue ) ?. node ; } public getCleanValue ( ) { const { checkedStrategy , getNodeValue } = this . opts ; const result = this . value . filter ( ( nodeValue ) => { return ! this . wrapperMap . has ( nodeValue ) ; } ) ; dfs ( this . rootWrapper . children ) ; return result ; function dfs ( wrappers ) { for ( const wrapper of wrappers ) { if ( wrapper . checked ) { if ( checkedStrategy === 'all' ) { result . push ( getNodeValue ( wrapper . node ) ) ; } else if ( checkedStrategy === 'parent' ) { if ( ! wrapper . parentChecked ) { result . push ( getNodeValue ( wrapper . node ) ) ; } } else { if ( isLeafNode ( wrapper ) ) { result . push ( getNodeValue ( wrapper . node ) ) ; } } } if ( ! isLeafNode ( wrapper ) ) { dfs ( wrapper . children ) ; } } } } }