This commit is contained in:
2026-08-10 09:35:57 +08:00 Unverified
parent c25f16ec20
commit 4be61ab921
4 changed files with 72 additions and 30 deletions
@@ -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,
@@ -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()
{
+39 -27
View File
@@ -151,21 +151,14 @@ const navigationGroups = computed<NavigationGroup[]>(() => [
),
...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<NavigationGroup[]>(() => [
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<NavigationGroup[]>(() => [
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<NavigationGroup[]>(() => [
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<NavigationGroup[]>(() => [
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: '学业预警' },
),
],
},
{
+3
View File
@@ -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; }