110 lines
3.8 KiB
TypeScript
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)
|
|
}
|