treeHelper.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. interface TreeHelperConfig {
  2. id: string;
  3. pid: string;
  4. children: string;
  5. callback: Fn;
  6. fullNameSplit: string;
  7. }
  8. const DEFAULT_CONFIG: TreeHelperConfig = {
  9. id: 'id',
  10. pid: 'pId',
  11. children: 'children',
  12. callback: () => {},
  13. fullNameSplit: '/',
  14. };
  15. const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config);
  16. // tree from list
  17. export function listToTree<T = any>(list?: any[], config: Partial<TreeHelperConfig> = {}): T[] {
  18. const conf = getConfig(config) as TreeHelperConfig;
  19. const nodeMap = new Map();
  20. const result: T[] = [];
  21. const { id, pid, children, fullNameSplit } = conf;
  22. for (const node of list || []) {
  23. node[children] = node[children] || [];
  24. nodeMap.set(node[id], node);
  25. }
  26. for (const node of list || []) {
  27. const parent = nodeMap.get(node[pid]);
  28. (parent ? parent[children] : result).push(node);
  29. if (node.name) {
  30. node._name = node.name;
  31. if (parent && parent._fullName) {
  32. node._fullName = parent._fullName + fullNameSplit + node._name;
  33. } else {
  34. node._fullName = node._name;
  35. }
  36. }
  37. conf.callback(parent, node);
  38. }
  39. return result;
  40. }
  41. export function treeToList<T = any>(tree: any, config: Partial<TreeHelperConfig> = {}): T {
  42. config = getConfig(config);
  43. const { children } = config;
  44. const result: any = [...tree];
  45. for (let i = 0; i < result.length; i++) {
  46. if (!result[i][children!]) continue;
  47. result.splice(i + 1, 0, ...result[i][children!]);
  48. }
  49. return result;
  50. }
  51. export function findNode<T = any>(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}): T | null {
  52. config = getConfig(config);
  53. const { children } = config;
  54. const list = [...tree];
  55. for (const node of list) {
  56. if (func(node)) return node;
  57. node[children!] && list.push(...node[children!]);
  58. }
  59. return null;
  60. }
  61. export function findNodeAll<T = any>(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}): T[] {
  62. config = getConfig(config);
  63. const { children } = config;
  64. const list = [...tree];
  65. const result: T[] = [];
  66. for (const node of list) {
  67. func(node) && result.push(node);
  68. node[children!] && list.push(...node[children!]);
  69. }
  70. return result;
  71. }
  72. export function findPath<T = any>(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}): T | T[] | null {
  73. config = getConfig(config);
  74. const path: T[] = [];
  75. const list = [...tree];
  76. const visitedSet = new Set();
  77. const { children } = config;
  78. while (list.length) {
  79. const node = list[0];
  80. if (visitedSet.has(node)) {
  81. path.pop();
  82. list.shift();
  83. } else {
  84. visitedSet.add(node);
  85. node[children!] && list.unshift(...node[children!]);
  86. path.push(node);
  87. if (func(node)) {
  88. return path;
  89. }
  90. }
  91. }
  92. return null;
  93. }
  94. export function findPathAll(tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}) {
  95. config = getConfig(config);
  96. const path: any[] = [];
  97. const list = [...tree];
  98. const result: any[] = [];
  99. const visitedSet = new Set(),
  100. { children } = config;
  101. while (list.length) {
  102. const node = list[0];
  103. if (visitedSet.has(node)) {
  104. path.pop();
  105. list.shift();
  106. } else {
  107. visitedSet.add(node);
  108. node[children!] && list.unshift(...node[children!]);
  109. path.push(node);
  110. func(node) && result.push([...path]);
  111. }
  112. }
  113. return result;
  114. }
  115. export function filter<T = any>(
  116. tree: T[],
  117. func: (n: T) => boolean,
  118. config: Partial<TreeHelperConfig> = {},
  119. onlySearchLevel?: number,
  120. ): T[] {
  121. config = getConfig(config);
  122. const children = config.children as string;
  123. function listFilter(list: T[], level?: number) {
  124. return list
  125. .map((node: any) => ({ ...node }))
  126. .filter((node) => {
  127. if (level === 1) {
  128. return func(node);
  129. }
  130. if (onlySearchLevel && level) {
  131. node[children] = node[children] && listFilter(node[children], level - 1);
  132. return node[children] && node[children].length;
  133. }
  134. node[children] = node[children] && listFilter(node[children]);
  135. return func(node) || (node[children] && node[children].length);
  136. });
  137. }
  138. return listFilter(tree, onlySearchLevel);
  139. }
  140. export function forEach<T = any>(tree: T[], func: (n: T) => any, config: Partial<TreeHelperConfig> = {}): void {
  141. config = getConfig(config);
  142. const list: any[] = [...tree];
  143. const { children } = config;
  144. for (let i = 0; i < list.length; i++) {
  145. //func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿
  146. if (func(list[i])) {
  147. return;
  148. }
  149. children && list[i][children] && list.splice(i + 1, 0, ...list[i][children]);
  150. }
  151. }
  152. /**
  153. * @description: Extract tree specified structure
  154. */
  155. export function treeMap<T = any>(treeData: T[], opt: { children?: string; conversion: Fn }): T[] {
  156. return treeData.map((item) => treeMapEach(item, opt));
  157. }
  158. /**
  159. * @description: Extract tree specified structure
  160. */
  161. export function treeMapEach(data: any, { children = 'children', conversion }: { children?: string; conversion: Fn }) {
  162. const haveChildren = Array.isArray(data[children]) && data[children].length > 0;
  163. const conversionData = conversion(data) || {};
  164. if (haveChildren) {
  165. return {
  166. ...conversionData,
  167. [children]: data[children].map((i: number) =>
  168. treeMapEach(i, {
  169. children,
  170. conversion,
  171. }),
  172. ),
  173. };
  174. } else {
  175. return {
  176. ...conversionData,
  177. };
  178. }
  179. }
  180. /**
  181. * 递归遍历树结构
  182. * @param treeDatas 树
  183. * @param callBack 回调
  184. * @param parentNode 父节点
  185. */
  186. export function eachTree(treeDatas: any[], callBack: Fn, parentNode = {}) {
  187. treeDatas.forEach((element) => {
  188. const newNode = callBack(element, parentNode) || element;
  189. if (element.children) {
  190. eachTree(element.children, callBack, newNode);
  191. }
  192. });
  193. }