import classNames from 'classnames'; import React, { useContext, useEffect, useMemo, useState } from 'react'; import { ConfigProvider, Tree as OldTree, TreeProps } from 'antd'; import { TreeNodeProps } from 'rc-tree-select/lib/TreeNode'; import { DataNode, EventDataNode } from 'antd/lib/tree'; import './index.less'; import { Icon } from '../Icon'; import { Input } from '../Input'; import { Tooltip } from '../Tooltip'; import { AOP } from '../utils/AOP'; export declare type NodeDragEventParams = { event: React.DragEvent; node: EventDataNode; }; export type EditableType = { show: boolean; beforeEnterEditMode?: (node: DataNode) => Promise; afterEnterEditMode?: (node: DataNode) => void; beforeExitEditMode?: (node: DataNode, value: string) => Promise; afterExitEditMode?: (node: DataNode, value: string) => void; }; export type DeleteableType = { show: boolean; beforeDelete?: (node: DataNode) => Promise; afterDelete?: (node: DataNode) => void; }; export type AddableType = { show: boolean | ((node: DataNode) => boolean); beforeAdd?: (parentNode: DataNode) => Promise; afterAdd?: (node: DataNode) => void; }; const RenderTitle = (props: { titleRender?: (node: DataNode) => React.ReactNode; nodeData: DataNode; onTitleChange?: (data: { key: string | number; value: string }) => void; onNodeDelete?: (data: { key: string | number; value: string }) => void; onAdd?: (data: { parentKey: string | number; data: DataNode }) => void; editable?: boolean | EditableType; deleteable?: boolean | DeleteableType; showIcon?: boolean; checkedKeys?: React.Key[]; icon?: any; addable?: boolean | AddableType; }) => { const { nodeData, onTitleChange, titleRender, onNodeDelete, onAdd, showIcon, checkedKeys, } = props; const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('btri-tree'); const [_isEdit, _setIsEdit] = useState(false); const [_inputValue, _setInputValue] = useState(''); useEffect(() => { if (typeof nodeData.title === 'string') { _setInputValue(nodeData.title); } }, [nodeData.title]); const isEdit = useMemo(() => { if (typeof props.editable === 'boolean') { return props.editable; } return props.editable?.show; }, [props.editable]); const isDelete = useMemo(() => { if (typeof props.deleteable === 'boolean') { return props.deleteable; } return props.deleteable?.show; }, [props.deleteable]); const isAdd = useMemo(() => { if (typeof props.addable === 'boolean') { return props.addable; } if (typeof props.addable?.show === 'function') { return props.addable.show(nodeData); } return props.addable?.show; }, [props.addable]); const defaultIcon = useMemo(() => { if (!nodeData.children) { return 'File'; } if (checkedKeys?.indexOf(nodeData.key) !== -1) { return 'FolderOpen'; } return 'FolderClose'; }, [nodeData, checkedKeys]); return (
{_isEdit && typeof nodeData.title === 'string' ? ( { _setInputValue(v.target.value); }} > ) : (
<> {showIcon && (nodeData.icon || props.icon || ( ))} {titleRender?.(nodeData) || nodeData.title}
)} {(isEdit || isDelete || isAdd) && (
{isAdd && ( { let newNodeData = null; // 添加子级需要请求完接口才知道key值,beforeAdd请求完接口后,返回数据,然后修改treeData if ( typeof props.addable !== 'boolean' && props.addable?.beforeAdd ) { newNodeData = await props.addable?.beforeAdd?.(nodeData); } if (newNodeData) { onAdd?.({ parentKey: nodeData.key, data: newNodeData, }); if (typeof props.addable !== 'boolean') { props.addable?.afterAdd?.(nodeData); } } }} > )} {isEdit && ( { let editHandle = true; if (typeof props.editable !== 'boolean') { if (_isEdit) { if (props.editable?.beforeExitEditMode) { editHandle = await props.editable?.beforeExitEditMode?.( nodeData, _inputValue, ); } } else { if (props.editable?.beforeEnterEditMode) { editHandle = await props.editable?.beforeEnterEditMode?.( nodeData, ); } } } if (editHandle) { onTitleChange?.({ key: nodeData.key, value: _inputValue as string, }); _setIsEdit((v) => !v); if (typeof props.editable !== 'boolean') { if (_isEdit) { props.editable?.afterExitEditMode?.( nodeData, _inputValue, ); } else { props.editable?.afterEnterEditMode?.(nodeData); } } } }} > )} {isDelete && ( { let deleteHandle = true; if ( typeof props.deleteable !== 'boolean' && props.deleteable?.beforeDelete ) { deleteHandle = await props.deleteable?.beforeDelete?.( nodeData, ); } if (deleteHandle) { onNodeDelete?.({ key: nodeData.key, value: _inputValue as string, }); if (typeof props.deleteable !== 'boolean') { props.deleteable?.afterDelete?.(nodeData); } } }} > )}
)}
); }; type ExtraTreeProps = { onTreeDataChange?: (data: DataNode[]) => void; editable?: boolean | EditableType; deleteable?: boolean | DeleteableType; addable?: boolean | AddableType; }; const Tree = (props: TreeProps & ExtraTreeProps) => { const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('btri-tree'); const { treeData } = props; const renderSwitchIcon = (switchProps: { expanded: boolean }) => { return ( ); }; const [_treeData, _setTreeData] = useState(treeData); useEffect(() => { _setTreeData(treeData); }, [treeData]); // 这里用中序遍历吧,改变节点 const _changeTreeData = ( key: string | number, value: string, oriTreeData: DataNode[], ) => { if (!oriTreeData) return; const resTreeData = oriTreeData; for (let i = 0; i < resTreeData.length; i++) { if (resTreeData[i].key === key) { resTreeData[i].title = value; return resTreeData; } const resChildren = _changeTreeData(key, value, oriTreeData[i].children); resTreeData[i].children = resChildren; } return resTreeData; }; // 删除节点,基本和改变节点一样 const _deleteTreeData = (key: string | number, oriTreeData: DataNode[]) => { if (!oriTreeData) return; const resTreeData = oriTreeData; for (let i = 0; i < resTreeData.length; i++) { if (resTreeData[i]?.key === key) { const res = resTreeData; res.splice(i, 1); return res; } const resChildren = _deleteTreeData(key, oriTreeData[i]?.children); resTreeData[i].children = resChildren; } return resTreeData; }; // 添加树的子级节点 const _addTreeData = ( parentKey: string | number, data: DataNode, oriTreeData: DataNode[], ) => { if (!oriTreeData) return; const resTreeData = oriTreeData; for (let i = 0; i < resTreeData.length; i++) { if (resTreeData[i].key === parentKey) { if (!resTreeData[i].children) { resTreeData[i].children = []; } resTreeData[i].children.push(data); return resTreeData; } const resChildren = _addTreeData( parentKey, data, oriTreeData[i].children, ); resTreeData[i].children = resChildren; } return resTreeData; }; useEffect(() => { props.onTreeDataChange?.(_treeData); }, [_treeData]); const [_checkedKeys, _setCheckedKeys] = useState([]); const _onExpand = new AOP< | React.Key[] | { node: EventDataNode; expanded: boolean; nativeEvent: MouseEvent; }, void >(props.onExpand) .after((keys) => { _setCheckedKeys(keys as React.Key[]); }) .getFunction(); const [_showKey, _setShowKey] = useState(null); return ( renderSwitchIcon(switchProps)} blockNode {...props} showIcon={false} virtual={false} treeData={_treeData} className={classNames(`${prefixCls}`, props.className)} style={{ width: '100%', ...props.style }} onExpand={_onExpand} titleRender={(nodeData) => { const isLeaf = typeof nodeData.isLeaf === 'boolean' ? nodeData.isLeaf : !nodeData.children || nodeData.children.length === 0; return ( { if ( (e.target as HTMLElement).clientWidth < (e.target as HTMLElement).scrollWidth ) { _setShowKey(nodeData.key); } else { _setShowKey(null); } }} onMouseLeave={(e) => { _setShowKey(null); }} className={`${prefixCls}-title-wrapper ${ isLeaf && !props.checkable ? 'is-leaf' : '' }`} > { _setTreeData((data) => _changeTreeData( v.key, v.value, JSON.parse(JSON.stringify(data)), ), ); }} onNodeDelete={(v) => { _setTreeData((data) => _deleteTreeData(v.key, JSON.parse(JSON.stringify(data))), ); }} onAdd={(v) => { _setTreeData((data) => _addTreeData( v.parentKey, v.data, JSON.parse(JSON.stringify(data)), ), ); }} titleRender={props.titleRender} editable={props.editable} deleteable={props.deleteable} addable={props.addable} showIcon={props.showIcon} checkedKeys={_checkedKeys} > ); }} > ); }; Tree.TreeNode = (props: TreeNodeProps) => { const { TreeNode } = OldTree; const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('btri-tree-node'); return ( ); }; export { Tree };