From eff4201cb7c02cc21ad79e8c21536f4f34beb1e9 Mon Sep 17 00:00:00 2001 From: chenyuan Date: Wed, 2 Sep 2026 22:26:46 +0800 Subject: [PATCH] fix: scope teacher students and grant admin access --- src/views/teacherEnd/class/index.vue | 5 ++++ src/views/teacherEnd/student/index.vue | 23 +++++++++++++++---- ...teacher-admin-class-access.static.test.cjs | 16 +++++++++++++ ...r-owned-student-management.static.test.cjs | 5 ++++ 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 tests/teacher-admin-class-access.static.test.cjs diff --git a/src/views/teacherEnd/class/index.vue b/src/views/teacherEnd/class/index.vue index 8376ff9..1c414c3 100644 --- a/src/views/teacherEnd/class/index.vue +++ b/src/views/teacherEnd/class/index.vue @@ -190,6 +190,7 @@ const selectedTeachingClass = ref(null); const currentUserId = computed(() => userStore.userInfo.userId); const currentSchoolId = computed(() => userStore.userInfo.schoolId); +const isManagerTeacher = computed(() => userStore.userInfo.teacherAdmin === true); const legacyAdminEnabled = computed(() => userStore.schoolProductConfig?.organizationMode !== "TEACHING_CLASS_ONLY"); const facultyManagementEnabled = computed(() => userStore.schoolProductConfig?.facultyManagementEnabled ?? legacyAdminEnabled.value); const majorManagementEnabled = computed(() => userStore.schoolProductConfig?.majorManagementEnabled ?? legacyAdminEnabled.value); @@ -290,6 +291,7 @@ function defaultMemberParams() { schoolClassId: "", name: "", userName: "", + operatorId: currentUserId.value, }; } @@ -304,6 +306,9 @@ function canOperateClass(schoolClass) { if (schoolClass?.dataSource === "ZHIYUN") { return false; } + if (isManagerTeacher.value) { + return !schoolClass?.schoolId || String(schoolClass.schoolId) === String(currentSchoolId.value); + } if (normalizeClassType(schoolClass) === "ADMIN") { return !schoolClass?.schoolId || schoolClass.schoolId === currentSchoolId.value; } diff --git a/src/views/teacherEnd/student/index.vue b/src/views/teacherEnd/student/index.vue index 56f7e9c..cc84d8d 100644 --- a/src/views/teacherEnd/student/index.vue +++ b/src/views/teacherEnd/student/index.vue @@ -11,7 +11,7 @@
班级: - +
@@ -403,6 +403,11 @@ const normalizeClassType = (schoolClass) => { return schoolClass?.createdBy ? "TEACHING" : "ADMIN"; }; const sameValue = (left, right) => String(left || "") === String(right || ""); +const classCreateTime = (schoolClass) => { + if (!schoolClass?.createTime) return Number.MAX_SAFE_INTEGER; + const value = new Date(schoolClass.createTime).getTime(); + return Number.isNaN(value) ? Number.MAX_SAFE_INTEGER : value; +}; const canUseAdminClassForStudent = (schoolClass) => { if (normalizeClassType(schoolClass) !== "ADMIN") return false; return !schoolClass?.schoolId || sameValue(schoolClass.schoolId, currentSchoolId.value); @@ -414,7 +419,7 @@ const teachingClassOptions = computed(() => normalizeClassType(schoolClass) === "TEACHING" && sameValue(schoolClass.schoolId, currentSchoolId.value) && (isManagerTeacher.value || sameValue(schoolClass.createdBy, currentUserId.value)) - ) + ).sort((left, right) => classCreateTime(left) - classCreateTime(right)) ); const studentClassOptions = computed(() => teachingClassOptions.value); const currentTeachingClassId = () => { @@ -430,10 +435,19 @@ const beforeImportStudents = (file) => { return true; }; const getClassList = () => { - indexApi.getClassListBySchoolId({ schoolId: userStore.userInfo.schoolId }).then((res) => { + return indexApi.getClassListBySchoolId({ schoolId: userStore.userInfo.schoolId }).then((res) => { classList.value = res.data; }); }; +const selectDefaultStudentClass = () => { + if (!isManagerTeacher.value && !paramsquery.value.schoolClassId) { + paramsquery.value.schoolClassId = teachingClassOptions.value[0]?.schoolClassId || ""; + } +}; +const handleClassFilterChange = () => { + paramsquery.value.index = 1; + getList(); +}; const classList1 = ref([]); // 根据专业查班级 const getClassByMajor = () => { @@ -484,8 +498,9 @@ const initPassword = (row, status) => { }; onMounted(async () => { await userStore.ensureSchoolProductConfig().catch(() => null); + await getClassList(); + selectDefaultStudentClass(); getList(); - getClassList(); if (!isTeacherRosterMode.value) { getFacultyList(); } diff --git a/tests/teacher-admin-class-access.static.test.cjs b/tests/teacher-admin-class-access.static.test.cjs new file mode 100644 index 0000000..0f9dfe8 --- /dev/null +++ b/tests/teacher-admin-class-access.static.test.cjs @@ -0,0 +1,16 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const page = fs.readFileSync( + path.resolve(__dirname, "../src/views/teacherEnd/class/index.vue"), + "utf8" +); + +assert(page.includes("const isManagerTeacher = computed(() => userStore.userInfo.teacherAdmin === true);")); +assert(/if \(isManagerTeacher\.value\) \{[\s\S]*schoolClass\.schoolId[\s\S]*currentSchoolId\.value[\s\S]*\}/.test(page)); +assert(page.includes("const selectedTeachingClassCanOperate = computed(() => canOperateClass(selectedTeachingClass.value));")); +assert(/function defaultMemberParams\(\) \{[\s\S]*operatorId: currentUserId\.value,[\s\S]*\}/.test(page)); +assert(page.includes('v-if="canOperateClass(row.schoolClass)"')); + +console.log("teacher administrator class access contract passed"); diff --git a/tests/teacher-owned-student-management.static.test.cjs b/tests/teacher-owned-student-management.static.test.cjs index 613ca94..6a0773b 100644 --- a/tests/teacher-owned-student-management.static.test.cjs +++ b/tests/teacher-owned-student-management.static.test.cjs @@ -9,6 +9,11 @@ assert(page.includes("operatorId: currentUserId.value"), "学生列表必须携 assert(page.includes("const isManagerTeacher = computed(() => userStore.userInfo.teacherAdmin === true);"), "教师管理员身份必须由当前登录账号识别"); assert(page.includes("const studentClassOptions = computed(() => teachingClassOptions.value);"), "学生筛选下拉框必须只展示教学班"); assert(page.includes("isManagerTeacher.value || sameValue(schoolClass.createdBy, currentUserId.value)"), "教师管理员必须可查看本校全部教学班"); +assert(page.includes(".sort((left, right) => classCreateTime(left) - classCreateTime(right))"), "普通教师的班级选项必须按创建时间升序排列"); +assert(page.includes("if (!isManagerTeacher.value && !paramsquery.value.schoolClassId)"), "普通教师必须默认选中自己最早创建的教学班"); +assert(page.includes('paramsquery.value.schoolClassId = teachingClassOptions.value[0]?.schoolClassId || "";'), "默认班级必须取排序后的第一个教学班"); +assert(page.includes("await getClassList();\n selectDefaultStudentClass();\n getList();"), "学生列表必须在默认班级确定后查询"); +assert(page.includes('@change="handleClassFilterChange"'), "切换班级后必须立即刷新学生列表"); assert(page.includes("const resolveRowTeachingClassId = (row) =>"), "未选择班级时操作必须从学生行解析教学班"); assert(page.includes('class="student-action-group"'), "学生操作按钮必须使用固定操作组布局"); assert(page.includes("flex-wrap: nowrap"), "学生操作按钮不得换行");