tree.ts 20 KB

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