tree.ts 9.9 KB

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