76f266645d
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
export type DeptNode = {
|
|
id: string;
|
|
name: string;
|
|
code?: string | null;
|
|
parentId?: string | null;
|
|
};
|
|
|
|
function companyRootId(depts: DeptNode[]) {
|
|
const roots = depts.filter((d) => !d.parentId);
|
|
const named = roots.find((d) => d.name?.includes('公司') || d.code === 'ROOT' || d.code === 'OA_1');
|
|
return named?.id || roots[0]?.id;
|
|
}
|
|
|
|
export function isTopLevelDept(depts: DeptNode[], dept: DeptNode) {
|
|
const rootId = companyRootId(depts);
|
|
if (!rootId) return !dept.parentId;
|
|
if (dept.id === rootId) return false;
|
|
return dept.parentId === rootId || !dept.parentId;
|
|
}
|
|
|
|
export function topLevelDepartments(depts: DeptNode[]) {
|
|
return depts.filter((d) => isTopLevelDept(depts, d)).sort((a, b) => a.name.localeCompare(b.name, 'zh'));
|
|
}
|
|
|
|
export function topLevelOf(depts: DeptNode[], deptId?: string | null): DeptNode | undefined {
|
|
if (!deptId) return undefined;
|
|
const byId = new Map(depts.map((d) => [d.id, d]));
|
|
const rootId = companyRootId(depts);
|
|
let cur = byId.get(deptId);
|
|
const seen = new Set<string>();
|
|
while (cur && !seen.has(cur.id)) {
|
|
seen.add(cur.id);
|
|
if (isTopLevelDept(depts, cur)) return cur;
|
|
if (!cur.parentId || cur.parentId === rootId) return cur.id === rootId ? undefined : cur;
|
|
cur = byId.get(cur.parentId);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function topLevelNameOf(depts: DeptNode[], deptId?: string | null, fallback?: string | null) {
|
|
return topLevelOf(depts, deptId)?.name || fallback || '';
|
|
}
|
|
|
|
export function topLevelNameByLeafName(depts: DeptNode[], name?: string | null) {
|
|
if (!name) return '';
|
|
const leaf = depts.find((d) => d.name === name);
|
|
if (!leaf) return name;
|
|
return topLevelOf(depts, leaf.id)?.name || name;
|
|
}
|
|
|
|
export function descendantIds(depts: DeptNode[], root?: string) {
|
|
if (!root) return null;
|
|
const kids = new Map<string, string[]>();
|
|
for (const d of depts) {
|
|
if (!d.parentId) continue;
|
|
const arr = kids.get(d.parentId) || [];
|
|
arr.push(d.id);
|
|
kids.set(d.parentId, arr);
|
|
}
|
|
const out = new Set<string>([root]);
|
|
const stack = [root];
|
|
while (stack.length) {
|
|
const id = stack.pop()!;
|
|
for (const c of kids.get(id) || []) {
|
|
if (!out.has(c)) {
|
|
out.add(c);
|
|
stack.push(c);
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function topLevelSelectOptions(depts: DeptNode[]) {
|
|
return topLevelDepartments(depts).map((d) => ({ value: d.id, label: d.name }));
|
|
}
|