tree.ts 9.8 KB

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