55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
|
|
import type { Department } from '@/api/types'
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Tree node consumed by `<el-tree :data>`. Element Plus looks at `label` for
|
||
|
|
* the text and `children` for sub-nodes (both configurable via props).
|
||
|
|
*/
|
||
|
|
export interface DeptTreeNode {
|
||
|
|
dept_id: string
|
||
|
|
label: string
|
||
|
|
raw: Department
|
||
|
|
children: DeptTreeNode[]
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Build a department forest from the backend's flat `parent_id` list.
|
||
|
|
*
|
||
|
|
* - Roots = departments with no `parent_id` (or whose parent is missing).
|
||
|
|
* - Children are sorted by `path` then `dept_id` to mirror the backend ordering
|
||
|
|
* (the API already returns ordered by path, but orphans re-ordering is cheap
|
||
|
|
* insurance).
|
||
|
|
* - Falls back gracefully on cycles / dangling parent_ids (orphans become roots
|
||
|
|
* rather than disappearing).
|
||
|
|
*/
|
||
|
|
export function buildDeptTree(departments: Department[]): DeptTreeNode[] {
|
||
|
|
const nodes = new Map<string, DeptTreeNode>()
|
||
|
|
for (const d of departments) {
|
||
|
|
nodes.set(d.dept_id, {
|
||
|
|
dept_id: d.dept_id,
|
||
|
|
label: d.name?.trim() || d.dept_id,
|
||
|
|
raw: d,
|
||
|
|
children: [],
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const roots: DeptTreeNode[] = []
|
||
|
|
for (const d of departments) {
|
||
|
|
const node = nodes.get(d.dept_id)!
|
||
|
|
const parent = d.parent_id ? nodes.get(d.parent_id) : undefined
|
||
|
|
if (parent) {
|
||
|
|
parent.children.push(node)
|
||
|
|
} else {
|
||
|
|
roots.push(node)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const sortRec = (list: DeptTreeNode[]) => {
|
||
|
|
list.sort((a, b) =>
|
||
|
|
(a.raw.path ?? a.dept_id).localeCompare(b.raw.path ?? b.dept_id),
|
||
|
|
)
|
||
|
|
for (const n of list) sortRec(n.children)
|
||
|
|
}
|
||
|
|
sortRec(roots)
|
||
|
|
return roots
|
||
|
|
}
|