tree.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. interface TreeHelperConfig {
  2. id: string
  3. children: string
  4. pid: string
  5. }
  6. const DEFAULT_CONFIG: TreeHelperConfig = {
  7. id: 'id',
  8. children: 'children',
  9. pid: 'pid'
  10. }
  11. export const defaultProps = {
  12. children: 'children',
  13. label: 'name',
  14. value: 'id'
  15. }
  16. const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
  17. // tree from list
  18. export const listToTree = <T = any>(list: any[], config: Partial<TreeHelperConfig> = {}): T[] => {
  19. const conf = getConfig(config) as TreeHelperConfig
  20. const nodeMap = new Map()
  21. const result: T[] = []
  22. const { id, children, pid } = conf
  23. for (const node of list) {
  24. node[children] = node[children] || []
  25. nodeMap.set(node[id], node)
  26. }
  27. for (const node of list) {
  28. const parent = nodeMap.get(node[pid])
  29. ;(parent ? parent.children : result).push(node)
  30. }
  31. return result
  32. }
  33. export const treeToList = <T = any>(tree: any, config: Partial<TreeHelperConfig> = {}): T => {
  34. config = getConfig(config)
  35. const { children } = config
  36. const result: any = [...tree]
  37. for (let i = 0; i < result.length; i++) {
  38. if (!result[i][children!]) continue
  39. result.splice(i + 1, 0, ...result[i][children!])
  40. }
  41. return result
  42. }
  43. export const findNode = <T = any>(
  44. tree: any,
  45. func: Fn,
  46. config: Partial<TreeHelperConfig> = {}
  47. ): T | null => {
  48. config = getConfig(config)
  49. const { children } = config
  50. const list = [...tree]
  51. for (const node of list) {
  52. if (func(node)) return node
  53. node[children!] && list.push(...node[children!])
  54. }
  55. return null
  56. }
  57. export const findNodeAll = <T = any>(
  58. tree: any,
  59. func: Fn,
  60. config: Partial<TreeHelperConfig> = {}
  61. ): 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 const findPath = <T = any>(
  73. tree: any,
  74. func: Fn,
  75. config: Partial<TreeHelperConfig> = {}
  76. ): T | T[] | null => {
  77. config = getConfig(config)
  78. const path: T[] = []
  79. const list = [...tree]
  80. const visitedSet = new Set()
  81. const { children } = config
  82. while (list.length) {
  83. const node = list[0]
  84. if (visitedSet.has(node)) {
  85. path.pop()
  86. list.shift()
  87. } else {
  88. visitedSet.add(node)
  89. node[children!] && list.unshift(...node[children!])
  90. path.push(node)
  91. if (func(node)) {
  92. return path
  93. }
  94. }
  95. }
  96. return null
  97. }
  98. export const findPathAll = (tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}) => {
  99. config = getConfig(config)
  100. const path: any[] = []
  101. const list = [...tree]
  102. const result: any[] = []
  103. const visitedSet = new Set(),
  104. { children } = config
  105. while (list.length) {
  106. const node = list[0]
  107. if (visitedSet.has(node)) {
  108. path.pop()
  109. list.shift()
  110. } else {
  111. visitedSet.add(node)
  112. node[children!] && list.unshift(...node[children!])
  113. path.push(node)
  114. func(node) && result.push([...path])
  115. }
  116. }
  117. return result
  118. }
  119. export const filter = <T = any>(
  120. tree: T[],
  121. func: (n: T) => boolean,
  122. config: Partial<TreeHelperConfig> = {}
  123. ): T[] => {
  124. config = getConfig(config)
  125. const children = config.children as string
  126. function listFilter(list: T[]) {
  127. return list
  128. .map((node: any) => ({ ...node }))
  129. .filter((node) => {
  130. node[children] = node[children] && listFilter(node[children])
  131. return func(node) || (node[children] && node[children].length)
  132. })
  133. }
  134. return listFilter(tree)
  135. }
  136. export const forEach = <T = any>(
  137. tree: T[],
  138. func: (n: T) => any,
  139. config: Partial<TreeHelperConfig> = {}
  140. ): 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 const treeMap = <T = any>(
  156. treeData: T[],
  157. opt: { children?: string; conversion: Fn }
  158. ): T[] => {
  159. return treeData.map((item) => treeMapEach(item, opt))
  160. }
  161. /**
  162. * @description: Extract tree specified structure
  163. */
  164. export const treeMapEach = (
  165. data: any,
  166. { children = 'children', conversion }: { children?: string; conversion: Fn }
  167. ) => {
  168. const haveChildren = Array.isArray(data[children]) && data[children].length > 0
  169. const conversionData = conversion(data) || {}
  170. if (haveChildren) {
  171. return {
  172. ...conversionData,
  173. [children]: data[children].map((i: number) =>
  174. treeMapEach(i, {
  175. children,
  176. conversion
  177. })
  178. )
  179. }
  180. } else {
  181. return {
  182. ...conversionData
  183. }
  184. }
  185. }
  186. /**
  187. * 递归遍历树结构
  188. * @param treeDatas 树
  189. * @param callBack 回调
  190. * @param parentNode 父节点
  191. */
  192. export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {
  193. treeDatas.forEach((element) => {
  194. const newNode = callBack(element, parentNode) || element
  195. if (element.children) {
  196. eachTree(element.children, callBack, newNode)
  197. }
  198. })
  199. }
  200. /**
  201. * 构造树型结构数据
  202. * @param {*} data 数据源
  203. * @param {*} id id字段 默认 'id'
  204. * @param {*} parentId 父节点字段 默认 'parentId'
  205. * @param {*} children 孩子节点字段 默认 'children'
  206. */
  207. export const handleTree = (data: any[], id?: string, parentId?: string, children?: string) => {
  208. if (!Array.isArray(data)) {
  209. console.warn('data must be an array')
  210. return []
  211. }
  212. const config = {
  213. id: id || 'id',
  214. parentId: parentId || 'parentId',
  215. childrenList: children || 'children'
  216. }
  217. const childrenListMap = {}
  218. const nodeIds = {}
  219. const tree: any[] = []
  220. for (const d of data) {
  221. const parentId = d[config.parentId]
  222. if (childrenListMap[parentId] == null) {
  223. childrenListMap[parentId] = []
  224. }
  225. nodeIds[d[config.id]] = d
  226. childrenListMap[parentId].push(d)
  227. }
  228. for (const d of data) {
  229. const parentId = d[config.parentId]
  230. if (nodeIds[parentId] == null) {
  231. tree.push(d)
  232. }
  233. }
  234. for (const t of tree) {
  235. adaptToChildrenList(t)
  236. }
  237. function adaptToChildrenList(o) {
  238. if (childrenListMap[o[config.id]] !== null) {
  239. o[config.childrenList] = childrenListMap[o[config.id]]
  240. }
  241. if (o[config.childrenList]) {
  242. for (const c of o[config.childrenList]) {
  243. adaptToChildrenList(c)
  244. }
  245. }
  246. }
  247. return tree
  248. }
  249. /**
  250. * 构造树型结构数据
  251. * @param {*} data 数据源
  252. * @param {*} id id字段 默认 'id'
  253. * @param {*} parentId 父节点字段 默认 'parentId'
  254. * @param {*} children 孩子节点字段 默认 'children'
  255. * @param {*} rootId 根Id 默认 0
  256. */
  257. // @ts-ignore
  258. export const handleTree2 = (data, id, parentId, children, rootId) => {
  259. id = id || 'id'
  260. parentId = parentId || 'parentId'
  261. // children = children || 'children'
  262. rootId =
  263. rootId ||
  264. Math.min(
  265. ...data.map((item) => {
  266. return item[parentId]
  267. })
  268. ) ||
  269. 0
  270. // 对源数据深度克隆
  271. const cloneData = JSON.parse(JSON.stringify(data))
  272. // 循环所有项
  273. const treeData = cloneData.filter((father) => {
  274. const branchArr = cloneData.filter((child) => {
  275. // 返回每一项的子级数组
  276. return father[id] === child[parentId]
  277. })
  278. branchArr.length > 0 ? (father.children = branchArr) : ''
  279. // 返回第一层
  280. return father[parentId] === rootId
  281. })
  282. return treeData !== '' ? treeData : data
  283. }