课程库 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
@@ -18,6 +18,7 @@ public sealed class DevelopmentSqliteMigrator(
private const string GraduationAuditsMigration = "20260724_10_graduation_audits";
private const string DegreeAwardsMigration = "20260724_11_degree_awards";
private const string GraduationClearanceMigration = "20260724_12_graduation_clearance";
private const string CourseCategoriesMigration = "20260724_13_course_categories";
public async Task MigrateAsync(CancellationToken cancellationToken = default)
{
@@ -90,6 +91,22 @@ public sealed class DevelopmentSqliteMigrator(
GraduationClearanceMigration,
GraduationClearanceStatements,
cancellationToken);
var courseCategoryColumnExists = await db.Database
.SqlQueryRaw<int>(
"""
SELECT COUNT(*) AS "Value"
FROM pragma_table_info('Courses')
WHERE name = 'CourseCategoryId'
""")
.AnyAsync(value => value > 0, cancellationToken);
await ApplyMigrationAsync(
CourseCategoriesMigration,
courseCategoryColumnExists
? CourseCategoriesStatements.Where(
statement => !statement.StartsWith(
"ALTER TABLE", StringComparison.OrdinalIgnoreCase))
: CourseCategoriesStatements,
cancellationToken);
}
private async Task ApplyMigrationAsync(
@@ -767,4 +784,34 @@ public sealed class DevelopmentSqliteMigrator(
"""CREATE UNIQUE INDEX IF NOT EXISTS "IX_GraduationClearanceRecords_GraduationClearanceItemId_StudentId" ON "GraduationClearanceRecords" ("GraduationClearanceItemId", "StudentId");""",
"""CREATE INDEX IF NOT EXISTS "IX_GraduationClearanceRecords_StudentId_Status" ON "GraduationClearanceRecords" ("StudentId", "Status");"""
];
private static readonly string[] CourseCategoriesStatements =
[
"""
CREATE TABLE IF NOT EXISTS "CourseCategories" (
"Id" TEXT NOT NULL CONSTRAINT "PK_CourseCategories" PRIMARY KEY,
"Code" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"SortOrder" INTEGER NOT NULL,
"IsEnabled" INTEGER NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
""",
"""
CREATE UNIQUE INDEX IF NOT EXISTS "IX_CourseCategories_Code"
ON "CourseCategories" ("Code");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_CourseCategories_IsEnabled_SortOrder"
ON "CourseCategories" ("IsEnabled", "SortOrder");
""",
"""
ALTER TABLE "Courses" ADD COLUMN "CourseCategoryId" TEXT NULL;
""",
"""
CREATE INDEX IF NOT EXISTS "IX_Courses_CourseCategoryId"
ON "Courses" ("CourseCategoryId");
"""
];
}