tree.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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. isLeaf: 'leaf'
  16. }
  17. const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
  18. // tree from list
  19. export const listToTree = <T = any>(list: any[], config: Partial<TreeHelperConfig> = {}): T[] => {
  20. const conf = getConfig(config) as TreeHelperConfig
  21. const nodeMap = new Map()
  22. const result: T[] = []
  23. const { id, children, pid } = conf
  24. for (const node of list) {
  25. node[children] = node[children] || []
  26. nodeMap.set(node[id], node)
  27. }
  28. for (const node of list) {
  29. const parent = nodeMap.get(node[pid])
  30. ;(parent ? parent.children : result).push(node)
  31. }
  32. return result
  33. }
  34. export const treeToList = <T = any>(tree: any, config: Partial<TreeHelperConfig> = {}): T => {
  35. config = getConfig(config)
  36. const { children } = config
  37. const result: any = [...tree]
  38. for (let i = 0; i < result.length; i++) {
  39. if (!result[i][children!]) continue
  40. result.splice(i + 1, 0, ...result[i][children!])
  41. }
  42. return result
  43. }
  44. export const findNode = <T = any>(
  45. tree: any,
  46. func: Fn,
  47. config: Partial<TreeHelperConfig> = {}
  48. ): T | null => {
  49. config = getConfig(config)
  50. const { children } = config
  51. const list = [...tree]
  52. for (const node of list) {
  53. if (func(node)) return node
  54. node[children!] && list.push(...node[children!])
  55. }
  56. return null
  57. }
  58. export const findNodeAll = <T = any>(
  59. tree: any,
  60. func: Fn,
  61. config: Partial<TreeHelperConfig> = {}
  62. ): T[] => {
  63. config = getConfig(config)
  64. const { children } = config
  65. const list = [...tree]
  66. const result: T[] = []
  67. for (const node of list) {
  68. func(node) && result.push(node)
  69. node[children!] && list.push(...node[children!])
  70. }
  71. return result
  72. }
  73. export const findPath = <T = any>(
  74. tree: any,
  75. func: Fn,
  76. config: Partial<TreeHelperConfig> = {}
  77. ): T | T[] | null => {
  78. config = getConfig(config)
  79. const path: T[] = []
  80. const list = [...tree]
  81. const visitedSet = new Set()
  82. const { children } = config
  83. while (list.length) {
  84. const node = list[0]
  85. if (visitedSet.has(node)) {
  86. path.pop()
  87. list.shift()
  88. } else {
  89. visitedSet.add(node)
  90. node[children!] && list.unshift(...node[children!])
  91. path.push(node)
  92. if (func(node)) {
  93. return path
  94. }
  95. }
  96. }
  97. return null
  98. }
  99. export const findPathAll = (tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}) => {
  100. config = getConfig(config)
  101. const path: any[] = []
  102. const list = [...tree]
  103. const result: any[] = []
  104. const visitedSet = new Set(),
  105. { children } = config
  106. while (list.length) {
  107. const node = list[0]
  108. if (visitedSet.has(node)) {
  109. path.pop()
  110. list.shift()
  111. } else {
  112. visitedSet.add(node)
  113. node[children!] && list.unshift(...node[children!])
  114. path.push(node)
  115. func(node) && result.push([...path])
  116. }
  117. }
  118. return result
  119. }
  120. export const filter = <T = any>(
  121. tree: T[],
  122. func: (n: T) => boolean,
  123. config: Partial<TreeHelperConfig> = {}
  124. ): T[] => {
  125. config = getConfig(config)
  126. const children = config.children as string
  127. function listFilter(list: T[]) {
  128. return list
  129. .map((node: any) => ({ ...node }))
  130. .filter((node) => {
  131. node[children] = node[children] && listFilter(node[children])
  132. return func(node) || (node[children] && node[children].length)
  133. })
  134. }
  135. return listFilter(tree)
  136. }
  137. export const forEach = <T = any>(
  138. tree: T[],
  139. func: (n: T) => any,
  140. config: Partial<TreeHelperConfig> = {}
  141. ): void => {
  142. config = getConfig(config)
  143. const list: any[] = [...tree]
  144. const { children } = config
  145. for (let i = 0; i < list.length; i++) {
  146. // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿
  147. if (func(list[i])) {
  148. return
  149. }
  150. children && list[i][children] && list.splice(i + 1, 0, ...list[i][children])
  151. }
  152. }
  153. /**
  154. * @description: Extract tree specified structure
  155. */
  156. export const treeMap = <T = any>(
  157. treeData: T[],
  158. opt: { children?: string; conversion: Fn }
  159. ): T[] => {
  160. return treeData.map((item) => treeMapEach(item, opt))
  161. }
  162. /**
  163. * @description: Extract tree specified structure
  164. */
  165. export const treeMapEach = (
  166. data: any,
  167. { children = 'children', conversion }: { children?: string; conversion: Fn }
  168. ) => {
  169. const haveChildren = Array.isArray(data[children]) && data[children].length > 0
  170. const conversionData = conversion(data) || {}
  171. if (haveChildren) {
  172. return {
  173. ...conversionData,
  174. [children]: data[children].map((i: number) =>
  175. treeMapEach(i, {
  176. children,
  177. conversion
  178. })
  179. )
  180. }
  181. } else {
  182. return {
  183. ...conversionData
  184. }
  185. }
  186. }
  187. /**
  188. * 递归遍历树结构
  189. * @param treeDatas 树
  190. * @param callBack 回调
  191. * @param parentNode 父节点
  192. */
  193. export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {
  194. treeDatas.forEach((element) => {
  195. const newNode = callBack(element, parentNode) || element
  196. if (element.children) {
  197. eachTree(element.children, callBack, newNode)
  198. }
  199. })
  200. }
  201. /**
  202. * 构造树型结构数据
  203. * @param {*} data 数据源
  204. * @param {*} id id字段 默认 'id'
  205. * @param {*} parentId 父节点字段 默认 'parentId'
  206. * @param {*} children 孩子节点字段 默认 'children'
  207. */
  208. export const handleTree = (data: any[], id?: string, parentId?: string, children?: string) => {
  209. if (!Array.isArray(data)) {
  210. console.warn('data must be an array')
  211. return []
  212. }
  213. const config = {
  214. id: id || 'id',
  215. parentId: parentId || 'parentId',
  216. childrenList: children || 'children'
  217. }
  218. const childrenListMap = {}
  219. const nodeIds = {}
  220. const tree: any[] = []
  221. for (const d of data) {
  222. const parentId = d[config.parentId]
  223. if (childrenListMap[parentId] == null) {
  224. childrenListMap[parentId] = []
  225. }
  226. nodeIds[d[config.id]] = d
  227. childrenListMap[parentId].push(d)
  228. }
  229. for (const d of data) {
  230. const parentId = d[config.parentId]
  231. if (nodeIds[parentId] == null) {
  232. tree.push(d)
  233. }
  234. }
  235. for (const t of tree) {
  236. adaptToChildrenList(t)
  237. }
  238. function adaptToChildrenList(o) {
  239. if (childrenListMap[o[config.id]] !== null) {
  240. o[config.childrenList] = childrenListMap[o[config.id]]
  241. }
  242. if (o[config.childrenList]) {
  243. for (const c of o[config.childrenList]) {
  244. adaptToChildrenList(c)
  245. }
  246. }
  247. }
  248. return tree
  249. }
  250. /**
  251. * 构造树型结构数据
  252. * @param {*} data 数据源
  253. * @param {*} id id字段 默认 'id'
  254. * @param {*} parentId 父节点字段 默认 'parentId'
  255. * @param {*} children 孩子节点字段 默认 'children'
  256. * @param {*} rootId 根Id 默认 0
  257. */
  258. // @ts-ignore
  259. export const handleTree2 = (data, id, parentId, children, rootId) => {
  260. id = id || 'id'
  261. parentId = parentId || 'parentId'
  262. // children = children || 'children'
  263. rootId =
  264. rootId ||
  265. Math.min(
  266. ...data.map((item) => {
  267. return item[parentId]
  268. })
  269. ) ||
  270. 0
  271. // 对源数据深度克隆
  272. const cloneData = JSON.parse(JSON.stringify(data))
  273. // 循环所有项
  274. const treeData = cloneData.filter((father) => {
  275. const branchArr = cloneData.filter((child) => {
  276. // 返回每一项的子级数组
  277. return father[id] === child[parentId]
  278. })
  279. branchArr.length > 0 ? (father.children = branchArr) : ''
  280. // 返回第一层
  281. return father[parentId] === rootId
  282. })
  283. return treeData !== '' ? treeData : data
  284. }
  285. /**
  286. * 校验选中的节点,是否为指定 level
  287. *
  288. * @param tree 要操作的树结构数据
  289. * @param nodeId 需要判断在什么层级的数据
  290. * @param level 检查的级别, 默认检查到二级
  291. * @return true 是;false 否
  292. */
  293. export const checkSelectedNode = (tree: any[], nodeId: any, level = 2): boolean => {
  294. if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
  295. console.warn('tree must be an array')
  296. return false
  297. }
  298. // 校验是否是一级节点
  299. if (tree.some((item) => item.id === nodeId)) {
  300. return false
  301. }
  302. // 递归计数
  303. let count = 1
  304. // 深层次校验
  305. function performAThoroughValidation(arr: any[]): boolean {
  306. count += 1
  307. for (const item of arr) {
  308. if (item.id === nodeId) {
  309. return true
  310. } else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
  311. if (performAThoroughValidation(item.children)) {
  312. return true
  313. }
  314. }
  315. }
  316. return false
  317. }
  318. for (const item of tree) {
  319. count = 1
  320. if (performAThoroughValidation(item.children)) {
  321. // 找到后对比是否是期望的层级
  322. if (count >= level) {
  323. return true
  324. }
  325. }
  326. }
  327. return false
  328. }
  329. /**
  330. * 获取节点的完整结构
  331. * @param tree 树数据
  332. * @param nodeId 节点 id
  333. */
  334. export const treeToString = (tree: any[], nodeId) => {
  335. if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
  336. console.warn('tree must be an array')
  337. return ''
  338. }
  339. // 校验是否是一级节点
  340. const node = tree.find((item) => item.id === nodeId)
  341. if (typeof node !== 'undefined') {
  342. return node.name
  343. }
  344. let str = ''
  345. function performAThoroughValidation(arr) {
  346. for (const item of arr) {
  347. if (item.id === nodeId) {
  348. str += `/${item.name}`
  349. return true
  350. } else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
  351. str += `/${item.name}`
  352. if (performAThoroughValidation(item.children)) {
  353. return true
  354. }
  355. }
  356. }
  357. return false
  358. }
  359. for (const item of tree) {
  360. str = `${item.name}`
  361. if (performAThoroughValidation(item.children)) {
  362. break
  363. }
  364. }
  365. return str
  366. }