新建时可选“自动生成顺序号”:例如 数据库实验 01、02…

或选“逐行粘贴名称”:每行一个名称,按实际生成顺序对应项目。
草稿项目支持勾选后“批量改名”,可直接粘贴多行名称。
服务端会校验名称数量、长度和草稿状态,避免错位或修改已发布项目。
This commit is contained in:
2026-08-10 09:45:02 +08:00 Unverified
parent 4be61ab921
commit 3bcb976aa4
3 changed files with 207 additions and 4 deletions
@@ -574,6 +574,27 @@ public sealed class ExperimentsController(
}
}
var names = NormalizeBatchProjectNames(request.Names);
if (request.GenerateSequentialNames)
{
if (names is not null)
return ValidationProblem("自动生成顺序号时无需填写逐行项目名称。");
var width = Math.Max(2, projects.Count.ToString().Length);
if (request.Name.Trim().Length + width + 1 > 120)
return ValidationProblem("项目名称前缀过长,无法追加顺序号。");
for (var index = 0; index < projects.Count; index++)
projects[index].Name = $"{request.Name.Trim()} {(index + 1).ToString($"D{width}")}";
}
else if (names is not null)
{
if (names.Any(name => string.IsNullOrWhiteSpace(name) || name.Length > 120))
return ValidationProblem("每个实验项目名称均不能为空,且不能超过 120 个字符。");
if (names.Count != projects.Count)
return ValidationProblem($"已填写 {names.Count} 个项目名称,但本次将生成 {projects.Count} 个实验项目;请确保每行对应一个项目。");
for (var index = 0; index < projects.Count; index++)
projects[index].Name = names[index];
}
db.ExperimentProjects.AddRange(projects);
await db.SaveChangesAsync(cancellationToken);
return Created(string.Empty, new
@@ -645,6 +666,34 @@ public sealed class ExperimentsController(
return NoContent();
}
[HttpPut("batch/names")]
[Authorize(Roles = Managers)]
public async Task<ActionResult> UpdateProjectNames(
ExperimentProjectBatchNamesRequest request,
CancellationToken cancellationToken)
{
var ids = ValidateBulkProjectIds(request.ProjectIds);
if (ids is null) return ValidationProblem("请选择 1 至 100 个实验项目。");
var names = NormalizeBatchProjectNames(request.Names);
if (names is null || names.Count != ids.Count || names.Any(name => string.IsNullOrWhiteSpace(name) || name.Length > 120))
return ValidationProblem("请为每个已选实验项目填写一个名称,每行一个且数量必须一致。");
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("批量修改名称只能包含草稿实验项目。");
var projectsById = projects.ToDictionary(x => x.Id);
for (var index = 0; index < ids.Count; index++)
projectsById[ids[index]].Name = names[index];
await db.SaveChangesAsync(cancellationToken);
return NoContent();
}
[HttpDelete("batch")]
[Authorize(Roles = Managers)]
public async Task<ActionResult> DeleteProjects(
@@ -1494,6 +1543,12 @@ public sealed class ExperimentsController(
private static string? Normalize(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private static List<string>? NormalizeBatchProjectNames(IReadOnlyList<string>? names)
{
if (names is null || names.Count == 0) return null;
return names.Select(name => name.Trim()).ToList();
}
private ActionResult ConflictProblem(string detail) =>
Conflict(new ProblemDetails
{
@@ -1522,7 +1577,9 @@ public sealed record ExperimentProjectBatchRequest(
[MaxLength(1000)] string? Description,
[MaxLength(1000)] string? Requirements,
DateOnly StartDate,
DateOnly EndDate)
DateOnly EndDate,
IReadOnlyList<string>? Names = null,
bool GenerateSequentialNames = false)
{
public ExperimentProjectRequest ForTeachingTask(Guid teachingTaskId) =>
new(
@@ -1539,6 +1596,10 @@ public sealed record ExperimentProjectBatchRequest(
public sealed record ExperimentProjectBulkRequest(
[Required] IReadOnlyList<Guid> ProjectIds);
public sealed record ExperimentProjectBatchNamesRequest(
[Required] IReadOnlyList<Guid> ProjectIds,
[Required] IReadOnlyList<string> Names);
public sealed record ExperimentSessionRequest(
Guid ClassroomId,
DateOnly SessionDate,