同学期、同课程、同教学班的实验项目合并展示,组内连续排列。

管理列表改为数据库筛选、按教学任务服务端分页(10–100 条/页)。
增加开课学院、课程名称/编码、教学班、任课教师、教学任务号筛选。
增加当前页草稿项目的批量选中、批量发布、批量删除;后端会再次校验权限、状态和发布条件。
确认框改为固定居中白色底板与阴影,不再出现只有文字按钮、没有底色的情况。
This commit is contained in:
biss committed 2026-08-09 19:44:45 +08:00
1 parent 25a1480739
commit 97aa23bc64
12 files changed
+793 -78

No files matched your search

@@ -31,12 +31,21 @@ public sealed class ExperimentsController(
[Authorize(Roles = Managers)] [Authorize(Roles = Managers)]
public async Task<ActionResult> GetOptions( public async Task<ActionResult> GetOptions(
Guid? academicTermId, Guid? academicTermId,
Guid? offeringCollegeId,
string? courseKeyword,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var tasks = AccessibleTeachingTasks().AsNoTracking() var tasks = AccessibleTeachingTasks().AsNoTracking()
.Where(x => x.Status == TeachingTaskStatus.Published); .Where(x => x.Status == TeachingTaskStatus.Published);
if (academicTermId.HasValue) if (academicTermId.HasValue)
tasks = tasks.Where(x => x.AcademicTermId == academicTermId); tasks = tasks.Where(x => x.AcademicTermId == academicTermId);
if (offeringCollegeId.HasValue)
tasks = tasks.Where(x => x.Course!.CollegeId == offeringCollegeId);
var normalizedCourseKeyword = Normalize(courseKeyword);
if (normalizedCourseKeyword is not null)
tasks = tasks.Where(x =>
x.Course!.Code.Contains(normalizedCourseKeyword) ||
x.Course.Name.Contains(normalizedCourseKeyword));
var periods = db.ScheduleTimeSlots.AsNoTracking() var periods = db.ScheduleTimeSlots.AsNoTracking()
.Where(x => x.IsEnabled); .Where(x => x.IsEnabled);
@@ -90,6 +99,13 @@ public sealed class ExperimentsController(
.OrderBy(item => item.AdministrativeClass!.Code) .OrderBy(item => item.AdministrativeClass!.Code)
.Select(item => item.AdministrativeClass!.Name) .Select(item => item.AdministrativeClass!.Name)
}) })
.Take(200)
.ToListAsync(cancellationToken),
Colleges = await AccessibleTeachingTasks().AsNoTracking()
.Where(x => !academicTermId.HasValue || x.AcademicTermId == academicTermId.Value)
.Select(x => new { x.Course!.CollegeId, CollegeName = x.Course.College!.Name })
.Distinct()
.OrderBy(x => x.CollegeName)
.ToListAsync(cancellationToken), .ToListAsync(cancellationToken),
ScheduleEntries = await db.ScheduleEntries.AsNoTracking() ScheduleEntries = await db.ScheduleEntries.AsNoTracking()
.Where(x => x.SchedulePlan!.Status == SchedulePlanStatus.Published && .Where(x => x.SchedulePlan!.Status == SchedulePlanStatus.Published &&
@@ -99,6 +115,11 @@ public sealed class ExperimentsController(
.Contains(x.TeachingTaskId)) .Contains(x.TeachingTaskId))
.Where(x => !academicTermId.HasValue || .Where(x => !academicTermId.HasValue ||
x.SchedulePlan!.AcademicTermId == academicTermId.Value) x.SchedulePlan!.AcademicTermId == academicTermId.Value)
.Where(x => !offeringCollegeId.HasValue ||
x.TeachingTask!.Course!.CollegeId == offeringCollegeId.Value)
.Where(x => normalizedCourseKeyword == null ||
x.TeachingTask!.Course!.Code.Contains(normalizedCourseKeyword) ||
x.TeachingTask.Course.Name.Contains(normalizedCourseKeyword))
.OrderBy(x => x.TeachingTask!.Course!.Code) .OrderBy(x => x.TeachingTask!.Course!.Code)
.ThenBy(x => x.TeachingTask!.TaskNumber) .ThenBy(x => x.TeachingTask!.TaskNumber)
.ThenBy(x => x.DayOfWeek) .ThenBy(x => x.DayOfWeek)
@@ -116,10 +137,14 @@ public sealed class ExperimentsController(
x.StartWeek, x.StartWeek,
x.EndWeek, x.EndWeek,
x.WeekPattern, x.WeekPattern,
ClassNames = x.TeachingTask!.Classes
.OrderBy(item => item.AdministrativeClass!.Code)
.Select(item => item.AdministrativeClass!.Name),
ClassroomName = x.Classroom!.Name, ClassroomName = x.Classroom!.Name,
BuildingName = x.Classroom.Building!.Name, BuildingName = x.Classroom.Building!.Name,
CampusName = x.Classroom.Building.Campus!.Name CampusName = x.Classroom.Building.Campus!.Name
}) })
.Take(500)
.ToListAsync(cancellationToken), .ToListAsync(cancellationToken),
Classrooms = await db.Classrooms.AsNoTracking() Classrooms = await db.Classrooms.AsNoTracking()
.Where(x => x.IsEnabled) .Where(x => x.IsEnabled)
@@ -146,7 +171,14 @@ public sealed class ExperimentsController(
Guid? academicTermId, Guid? academicTermId,
ExperimentArrangementMode? arrangementMode, ExperimentArrangementMode? arrangementMode,
ExperimentProjectStatus? status, ExperimentProjectStatus? status,
CancellationToken cancellationToken) Guid? offeringCollegeId,
string? courseKeyword,
string? classKeyword,
string? teacherKeyword,
string? taskKeyword,
CancellationToken cancellationToken,
int page = 1,
int pageSize = 20)
{ {
var source = ScopedProjects().AsNoTracking(); var source = ScopedProjects().AsNoTracking();
if (academicTermId.HasValue) if (academicTermId.HasValue)
@@ -158,7 +190,47 @@ public sealed class ExperimentsController(
if (status.HasValue) if (status.HasValue)
source = source.Where(x => x.Status == status); source = source.Where(x => x.Status == status);
return Ok(await source var normalizedCourseKeyword = Normalize(courseKeyword);
var normalizedClassKeyword = Normalize(classKeyword);
var normalizedTeacherKeyword = Normalize(teacherKeyword);
var normalizedTaskKeyword = Normalize(taskKeyword);
var taskSource = AccessibleTeachingTasks().AsNoTracking()
.Where(task => source.Select(project => project.TeachingTaskId).Contains(task.Id));
if (academicTermId.HasValue)
taskSource = taskSource.Where(x => x.AcademicTermId == academicTermId);
if (offeringCollegeId.HasValue)
taskSource = taskSource.Where(x => x.Course!.CollegeId == offeringCollegeId);
if (normalizedCourseKeyword is not null)
taskSource = taskSource.Where(x =>
x.Course!.Code.Contains(normalizedCourseKeyword) ||
x.Course.Name.Contains(normalizedCourseKeyword));
if (normalizedClassKeyword is not null)
taskSource = taskSource.Where(x => x.Classes.Any(item =>
item.AdministrativeClass!.Code.Contains(normalizedClassKeyword) ||
item.AdministrativeClass.Name.Contains(normalizedClassKeyword)));
if (normalizedTeacherKeyword is not null)
taskSource = taskSource.Where(x => x.Teachers.Any(item =>
item.Teacher!.TeacherNumber.Contains(normalizedTeacherKeyword) ||
item.Teacher.Name.Contains(normalizedTeacherKeyword)));
if (normalizedTaskKeyword is not null)
taskSource = taskSource.Where(x =>
x.TaskNumber.Contains(normalizedTaskKeyword) ||
x.Name.Contains(normalizedTaskKeyword));
page = Math.Max(1, page);
pageSize = Math.Clamp(pageSize, 10, 100);
var total = await taskSource.CountAsync(cancellationToken);
var taskIds = await taskSource
.OrderByDescending(x => x.AcademicTerm!.StartDate)
.ThenBy(x => x.Course!.Code)
.ThenBy(x => x.TaskNumber)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => x.Id)
.ToListAsync(cancellationToken);
var items = await source
.Where(x => taskIds.Contains(x.TeachingTaskId))
.OrderByDescending(x => x.Status == ExperimentProjectStatus.Published) .OrderByDescending(x => x.Status == ExperimentProjectStatus.Published)
.ThenBy(x => x.StartDate) .ThenBy(x => x.StartDate)
.ThenBy(x => x.Code) .ThenBy(x => x.Code)
@@ -167,6 +239,7 @@ public sealed class ExperimentsController(
x.Id, x.Id,
x.TeachingTaskId, x.TeachingTaskId,
x.ScheduleEntryId, x.ScheduleEntryId,
x.ScheduleWeek,
x.Code, x.Code,
x.Name, x.Name,
x.ArrangementMode, x.ArrangementMode,
@@ -197,6 +270,7 @@ public sealed class ExperimentsController(
x.ScheduleEntry.StartWeek, x.ScheduleEntry.StartWeek,
x.ScheduleEntry.EndWeek, x.ScheduleEntry.EndWeek,
x.ScheduleEntry.WeekPattern, x.ScheduleEntry.WeekPattern,
ProjectWeek = x.ScheduleWeek,
ClassroomName = x.ScheduleEntry.Classroom!.Name, ClassroomName = x.ScheduleEntry.Classroom!.Name,
BuildingName = x.ScheduleEntry.Classroom.Building!.Name, BuildingName = x.ScheduleEntry.Classroom.Building!.Name,
CampusName = x.ScheduleEntry.Classroom.Building.Campus!.Name CampusName = x.ScheduleEntry.Classroom.Building.Campus!.Name
@@ -220,7 +294,9 @@ public sealed class ExperimentsController(
CampusName = item.Classroom.Building.Campus!.Name CampusName = item.Classroom.Building.Campus!.Name
}) })
}) })
.ToListAsync(cancellationToken)); .ToListAsync(cancellationToken);
return Ok(new { Items = items, Total = total, Page = page, PageSize = pageSize });
} }
[HttpGet("student")] [HttpGet("student")]
@@ -254,6 +330,7 @@ public sealed class ExperimentsController(
x.Name, x.Name,
x.ArrangementMode, x.ArrangementMode,
x.ScheduleEntryId, x.ScheduleEntryId,
x.ScheduleWeek,
x.Description, x.Description,
x.Requirements, x.Requirements,
x.StartDate, x.StartDate,
@@ -275,6 +352,7 @@ public sealed class ExperimentsController(
x.ScheduleEntry.StartWeek, x.ScheduleEntry.StartWeek,
x.ScheduleEntry.EndWeek, x.ScheduleEntry.EndWeek,
x.ScheduleEntry.WeekPattern, x.ScheduleEntry.WeekPattern,
ProjectWeek = x.ScheduleWeek,
ClassroomName = x.ScheduleEntry.Classroom!.Name, ClassroomName = x.ScheduleEntry.Classroom!.Name,
BuildingName = x.ScheduleEntry.Classroom.Building!.Name, BuildingName = x.ScheduleEntry.Classroom.Building!.Name,
CampusName = x.ScheduleEntry.Classroom.Building.Campus!.Name CampusName = x.ScheduleEntry.Classroom.Building.Campus!.Name
@@ -363,8 +441,6 @@ public sealed class ExperimentsController(
ExperimentProjectBatchRequest request, ExperimentProjectBatchRequest request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
if (request.ArrangementMode == ExperimentArrangementMode.Centralized)
return ValidationProblem("集中安排的实验项目请逐项绑定已发布课表中的实验课。");
var taskIds = request.TeachingTaskIds var taskIds = request.TeachingTaskIds
.Where(x => x != Guid.Empty) .Where(x => x != Guid.Empty)
.Distinct() .Distinct()
@@ -384,21 +460,72 @@ public sealed class ExperimentsController(
return ValidationProblem("部分教学任务不存在、未发布或不在当前管理范围内。"); return ValidationProblem("部分教学任务不存在、未发布或不在当前管理范围内。");
var first = tasks[0]; var first = tasks[0];
if (tasks.Any(x => if (request.ArrangementMode == ExperimentArrangementMode.SelfScheduled && tasks.Any(x =>
x.AcademicTermId != first.AcademicTermId || x.AcademicTermId != first.AcademicTermId ||
x.CourseId != first.CourseId)) x.CourseId != first.CourseId))
return ValidationProblem("批量设置仅支持同一学期、同一课程的教学任务。"); return ValidationProblem("批量设置仅支持同一学期、同一课程的教学任务。");
var code = request.Code.Trim(); var code = request.Code.Trim();
var conflictingTaskNumbers = await db.ExperimentProjects.AsNoTracking() if (request.ArrangementMode == ExperimentArrangementMode.SelfScheduled)
.WhereIn(taskIds, x => x.TeachingTaskId) {
.Where(x => x.Code == code) var conflictingTaskNumbers = await db.ExperimentProjects.AsNoTracking()
.Select(x => x.TeachingTask!.TaskNumber) .WhereIn(taskIds, x => x.TeachingTaskId)
.OrderBy(x => x) .Where(x => x.Code == code)
.ToListAsync(cancellationToken); .Select(x => x.TeachingTask!.TaskNumber)
if (conflictingTaskNumbers.Count > 0) .OrderBy(x => x)
return ConflictProblem( .ToListAsync(cancellationToken);
$"以下教学任务已存在实验项目编码 {code}:{string.Join("、", conflictingTaskNumbers)}。"); if (conflictingTaskNumbers.Count > 0)
return ConflictProblem(
$"以下教学任务已存在实验项目编码 {code}:{string.Join("、", conflictingTaskNumbers)}。");
}
var scheduledEntries = request.ArrangementMode == ExperimentArrangementMode.Centralized
? await db.ScheduleEntries.AsNoTracking()
.Where(x => x.SchedulePlan!.Status == SchedulePlanStatus.Published &&
x.Kind == ScheduleEntryKind.Experiment &&
x.ClassroomId.HasValue &&
taskIds.Contains(x.TeachingTaskId))
.OrderBy(x => x.TeachingTaskId)
.ThenBy(x => x.DayOfWeek)
.ThenBy(x => x.StartPeriod)
.ToListAsync(cancellationToken)
: [];
if (request.ArrangementMode == ExperimentArrangementMode.Centralized &&
tasks.Any(task => scheduledEntries.All(entry => entry.TeachingTaskId != task.Id)))
return ValidationProblem("所选教学班中包含未排入实验室的已发布实验课,请先完成课表安排。");
var scheduledOccurrences = scheduledEntries
.SelectMany(entry => Enumerable.Range(
entry.StartWeek,
entry.EndWeek - entry.StartWeek + 1)
.Where(week => FreeClassroomRules.MatchesWeek(entry.WeekPattern, week))
.Select(week => (Entry: entry, Week: week)))
.ToList();
var legacyProjects = scheduledEntries.Count > 0
? await db.ExperimentProjects
.Where(x => x.Code == code && x.ScheduleEntryId.HasValue &&
x.ScheduleWeek == null &&
taskIds.Contains(x.TeachingTaskId))
.ToListAsync(cancellationToken)
: [];
if (legacyProjects.Any(x => x.Status != ExperimentProjectStatus.Draft))
return ConflictProblem("存在旧版已发布实验项目,不能自动拆分为每周项目;请先关闭后重新设置。");
if (scheduledOccurrences.Count > 0)
{
var entryIds = scheduledOccurrences.Select(x => x.Entry.Id).Distinct().ToList();
var existingOccurrences = await db.ExperimentProjects.AsNoTracking()
.Where(x => x.Code == code && x.ScheduleEntryId.HasValue &&
x.ScheduleWeek.HasValue && entryIds.Contains(x.ScheduleEntryId.Value))
.Select(x => new { ScheduleEntryId = x.ScheduleEntryId!.Value, ScheduleWeek = x.ScheduleWeek!.Value })
.ToListAsync(cancellationToken);
var existingKeys = existingOccurrences
.Select(x => (x.ScheduleEntryId, x.ScheduleWeek))
.ToHashSet();
if (scheduledOccurrences.Any(x => existingKeys.Contains((x.Entry.Id, x.Week))))
return ConflictProblem("所选实验课中已存在相同实验项目编码和周次,不能重复生成。");
}
var projects = new List<ExperimentProject>(tasks.Count); var projects = new List<ExperimentProject>(tasks.Count);
foreach (var task in tasks) foreach (var task in tasks)
@@ -407,17 +534,42 @@ public sealed class ExperimentsController(
var problem = ValidateProjectRequest(item, task.AcademicTerm!); var problem = ValidateProjectRequest(item, task.AcademicTerm!);
if (problem is not null) return ValidationProblem(problem); if (problem is not null) return ValidationProblem(problem);
projects.Add(new ExperimentProject IEnumerable<(ScheduleEntry? Entry, int? Week)> taskOccurrences =
request.ArrangementMode == ExperimentArrangementMode.Centralized
? scheduledOccurrences
.Where(item => item.Entry.TeachingTaskId == task.Id)
.Select(item => ((ScheduleEntry?)item.Entry, (int?)item.Week))
: [(null, null)];
foreach (var occurrence in taskOccurrences)
{ {
TeachingTaskId = task.Id, var scheduleEntry = occurrence.Entry;
Code = code, var legacy = scheduleEntry is null ? null : legacyProjects
Name = request.Name.Trim(), .SingleOrDefault(x => x.ScheduleEntryId == scheduleEntry.Id);
ArrangementMode = request.ArrangementMode, if (legacy is not null)
Description = Normalize(request.Description), {
Requirements = Normalize(request.Requirements), var firstWeek = scheduledOccurrences
StartDate = request.StartDate, .Where(item => item.Entry.Id == scheduleEntry!.Id)
EndDate = request.EndDate .Min(item => item.Week);
}); if (occurrence.Week == firstWeek)
{
legacy.ScheduleWeek = occurrence.Week;
continue;
}
}
projects.Add(new ExperimentProject
{
TeachingTaskId = task.Id,
ScheduleEntryId = occurrence.Entry?.Id,
ScheduleWeek = occurrence.Week,
Code = code,
Name = request.Name.Trim(),
ArrangementMode = request.ArrangementMode,
Description = Normalize(request.Description),
Requirements = Normalize(request.Requirements),
StartDate = request.StartDate,
EndDate = request.EndDate
});
}
} }
db.ExperimentProjects.AddRange(projects); db.ExperimentProjects.AddRange(projects);
@@ -491,6 +643,26 @@ public sealed class ExperimentsController(
return NoContent(); return NoContent();
} }
[HttpDelete("batch")]
[Authorize(Roles = Managers)]
public async Task<ActionResult> DeleteProjects(
ExperimentProjectBulkRequest request,
CancellationToken cancellationToken)
{
var ids = ValidateBulkProjectIds(request.ProjectIds);
if (ids is null) return ValidationProblem("请选择 1 至 100 个实验项目。");
var projects = await ScopedProjects()
.Where(x => ids.Contains(x.Id))
.ToListAsync(cancellationToken);
if (projects.Count != ids.Count) return NotFound();
if (projects.Any(x => x.Status != ExperimentProjectStatus.Draft))
return ConflictProblem("批量删除只能包含草稿实验项目。");
db.ExperimentProjects.RemoveRange(projects);
await db.SaveChangesAsync(cancellationToken);
return NoContent();
}
[HttpPost("{id:guid}/publish")] [HttpPost("{id:guid}/publish")]
[Authorize(Roles = Managers)] [Authorize(Roles = Managers)]
public async Task<ActionResult> PublishProject( public async Task<ActionResult> PublishProject(
@@ -522,28 +694,49 @@ public sealed class ExperimentsController(
project.Status = ExperimentProjectStatus.Published; project.Status = ExperimentProjectStatus.Published;
project.PublishedAt = DateTime.UtcNow; project.PublishedAt = DateTime.UtcNow;
await db.SaveChangesAsync(cancellationToken); await db.SaveChangesAsync(cancellationToken);
await NotifyProjectPublishedAsync(project, cancellationToken);
return NoContent();
}
var userIds = await TeachingTaskRosterQuery [HttpPost("batch/publish")]
.ForTask(db, project.TeachingTaskId) [Authorize(Roles = Managers)]
.Where(x => x.UserId.HasValue) public async Task<ActionResult> PublishProjects(
.Select(x => x.UserId!.Value) ExperimentProjectBulkRequest request,
.Distinct() CancellationToken cancellationToken)
{
var ids = ValidateBulkProjectIds(request.ProjectIds);
if (ids is null) return ValidationProblem("请选择 1 至 100 个实验项目。");
var projects = await ScopedProjects()
.Include(x => x.Sessions)
.Include(x => x.TeachingTask)
.ThenInclude(x => x!.Course)
.Where(x => ids.Contains(x.Id))
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
if (userIds.Count > 0) if (projects.Count != ids.Count) return NotFound();
foreach (var project in projects)
{ {
var mode = project.ArrangementMode == if (project.Status != ExperimentProjectStatus.Draft)
ExperimentArrangementMode.Centralized return ConflictProblem("批量发布只能包含草稿实验项目。");
? "集中安排" var hasSchedule = project.ArrangementMode == ExperimentArrangementMode.Centralized
: "自行预约"; ? project.ScheduleEntryId.HasValue || project.Sessions.Any(x => x.Status == ExperimentSessionStatus.Scheduled)
await NotificationService.SendToUserIdsAsync( : project.Sessions.Any(x => x.Status == ExperimentSessionStatus.Scheduled);
db, if (!hasSchedule)
userIds, return ConflictProblem($"“{project.Name}”尚未具备发布条件。");
"实验项目已发布", if (project.Sessions.Any(x => x.Status == ExperimentSessionStatus.Scheduled &&
$"《{project.TeachingTask!.Course!.Name}》已发布“{project.Name}”({mode}),请查看实验安排。", (x.SessionDate < project.StartDate || x.SessionDate > project.EndDate)))
"/experiments", return ConflictProblem($"“{project.Name}”存在不在开放日期范围内的实验场次。");
cancellationToken,
NotificationCategory.Schedule);
} }
var publishedAt = DateTime.UtcNow;
foreach (var project in projects)
{
project.Status = ExperimentProjectStatus.Published;
project.PublishedAt = publishedAt;
}
await db.SaveChangesAsync(cancellationToken);
foreach (var project in projects)
await NotifyProjectPublishedAsync(project, cancellationToken);
return NoContent(); return NoContent();
} }
@@ -1234,6 +1427,31 @@ public sealed class ExperimentsController(
.Distinct() .Distinct()
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
private async Task NotifyProjectPublishedAsync(
ExperimentProject project,
CancellationToken cancellationToken)
{
var userIds = await RosterUserIdsAsync(project.TeachingTaskId, cancellationToken);
if (userIds.Count == 0) return;
var mode = project.ArrangementMode == ExperimentArrangementMode.Centralized
? "集中安排"
: "自行预约";
await NotificationService.SendToUserIdsAsync(
db,
userIds,
"实验项目已发布",
$"《{project.TeachingTask!.Course!.Name}》已发布“{project.Name}”({mode}),请查看实验安排。",
"/experiments",
cancellationToken,
NotificationCategory.Schedule);
}
private static List<Guid>? ValidateBulkProjectIds(IReadOnlyList<Guid>? projectIds)
{
var ids = projectIds?.Where(x => x != Guid.Empty).Distinct().ToList();
return ids is { Count: > 0 and <= 100 } ? ids : null;
}
private static string? ValidateProjectRequest( private static string? ValidateProjectRequest(
ExperimentProjectRequest request, ExperimentProjectRequest request,
AcademicTerm term) AcademicTerm term)
@@ -1321,6 +1539,9 @@ public sealed record ExperimentProjectBatchRequest(
EndDate); EndDate);
} }
public sealed record ExperimentProjectBulkRequest(
[Required] IReadOnlyList<Guid> ProjectIds);
public sealed record ExperimentSessionRequest( public sealed record ExperimentSessionRequest(
Guid ClassroomId, Guid ClassroomId,
DateOnly SessionDate, DateOnly SessionDate,
@@ -9,6 +9,8 @@ public sealed class ExperimentProject : EntityBase
// 集中安排的项目复用已发布课表中的实验课,不再维护一份重复的场次。 // 集中安排的项目复用已发布课表中的实验课,不再维护一份重复的场次。
public Guid? ScheduleEntryId { get; set; } public Guid? ScheduleEntryId { get; set; }
public ScheduleEntry? ScheduleEntry { get; set; } public ScheduleEntry? ScheduleEntry { get; set; }
// 集中安排按课表的具体周次拆分为实验项目;自行安排为空。
public int? ScheduleWeek { get; set; }
public required string Code { get; set; } public required string Code { get; set; }
public required string Name { get; set; } public required string Name { get; set; }
public ExperimentArrangementMode ArrangementMode { get; set; } public ExperimentArrangementMode ArrangementMode { get; set; }
@@ -678,7 +678,16 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.Name).HasMaxLength(120);
entity.Property(x => x.Description).HasMaxLength(1000); entity.Property(x => x.Description).HasMaxLength(1000);
entity.Property(x => x.Requirements).HasMaxLength(1000); entity.Property(x => x.Requirements).HasMaxLength(1000);
entity.HasIndex(x => new { x.TeachingTaskId, x.Code }).IsUnique(); // 集中安排会为同一教学任务的每一条实验课表记录生成项目;
// 自行安排仍由控制器保持“教学任务 + 编码”唯一。
entity.HasIndex(x => new
{
x.TeachingTaskId,
x.Code,
x.ScheduleEntryId,
x.ScheduleWeek
})
.IsUnique();
entity.HasIndex(x => new { x.Status, x.StartDate, x.EndDate }); entity.HasIndex(x => new { x.Status, x.StartDate, x.EndDate });
entity.HasOne(x => x.TeachingTask).WithMany() entity.HasOne(x => x.TeachingTask).WithMany()
.HasForeignKey(x => x.TeachingTaskId) .HasForeignKey(x => x.TeachingTaskId)
Loaded 3 of 12 files, more files were not shown because too many files have changed in this diff. Show more