tree.ts 18 KB

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