Files
zkxw/web-admin/src/utils/tree-path.ts
T
2026-05-26 19:04:27 +08:00

110 lines
3.8 KiB
TypeScript

/**
* 树形业务数据:全路径展示文案(与 Fresh Mint 规范一致)
* 扁平列表沿 parentId 回溯,或树结构 DFS 预计算 id → 路径
*/
export const DEFAULT_TREE_PATH_SEPARATOR = ' / '
export type TreePathFlatNode = Record<string, unknown>
/** 从扁平列表(含 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<string, TreePathFlatNode>()
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<string>()
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<string, string> {
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<string, string>()
const walk = (nodes: unknown[], pathNames: string[]) => {
for (const raw of nodes || []) {
const n = raw as Record<string, unknown>
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)
}