课程库 Excel 导入导出已完成:

支持下载标准导入模板。
导出严格沿用当前关键词、学院、分类、课程性质和状态筛选。
以课程编码为唯一键:存在则更新,不存在则新增。
校验学院编码、课程分类、课程性质、学分学时及考核方式。
整批原子导入,任一行错误则全部不写入,并提示具体行号。
学院管理员仍只能维护本学院的专业课和实践课,无法借 Excel 越权。
界面入口沿用现有课程库工具栏样式。
This commit is contained in:
2026-07-24 18:56:25 +08:00 Unverified
parent 5a7a8d530b
commit d7cf3f9e76
24 changed files with 3710 additions and 32 deletions
@@ -120,6 +120,44 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
})
.ToListAsync(cancellationToken));
[HttpGet("course-categories")]
public async Task<ActionResult<IReadOnlyCollection<CourseCategory>>> GetCourseCategories(
CancellationToken cancellationToken) =>
await db.CourseCategories.AsNoTracking()
.OrderBy(x => x.SortOrder)
.ThenBy(x => x.Code)
.ToListAsync(cancellationToken);
[HttpPost("course-categories")]
[Authorize(Roles = Administrators)]
public async Task<ActionResult<CourseCategory>> CreateCourseCategory(
CatalogRequest request,
CancellationToken cancellationToken)
{
var entity = new CourseCategory
{
Code = request.Code.Trim(),
Name = request.Name.Trim(),
SortOrder = request.SortOrder,
IsEnabled = request.IsEnabled
};
return await CreateAsync(entity, nameof(GetCourseCategories), cancellationToken);
}
[HttpPut("course-categories/{id:guid}")]
[Authorize(Roles = Administrators)]
public async Task<ActionResult<CourseCategory>> UpdateCourseCategory(
Guid id,
CatalogRequest request,
CancellationToken cancellationToken)
{
var entity = await db.CourseCategories.FindAsync([id], cancellationToken);
if (entity is null) return NotFound();
ApplyCatalog(entity, request);
await db.SaveChangesAsync(cancellationToken);
return entity;
}
[HttpPost("majors")]
[Authorize(Roles = Administrators)]
public async Task<ActionResult<Major>> CreateMajor(
@@ -403,6 +441,7 @@ public sealed class BaseDataController(AppDbContext db) : ControllerBase
"terms" => await db.AcademicTerms.FindAsync([id], cancellationToken),
"buildings" => await db.Buildings.FindAsync([id], cancellationToken),
"classrooms" => await db.Classrooms.FindAsync([id], cancellationToken),
"course-categories" => await db.CourseCategories.FindAsync([id], cancellationToken),
_ => null
};