diff --git a/src/Jiaowu.Api/Infrastructure/Grades/ExperimentGradeAggregationService.cs b/src/Jiaowu.Api/Infrastructure/Grades/ExperimentGradeAggregationService.cs index e9baff4..e30c0bf 100644 --- a/src/Jiaowu.Api/Infrastructure/Grades/ExperimentGradeAggregationService.cs +++ b/src/Jiaowu.Api/Infrastructure/Grades/ExperimentGradeAggregationService.cs @@ -58,9 +58,8 @@ public static class ExperimentGradeAggregationService cancellationToken); var requestedStudentIds = studentIds.Distinct().ToArray(); var persistedScores = await db.ExperimentCourseGrades.AsNoTracking() - .Where(x => - x.TeachingTaskId == teachingTaskId && - requestedStudentIds.Contains(x.StudentId)) + .Where(x => x.TeachingTaskId == teachingTaskId) + .WhereIn(requestedStudentIds, x => x.StudentId) .ToDictionaryAsync( x => x.StudentId, x => x.WeightedAverageScore, diff --git a/tests/Jiaowu.Api.Tests/MySqlMigrationTests.cs b/tests/Jiaowu.Api.Tests/MySqlMigrationTests.cs index 724dfeb..63e038a 100644 --- a/tests/Jiaowu.Api.Tests/MySqlMigrationTests.cs +++ b/tests/Jiaowu.Api.Tests/MySqlMigrationTests.cs @@ -109,6 +109,34 @@ public sealed class MySqlMigrationTests Assert.Contains(ids[1].ToString(), sql, StringComparison.OrdinalIgnoreCase); } + [Fact] + public void MySql_experiment_course_grade_lookup_uses_provider_safe_predicates() + { + using var db = new AppDbContext(CreateMySqlOptions()); + var teachingTaskId = Guid.Parse( + "33333333-3333-3333-3333-333333333333"); + var studentIds = new[] + { + Guid.Parse("11111111-1111-1111-1111-111111111111"), + Guid.Parse("22222222-2222-2222-2222-222222222222") + }; + + var sql = db.ExperimentCourseGrades + .Where(x => x.TeachingTaskId == teachingTaskId) + .WhereIn(studentIds, x => x.StudentId) + .ToQueryString(); + + Assert.Contains(" IN (", sql, StringComparison.OrdinalIgnoreCase); + Assert.Contains( + teachingTaskId.ToString(), + sql, + StringComparison.OrdinalIgnoreCase); + Assert.All(studentIds, studentId => Assert.Contains( + studentId.ToString(), + sql, + StringComparison.OrdinalIgnoreCase)); + } + [Fact] public void MySql_attendance_device_hash_query_uses_provider_safe_predicates() { diff --git a/web/src/layouts/AdminLayout.vue b/web/src/layouts/AdminLayout.vue index 7d97de7..d022158 100644 --- a/web/src/layouts/AdminLayout.vue +++ b/web/src/layouts/AdminLayout.vue @@ -151,21 +151,14 @@ const navigationGroups = computed(() => [ ), ...whenVisible(isStudent.value || isTeacher.value, { path: '/my-timetable', label: isTeacher.value ? '我的授课课表' : '我的课表' }), ...whenVisible(isStudent.value, { path: '/free-classrooms', label: '空闲教室' }), + { + path: '/class-timetable', + label: isTimetableManager.value ? '课表查询中心' : '班级课表查询', + }, { path: '/classroom-reservations', label: hasAnyRole(['CollegeAdmin']) ? '教室借用审核' : '教室借用', }, - ...whenVisible(isTeacher.value || isTeachingAdmin.value || hasAnyRole(['Counselor']), { path: '/teacher-attendance', label: '教学点名' }), - ...whenVisible(isStudent.value, { path: '/my-attendance', label: '我的考勤' }), - { - path: '/evaluations', - label: isStudent.value ? '学生评教' : isTeacher.value ? '评教结果' : '教学评价', - }, - ...whenVisible(isTeacher.value, { path: '/teacher-roster', label: '选课名单' }), - ...whenVisible(!isStudent.value, { - path: '/class-timetable', - label: isTimetableManager.value ? '课表查询中心' : '班级课表查询', - }), ...whenVisible( hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Student']), { @@ -173,6 +166,26 @@ const navigationGroups = computed(() => [ label: isTeachingAdmin.value ? '选课管理' : '学生选课', }, ), + ...whenVisible( + hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Teacher']), + { + path: '/course-adjustments', + label: isTeacher.value ? '我的调停课' : '调停课审核', + }, + ), + ], + }, + { + key: 'teaching-assessment', + label: '教学考核', + items: [ + ...whenVisible(isTeacher.value || isTeachingAdmin.value || hasAnyRole(['Counselor']), { path: '/teacher-attendance', label: '教学点名' }), + ...whenVisible(isStudent.value, { path: '/my-attendance', label: '我的考勤' }), + { + path: '/evaluations', + label: isStudent.value ? '学生评教' : isTeacher.value ? '评教结果' : '教学评价', + }, + ...whenVisible(isTeacher.value, { path: '/teacher-roster', label: '选课名单' }), ...whenVisible( hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Teacher', 'Student', 'Counselor']), { path: '/grades', label: isStudent.value ? '学业成绩' : isTeacher.value ? '成绩录入' : '成绩管理' }, @@ -181,6 +194,12 @@ const navigationGroups = computed(() => [ hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Leader', 'Teacher']), { path: '/grade-analytics', label: isTeacher.value ? '教学班成绩分析' : '成绩分析中心' }, ), + ], + }, + { + key: 'examinations', + label: '考试管理', + items: [ ...whenVisible( hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'Student']), { path: '/other-exams', label: '其他考试成绩' }, @@ -199,26 +218,11 @@ const navigationGroups = computed(() => [ label: isStudent.value ? '我的补考' : isTeacher.value ? '补考监考' : '补考安排', }, ), - ...whenVisible( - hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Teacher']), - { - path: '/course-adjustments', - label: isTeacher.value ? '我的调停课' : '调停课审核', - }, - ), - ...whenVisible( - hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor', 'Teacher', 'Student']), - { path: '/approvals', label: '审批中心' }, - ), - ...whenVisible( - hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'Counselor', 'Student']), - { path: '/warnings', label: '学业预警' }, - ), ], }, { key: 'student-status', - label: '学籍管理', + label: '学籍与服务', items: [ ...whenVisible( isStudent.value, @@ -232,6 +236,14 @@ const navigationGroups = computed(() => [ hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Student']), { path: '/official-documents', label: isStudent.value ? '电子成绩单与证明' : '官方凭证签发' }, ), + ...whenVisible( + hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor', 'Teacher', 'Student']), + { path: '/approvals', label: '审批中心' }, + ), + ...whenVisible( + hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'Counselor', 'Student']), + { path: '/warnings', label: '学业预警' }, + ), ], }, { diff --git a/web/src/style.css b/web/src/style.css index 3213825..c9389a8 100644 --- a/web/src/style.css +++ b/web/src/style.css @@ -1274,6 +1274,9 @@ button { cursor: pointer; } .sso-binding-card > a { color: #315b73; font-size: 13px; font-weight: 650; text-decoration: none; } @media (max-width: 1100px) { + .desktop-navigation { padding-inline: 20px; } + .horizontal-menu.el-menu--horizontal > .el-menu-item, + .horizontal-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title { padding-inline: 12px; } .metric-grid { grid-template-columns: repeat(2, 1fr); } .dashboard-grid { grid-template-columns: 1fr; } .login-page { grid-template-columns: 1fr 430px; }