已修改完成,同时解决了“考生名单都是 0 人”的根因。

考生范围统一为“行政班关联学生 + 已选课学生”,并自动去重;考场分配、人数显示、冲突检测、学生考试安排均使用同一口径。[TeachingTaskRosterQuery.cs (line 40)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Teaching/TeachingTaskRosterQuery.cs:40)
新增考场签名单 Excel 导出接口。[ExamsController.cs (line 568)](E:/jiaowu/src/Jiaowu.Api/Controllers/ExamsController.cs:568)
考试计划页面新增“导出考场签名单”按钮。[ExamsView.vue (line 255)](E:/jiaowu/web/src/views/ExamsView.vue:255)
工作簿包含“考场汇总”以及每个考试场次独立的 A4 签到表,字段包括座位号、学号、姓名、行政班、考生签名和备注。[ExamSignInWorkbookExporter.cs (line 12)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Exams/ExamSignInWorkbookExporter.cs:12)
未分配考场或监考教师时,也能导出并显示“待分配”。
This commit is contained in:
2026-07-27 18:24:32 +08:00 Unverified
parent 2e422acd19
commit fb2acc6751
9 changed files with 856 additions and 71 deletions
@@ -1,5 +1,6 @@
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
namespace Jiaowu.Api.Infrastructure.Teaching;
@@ -15,4 +16,107 @@ public static class TeachingTaskRosterQuery
enrollment.StudentId == student.Id &&
enrollment.Status == CourseEnrollmentStatus.Enrolled &&
enrollment.CourseSelectionOffering!.TeachingTaskId == teachingTaskId));
public static IQueryable<Guid> TaskIdsForStudent(AppDbContext db, Guid studentId)
{
var classTaskIds = db.Students
.Where(student =>
student.Id == studentId &&
student.Status == StudentStatus.Active)
.SelectMany(student => db.TeachingTaskClasses
.Where(assignment =>
assignment.AdministrativeClassId ==
student.AdministrativeClassId)
.Select(assignment => assignment.TeachingTaskId));
var enrolledTaskIds = db.CourseEnrollments
.Where(enrollment =>
enrollment.StudentId == studentId &&
enrollment.Status == CourseEnrollmentStatus.Enrolled)
.Select(enrollment =>
enrollment.CourseSelectionOffering!.TeachingTaskId);
return classTaskIds.Concat(enrolledTaskIds).Distinct();
}
public static async Task<IReadOnlyList<TeachingTaskRosterEntry>> LoadForTasksAsync(
AppDbContext db,
IEnumerable<Guid> teachingTaskIds,
CancellationToken cancellationToken)
{
var taskIds = teachingTaskIds.Distinct().ToArray();
if (taskIds.Length == 0) return [];
var assignments = await db.TeachingTaskClasses.AsNoTracking()
.WhereIn(taskIds, x => x.TeachingTaskId)
.Select(x => new
{
x.TeachingTaskId,
x.AdministrativeClassId
})
.ToListAsync(cancellationToken);
var classIds = assignments
.Select(x => x.AdministrativeClassId)
.Distinct()
.ToArray();
var classStudents = classIds.Length == 0
? []
: await db.Students.AsNoTracking()
.Where(x => x.Status == StudentStatus.Active)
.WhereIn(classIds, x => x.AdministrativeClassId)
.Select(x => new RosterStudent(
x.Id,
x.StudentNumber,
x.Name,
x.AdministrativeClassId,
x.AdministrativeClass!.Name))
.ToListAsync(cancellationToken);
var enrolledStudents = await db.CourseEnrollments.AsNoTracking()
.Where(x => x.Status == CourseEnrollmentStatus.Enrolled)
.WhereIn(taskIds, x => x.CourseSelectionOffering!.TeachingTaskId)
.Select(x => new TeachingTaskRosterEntry(
x.CourseSelectionOffering!.TeachingTaskId,
x.StudentId,
x.Student!.StudentNumber,
x.Student.Name,
x.Student.AdministrativeClass!.Name))
.ToListAsync(cancellationToken);
var result = enrolledStudents.ToDictionary(
x => (x.TeachingTaskId, x.StudentId));
var studentsByClass = classStudents.ToLookup(x => x.AdministrativeClassId);
foreach (var assignment in assignments)
{
foreach (var student in studentsByClass[assignment.AdministrativeClassId])
{
result.TryAdd(
(assignment.TeachingTaskId, student.StudentId),
new TeachingTaskRosterEntry(
assignment.TeachingTaskId,
student.StudentId,
student.StudentNumber,
student.Name,
student.ClassName));
}
}
return result.Values
.OrderBy(x => x.TeachingTaskId)
.ThenBy(x => x.StudentNumber)
.ToList();
}
private sealed record RosterStudent(
Guid StudentId,
string StudentNumber,
string Name,
Guid AdministrativeClassId,
string ClassName);
}
public sealed record TeachingTaskRosterEntry(
Guid TeachingTaskId,
Guid StudentId,
string StudentNumber,
string Name,
string ClassName);