EditableTree.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import React, { useState } from "react";
  2. import { useEffect } from "react";
  3. import { Tree } from "antd";
  4. import type { DataNode, TreeProps } from "antd/es/tree";
  5. import { Key } from "antd/lib/table/interface";
  6. import {
  7. FileAddOutlined,
  8. DeleteOutlined,
  9. SaveOutlined,
  10. } from "@ant-design/icons";
  11. import { Button, Divider, Space } from "antd";
  12. interface TreeNodeData {
  13. key: string;
  14. title: string;
  15. children: TreeNodeData[];
  16. level: number;
  17. }
  18. export type ListNodeData = {
  19. key: string;
  20. title: string;
  21. level: number;
  22. children?: number;
  23. };
  24. var tocActivePath: TreeNodeData[] = [];
  25. function tocGetTreeData(articles: ListNodeData[], active = "") {
  26. let treeData = [];
  27. let treeParents = [];
  28. let rootNode: TreeNodeData = {
  29. key: "0",
  30. title: "root",
  31. level: 0,
  32. children: [],
  33. };
  34. treeData.push(rootNode);
  35. let lastInsNode: TreeNodeData = rootNode;
  36. let iCurrLevel = 0;
  37. for (let index = 0; index < articles.length; index++) {
  38. const element = articles[index];
  39. let newNode: TreeNodeData = {
  40. key: element.key,
  41. title: element.title,
  42. children: [],
  43. level: element.level,
  44. };
  45. /*
  46. if (active == element.article) {
  47. newNode["extraClasses"] = "active";
  48. }
  49. */
  50. if (newNode.level > iCurrLevel) {
  51. //新的层级比较大,为上一个的子目录
  52. treeParents.push(lastInsNode);
  53. lastInsNode.children.push(newNode);
  54. } else if (newNode.level === iCurrLevel) {
  55. //目录层级相同,为平级
  56. treeParents[treeParents.length - 1].children.push(newNode);
  57. } else {
  58. // 小于 挂在上一个层级
  59. while (treeParents.length > 1) {
  60. treeParents.pop();
  61. if (treeParents[treeParents.length - 1].level < newNode.level) {
  62. break;
  63. }
  64. }
  65. treeParents[treeParents.length - 1].children.push(newNode);
  66. }
  67. lastInsNode = newNode;
  68. iCurrLevel = newNode.level;
  69. if (active === element.key) {
  70. tocActivePath = [];
  71. for (let index = 1; index < treeParents.length; index++) {
  72. tocActivePath.push(treeParents[index]);
  73. }
  74. }
  75. }
  76. return treeData[0].children;
  77. }
  78. function treeToList(treeNode: TreeNodeData[]): ListNodeData[] {
  79. let iTocTreeCurrLevel = 1;
  80. let arrTocTree: ListNodeData[] = [];
  81. for (const iterator of treeNode) {
  82. getTreeNodeData(iterator);
  83. }
  84. function getTreeNodeData(node: TreeNodeData) {
  85. let children = 0;
  86. if (typeof node.children != "undefined") {
  87. children = node.children.length;
  88. }
  89. arrTocTree.push({
  90. key: node.key,
  91. title: node.title,
  92. level: iTocTreeCurrLevel,
  93. children: children,
  94. });
  95. if (children > 0) {
  96. iTocTreeCurrLevel++;
  97. for (const iterator of node.children) {
  98. getTreeNodeData(iterator);
  99. }
  100. iTocTreeCurrLevel--;
  101. }
  102. }
  103. return arrTocTree;
  104. }
  105. interface IWidgetEditableTree {
  106. treeData: ListNodeData[];
  107. onChange?: Function;
  108. onSelect?: Function;
  109. onSave?: Function;
  110. }
  111. const Widget = ({
  112. treeData,
  113. onChange,
  114. onSelect,
  115. onSave,
  116. }: IWidgetEditableTree) => {
  117. const [gData, setGData] = useState<TreeNodeData[]>([]);
  118. const [listTreeData, setListTreeData] = useState<ListNodeData[]>();
  119. const [keys, setKeys] = useState<Key>("");
  120. useEffect(() => {
  121. const data = tocGetTreeData(treeData);
  122. console.log("tree data", data);
  123. setGData(data);
  124. }, [treeData]);
  125. const onDragEnter: TreeProps["onDragEnter"] = (info) => {
  126. console.log(info);
  127. // expandedKeys 需要受控时设置
  128. // setExpandedKeys(info.expandedKeys)
  129. };
  130. const onDrop: TreeProps["onDrop"] = (info) => {
  131. console.log(info);
  132. const dropKey = info.node.key;
  133. const dragKey = info.dragNode.key;
  134. const dropPos = info.node.pos.split("-");
  135. const dropPosition =
  136. info.dropPosition - Number(dropPos[dropPos.length - 1]);
  137. const loop = (
  138. data: DataNode[],
  139. key: React.Key,
  140. callback: (node: DataNode, i: number, data: DataNode[]) => void
  141. ) => {
  142. for (let i = 0; i < data.length; i++) {
  143. if (data[i].key === key) {
  144. return callback(data[i], i, data);
  145. }
  146. if (data[i].children) {
  147. loop(data[i].children!, key, callback);
  148. }
  149. }
  150. };
  151. const data = [...gData];
  152. // Find dragObject
  153. let dragObj: DataNode;
  154. loop(data, dragKey, (item, index, arr) => {
  155. arr.splice(index, 1);
  156. dragObj = item;
  157. });
  158. if (!info.dropToGap) {
  159. // Drop on the content
  160. loop(data, dropKey, (item) => {
  161. item.children = item.children || [];
  162. // where to insert 示例添加到头部,可以是随意位置
  163. item.children.unshift(dragObj);
  164. });
  165. } else if (
  166. ((info.node as any).props.children || []).length > 0 && // Has children
  167. (info.node as any).props.expanded && // Is expanded
  168. dropPosition === 1 // On the bottom gap
  169. ) {
  170. loop(data, dropKey, (item) => {
  171. item.children = item.children || [];
  172. // where to insert 示例添加到头部,可以是随意位置
  173. item.children.unshift(dragObj);
  174. // in previous version, we use item.children.push(dragObj) to insert the
  175. // item to the tail of the children
  176. });
  177. } else {
  178. let ar: DataNode[] = [];
  179. let i: number;
  180. loop(data, dropKey, (_item, index, arr) => {
  181. ar = arr;
  182. i = index;
  183. });
  184. if (dropPosition === -1) {
  185. ar.splice(i!, 0, dragObj!);
  186. } else {
  187. ar.splice(i! + 1, 0, dragObj!);
  188. }
  189. }
  190. setGData(data);
  191. if (typeof onChange !== "undefined") {
  192. const list = treeToList(data);
  193. onChange(list);
  194. setListTreeData(list);
  195. }
  196. };
  197. return (
  198. <>
  199. <Space>
  200. <Button icon={<FileAddOutlined />}>添加</Button>
  201. <Button
  202. icon={<DeleteOutlined />}
  203. danger
  204. onClick={() => {
  205. const delTree = (node: TreeNodeData[]): boolean => {
  206. for (let index = 0; index < node.length; index++) {
  207. if (node[index].key === keys) {
  208. node.splice(index, 1);
  209. return true;
  210. } else {
  211. const cf = delTree(node[index].children);
  212. if (cf) {
  213. return cf;
  214. }
  215. }
  216. }
  217. return false;
  218. };
  219. const tmp = [...gData];
  220. const find = delTree(tmp);
  221. console.log("delete", keys, find, tmp);
  222. setGData(tmp);
  223. }}
  224. >
  225. 删除
  226. </Button>
  227. <Button
  228. icon={<SaveOutlined />}
  229. onClick={() => {
  230. if (typeof onSave !== "undefined") {
  231. onSave(listTreeData);
  232. }
  233. }}
  234. type="primary"
  235. >
  236. 保存
  237. </Button>
  238. </Space>
  239. <Divider></Divider>
  240. <Tree
  241. rootClassName="draggable-tree"
  242. draggable
  243. blockNode
  244. onDragEnter={onDragEnter}
  245. onDrop={onDrop}
  246. onSelect={(selectedKeys: Key[]) => {
  247. if (selectedKeys.length > 0) {
  248. setKeys(selectedKeys[0]);
  249. } else {
  250. setKeys("");
  251. }
  252. console.log(selectedKeys);
  253. if (typeof onSelect !== "undefined") {
  254. onSelect(selectedKeys);
  255. }
  256. }}
  257. treeData={gData}
  258. />
  259. </>
  260. );
  261. };
  262. export default Widget;