/** * 树形业务数据:全路径展示文案(与 Fresh Mint 规范一致) * 扁平列表沿 parentId 回溯,或树结构 DFS 预计算 id → 路径 */ export const DEFAULT_TREE_PATH_SEPARATOR = ' / ' export type TreePathFlatNode = Record /** 从扁平列表(含 parentId / parent_id、name)解析「根 → 当前」路径文案 */ export function buildTreePathLabelFromFlat( nodeId: string | null | undefined, flatList: TreePathFlatNode[] | null | undefined, separatorOrOpts?: string | { separator?: string; nameKey?: string } ): string { const separator = typeof separatorOrOpts === 'string' ? separatorOrOpts : (separatorOrOpts?.separator ?? DEFAULT_TREE_PATH_SEPARATOR) const nameKey = typeof separatorOrOpts === 'string' ? 'name' : (separatorOrOpts?.nameKey ?? 'name') const cid = String(nodeId ?? '').trim() if (!cid) return '' const byId = new Map() for (const c of flatList || []) { const id = String(c?.id ?? '').trim() if (id) byId.set(id, c) } const names: string[] = [] let cur: string | null = cid const seen = new Set() while (cur && byId.has(cur) && !seen.has(cur)) { seen.add(cur) const cat: TreePathFlatNode = byId.get(cur)! const nm = String(cat?.[nameKey] ?? '').trim() if (nm) names.unshift(nm) const pid: string = String(cat?.parentId ?? cat?.parent_id ?? '').trim() cur = pid || null } return names.length ? names.join(separator) : '' } /** 从树形根列表预计算 id → 全路径(用于列表/卡片等高频展示) */ export function buildPathLabelMapFromTreeRoots( roots: unknown[] | null | undefined, options?: { idKey?: string nameKey?: string childrenKey?: string separator?: string } ): Map { const idKey = options?.idKey ?? 'id' const nameKey = options?.nameKey ?? 'name' const childrenKey = options?.childrenKey ?? 'children' const sep = options?.separator ?? DEFAULT_TREE_PATH_SEPARATOR const map = new Map() const walk = (nodes: unknown[], pathNames: string[]) => { for (const raw of nodes || []) { const n = raw as Record const id = n?.[idKey] != null ? String(n[idKey]).trim() : '' const curName = String(n?.[nameKey] ?? '').trim() const nextPath = curName ? [...pathNames, curName] : [...pathNames] if (id) map.set(id, nextPath.join(sep)) const ch = n?.[childrenKey] if (Array.isArray(ch) && ch.length) walk(ch as unknown[], nextPath) } } walk(roots || [], []) return map } /** 优先全路径;无法解析时用接口给的 fallback(多为末级名);皆无则占位符 */ export function resolveTreePathDisplay( nodeId: unknown, flatList: TreePathFlatNode[] | null | undefined, fallbackName?: unknown, separator: string = DEFAULT_TREE_PATH_SEPARATOR, emptyPlaceholder = '—', nameKey?: string ): string { const path = buildTreePathLabelFromFlat(String(nodeId ?? '').trim() || undefined, flatList, { separator, nameKey }) if (path) return path const fb = String(fallbackName ?? '').trim() if (fb) return fb return emptyPlaceholder } /** `el-tree-select` 的 data 节点上按 value 查找本级 label(扁平列表缺父链时的回退) */ export function findTreeSelectShortLabel( nodes: Array<{ value?: unknown; label?: unknown; children?: unknown[] }> | null | undefined, nodeId: string | null | undefined ): string { const id = String(nodeId ?? '').trim() if (!id) return '' const walk = (arr: typeof nodes): string => { for (const n of arr || []) { if (!n) continue if (String(n.value ?? '') === id) return String(n.label ?? '').trim() const hit = walk(n.children as typeof nodes) if (hit) return hit } return '' } return walk(nodes) }