forked from wangziqi/gongxue-base
220 lines
7.4 KiB
TypeScript
220 lines
7.4 KiB
TypeScript
export interface AppMenuItem {
|
|
key: string;
|
|
label: string;
|
|
icon?: string;
|
|
children?: AppMenuItem[];
|
|
}
|
|
|
|
interface MenuEntry extends AppMenuItem {
|
|
permission: string;
|
|
}
|
|
|
|
interface MenuSection extends AppMenuItem {
|
|
roles: string[];
|
|
children: MenuEntry[];
|
|
}
|
|
|
|
const ROLE_ALIASES: Record<string, string> = {
|
|
老师: 'teacher',
|
|
任课老师: 'teacher',
|
|
teacher: 'teacher',
|
|
教务: 'academic',
|
|
教务管理员: 'academic',
|
|
academic: 'academic',
|
|
宿管老师: 'accommodation',
|
|
宿管: 'accommodation',
|
|
财务: 'accommodation',
|
|
住宿运营管理员: 'accommodation',
|
|
accommodation_operations: 'accommodation',
|
|
机构负责人: 'classroom',
|
|
教室运营管理员: 'classroom',
|
|
classroom_operations: 'classroom',
|
|
系统管理员: 'system',
|
|
system_admin: 'system',
|
|
超管: 'super',
|
|
超级管理员: 'super',
|
|
super_admin: 'super',
|
|
};
|
|
|
|
const SECTIONS: MenuSection[] = [
|
|
{
|
|
key: 'teaching-group',
|
|
label: '教学工作',
|
|
icon: 'calendar',
|
|
roles: ['teacher'],
|
|
children: [
|
|
{
|
|
key: '/teacher-workspace',
|
|
label: '今日教学',
|
|
icon: 'workspace',
|
|
permission: 'teacher-workspace:view',
|
|
},
|
|
{ key: '/schedules', label: '我的排课', icon: 'calendar', permission: 'schedule:view' },
|
|
{ key: '/attendance', label: '课程考勤', icon: 'attendance', permission: 'attendance:view' },
|
|
],
|
|
},
|
|
{
|
|
key: 'academic-group',
|
|
label: '教务管理',
|
|
icon: 'academic',
|
|
roles: ['academic', 'super'],
|
|
children: [
|
|
{ key: '/students', label: '学生管理', icon: 'students', permission: 'student:view' },
|
|
{ key: '/classes', label: '班级管理', icon: 'classes', permission: 'class:view' },
|
|
{ key: '/exams', label: '考试管理', icon: 'exam', permission: 'exam:view' },
|
|
{ key: '/teachers', label: '教师管理', icon: 'teachers', permission: 'teacher:view' },
|
|
{ key: '/schedules', label: '排课管理', icon: 'calendar', permission: 'schedule:view' },
|
|
{ key: '/attendance', label: '历史考勤', icon: 'attendance', permission: 'attendance:view' },
|
|
{ key: '/classrooms', label: '教室查看', icon: 'classroom', permission: 'classroom:view' },
|
|
],
|
|
},
|
|
{
|
|
key: 'accommodation-group',
|
|
label: '住宿运营',
|
|
icon: 'home',
|
|
roles: ['accommodation', 'super'],
|
|
children: [
|
|
{ key: '/room-visual', label: '住宿总览', icon: 'overview', permission: 'room:view' },
|
|
{ key: '/rooms', label: '房间管理', icon: 'home', permission: 'room:view' },
|
|
{ key: '/occupancies', label: '入住管理', icon: 'occupancy', permission: 'occupancy:view' },
|
|
{ key: '/expenses', label: '费用管理', icon: 'expense', permission: 'expense:view' },
|
|
{ key: '/bills', label: '账单管理', icon: 'bill', permission: 'bill:view' },
|
|
{ key: '/wallets', label: '学生余额', icon: 'wallet', permission: 'wallet:view' },
|
|
{ key: '/deposits', label: '押金管理', icon: 'deposit', permission: 'deposit:view' },
|
|
],
|
|
},
|
|
{
|
|
key: 'classroom-group',
|
|
label: '教室运营',
|
|
icon: 'classroom',
|
|
roles: ['classroom', 'super'],
|
|
children: [
|
|
{
|
|
key: '/classroom-schedule',
|
|
label: '教室排期',
|
|
icon: 'calendar',
|
|
permission: 'rental:view',
|
|
},
|
|
{ key: '/classrooms', label: '教室管理', icon: 'classroom', permission: 'classroom:view' },
|
|
{
|
|
key: '/attendance-devices',
|
|
label: '考勤机绑定',
|
|
icon: 'attendance',
|
|
permission: 'classroom:view',
|
|
},
|
|
{ key: '/classroom-rentals', label: '租赁订单', icon: 'rental', permission: 'rental:view' },
|
|
{
|
|
key: '/organizations',
|
|
label: '机构管理',
|
|
icon: 'organization',
|
|
permission: 'organization:view',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
key: 'system-group',
|
|
label: '系统管理',
|
|
icon: 'settings',
|
|
roles: ['system', 'super'],
|
|
children: [
|
|
{ key: '/users', label: '账号管理', icon: 'users', permission: 'user:view' },
|
|
{ key: '/roles', label: '角色管理', icon: 'role', permission: 'role:view' },
|
|
{ key: '/permissions', label: '权限一览', icon: 'permission', permission: 'role:view' },
|
|
{ key: '/operation-logs', label: '操作日志', icon: 'log', permission: 'log:view' },
|
|
{
|
|
key: '/integration-config',
|
|
label: '钉钉集成',
|
|
icon: 'integration',
|
|
permission: 'integration:read',
|
|
},
|
|
{ key: '/ai-config', label: 'AI 配置', icon: 'ai', permission: 'ai:config:read' },
|
|
],
|
|
},
|
|
];
|
|
|
|
export function getRoleDomains(
|
|
roles: readonly string[],
|
|
permissions: readonly string[],
|
|
): Set<string> {
|
|
const normalized = new Set(roles.map((role) => ROLE_ALIASES[role]).filter(Boolean));
|
|
// 权限可以来自多个叠加角色,因此业务域按能力累加,而不是只选择一个。
|
|
if (permissions.includes('student:view') || permissions.includes('class:view')) {
|
|
normalized.add('academic');
|
|
}
|
|
if (
|
|
(permissions.includes('room:view') &&
|
|
(permissions.includes('occupancy:view') || permissions.includes('expense:view'))) ||
|
|
permissions.includes('wallet:view')
|
|
) {
|
|
normalized.add('accommodation');
|
|
}
|
|
if (permissions.includes('rental:view') || permissions.includes('organization:view')) {
|
|
normalized.add('classroom');
|
|
}
|
|
if (permissions.includes('user:view') || permissions.includes('role:view')) {
|
|
normalized.add('system');
|
|
}
|
|
const hasAdministrativeDomain = [...normalized].some((role) => role !== 'teacher');
|
|
if (!hasAdministrativeDomain && permissions.includes('teacher-workspace:view')) {
|
|
normalized.add('teacher');
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function dedupeMenu(items: AppMenuItem[]): AppMenuItem[] {
|
|
const usedPaths = new Set<string>();
|
|
const result: AppMenuItem[] = [];
|
|
for (const item of items) {
|
|
if (item.children) {
|
|
const children = item.children.filter((child) => {
|
|
if (usedPaths.has(child.key)) return false;
|
|
usedPaths.add(child.key);
|
|
return true;
|
|
});
|
|
if (children.length > 0) result.push({ ...item, children });
|
|
continue;
|
|
}
|
|
if (!usedPaths.has(item.key)) {
|
|
usedPaths.add(item.key);
|
|
result.push(item);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function buildMenu(roles: readonly string[], permissions: readonly string[]): AppMenuItem[] {
|
|
const roleSet = getRoleDomains(roles, permissions);
|
|
const permissionSet = new Set(permissions);
|
|
const sections: AppMenuItem[] = [];
|
|
|
|
if (permissionSet.has('dashboard:view') && !roleSet.has('teacher')) {
|
|
sections.push({ key: '/dashboard', label: '数据面板', icon: 'dashboard' });
|
|
}
|
|
|
|
for (const section of SECTIONS) {
|
|
if (!section.roles.some((role) => roleSet.has(role))) continue;
|
|
const children = section.children
|
|
.filter((child) => permissionSet.has(child.permission))
|
|
.map(({ permission: _, ...child }) => child);
|
|
if (children.length > 0)
|
|
sections.push({ ...section, children, roles: undefined } as AppMenuItem);
|
|
}
|
|
|
|
if (permissionSet.has('notification:view')) {
|
|
sections.push({ key: '/notifications', label: '通知中心', icon: 'notification' });
|
|
}
|
|
|
|
return dedupeMenu(sections);
|
|
}
|
|
|
|
export function collectMenuPaths(items: readonly AppMenuItem[]): string[] {
|
|
return items.flatMap((item) => (item.children ? collectMenuPaths(item.children) : [item.key]));
|
|
}
|
|
|
|
export function findRoleAwareLandingPath(
|
|
roles: readonly string[],
|
|
permissions: readonly string[],
|
|
): string | null {
|
|
return collectMenuPaths(buildMenu(roles, permissions))[0] ?? null;
|
|
}
|