EditableTree.tsx 12 KB

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