feat(rbac): 成绩录入/导入放开到本班班主任(需求 6 与需求 7 口径统一)
依据: 需求 6 明确要求'成绩与表格导入……老师端与学生端班级账号均可用且不越权',
需求 7 的权限清单并未把成绩收归管理员专属(只限制班级增删改、人员分配与科任权限)。
变更:
- shared/permissions: canManageGrades(classRole) = ADMIN | HEAD;canImport 按 kind 分发
(成绩=ADMIN|HEAD、名单=仅管理员、课表时间=ADMIN|HEAD);PERMISSION_MATRIX 同步
- lib/access: assertCanManageGrades(user, classId) 异步按班级角色判定,科任/跨班给出针对性提示
- grades 服务与路由、imports 的模板/预览/提交路由放宽为 ADMIN|TEACHER,
服务层按 kind + 班级二次校验(名单仍仅管理员,科任仍 403)
- Web 管理端: 成绩录入菜单对教师可见;写入/导入按钮仅对'管理员或所选班级班主任'显示
验证:
- verify:e2e 133/133(新增: 班主任录入本班成绩 201 + 跨班 403、班主任模板/预览/导入本班成绩可用、
科任导入 403、班主任名单模板与导入 403、append 重复导入跳过)
- verify:web 19/19(新增: 班主任可见成绩导入入口并打开弹窗,越权入口仍隐藏、菜单 5 项)
- verify:desktop 47/47(未受影响)
文档: README 权限矩阵与'需求 6/7 口径统一'对照表、各项计数更新
This commit is contained in:
1 parent
21485ea294
commit
97a89de944
11 files changed
+359
-89
No files matched your search
@@ -18,8 +18,9 @@ const { isMobile } = useResponsive();
|
||||
* 菜单项与可见角色(与后端 `@classhelper/shared/permissions` 矩阵保持一致):
|
||||
* - 班级管理 / 学生管理:仅管理员(班主任与科任老师都没有班级增删改与人员分配权限)
|
||||
* - 课表管理:管理员 + 班主任(页面内再按"是否本班班主任"控制增删改按钮)
|
||||
* - 成绩录入:管理员 + 教师;页面内只对"管理员或所选班级的班主任"显示写入/导入按钮
|
||||
* (需求 6:成绩与表格导入老师端可用且不越权;科任老师只能看,写入会被后端 403)
|
||||
* - 作业 / 通知:所有教师
|
||||
* - 成绩录入:仅管理员(成绩写入仅管理员;教师仍可在仪表盘查看汇总)
|
||||
*/
|
||||
const menuItems = [
|
||||
{ path: '/dashboard', title: '仪表盘', icon: 'Odometer', roles: ['ADMIN', 'TEACHER'] },
|
||||
@@ -28,10 +29,10 @@ const menuItems = [
|
||||
{ path: '/schedules', title: '课表管理', icon: 'Calendar', roles: ['ADMIN', 'TEACHER'] },
|
||||
{ path: '/homeworks', title: '作业发布', icon: 'Notebook', roles: ['ADMIN', 'TEACHER'] },
|
||||
{ path: '/notifications', title: '通知发布', icon: 'Bell', roles: ['ADMIN', 'TEACHER'] },
|
||||
{ path: '/grades', title: '成绩录入', icon: 'Trophy', roles: ['ADMIN'] },
|
||||
{ path: '/grades', title: '成绩录入', icon: 'Trophy', roles: ['ADMIN', 'TEACHER'] },
|
||||
];
|
||||
|
||||
/** 当前账号可见的菜单(教师看不到班级/学生/成绩录入等无权入口) */
|
||||
/** 当前账号可见的菜单(班级/学生管理仅管理员可见) */
|
||||
const visibleMenuItems = computed(() =>
|
||||
menuItems.filter((item) => (auth.role ? item.roles.includes(auth.role) : false)),
|
||||
);
|
||||
|
||||
@@ -20,8 +20,16 @@ import { useRealtimeStore } from '@/stores/realtime';
|
||||
|
||||
const auth = useAuthStore();
|
||||
|
||||
/** 成绩写入仅管理员(教师仍可查看列表与统计,符合需求 7 的权限清单) */
|
||||
const isAdmin = computed(() => auth.role === 'ADMIN');
|
||||
/**
|
||||
* 成绩写入/导入权限:管理员,或"所选班级的班主任"(需求 6:老师端可用且不越权)。
|
||||
* 科任老师只能查看列表与统计,写入会被后端 403(需求 7:科任仅作业/叫人/通知)。
|
||||
*/
|
||||
const canManageGrades = computed(() => {
|
||||
if (auth.role === 'ADMIN') return true;
|
||||
if (auth.role !== 'TEACHER') return false;
|
||||
const current = classes.value.find((item) => item.id === filter.classId);
|
||||
return Boolean(current && current.teacherId === auth.user?.id);
|
||||
});
|
||||
const realtime = useRealtimeStore();
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -295,9 +303,15 @@ onUnmounted(() => {
|
||||
<el-select v-model="filter.examName" placeholder="全部考试" clearable style="width: 160px">
|
||||
<el-option v-for="item in examOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<el-button v-if="isAdmin" type="primary" :icon="'Plus'" @click="openSingle">单条录入</el-button>
|
||||
<el-button v-if="isAdmin" type="success" :icon="'Upload'" @click="openBulk">批量录入</el-button>
|
||||
<el-button v-if="isAdmin" type="warning" :icon="'Document'" @click="openImport">导入表格</el-button>
|
||||
<el-button v-if="canManageGrades" type="primary" :icon="'Plus'" @click="openSingle">
|
||||
单条录入
|
||||
</el-button>
|
||||
<el-button v-if="canManageGrades" type="success" :icon="'Upload'" @click="openBulk">
|
||||
批量录入
|
||||
</el-button>
|
||||
<el-button v-if="canManageGrades" type="warning" :icon="'Document'" @click="openImport">
|
||||
导入表格
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -358,7 +372,7 @@ onUnmounted(() => {
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="isAdmin" link type="danger" @click="removeGrade(row)">删除</el-button>
|
||||
<el-button v-if="canManageGrades" link type="danger" @click="removeGrade(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
Reference in new issue
Block a user