课程库 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
@@ -0,0 +1,57 @@
using Jiaowu.Api.Domain.Academic;
namespace Jiaowu.Api.Infrastructure.Graduation;
public sealed record CurriculumCompletionSummary(
int RequirementCount,
int PassedRequirementCount,
IReadOnlyList<string> MissingRequirements);
public static class CurriculumCompletionRules
{
public static CurriculumCompletionSummary Evaluate(
IEnumerable<CurriculumModule> modules,
IReadOnlySet<Guid> passedCourseIds)
{
var moduleList = modules.ToList();
var requiredCourses = moduleList
.SelectMany(x => x.Courses)
.Where(x => x.Type == CurriculumCourseType.Required)
.ToList();
var optionGroups = moduleList
.Where(x =>
x.RequiredCredits > 0 &&
x.Courses.Any(course => course.Type == CurriculumCourseType.Elective))
.ToList();
var missing = requiredCourses
.Where(x => !passedCourseIds.Contains(x.CourseId))
.Select(x => x.Course?.Name ?? "未命名必修课程")
.Distinct()
.ToList();
var passedRequiredCourses = requiredCourses.Count -
requiredCourses.Count(x =>
!passedCourseIds.Contains(x.CourseId));
var passedGroups = 0;
foreach (var module in optionGroups)
{
var earnedGroupCredits = module.Courses
.Where(x => passedCourseIds.Contains(x.CourseId))
.GroupBy(x => x.CourseId)
.Sum(group => group.Max(x => x.Course?.Credits ?? 0));
if (earnedGroupCredits >= module.RequiredCredits)
{
passedGroups++;
continue;
}
var shortage = module.RequiredCredits - earnedGroupCredits;
missing.Add($"{module.Name}(还差 {shortage:0.#} 学分)");
}
return new CurriculumCompletionSummary(
requiredCourses.Count + optionGroups.Count,
passedRequiredCourses + passedGroups,
missing);
}
}