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:
2026-09-02 10:04:03 +00:00
commit 76f266645d
507 changed files with 99891 additions and 0 deletions
+76
View File
@@ -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 }));
}