|
|
@@ -33,29 +33,78 @@ function hasValidToken(ctx: MockCtx): boolean {
|
|
|
}
|
|
|
|
|
|
/** 超管/教务/财务 全量菜单树(PC 端;与原型功能页一一对应)。 */
|
|
|
+/**
|
|
|
+ * 两级菜单(父 = 域分组节点,仅展开、无 path;子 = 叶子节点,带 path 与真实权限码)。
|
|
|
+ * 对齐后端 RBAC Resolver 下发的层级结构:父节点 code 为 `域.group`,子节点 code 为 `域.资源.动作`。
|
|
|
+ * side-menu.vue 递归渲染:父节点 → el-sub-menu(可折叠),叶子 → el-menu-item(router 点击跳转)。
|
|
|
+ */
|
|
|
const FULL_MENU: MenuNode[] = [
|
|
|
- { code: 'org.campus.view', label: '机构 / 校区', path: '/org/campuses' },
|
|
|
- { code: 'org.member.view', label: '成员 / 班级', path: '/org/members' },
|
|
|
- { code: 'academic.fee.view', label: '费用管理', path: '/academic/fee' },
|
|
|
- { code: 'academic.schedule.view', label: '排班管理', path: '/academic/schedule' },
|
|
|
- { code: 'academic.activity.view', label: '活动', path: '/academic/activity' },
|
|
|
- { code: 'academic.divide.view', label: '分账', path: '/academic/divide' },
|
|
|
- { code: 'academic.payroll.view', label: '课酬方案', path: '/academic/payroll' },
|
|
|
- { code: 'academic.expenditure.view', label: '收支管理', path: '/academic/expenditure' },
|
|
|
- { code: 'teaching.exam.view', label: '题库 / 组卷', path: '/teaching/exam' },
|
|
|
- { code: 'teaching.analysis.view', label: '练习分析', path: '/teaching/analysis' },
|
|
|
- { code: 'system.user.view', label: '用户 / 角色', path: '/system/users' },
|
|
|
- { code: 'system.task.view', label: '任务监控', path: '/system/tasks' },
|
|
|
- { code: 'system.log.view', label: '操作日志', path: '/system/logs' },
|
|
|
+ {
|
|
|
+ code: 'org.group',
|
|
|
+ label: '机构管理',
|
|
|
+ children: [
|
|
|
+ { code: 'org.campus.view', label: '机构 / 校区', path: '/org/campuses' },
|
|
|
+ { code: 'org.member.view', label: '成员 / 班级', path: '/org/members' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ code: 'academic.group',
|
|
|
+ label: '业务管理',
|
|
|
+ children: [
|
|
|
+ { code: 'academic.fee.view', label: '费用管理', path: '/academic/fee' },
|
|
|
+ { code: 'academic.schedule.view', label: '排班管理', path: '/academic/schedule' },
|
|
|
+ { code: 'academic.activity.view', label: '活动', path: '/academic/activity' },
|
|
|
+ { code: 'academic.divide.view', label: '分账', path: '/academic/divide' },
|
|
|
+ { code: 'academic.payroll.view', label: '课酬方案', path: '/academic/payroll' },
|
|
|
+ { code: 'academic.expenditure.view', label: '收支管理', path: '/academic/expenditure' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ code: 'teaching.group',
|
|
|
+ label: '教学管理',
|
|
|
+ children: [
|
|
|
+ { code: 'teaching.exam.view', label: '题库 / 组卷', path: '/teaching/exam' },
|
|
|
+ { code: 'teaching.analysis.view', label: '练习分析', path: '/teaching/analysis' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ code: 'system.group',
|
|
|
+ label: '系统管理',
|
|
|
+ children: [
|
|
|
+ { code: 'system.user.view', label: '用户 / 角色', path: '/system/users' },
|
|
|
+ { code: 'system.task.view', label: '任务监控', path: '/system/tasks' },
|
|
|
+ { code: 'system.log.view', label: '操作日志', path: '/system/logs' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
];
|
|
|
|
|
|
-/** 老师子集(仅排班/课程 + 教学)。 */
|
|
|
+/** 老师子集(仅排班/课程 + 教学;两级结构与 FULL_MENU 一致)。 */
|
|
|
const TEACHER_MENU: MenuNode[] = [
|
|
|
- { code: 'academic.schedule.view', label: '排班管理', path: '/academic/schedule' },
|
|
|
- { code: 'teaching.exam.view', label: '题库 / 组卷', path: '/teaching/exam' },
|
|
|
- { code: 'teaching.analysis.view', label: '练习分析', path: '/teaching/analysis' },
|
|
|
+ {
|
|
|
+ code: 'academic.group',
|
|
|
+ label: '业务管理',
|
|
|
+ children: [{ code: 'academic.schedule.view', label: '排班管理', path: '/academic/schedule' }],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ code: 'teaching.group',
|
|
|
+ label: '教学管理',
|
|
|
+ children: [
|
|
|
+ { code: 'teaching.exam.view', label: '题库 / 组卷', path: '/teaching/exam' },
|
|
|
+ { code: 'teaching.analysis.view', label: '练习分析', path: '/teaching/analysis' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
];
|
|
|
|
|
|
+/** 递归收集菜单树所有**叶子**节点的权限码(父节点 group 码不算;供 v-perm 菜单项显隐用)。 */
|
|
|
+function leafCodes(tree: MenuNode[]): string[] {
|
|
|
+ const out: string[] = [];
|
|
|
+ for (const n of tree) {
|
|
|
+ if (n.children && n.children.length > 0) out.push(...leafCodes(n.children));
|
|
|
+ else out.push(n.code);
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+}
|
|
|
+
|
|
|
/** 按 role 取菜单树。入参:role。出参:MenuNode[]。 */
|
|
|
function menuFor(role: string): MenuNode[] {
|
|
|
return role === 'teacher' ? TEACHER_MENU : FULL_MENU;
|
|
|
@@ -64,7 +113,7 @@ function menuFor(role: string): MenuNode[] {
|
|
|
/** 按 role 取权限码集(含 view + 常见 button;mock 从宽,保证 v-perm 按钮可见)。 */
|
|
|
function permsFor(role: string): string[] {
|
|
|
const tree = menuFor(role);
|
|
|
- const view = tree.map((n) => n.code);
|
|
|
+ const view = leafCodes(tree);
|
|
|
// 按钮级:费用/活动/工单等常见动作
|
|
|
const buttons = [
|
|
|
'academic.fee.recharge', 'academic.fee.refund', 'academic.fee.clear',
|