EditableTree.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. import React, { useState } from "react";
  2. import { useEffect } from "react";
  3. import { message, Tree } from "antd";
  4. import type { DataNode, TreeProps } from "antd/es/tree";
  5. import { Key } from "antd/lib/table/interface";
  6. import { DeleteOutlined, SaveOutlined } from "@ant-design/icons";
  7. import { FileAddOutlined, LinkOutlined } from "@ant-design/icons";
  8. import { Button, Divider, Space } from "antd";
  9. import { useIntl } from "react-intl";
  10. import EditableTreeNode from "./EditableTreeNode";
  11. import { randomString } from "../../utils";
  12. export interface TreeNodeData {
  13. key: string;
  14. id: string;
  15. title: string | React.ReactNode;
  16. icon?: React.ReactNode;
  17. children: TreeNodeData[];
  18. deletedAt?: string | null;
  19. level: number;
  20. }
  21. export type ListNodeData = {
  22. key: string;
  23. title: string | React.ReactNode;
  24. level: number;
  25. children?: number;
  26. deletedAt?: string | null;
  27. };
  28. var tocActivePath: TreeNodeData[] = [];
  29. function tocGetTreeData(articles: ListNodeData[], active = "") {
  30. let treeData = [];
  31. let treeParents = [];
  32. let rootNode: TreeNodeData = {
  33. key: randomString(),
  34. id: "0",
  35. title: "root",
  36. level: 0,
  37. children: [],
  38. };
  39. treeData.push(rootNode);
  40. let lastInsNode: TreeNodeData = rootNode;
  41. let iCurrLevel = 0;
  42. let keys: string[] = [];
  43. for (let index = 0; index < articles.length; index++) {
  44. const element = articles[index];
  45. let newNode: TreeNodeData = {
  46. key: randomString(),
  47. id: element.key,
  48. title: element.title,
  49. children: [],
  50. icon: keys.includes(element.key) ? <LinkOutlined /> : undefined,
  51. level: element.level,
  52. deletedAt: element.deletedAt,
  53. };
  54. if (!keys.includes(element.key)) {
  55. keys.push(element.key);
  56. }
  57. /*
  58. if (active == element.article) {
  59. newNode["extraClasses"] = "active";
  60. }
  61. */
  62. if (newNode.level > iCurrLevel) {
  63. //新的层级比较大,为上一个的子目录
  64. treeParents.push(lastInsNode);
  65. lastInsNode.children.push(newNode);
  66. } else if (newNode.level === iCurrLevel) {
  67. //目录层级相同,为平级
  68. treeParents[treeParents.length - 1].children.push(newNode);
  69. } else {
  70. // 小于 挂在上一个层级
  71. while (treeParents.length > 1) {
  72. treeParents.pop();
  73. if (treeParents[treeParents.length - 1].level < newNode.level) {
  74. break;
  75. }
  76. }
  77. treeParents[treeParents.length - 1].children.push(newNode);
  78. }
  79. lastInsNode = newNode;
  80. iCurrLevel = newNode.level;
  81. if (active === element.key) {
  82. tocActivePath = [];
  83. for (let index = 1; index < treeParents.length; index++) {
  84. tocActivePath.push(treeParents[index]);
  85. }
  86. }
  87. }
  88. return treeData[0].children;
  89. }
  90. function treeToList(treeNode: TreeNodeData[]): ListNodeData[] {
  91. let iTocTreeCurrLevel = 1;
  92. let arrTocTree: ListNodeData[] = [];
  93. for (const iterator of treeNode) {
  94. getTreeNodeData(iterator);
  95. }
  96. function getTreeNodeData(node: TreeNodeData) {
  97. let children = 0;
  98. if (typeof node.children != "undefined") {
  99. children = node.children.length;
  100. }
  101. arrTocTree.push({
  102. key: node.id,
  103. title: node.title,
  104. level: iTocTreeCurrLevel,
  105. children: children,
  106. deletedAt: node.deletedAt,
  107. });
  108. if (children > 0) {
  109. iTocTreeCurrLevel++;
  110. for (const iterator of node.children) {
  111. getTreeNodeData(iterator);
  112. }
  113. iTocTreeCurrLevel--;
  114. }
  115. }
  116. return arrTocTree;
  117. }
  118. interface IWidget {
  119. treeData: ListNodeData[];
  120. addFileButton?: React.ReactNode;
  121. addOnArticle?: TreeNodeData;
  122. updatedNode?: TreeNodeData;
  123. onChange?: Function;
  124. onSelect?: Function;
  125. onSave?: Function;
  126. onAddFile?: Function;
  127. onAppend?: Function;
  128. onNodeEdit?: Function;
  129. onTitleClick?: Function;
  130. }
  131. const EditableTreeWidget = ({
  132. treeData,
  133. addFileButton,
  134. addOnArticle,
  135. updatedNode,
  136. onChange,
  137. onSelect,
  138. onSave,
  139. onAddFile,
  140. onAppend,
  141. onNodeEdit,
  142. onTitleClick,
  143. }: IWidget) => {
  144. const intl = useIntl();
  145. const [gData, setGData] = useState<TreeNodeData[]>([]);
  146. const [listTreeData, setListTreeData] = useState<ListNodeData[]>();
  147. const [keys, setKeys] = useState<Key>("");
  148. useEffect(() => {
  149. if (typeof onChange !== "undefined") {
  150. onChange(listTreeData);
  151. }
  152. }, [listTreeData]);
  153. useEffect(() => {
  154. //找到节点并更新
  155. if (typeof updatedNode === "undefined") {
  156. return;
  157. }
  158. const update = (_node: TreeNodeData[]) => {
  159. _node.forEach((value, index, array) => {
  160. if (value.id === updatedNode.id) {
  161. array[index].title = updatedNode.title;
  162. console.log("key found");
  163. return;
  164. } else {
  165. update(array[index].children);
  166. }
  167. return;
  168. });
  169. };
  170. const newTree = [...gData];
  171. update(newTree);
  172. setGData(newTree);
  173. const list = treeToList(newTree);
  174. setListTreeData(list);
  175. }, [updatedNode]);
  176. const appendNode = (key: string, node: TreeNodeData) => {
  177. console.log("key", key);
  178. const append = (_node: TreeNodeData[]) => {
  179. _node.forEach((value, index, array) => {
  180. if (value.key === key) {
  181. array[index].children.push(node);
  182. console.log("key found");
  183. return;
  184. } else {
  185. append(array[index].children);
  186. }
  187. return;
  188. });
  189. };
  190. const newTree = [...gData];
  191. append(newTree);
  192. setGData(newTree);
  193. const list = treeToList(newTree);
  194. setListTreeData(list);
  195. };
  196. useEffect(() => {
  197. if (typeof addOnArticle === "undefined") {
  198. return;
  199. }
  200. console.log("add ", addOnArticle);
  201. const newTreeData = [...gData, addOnArticle];
  202. setGData(newTreeData);
  203. const list = treeToList(newTreeData);
  204. setListTreeData(list);
  205. }, [addOnArticle]);
  206. useEffect(() => {
  207. const data = tocGetTreeData(treeData);
  208. console.log("tree data", data);
  209. setGData(data);
  210. }, [treeData]);
  211. const onDragEnter: TreeProps["onDragEnter"] = (info) => {
  212. console.log(info);
  213. // expandedKeys 需要受控时设置
  214. // setExpandedKeys(info.expandedKeys)
  215. };
  216. const onDrop: TreeProps["onDrop"] = (info) => {
  217. console.log(info);
  218. const dropKey = info.node.key;
  219. const dragKey = info.dragNode.key;
  220. const dropPos = info.node.pos.split("-");
  221. const dropPosition =
  222. info.dropPosition - Number(dropPos[dropPos.length - 1]);
  223. const loop = (
  224. data: DataNode[],
  225. key: React.Key,
  226. callback: (node: DataNode, i: number, data: DataNode[]) => void
  227. ) => {
  228. for (let i = 0; i < data.length; i++) {
  229. if (data[i].key === key) {
  230. return callback(data[i], i, data);
  231. }
  232. if (data[i].children) {
  233. loop(data[i].children!, key, callback);
  234. }
  235. }
  236. };
  237. const data = [...gData];
  238. // Find dragObject
  239. let dragObj: DataNode;
  240. loop(data, dragKey, (item, index, arr) => {
  241. arr.splice(index, 1);
  242. dragObj = item;
  243. });
  244. if (!info.dropToGap) {
  245. // Drop on the content
  246. loop(data, dropKey, (item) => {
  247. item.children = item.children || [];
  248. // where to insert 示例添加到头部,可以是随意位置
  249. item.children.unshift(dragObj);
  250. });
  251. } else if (
  252. ((info.node as any).props.children || []).length > 0 && // Has children
  253. (info.node as any).props.expanded && // Is expanded
  254. dropPosition === 1 // On the bottom gap
  255. ) {
  256. loop(data, dropKey, (item) => {
  257. item.children = item.children || [];
  258. // where to insert 示例添加到头部,可以是随意位置
  259. item.children.unshift(dragObj);
  260. // in previous version, we use item.children.push(dragObj) to insert the
  261. // item to the tail of the children
  262. });
  263. } else {
  264. let ar: DataNode[] = [];
  265. let i: number;
  266. loop(data, dropKey, (_item, index, arr) => {
  267. ar = arr;
  268. i = index;
  269. });
  270. if (dropPosition === -1) {
  271. ar.splice(i!, 0, dragObj!);
  272. } else {
  273. ar.splice(i! + 1, 0, dragObj!);
  274. }
  275. }
  276. setGData(data);
  277. const list = treeToList(data);
  278. setListTreeData(list);
  279. };
  280. return (
  281. <>
  282. <Space>
  283. {addFileButton}
  284. <Button
  285. icon={<FileAddOutlined />}
  286. onClick={async () => {
  287. if (typeof onAppend !== "undefined") {
  288. const newNode = await onAppend({
  289. key: "",
  290. title: "",
  291. children: [],
  292. level: 0,
  293. });
  294. console.log("newNode", newNode);
  295. if (newNode) {
  296. const append = [...gData, newNode];
  297. setGData(append);
  298. const list = treeToList(append);
  299. setListTreeData(list);
  300. return true;
  301. } else {
  302. message.error("添加失败");
  303. return false;
  304. }
  305. } else {
  306. return false;
  307. }
  308. }}
  309. >
  310. {intl.formatMessage({ id: "buttons.create" })}
  311. </Button>
  312. <Button
  313. icon={<DeleteOutlined />}
  314. danger
  315. onClick={() => {
  316. const delTree = (node: TreeNodeData[]): boolean => {
  317. for (let index = 0; index < node.length; index++) {
  318. if (node[index].key === keys) {
  319. node.splice(index, 1);
  320. return true;
  321. } else {
  322. const cf = delTree(node[index].children);
  323. if (cf) {
  324. return cf;
  325. }
  326. }
  327. }
  328. return false;
  329. };
  330. const tmp = [...gData];
  331. const find = delTree(tmp);
  332. console.log("delete", keys, find, tmp);
  333. setGData(tmp);
  334. const list = treeToList(tmp);
  335. setListTreeData(list);
  336. }}
  337. >
  338. {intl.formatMessage({ id: "buttons.remove" })}
  339. </Button>
  340. <Button
  341. icon={<SaveOutlined />}
  342. onClick={() => {
  343. if (typeof onSave !== "undefined") {
  344. onSave(listTreeData);
  345. }
  346. }}
  347. type="primary"
  348. >
  349. {intl.formatMessage({ id: "buttons.save" })}
  350. </Button>
  351. </Space>
  352. <Divider></Divider>
  353. <Tree
  354. showIcon
  355. rootClassName="draggable-tree"
  356. draggable
  357. blockNode
  358. onDragEnter={onDragEnter}
  359. onDrop={onDrop}
  360. onSelect={(selectedKeys: Key[]) => {
  361. if (selectedKeys.length > 0) {
  362. setKeys(selectedKeys[0]);
  363. } else {
  364. setKeys("");
  365. }
  366. if (typeof onSelect !== "undefined") {
  367. onSelect(selectedKeys);
  368. }
  369. }}
  370. treeData={gData}
  371. titleRender={(node: TreeNodeData) => {
  372. return (
  373. <EditableTreeNode
  374. node={node}
  375. onEdit={() => {
  376. if (typeof onNodeEdit !== "undefined") {
  377. onNodeEdit(node.id);
  378. }
  379. }}
  380. onAdd={async () => {
  381. if (typeof onAppend !== "undefined") {
  382. const newNode = await onAppend(node);
  383. console.log("newNode", newNode);
  384. if (newNode) {
  385. appendNode(node.key, newNode);
  386. return true;
  387. } else {
  388. message.error("添加失败");
  389. return false;
  390. }
  391. } else {
  392. return false;
  393. }
  394. }}
  395. onTitleClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
  396. if (typeof onTitleClick !== "undefined") {
  397. onTitleClick(e, node);
  398. }
  399. }}
  400. />
  401. );
  402. }}
  403. />
  404. </>
  405. );
  406. };
  407. export default EditableTreeWidget;