班级查询按“考试场次”合并,显示为“分散至 N 个考场”,不再生成大量重复卡片。[后端实现 (line 416)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Timetables/TimetableDataService.cs:416)

学生登录后只显示本人座位对应的考场,同时修复混排考场可能带出其他课程的问题。[筛选逻辑 (line 561)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Timetables/TimetableDataService.cs:561)
多考场卡片提示学生登录查看本人考场及监考教师。[前端展示 (line 1122)](E:/jiaowu/web/src/views/TimetableView.vue:1122)
教室课表、监考教师课表仍按具体考场显示,不受合并影响。
已增加班级合并和学生精准考场回归测试。[测试 (line 183)](E:/jiaowu/tests/Jiaowu.Api.Tests/ExamArrangementServiceTests.cs:183)
This commit is contained in:
2026-08-02 17:09:26 +08:00 Unverified
parent f0f59419d5
commit 8cb9b4f57b
3 changed files with 98 additions and 9 deletions
@@ -340,6 +340,7 @@ public sealed class TimetableDataService(AppDbContext db)
.OrderBy(x => x.ExamDate)
.ThenBy(x => x.StartPeriod)
.Select(x => new ExamSessionProjection(
x.Id,
x.Id,
x.TeachingTaskId,
x.TeachingTask!.TaskNumber,
@@ -384,6 +385,7 @@ public sealed class TimetableDataService(AppDbContext db)
.ThenBy(link => link.ExamRoom!.StartPeriod)
.ThenBy(link => link.ExamSession!.TeachingTask!.Course!.Code)
.Select(link => new ExamSessionProjection(
link.ExamSessionId,
link.ExamRoomId,
link.ExamSession!.TeachingTaskId,
link.ExamSession.TeachingTask!.TaskNumber,
@@ -408,8 +410,17 @@ public sealed class TimetableDataService(AppDbContext db)
link.ExamRoom.UpdatedAt))
.ToListAsync(cancellationToken);
result.AddRange(mixedSessions.Select(x =>
MapToEntryDto(x, slotLookup)));
if (resourceType == TimetableResourceType.Class && !studentId.HasValue)
{
result.AddRange(mixedSessions
.GroupBy(x => x.ExamSessionId)
.Select(group => MapClassExamEntryDto(group, slotLookup)));
}
else
{
result.AddRange(mixedSessions.Select(x =>
MapToEntryDto(x, slotLookup)));
}
return result;
}
@@ -545,10 +556,9 @@ public sealed class TimetableDataService(AppDbContext db)
if (studentId.HasValue)
{
return source.Where(link =>
link.ExamSession!.TeachingTask!.Classes.Any(c =>
c.AdministrativeClassId == resourceId) ||
link.ExamRoom!.Seats.Any(s =>
s.StudentId == studentId.Value));
s.StudentId == studentId.Value &&
s.ExamSessionId == link.ExamSessionId));
}
return source.Where(link =>
link.ExamSession!.TeachingTask!.Classes.Any(c =>
@@ -604,7 +614,7 @@ public sealed class TimetableDataService(AppDbContext db)
new[] { "监考:" + string.Join("、", x.InvigilatorNames) })
: x.TeacherNames;
return new TimetableEntryDto(
x.Id,
x.ExamRoomId,
x.TeachingTaskId,
x.TaskNumber,
x.TaskName,
@@ -627,8 +637,42 @@ public sealed class TimetableDataService(AppDbContext db)
x.UpdatedAt);
}
private static TimetableEntryDto MapClassExamEntryDto(
IEnumerable<ExamSessionProjection> sessions,
IReadOnlyDictionary<int, TimetableSlotDto> slotLookup)
{
var rooms = sessions.ToList();
var first = rooms[0];
var roomCount = rooms
.Select(x => x.ExamRoomId)
.Distinct()
.Count();
var buildingNames = rooms
.Select(x => x.BuildingName)
.Distinct()
.ToList();
var campusNames = rooms
.Select(x => x.CampusName)
.Distinct()
.ToList();
var entry = MapToEntryDto(first, slotLookup);
return entry with
{
Id = first.ExamSessionId,
TeacherNames = first.TeacherNames,
ClassroomName = roomCount == 1
? first.ClassroomName
: $"分散至 {roomCount} 个考场",
BuildingName = buildingNames.Count == 1 ? buildingNames[0] : null,
CampusName = campusNames.Count == 1 ? campusNames[0] : null,
UpdatedAt = rooms.Max(x => x.UpdatedAt),
ExamRoomCount = roomCount
};
}
private sealed record ExamSessionProjection(
Guid Id,
Guid ExamSessionId,
Guid ExamRoomId,
Guid TeachingTaskId,
string TaskNumber,
string TaskName,
@@ -759,7 +803,8 @@ public sealed record TimetableEntryDto(
string? ExperimentProjectName = null,
DateOnly? ExperimentDate = null,
ExperimentArrangementMode? ExperimentArrangementMode = null,
ScheduleEntryKind Kind = ScheduleEntryKind.Lecture);
ScheduleEntryKind Kind = ScheduleEntryKind.Lecture,
int ExamRoomCount = 1);
public sealed record FlexibleCourseDto(
Guid Id,