tree.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. import { NodeContext } from './nodeCtx';
  2. export interface TreeRaw {
  3. ID: string;
  4. parentID: string;
  5. seq: number;
  6. [props: string]: any;
  7. }
  8. export interface TreeNode extends TreeRaw {
  9. getCtx: () => NodeContext; // ctx对象改为getCtx(),因为handsontable数据循环引用会报错
  10. }
  11. export interface TreeIDMap {
  12. [propName: string]: TreeNode;
  13. }
  14. export interface CtxIDMap {
  15. [propName: string]: NodeContext;
  16. }
  17. export interface TreeParentMap {
  18. [propName: string]: TreeNode[];
  19. }
  20. export interface ParentMap {
  21. [propName: string]: (TreeRaw | TreeNode)[];
  22. }
  23. export interface UpdateItem {
  24. parentID?: string;
  25. seq?: number;
  26. [propName: string]: any;
  27. }
  28. export interface UpdateData {
  29. ID: string;
  30. update: UpdateItem;
  31. }
  32. export type None = null | undefined;
  33. export class Tree {
  34. // 原始数据,不经过排序的数据
  35. rawData: TreeNode[];
  36. // 按照树结构拼装好、排好序的数据。实际只是原始数据进行排序,内部元素跟原始数据内部元素的引用是一致的
  37. data: TreeNode[];
  38. rootID: string;
  39. // 默认初始顺序号。在对树结构进行操作后,没必要保证seq连号,只需保证正确排序就行,以减少需要更新的节点。
  40. readonly seqStartIndex = 0;
  41. // ID与原始数据条目映射
  42. IDMap: TreeIDMap;
  43. // parentID与多条同parentID的原始数据条目映射,parentMap内部数组要始终保持正确排序
  44. parentMap: TreeParentMap;
  45. // 节点上下文与节点ID映射
  46. ctxMap: CtxIDMap;
  47. constructor(rawData: TreeRaw[], rootID = '-1') {
  48. this.rootID = rootID;
  49. this.rawData = this.genNodeContext(rawData);
  50. this.rawData = Tree.sort(this.rawData);
  51. this.data = [];
  52. this.IDMap = {};
  53. this.parentMap = {};
  54. this.ctxMap = {};
  55. this.genMap(this.rawData);
  56. this.genData();
  57. }
  58. // 根据seq进行排序
  59. static sort<T extends TreeNode | TreeRaw>(nodes: T[]): T[] {
  60. return nodes.sort((a, b) => a.seq - b.seq);
  61. }
  62. // 获取节点的所有parentID
  63. static getParentIDList(nodes: TreeNode[]): Set<string> {
  64. const parentIDList: Set<string> = new Set();
  65. nodes.forEach(node => parentIDList.add(node.parentID));
  66. return parentIDList;
  67. }
  68. // 生成节点getCtx方法(由于handsontable循环引用bug,将ctx对象改为getCtx方法),用于获取节点上下文,挂载相关节点方法,TreeRaw转换为TreeNode
  69. private genNodeContext(rawData: TreeRaw[]): TreeNode[] {
  70. rawData.forEach(item => {
  71. item.getCtx = () => this.ctxMap[item.ID];
  72. });
  73. return rawData as TreeNode[];
  74. }
  75. // 生成映射表
  76. private genMap(data: TreeNode[]): void {
  77. data.forEach(item => {
  78. this.IDMap[item.ID] = item;
  79. this.ctxMap[item.ID] = new NodeContext(item, this);
  80. (
  81. this.parentMap[item.parentID] || (this.parentMap[item.parentID] = [])
  82. ).push(item);
  83. });
  84. }
  85. // 获取顶层原始数据
  86. getRoots(): TreeNode[] {
  87. return this.parentMap[this.rootID] || [];
  88. }
  89. // 生成按照树结构排好序的数据
  90. private genData(): void {
  91. // genData时不需要排序,因为rawData已经排好序
  92. const roots = this.getRoots();
  93. const pushNodesToData = (nodes: TreeNode[]): void => {
  94. nodes.forEach(node => {
  95. this.data.push(node);
  96. const children = this.parentMap[node.ID];
  97. if (children && children.length) {
  98. pushNodesToData(children);
  99. }
  100. });
  101. };
  102. pushNodesToData(roots);
  103. }
  104. // 重新生成排序序好的数据,不改变原引用
  105. private reGenData(): void {
  106. this.data.splice(0, this.data.length);
  107. this.genData();
  108. }
  109. // 将相关parentMap内的数据、data进行重新排序。新增、删除等操作进行完后,数据需要重新排序
  110. reSortData(nodes: TreeNode[]): void {
  111. const toSortList = Tree.getParentIDList(nodes);
  112. toSortList.forEach(parentID => {
  113. if (this.parentMap[parentID] && this.parentMap[parentID].length) {
  114. Tree.sort(this.parentMap[parentID]);
  115. }
  116. });
  117. this.reGenData();
  118. }
  119. // 根据parentID对parentMap进行重新排序,并重新排序生成data
  120. resortDataByID(parentIDList: string[]): void {
  121. parentIDList.forEach(parentID => {
  122. if (this.parentMap[parentID] && this.parentMap[parentID].length) {
  123. Tree.sort(this.parentMap[parentID]);
  124. }
  125. });
  126. this.reGenData();
  127. }
  128. // 查找ID节点
  129. find(ID: string): TreeNode | None {
  130. return this.IDMap[ID];
  131. }
  132. // 查找ID节点的父节点
  133. findParent(ID: string): TreeNode | None {
  134. const node = this.find(ID);
  135. if (!node) {
  136. return null;
  137. }
  138. return this.find(node.parentID);
  139. }
  140. // 查找ID节点的下一个节点
  141. findNext(ID: string): TreeNode | None {
  142. const node = this.find(ID);
  143. if (!node) {
  144. return null;
  145. }
  146. const nodes = this.parentMap[node.parentID];
  147. const nodeIndex = nodes.indexOf(node);
  148. if (nodeIndex < 0) {
  149. return null;
  150. }
  151. return nodes[nodeIndex + 1];
  152. }
  153. // 查找ID节点的上一个节点
  154. findPrev(ID: string): TreeNode | None {
  155. const node = this.find(ID);
  156. if (!node) {
  157. return null;
  158. }
  159. const nodes = this.parentMap[node.parentID];
  160. const nodeIndex = nodes.indexOf(node);
  161. if (nodeIndex < 0) {
  162. return null;
  163. }
  164. return nodes[nodeIndex - 1];
  165. }
  166. // 查询ID节点的子节点
  167. findChildren(ID: string): TreeNode[] {
  168. return this.parentMap[ID] || [];
  169. }
  170. updateValue(updateData: UpdateData[]): void {
  171. if (updateData.length) {
  172. updateData.forEach(updateItem => {
  173. const node = this.find(updateItem.ID);
  174. if (node) {
  175. Object.assign(node, updateItem.update);
  176. }
  177. });
  178. }
  179. }
  180. // 递归获取节点
  181. getNodesPosterity(treeNodes: TreeNode[], includeSelf = true): TreeNode[] {
  182. const rst: TreeNode[] = [];
  183. const pushNodes = (nodes: TreeNode[]): void => {
  184. nodes.forEach(node => {
  185. if (includeSelf || !treeNodes.includes(node)) {
  186. rst.push(node);
  187. }
  188. const children = this.findChildren(node.ID);
  189. if (children.length) {
  190. pushNodes(children);
  191. }
  192. });
  193. };
  194. pushNodes(treeNodes);
  195. return rst;
  196. }
  197. // 节选中点块是否是可操作的节点块,选中一批节点进行升降、上下移的时候可能需要用到这个判断。
  198. // 以第一个节点为基准,如果后续的节点深度均大于于于等于第一个节点的深度,且节点间是连续的,则此为可操作节点块
  199. isOperable(nodes: TreeNode[]): boolean {
  200. const baseDepth = nodes[0].getCtx().depth();
  201. for (let i = 0; i < nodes.length; i++) {
  202. const node = nodes[i];
  203. const depth = node.getCtx().depth();
  204. if (depth < baseDepth) {
  205. return false;
  206. }
  207. const nextRowNode = nodes[i + 1];
  208. if (
  209. nextRowNode &&
  210. node.getCtx().row() + 1 !== nextRowNode.getCtx().row()
  211. ) {
  212. return false;
  213. }
  214. }
  215. return true;
  216. }
  217. // 从节点块中获取相同深度的节点
  218. sameDepthNodes(nodes: TreeNode[], depth: number | None): TreeNode[] {
  219. if (!depth) {
  220. depth = nodes[0].getCtx().depth();
  221. }
  222. return nodes.filter(node => node.getCtx().depth() === depth);
  223. }
  224. // 准备插入节点,插入节点前,计算出需要更新的数据。(可能会直接覆盖插入数据的seq)
  225. // 可调用完此方法后,将需要更新、插入的数据提交至数据库,成功响应后调用插入节点更新缓存的方法
  226. prepareInsert(rawData: TreeRaw[]): UpdateData[] {
  227. const updateData: UpdateData[] = [];
  228. const insertParentMap: ParentMap = {};
  229. // 将相同父项的插入数据和已存在数据进行合并
  230. rawData.forEach(item => {
  231. if (typeof item.seq === 'undefined') {
  232. item.seq = this.seqStartIndex;
  233. }
  234. (
  235. insertParentMap[item.parentID] || (insertParentMap[item.parentID] = [])
  236. ).push(item);
  237. });
  238. const combineMap: ParentMap = {};
  239. Object.entries(insertParentMap).forEach(([parentID, insertItems]) => {
  240. const items = this.parentMap[parentID];
  241. combineMap[parentID] = [...insertParentMap[parentID]];
  242. if (items) {
  243. combineMap[parentID].push(...items);
  244. }
  245. // 重新排序
  246. const combineItems = combineMap[parentID];
  247. Tree.sort(combineItems);
  248. combineItems.forEach((item, index) => {
  249. // 插入数据重新赋值
  250. if (insertItems.includes(item)) {
  251. item.seq = index;
  252. } else if (item.seq !== index) {
  253. // 需要更新的原数据
  254. updateData.push({
  255. ID: item.ID,
  256. update: {
  257. seq: index,
  258. },
  259. });
  260. }
  261. });
  262. });
  263. return updateData;
  264. }
  265. // 插入节点数据
  266. insert(items: TreeRaw[], updateData: UpdateData[] = []): TreeNode[] {
  267. // 更新需要更新的节点(一般为更新seq)
  268. this.updateValue(updateData);
  269. // 建立映射、插入数据
  270. const nodes = this.genNodeContext(items);
  271. this.genMap(nodes);
  272. this.rawData.push(...nodes);
  273. // 排序
  274. this.reSortData(nodes);
  275. return nodes;
  276. }
  277. // 准备删除,返回所有需要删除的节点,包括嵌套节点
  278. prepareDelete(deleteNodes: TreeNode[]): TreeNode[] {
  279. return this.getNodesPosterity(deleteNodes);
  280. }
  281. /**
  282. * 删除节点
  283. * @param {TreeNode[]} treeNodes - 要删除的节点,不需要包含嵌套节点
  284. * @return {TreeNode[]} 返回被删除的节点
  285. */
  286. delete(treeNodes: TreeNode[]): TreeNode[] {
  287. const allDeletedNodes: TreeNode[] = [];
  288. // 递归删除节点
  289. const deleteNodes = (nodes: TreeNode[]): void => {
  290. // 删除映射、删除数据
  291. const toDels: { nodes: TreeNode[]; delNode: TreeNode }[] = [];
  292. nodes.forEach(node => {
  293. allDeletedNodes.push(node);
  294. delete this.IDMap[node.ID];
  295. delete this.ctxMap[node.ID];
  296. const children = this.parentMap[node.ID];
  297. delete this.parentMap[node.ID];
  298. const nodesInParentMap = this.parentMap[node.parentID];
  299. if (nodesInParentMap && nodesInParentMap.length) {
  300. const nIndex = nodesInParentMap.indexOf(node);
  301. if (nIndex >= 0) {
  302. toDels.push({ nodes: nodesInParentMap, delNode: node });
  303. }
  304. }
  305. const index = this.rawData.indexOf(node);
  306. if (index >= 0) {
  307. this.rawData.splice(index, 1);
  308. }
  309. if (children && children.length) {
  310. deleteNodes(children);
  311. }
  312. });
  313. // 删除parentMap的数据
  314. toDels.forEach(delItem => {
  315. const delIndex = delItem.nodes.indexOf(delItem.delNode);
  316. if (delIndex >= 0) {
  317. delItem.nodes.splice(delIndex, 1);
  318. }
  319. });
  320. };
  321. deleteNodes(treeNodes);
  322. // 排序
  323. this.reSortData(allDeletedNodes);
  324. return allDeletedNodes;
  325. }
  326. // IDList 返回所有需要删除的节点,包括嵌套节点
  327. prepareDeleteByID(IDList: string[]): TreeNode[] {
  328. const deleteNodes: TreeNode[] = [];
  329. IDList.forEach(ID => {
  330. const node = this.find(ID);
  331. if (node) {
  332. deleteNodes.push(node);
  333. }
  334. });
  335. return this.prepareDelete(deleteNodes);
  336. }
  337. /**
  338. * 根据ID删除节点
  339. * @param {string[]} IDList - 要删除的节点的ID列表(不包含嵌套节点ID)
  340. * @return {TreeNode[]} - 返回被删除的所有节点
  341. */
  342. deleteByID(IDList: string[]): TreeNode[] {
  343. const deleteNodes: TreeNode[] = [];
  344. IDList.forEach(ID => {
  345. const node = this.find(ID);
  346. if (node) {
  347. deleteNodes.push(node);
  348. }
  349. });
  350. this.delete(deleteNodes);
  351. return deleteNodes;
  352. }
  353. // 准备上移节点块(连续的兄弟节点),注意节点的seq可能不连号
  354. prepareUpMove(nodes: TreeNode[]): UpdateData[] {
  355. const updateData: UpdateData[] = [];
  356. const firstNode = nodes[0];
  357. const firstNodePrev = this.findPrev(firstNode.ID);
  358. if (!firstNodePrev) {
  359. return [];
  360. }
  361. let tempSeq = firstNodePrev.seq;
  362. nodes.forEach(node => {
  363. const orgSeq = node.seq;
  364. updateData.push({
  365. ID: node.ID,
  366. update: { seq: tempSeq },
  367. });
  368. tempSeq = orgSeq;
  369. });
  370. updateData.push({
  371. ID: firstNodePrev.ID,
  372. update: { seq: tempSeq },
  373. });
  374. return updateData;
  375. }
  376. // 准备下移节点块(连续的兄弟节点),注意节点的seq可能不连号
  377. prepareDownMove(nodes: TreeNode[]): UpdateData[] {
  378. const updateData: UpdateData[] = [];
  379. const lastNode = nodes[nodes.length - 1];
  380. const lastNodeNext = this.findNext(lastNode.ID);
  381. if (!lastNodeNext) {
  382. return [];
  383. }
  384. let tempSeq = lastNodeNext.seq;
  385. for (let i = nodes.length - 1; i >= 0; i--) {
  386. const node = nodes[i];
  387. const orgSeq = node.seq;
  388. updateData.push({
  389. ID: node.ID,
  390. update: { seq: tempSeq },
  391. });
  392. tempSeq = orgSeq;
  393. }
  394. updateData.push({
  395. ID: lastNodeNext.ID,
  396. update: { seq: tempSeq },
  397. });
  398. return updateData;
  399. }
  400. // 上下移
  401. move(nodes: TreeNode[], updateData: UpdateData[]): void {
  402. this.updateValue(updateData);
  403. this.reSortData(nodes);
  404. }
  405. // 准备升级节点块(连续的兄弟节点),不维护seq连号
  406. prepareUpLevel(nodes: TreeNode[]): UpdateData[] {
  407. const updateData: UpdateData[] = [];
  408. const firstNode = nodes[0];
  409. const lastNode = nodes[nodes.length - 1];
  410. const parent = this.findParent(firstNode.ID);
  411. if (!parent) {
  412. return [];
  413. }
  414. const baseSeq = parent.seq + 1;
  415. nodes.forEach((node, index) => {
  416. updateData.push({
  417. ID: node.ID,
  418. update: { parentID: parent.parentID, seq: baseSeq + index },
  419. });
  420. });
  421. const parentNextBrothers = parent.getCtx().nextBrothers();
  422. // 因为seq可能是不连号的,如果上移的最末节点seq,小于下一节点的seq,那就不更新所有下兄弟节点的seq,减少更新的数据量
  423. const lastNodeCurSeq = updateData[updateData.length - 1].update.seq;
  424. const firstBrohter = parentNextBrothers[0];
  425. if (lastNodeCurSeq && lastNodeCurSeq >= firstBrohter.seq) {
  426. parentNextBrothers.forEach((node, index) => {
  427. updateData.push({
  428. ID: node.ID,
  429. update: { seq: baseSeq + index + nodes.length },
  430. });
  431. });
  432. }
  433. // 最末节点的所有后兄弟节点,成为最末节点的子节点
  434. const lastNodeNextBrothers = lastNode.getCtx().nextBrothers();
  435. const lastNodeLastChild = lastNode.getCtx().lastChild();
  436. const lastNodeLastChildSeq = lastNodeLastChild ? lastNodeLastChild.seq : 0;
  437. lastNodeNextBrothers.forEach((node, index) => {
  438. updateData.push({
  439. ID: node.ID,
  440. update: { parentID: lastNode.ID, seq: index + lastNodeLastChildSeq },
  441. });
  442. });
  443. return updateData;
  444. }
  445. upLevel(nodes: TreeNode[], updateData: UpdateData[]): void {
  446. const firstNode = nodes[0];
  447. const lastNode = nodes[nodes.length - 1];
  448. if (!firstNode.getCtx().canUpLevel()) {
  449. return;
  450. }
  451. const orgParentID = firstNode.parentID;
  452. const orgBrothers = this.parentMap[orgParentID];
  453. const lastNodeNextBrothers = lastNode.getCtx().nextBrothers();
  454. orgBrothers.splice(
  455. orgBrothers.indexOf(firstNode),
  456. nodes.length + lastNodeNextBrothers.length
  457. );
  458. (this.parentMap[lastNode.ID] || (this.parentMap[lastNode.ID] = [])).push(
  459. ...lastNodeNextBrothers
  460. );
  461. this.updateValue(updateData);
  462. const newParentID = firstNode.parentID;
  463. this.parentMap[newParentID].push(...nodes);
  464. this.resortDataByID([orgParentID, newParentID, lastNode.ID]);
  465. }
  466. // 准备降级节点块(连续的兄弟节点),不维护seq连号
  467. prepareDownLevel(nodes: TreeNode[]): UpdateData[] {
  468. const updateData: UpdateData[] = [];
  469. const firstNode = nodes[0];
  470. const prevNode = this.findPrev(firstNode.ID);
  471. if (!prevNode) {
  472. return [];
  473. }
  474. // 节点块成为前节点的子节点
  475. const prevNodeLastChild = prevNode.getCtx().lastChild();
  476. const baseSeq = prevNodeLastChild
  477. ? prevNodeLastChild.seq + 1
  478. : this.seqStartIndex;
  479. nodes.forEach((node, index) => {
  480. updateData.push({
  481. ID: node.ID,
  482. update: { parentID: prevNode.ID, seq: baseSeq + index },
  483. });
  484. });
  485. return updateData;
  486. }
  487. downLevel(nodes: TreeNode[], updateData: UpdateData[]): void {
  488. const firstNode = nodes[0];
  489. if (!firstNode.getCtx().canDownLevel()) {
  490. return;
  491. }
  492. const prevNode = this.findPrev(firstNode.ID);
  493. if (!prevNode) {
  494. return;
  495. }
  496. const orgBrothers = this.parentMap[firstNode.parentID];
  497. orgBrothers.splice(orgBrothers.indexOf(firstNode), nodes.length);
  498. (this.parentMap[prevNode.ID] || (this.parentMap[prevNode.ID] = [])).push(
  499. ...nodes
  500. );
  501. this.updateValue(updateData);
  502. this.reGenData();
  503. }
  504. // 准备移动节点块(连续的兄弟节点),不维护seq连号
  505. prepareMoveTo(
  506. nodes: TreeNode[],
  507. parent: TreeNode | None,
  508. next: TreeNode | None
  509. ): UpdateData[] {
  510. const updateData: UpdateData[] = [];
  511. let prev: TreeNode | None;
  512. if (next) {
  513. prev = next.getCtx().prev();
  514. } else {
  515. prev = parent ? parent.getCtx().lastChild() : null;
  516. }
  517. const baseSeq = prev ? prev.seq + 1 : this.seqStartIndex;
  518. updateData.push(
  519. ...nodes.map((node, index) => ({
  520. ID: node.ID,
  521. update: {
  522. parentID: (parent && parent.ID) || this.rootID,
  523. seq: baseSeq + index,
  524. },
  525. }))
  526. );
  527. const curBaseSeq = baseSeq + nodes.length;
  528. const nextBrothers = prev ? prev.getCtx().nextBrothers() : [];
  529. updateData.push(
  530. ...nextBrothers.map((node, index) => ({
  531. ID: node.ID,
  532. update: {
  533. seq: curBaseSeq + index,
  534. },
  535. }))
  536. );
  537. return updateData;
  538. }
  539. moveTo(
  540. nodes: TreeNode[],
  541. parent: TreeNode | None,
  542. updateData: UpdateData[]
  543. ): void {
  544. const firstNode = nodes[0];
  545. const newParentID = parent ? parent.ID : this.rootID;
  546. const orgParentID = firstNode.parentID;
  547. const orgBrothers = this.parentMap[orgParentID];
  548. orgBrothers.splice(orgBrothers.indexOf(firstNode), nodes.length);
  549. (this.parentMap[newParentID] || (this.parentMap[newParentID] = [])).push(
  550. ...nodes
  551. );
  552. this.updateValue(updateData);
  553. this.resortDataByID([orgParentID, newParentID]);
  554. }
  555. // 清除所有节点(不变更data引用)
  556. clear(): void {
  557. this.rawData.splice(0, this.rawData.length);
  558. this.data.splice(0, this.data.length);
  559. Object.keys(this.parentMap).forEach(key => delete this.parentMap[key]);
  560. Object.keys(this.IDMap).forEach(key => delete this.IDMap[key]);
  561. Object.keys(this.ctxMap).forEach(key => delete this.ctxMap[key]);
  562. }
  563. }