index.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import React, { ReactNode, useEffect, useState, MouseEvent } from 'react'
  2. import { Input, Spin, Image, Popconfirm } from 'antd';
  3. import { PlusOutlined } from '@ant-design/icons';
  4. import DirectoryTree from './components/DirectoryTree';
  5. import NodeTextArea from './components/NodeTextArea';
  6. import './index.less'
  7. import DelActionIcon from './images/del14px.png';
  8. import EditActionIcon from './images/edit14px.png';
  9. import AddActionIcon from './images/add.png';
  10. import FileIcon from './images/file.png';
  11. // const { DirectoryTree } = Tree;
  12. const { Search } = Input;
  13. //树形文件组件
  14. enum Types { 'add', 'del', 'edit', 'search' };
  15. type TypeVals = keyof typeof Types;
  16. type MccsFileTreeProps = {
  17. onSelectHandle?: (data: MccsFileTree.childTree) => void, //选中回调
  18. actionHandle?: ({ type, data }: { type: string, data: MccsFileTree.childTree }) => void, //操作回调,包括编辑/删除/新增/搜索
  19. searchHandle?: (val: any) => void,
  20. switcherIcon?: ReactNode,
  21. treeData: MccsFileTree.childTree[] | [],
  22. defaultSelected?: string, //传id
  23. formContent?: ReactNode,
  24. editable?: boolean,
  25. renderFilter?: () => ReactNode,//添加自定义过滤操作
  26. }
  27. const MccsFileTree: React.FC<MccsFileTreeProps> = (props) => {
  28. const { treeData, onSelectHandle, actionHandle, defaultSelected, searchHandle, editable, renderFilter } = props;
  29. const [isLoading, setIsLoading] = useState(true);
  30. //当前选中的
  31. const [currentActivedIndex, setcurrentActivedIndex] = useState('');
  32. const actionFunc = (event: MouseEvent | undefined, type: TypeVals, data: MccsFileTree.childTree) => {
  33. event?.stopPropagation();
  34. actionHandle && actionHandle({ type, data: data });
  35. }
  36. //操作
  37. const Action = (props: MccsFileTree.childTree) => {
  38. const { isLeaf } = props;
  39. return (
  40. <div className='action'>
  41. {
  42. !isLeaf && ( //非叶子结点才可以新增
  43. <>
  44. <Image width={15} preview={false} onClick={(e) => actionFunc(e, 'add', props)} src={AddActionIcon} />
  45. <div style={{ width: 5 }}></div>
  46. </>
  47. )
  48. }
  49. <Image width={15} preview={false} onClick={(e) => actionFunc(e, 'edit', props)} src={EditActionIcon} />
  50. <div style={{ width: 5 }}></div>
  51. <Popconfirm
  52. title="是否确定删除?"
  53. onConfirm={(e) => { actionFunc(e, 'del', props) }}
  54. okText="确定"
  55. cancelText="取消"
  56. >
  57. <Image width={15} preview={false} onClick={e => e.stopPropagation()} src={DelActionIcon} />
  58. </Popconfirm>
  59. </div>
  60. )
  61. }
  62. //叶子结点结构
  63. const TreeNode = (nodeProps: MccsFileTree.childTree) => {
  64. const { title, id, code, ...rest } = nodeProps;
  65. return (
  66. <div className={currentActivedIndex == id ? 'treeNode actived' : 'treeNode'} onClick={onSelectHandle ? () => {
  67. setcurrentActivedIndex(id);
  68. onSelectHandle(nodeProps)
  69. } : () => { setcurrentActivedIndex(id); }}>
  70. <div style={{ display: 'flex', marginRight: 5, justifyContent: 'center', alignItems: 'center' }} ><Image width={15} preview={false} src={FileIcon} /> </div>
  71. {/* <div className='treeNodeInner'>{`${code} ${title}`}</div> */}
  72. <NodeTextArea className="treeNodeInner" text={`${code} ${title}`}></NodeTextArea>
  73. {(currentActivedIndex == id && editable) && <Action {...nodeProps} /> /*点击展示操作项*/}
  74. </div>
  75. )
  76. }
  77. //递归树形结构
  78. const loop = (data: MccsFileTree.childTree, i: number) => {
  79. const { title, children = [], ...restProps } = data;
  80. // const node = <div className="node">{`${restProps.code} ${title}`}</div>;
  81. const node = <NodeTextArea className="node" text={`${restProps.code} ${title}`}></NodeTextArea>
  82. if (data.isLeaf) {
  83. return <TreeNode key={data.id} currentActivedIndex={currentActivedIndex} setcurrentActivedIndex={setcurrentActivedIndex} title={title} {...restProps} />
  84. }
  85. return (
  86. <DirectoryTree key={data.id} currentActivedIndex={currentActivedIndex} setcurrentActivedIndex={setcurrentActivedIndex}
  87. nodeLabel={node} {...data} onClick={() => onSelectHandle ? onSelectHandle(data) : () => { }}
  88. action={editable && <Action {...data} />} defaultCollapsed={!(currentActivedIndex==data.id)}
  89. >
  90. {
  91. children.map((item, index) => {
  92. if (item.isLeaf) {
  93. return <TreeNode currentActivedIndex={currentActivedIndex} setcurrentActivedIndex={setcurrentActivedIndex} key={index} {...item} />
  94. } else {
  95. return loop({...item,isLeaf:item.leaf}, index);
  96. }
  97. })
  98. }
  99. </DirectoryTree>
  100. )
  101. }
  102. const deepGetVal = (dataToDeep: any[], key: string, subArr: string, findVal: number) => {
  103. // console.log({dataToDeep,key,subArr});
  104. let needVal = {};
  105. function looper(dataToDeep: any[], key: string, subArr: string) {
  106. dataToDeep.forEach(item => {
  107. if (item[key] == findVal) {
  108. needVal = item;
  109. return;
  110. }
  111. if (item[subArr] && item[subArr].length > 0) {
  112. looper(item[subArr], key, subArr);
  113. }
  114. });
  115. }
  116. looper(dataToDeep, key, subArr);
  117. return needVal
  118. }
  119. useEffect(() => {
  120. //当设置默认激活项时触发
  121. if (defaultSelected != currentActivedIndex) {
  122. setcurrentActivedIndex(defaultSelected ? defaultSelected : '');
  123. const result = deepGetVal(treeData,'id','children',Number(defaultSelected));
  124. onSelectHandle&&onSelectHandle(result as MccsFileTree.childTree);
  125. }
  126. }, [defaultSelected]);
  127. useEffect(() => {
  128. if (treeData.length > 0) {
  129. setIsLoading(false);
  130. }
  131. }, [treeData]);
  132. return (
  133. <div className='fileTree'>
  134. <div className="searchBar">
  135. {
  136. editable && (
  137. <div className="add" onClick={e => actionFunc(e, 'add', {
  138. title: '',
  139. id: `0`,
  140. isLeaf: false,
  141. code: `${new Date().getTime()}`,
  142. children: []
  143. })}><PlusOutlined /></div>
  144. )
  145. }
  146. <Search placeholder='请输入' className="inputArea" allowClear onSearch={(val, e) => searchHandle && searchHandle(val)} />
  147. </div>
  148. {
  149. renderFilter && (
  150. <div className='filter'>
  151. {
  152. renderFilter()
  153. }
  154. </div>
  155. )
  156. }
  157. <div className='treeContainer'>
  158. {
  159. isLoading ? <div className='spinWrap'><Spin delay={500} /></div> : (
  160. <>
  161. {treeData.map((node, i) => {
  162. return loop({...node,isLeaf:node.leaf}, i);
  163. })}
  164. </>
  165. )
  166. }
  167. </div>
  168. </div>
  169. );
  170. };
  171. export default MccsFileTree