/** * 团队层级树工具(纯函数) * * 后端 /api/commission/team/tree 返回扁平 nodes(不含根节点"我"),字段含: * userId/nickname/avatar/level/parentId/referrerId/memberConsumption/myCfEarnedFromMember。 * * 注意:后端按 referral_tree 物化表查询(parent_id = 当前用户), * 因此响应里的 parentId 恒为根节点,真正的「直接上级」是 referrerId。 * 本模块优先用 referrerId 建立父子关系,缺失时回退 parentId。 * * 本模块把扁平数据按父子关系组装成树并拍平成「前序遍历」行, * 供 team.vue 的树形视图(column-reverse 自下而上生长)渲染。 */ /** * 取节点直接上级:优先 referrerId,回退 parentId。 * @param {Object} node 树节点 * @returns {*} 上级 id,无上级时返回 null */ function directParentId(node) { if (!node) return null if (node.referrerId !== null && node.referrerId !== undefined) return node.referrerId if (node.parentId !== null && node.parentId !== undefined) return node.parentId return null } /** * 将扁平 nodes 构建为前序遍历行。 * * @param {Array} nodes 后端返回的扁平节点数组(可为空/undefined) * @param {Array} collapsedIds 已折叠的 userId 列表(其子节点不再输出) * @param {*} rootUserId 根用户("我")id;传入时 L1 节点 depth 从 1 开始 * @returns {Array} rows = [{ node, depth, hasChildren, collapsed }] */ export function buildTeamTreeRows(nodes, collapsedIds, rootUserId) { const list = Array.isArray(nodes) ? nodes : [] if (list.length === 0) return [] const collapseSet = {} const collapsed = Array.isArray(collapsedIds) ? collapsedIds : [] for (let i = 0; i < collapsed.length; i++) { collapseSet[String(collapsed[i])] = true } // 1. 建立 userId 集合与「直接上级 → children」映射 const ids = {} for (let i = 0; i < list.length; i++) { const n = list[i] if (n && n.userId !== null && n.userId !== undefined) ids[String(n.userId)] = true } const childrenMap = {} for (let i = 0; i < list.length; i++) { const n = list[i] if (!n || n.userId === null || n.userId === undefined) continue const key = String(n.userId) if (!childrenMap[key]) childrenMap[key] = [] const pid = directParentId(n) if (pid !== null && String(pid) !== key) { if (!childrenMap[String(pid)]) childrenMap[String(pid)] = [] childrenMap[String(pid)].push(n) } } // 2. 根节点:直接上级为空、上级是"我",或上级不在节点集合中 const hasRoot = rootUserId !== null && rootUserId !== undefined const rootKey = hasRoot ? String(rootUserId) : null const roots = [] for (let i = 0; i < list.length; i++) { const n = list[i] if (!n || n.userId === null || n.userId === undefined) continue const pid = directParentId(n) const isRoot = pid === null || String(pid) === String(n.userId) || (hasRoot && String(pid) === rootKey) || !ids[String(pid)] if (isRoot) roots.push(n) } // 3. 前序遍历;折叠节点跳过子节点;visited 防循环 const rows = [] const visited = {} function walk(node, depth) { const key = String(node.userId) if (visited[key]) return visited[key] = true const children = childrenMap[key] || [] const isCollapsed = !!collapseSet[key] rows.push({ node, depth, hasChildren: children.length > 0, collapsed: isCollapsed }) if (isCollapsed) return for (let i = 0; i < children.length; i++) { walk(children[i], depth + 1) } } const rootDepth = hasRoot ? 1 : 0 for (let i = 0; i < roots.length; i++) { walk(roots[i], rootDepth) } return rows }