2026-09-12 15:02:02 +08:00
|
|
|
|
import type { UserRole } from './types.js';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 班级内的角色(相对"某一个班级"而言)。
|
|
|
|
|
|
*
|
2026-10-06 19:41:18 +08:00
|
|
|
|
* - `ADMIN` 系统管理员:全部班级、全部科目
|
|
|
|
|
|
* - `HEAD` 班主任(`Class.teacherId`):本班学生名单、本班课表、本班汇总。
|
|
|
|
|
|
* **不自动拥有本班所有科目的作业/成绩编辑权** —— 只有当他同时是
|
|
|
|
|
|
* 该科科任老师(`Course.teacherId === 他`)时才有那一科的权限。
|
|
|
|
|
|
* - `SUBJECT` 科任老师(本班存在 `Course.teacherId === 他`):只能动自己任教科目的作业与成绩
|
|
|
|
|
|
* - `CLASS_DEVICE` ClassHelper 班级端(班级码 + 班级密码登录):代本班读写个人数据
|
|
|
|
|
|
* - `NONE` 与该班级无关
|
2026-09-12 15:02:02 +08:00
|
|
|
|
*/
|
2026-10-06 19:41:18 +08:00
|
|
|
|
export type ClassRole = 'ADMIN' | 'HEAD' | 'SUBJECT' | 'CLASS_DEVICE' | 'NONE';
|
2026-09-12 15:02:02 +08:00
|
|
|
|
|
|
|
|
|
|
export interface ClassRoleContext {
|
|
|
|
|
|
role: UserRole;
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/** 是否是该班班主任(`Class.teacherId === 当前用户`) */
|
2026-09-12 15:02:02 +08:00
|
|
|
|
isHeadTeacher?: boolean;
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 是否在该班任教(该班存在 `Course.teacherId === 当前用户`)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 判定源是 **`Course`**(班级 + 科目 + 教师)这一条唯一的任课关系,
|
|
|
|
|
|
* 不再是过去那张没有科目的 `ClassTeacher` 协作表。
|
|
|
|
|
|
*/
|
|
|
|
|
|
teachesSubjects?: boolean;
|
2026-09-12 15:02:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 依据账号角色与该班归属关系,解析出班级内角色 */
|
|
|
|
|
|
export function resolveClassRole(context: ClassRoleContext): ClassRole {
|
|
|
|
|
|
if (context.role === 'ADMIN') return 'ADMIN';
|
2026-10-06 19:41:18 +08:00
|
|
|
|
if (context.role === 'CLASS_DEVICE') return 'CLASS_DEVICE';
|
2026-09-12 15:02:02 +08:00
|
|
|
|
if (context.role === 'TEACHER') {
|
2026-10-06 19:41:18 +08:00
|
|
|
|
// 班主任同时是科任老师时取更高的那一个;权限在下面各 canXxx 里合并,不会丢科任权限
|
2026-09-12 15:02:02 +08:00
|
|
|
|
if (context.isHeadTeacher) return 'HEAD';
|
2026-10-06 19:41:18 +08:00
|
|
|
|
if (context.teachesSubjects) return 'SUBJECT';
|
2026-09-12 15:02:02 +08:00
|
|
|
|
return 'NONE';
|
|
|
|
|
|
}
|
|
|
|
|
|
return 'NONE';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 班级本身(创建/修改/删除)与人员分配:仅管理员 */
|
|
|
|
|
|
export function canManageClasses(role: UserRole): boolean {
|
|
|
|
|
|
return role === 'ADMIN';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/** 班主任与科任老师的分配:仅管理员 */
|
2026-09-12 15:02:02 +08:00
|
|
|
|
export function canAssignTeachers(role: UserRole): boolean {
|
|
|
|
|
|
return role === 'ADMIN';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/** 学生名单(增删学生、调班、导入名单):管理员,或**本班班主任** */
|
|
|
|
|
|
export function canManageRoster(classRole: ClassRole): boolean {
|
|
|
|
|
|
return classRole === 'ADMIN' || classRole === 'HEAD';
|
2026-09-12 15:02:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 课表管理:管理员或本班班主任(科任老师不可以) */
|
|
|
|
|
|
export function canManageSchedule(classRole: ClassRole): boolean {
|
|
|
|
|
|
return classRole === 'ADMIN' || classRole === 'HEAD';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 课程(课表的前置数据):与课表同权限 */
|
|
|
|
|
|
export function canManageCourses(classRole: ClassRole): boolean {
|
|
|
|
|
|
return canManageSchedule(classRole);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 发布通知 / 叫人:管理员、班主任、科任老师皆可。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 通知与"叫人"是**面向全班**的信息,不像作业/成绩那样挂在某一科目上,
|
|
|
|
|
|
* 因此凡是与本班有关联的教师(班主任或任课老师)都能发;ClassHelper 班级端也可以。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function canPublishNotification(classRole: ClassRole): boolean {
|
|
|
|
|
|
return classRole === 'ADMIN' || classRole === 'HEAD' || classRole === 'SUBJECT' || classRole === 'CLASS_DEVICE';
|
2026-09-12 15:02:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-12 16:21:39 +08:00
|
|
|
|
/**
|
2026-10-06 19:41:18 +08:00
|
|
|
|
* 成绩录入 / 修改 / 导入的**班级维度**准入:管理员,或**本班班主任**。
|
2026-09-12 16:21:39 +08:00
|
|
|
|
*
|
2026-10-06 19:41:18 +08:00
|
|
|
|
* 注意这只是第一道门:真正能不能动某一条成绩,还要看**科目**是否由他任教
|
|
|
|
|
|
* (见服务端的 `assertCanManageCourseContent`)—— 班主任不教这一科就改不了这一科。
|
2026-09-12 16:21:39 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export function canManageGrades(classRole: ClassRole): boolean {
|
|
|
|
|
|
return classRole === 'ADMIN' || classRole === 'HEAD';
|
2026-09-12 15:02:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 科目维度的写权限(作业与成绩共用的那道细门):
|
|
|
|
|
|
*
|
|
|
|
|
|
* | 情形 | 谁可以写 |
|
|
|
|
|
|
* | ---- | ---- |
|
|
|
|
|
|
* | 管理员 | 全部 |
|
|
|
|
|
|
* | ClassHelper 班级端 | 仅当传入 `allowClassDevice`(**作业**如此;成绩不给,与既有行为一致) |
|
|
|
|
|
|
* | 作业/成绩**选了科目** | 该科的任课老师(`Course.teacherId`) |
|
|
|
|
|
|
* | 作业/成绩**没选科目** | 本班班主任 |
|
|
|
|
|
|
*
|
|
|
|
|
|
* 这是需求「班主任不自动拥有所有科目作业成绩编辑权」的落点。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function canManageSubjectContent(input: {
|
|
|
|
|
|
classRole: ClassRole;
|
|
|
|
|
|
/** 目标是否挂在某个科目(Course)上 */
|
|
|
|
|
|
hasSubject: boolean;
|
|
|
|
|
|
/** 当前用户是否是该科目的任课老师 */
|
|
|
|
|
|
isSubjectTeacherOfCourse: boolean;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 是否允许 ClassHelper 班级端代写。
|
|
|
|
|
|
* 教室机器可以录**作业**(老师在教室随手录一条很常见,见 AGENTS.md §5 第 20 条),
|
|
|
|
|
|
* 但**成绩**不给 —— 成绩是面向家长/学生的评价数据,仍由教师在 Web 端录入。
|
|
|
|
|
|
*/
|
|
|
|
|
|
allowClassDevice?: boolean;
|
|
|
|
|
|
}): boolean {
|
|
|
|
|
|
if (input.classRole === 'ADMIN') return true;
|
|
|
|
|
|
if (input.classRole === 'CLASS_DEVICE') return input.allowClassDevice === true;
|
|
|
|
|
|
if (input.hasSubject) return input.isSubjectTeacherOfCourse;
|
|
|
|
|
|
return input.classRole === 'HEAD';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 按学号查询学生成绩明细的准入。
|
|
|
|
|
|
*
|
|
|
|
|
|
* - 教师/管理员:班主任看本班、科任看本班、管理员看全部(都是"有权限就能看明细");
|
|
|
|
|
|
* - ClassHelper 班级端:需要该班打开 `studentGradeQueryEnabled` —— 这就是
|
|
|
|
|
|
* 「是否允许学生通过班级端查看本人明细,由教师或管理员开关控制」。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function canQueryStudentGrades(input: { classRole: ClassRole; gradeQueryEnabled: boolean }): boolean {
|
|
|
|
|
|
if (input.classRole === 'ADMIN' || input.classRole === 'HEAD' || input.classRole === 'SUBJECT') return true;
|
|
|
|
|
|
if (input.classRole === 'CLASS_DEVICE') return input.gradeQueryEnabled;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-12 15:02:02 +08:00
|
|
|
|
/** 可导入的数据类型 */
|
2026-10-06 19:41:18 +08:00
|
|
|
|
export type ImportKind = 'grades' | 'students' | 'teachers' | 'classTeachers' | 'scheduleTimes';
|
2026-09-12 15:02:02 +08:00
|
|
|
|
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 导入权限:
|
|
|
|
|
|
* - 学生名单 / 教师名单:`students` 允许管理员或本班班主任;`teachers` 仍仅管理员
|
|
|
|
|
|
* - 成绩:管理员或本班班主任(再按科目收口)
|
|
|
|
|
|
* - 班级任课老师:仅管理员(它直接改写班主任与任课关系)
|
|
|
|
|
|
* - 课表时间:管理员或本班班主任
|
|
|
|
|
|
*/
|
2026-09-12 15:02:02 +08:00
|
|
|
|
export function canImport(role: UserRole, classRole: ClassRole, kind: ImportKind): boolean {
|
2026-10-06 19:41:18 +08:00
|
|
|
|
if (kind === 'teachers' || kind === 'classTeachers') return canManageClasses(role);
|
|
|
|
|
|
if (kind === 'students') return canManageRoster(classRole);
|
2026-09-12 15:02:02 +08:00
|
|
|
|
if (kind === 'scheduleTimes') return canManageSchedule(classRole);
|
2026-09-12 16:21:39 +08:00
|
|
|
|
return canManageGrades(classRole);
|
2026-09-12 15:02:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 人类可读的权限矩阵(README / 设置页展示,单一事实来源) */
|
|
|
|
|
|
export const PERMISSION_MATRIX: readonly {
|
|
|
|
|
|
action: string;
|
|
|
|
|
|
admin: boolean;
|
|
|
|
|
|
head: boolean;
|
|
|
|
|
|
subject: boolean;
|
2026-10-06 19:41:18 +08:00
|
|
|
|
classDevice?: boolean;
|
2026-09-12 15:02:02 +08:00
|
|
|
|
}[] = [
|
|
|
|
|
|
{ action: '班级创建 / 修改 / 删除', admin: true, head: false, subject: false },
|
2026-10-06 19:41:18 +08:00
|
|
|
|
{ action: '分配班主任与各科科任老师', admin: true, head: false, subject: false },
|
|
|
|
|
|
{ action: '学生名单管理(增删 / 导入 / 调班)', admin: true, head: true, subject: false },
|
2026-09-13 10:34:46 +08:00
|
|
|
|
{ action: '教师录入(新建 / 导入名单 / 重置密码)', admin: true, head: false, subject: false },
|
2026-09-12 15:02:02 +08:00
|
|
|
|
{ action: '课表管理(增删改 / 时间配置导入)', admin: true, head: true, subject: false },
|
2026-10-06 19:41:18 +08:00
|
|
|
|
{ action: '布置作业(自己任教的科目)', admin: true, head: true, subject: true },
|
|
|
|
|
|
{ action: '叫人', admin: true, head: true, subject: true, classDevice: true },
|
|
|
|
|
|
{ action: '发布通知', admin: true, head: true, subject: true, classDevice: true },
|
|
|
|
|
|
{ action: '成绩录入 / 修改 / 导入(自己任教的科目)', admin: true, head: true, subject: true },
|
|
|
|
|
|
{ action: '查看本班成绩汇总', admin: true, head: true, subject: true },
|
|
|
|
|
|
{ action: '按学号查询学生成绩明细', admin: true, head: true, subject: true, classDevice: true },
|
|
|
|
|
|
{ action: '查看 ClassHelper 班级端在线状态', admin: true, head: true, subject: true },
|
2026-09-12 15:02:02 +08:00
|
|
|
|
];
|