Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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 }));
|
||||
}
|
||||
Reference in New Issue
Block a user