team-tree.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. /**
  2. * 团队层级树工具(纯函数)
  3. *
  4. * 后端 /api/commission/team/tree 返回扁平 nodes(不含根节点"我"),字段含:
  5. * userId/nickname/avatar/level/parentId/referrerId/memberConsumption/myCfEarnedFromMember。
  6. *
  7. * 注意:后端按 referral_tree 物化表查询(parent_id = 当前用户),
  8. * 因此响应里的 parentId 恒为根节点,真正的「直接上级」是 referrerId。
  9. * 本模块优先用 referrerId 建立父子关系,缺失时回退 parentId。
  10. *
  11. * 本模块把扁平数据按父子关系组装成树并拍平成「前序遍历」行,
  12. * 供 team.vue 的树形视图(column-reverse 自下而上生长)渲染。
  13. */
  14. /**
  15. * 取节点直接上级:优先 referrerId,回退 parentId。
  16. * @param {Object} node 树节点
  17. * @returns {*} 上级 id,无上级时返回 null
  18. */
  19. function directParentId(node) {
  20. if (!node) return null
  21. if (node.referrerId !== null && node.referrerId !== undefined) return node.referrerId
  22. if (node.parentId !== null && node.parentId !== undefined) return node.parentId
  23. return null
  24. }
  25. /**
  26. * 将扁平 nodes 构建为前序遍历行。
  27. *
  28. * @param {Array} nodes 后端返回的扁平节点数组(可为空/undefined)
  29. * @param {Array} collapsedIds 已折叠的 userId 列表(其子节点不再输出)
  30. * @param {*} rootUserId 根用户("我")id;传入时 L1 节点 depth 从 1 开始
  31. * @returns {Array} rows = [{ node, depth, hasChildren, collapsed }]
  32. */
  33. export function buildTeamTreeRows(nodes, collapsedIds, rootUserId) {
  34. const list = Array.isArray(nodes) ? nodes : []
  35. if (list.length === 0) return []
  36. const collapseSet = {}
  37. const collapsed = Array.isArray(collapsedIds) ? collapsedIds : []
  38. for (let i = 0; i < collapsed.length; i++) {
  39. collapseSet[String(collapsed[i])] = true
  40. }
  41. // 1. 建立 userId 集合与「直接上级 → children」映射
  42. const ids = {}
  43. for (let i = 0; i < list.length; i++) {
  44. const n = list[i]
  45. if (n && n.userId !== null && n.userId !== undefined) ids[String(n.userId)] = true
  46. }
  47. const childrenMap = {}
  48. for (let i = 0; i < list.length; i++) {
  49. const n = list[i]
  50. if (!n || n.userId === null || n.userId === undefined) continue
  51. const key = String(n.userId)
  52. if (!childrenMap[key]) childrenMap[key] = []
  53. const pid = directParentId(n)
  54. if (pid !== null && String(pid) !== key) {
  55. if (!childrenMap[String(pid)]) childrenMap[String(pid)] = []
  56. childrenMap[String(pid)].push(n)
  57. }
  58. }
  59. // 2. 根节点:直接上级为空、上级是"我",或上级不在节点集合中
  60. const hasRoot = rootUserId !== null && rootUserId !== undefined
  61. const rootKey = hasRoot ? String(rootUserId) : null
  62. const roots = []
  63. for (let i = 0; i < list.length; i++) {
  64. const n = list[i]
  65. if (!n || n.userId === null || n.userId === undefined) continue
  66. const pid = directParentId(n)
  67. const isRoot = pid === null ||
  68. String(pid) === String(n.userId) ||
  69. (hasRoot && String(pid) === rootKey) ||
  70. !ids[String(pid)]
  71. if (isRoot) roots.push(n)
  72. }
  73. // 3. 前序遍历;折叠节点跳过子节点;visited 防循环
  74. const rows = []
  75. const visited = {}
  76. function walk(node, depth) {
  77. const key = String(node.userId)
  78. if (visited[key]) return
  79. visited[key] = true
  80. const children = childrenMap[key] || []
  81. const isCollapsed = !!collapseSet[key]
  82. rows.push({
  83. node,
  84. depth,
  85. hasChildren: children.length > 0,
  86. collapsed: isCollapsed
  87. })
  88. if (isCollapsed) return
  89. for (let i = 0; i < children.length; i++) {
  90. walk(children[i], depth + 1)
  91. }
  92. }
  93. const rootDepth = hasRoot ? 1 : 0
  94. for (let i = 0; i < roots.length; i++) {
  95. walk(roots[i], rootDepth)
  96. }
  97. return rows
  98. }