学年学期页面新增“设为当前”“归档”“撤销归档”,当前学期不能归档、删除或直接取消;已归档学期需先撤销才能重新设为当前。[BaseDataController.cs (line 286)](/E:/jiaowu/src/Jiaowu.Api/Controllers/BaseDataController.cs:286)

切换后,其他学期自动变为历史态;已归档学期进一步弱化。所有主要学期选择器统一默认当前,并显示“当前 / 历史 / 已归档”。[academicTerms.ts (line 1)](/E:/jiaowu/web/src/utils/academicTerms.ts:1)
桌面使用分层表格,390px 手机端改为独立学期卡片,操作不会再挤压表格。[BaseDataView.vue (line 211)](/E:/jiaowu/web/src/views/BaseDataView.vue:211)
归档定义为“历史显示状态”,不是成绩冻结:补考成绩仍可录入并回写,已发布成绩仍可走成绩更正审批。
已加入 SQLite 开发迁移、MySQL 正式迁移和切换/归档/撤销测试。[20260726143000_AcademicTermArchiving.cs (line 1)](/E:/jiaowu/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260726143000_AcademicTermArchiving.cs:1)
This commit is contained in:
2026-07-26 11:14:05 +08:00 Unverified
parent 3b73292d93
commit c91d826169
32 changed files with 752 additions and 73 deletions
@@ -167,8 +167,11 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
.HasForeignKey(x => x.BuildingId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<AcademicTerm>()
.HasIndex(x => x.IsCurrent);
builder.Entity<AcademicTerm>(entity =>
{
entity.HasIndex(x => x.IsCurrent);
entity.HasIndex(x => x.IsArchived);
});
builder.Entity<Teacher>(entity =>
{
@@ -46,6 +46,8 @@ public sealed class DevelopmentSqliteMigrator(
"20260725_24_approval_tables";
private const string AcademicWarningsMigration =
"20260725_25_academic_warnings";
private const string AcademicTermArchivingMigration =
"20260726_27_academic_term_archiving";
public async Task MigrateAsync(CancellationToken cancellationToken = default)
{
@@ -329,6 +331,19 @@ public sealed class DevelopmentSqliteMigrator(
MakeupExamAutoJobsMigration,
makeupAutoJobsExist ? [] : MakeupExamAutoJobStatements,
cancellationToken);
var academicTermArchivingExists = await db.Database
.SqlQueryRaw<int>(
"""
SELECT COUNT(*) AS "Value"
FROM pragma_table_info('AcademicTerms')
WHERE name = 'IsArchived'
""")
.AnyAsync(value => value > 0, cancellationToken);
await ApplyMigrationAsync(
AcademicTermArchivingMigration,
academicTermArchivingExists ? [] : AcademicTermArchivingStatements,
cancellationToken);
}
private async Task ApplyMigrationAsync(
@@ -1688,6 +1703,13 @@ public sealed class DevelopmentSqliteMigrator(
"""ALTER TABLE "WarningRules" ADD COLUMN "LastCheckAt" TEXT NULL;"""
];
private static readonly string[] AcademicTermArchivingStatements =
[
"""ALTER TABLE "AcademicTerms" ADD COLUMN "IsArchived" INTEGER NOT NULL DEFAULT 0;""",
"""ALTER TABLE "AcademicTerms" ADD COLUMN "ArchivedAt" TEXT NULL;""",
"""CREATE INDEX IF NOT EXISTS "IX_AcademicTerms_IsArchived" ON "AcademicTerms" ("IsArchived");"""
];
private const string TeachingEvaluationMigration = "TeachingEvaluation";
private static readonly string[] TeachingEvaluationStatements =
@@ -0,0 +1,48 @@
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql;
[DbContext(typeof(AppDbContext))]
[Migration("20260726143000_AcademicTermArchiving")]
public partial class AcademicTermArchiving : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "ArchivedAt",
table: "AcademicTerms",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "IsArchived",
table: "AcademicTerms",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.CreateIndex(
name: "IX_AcademicTerms_IsArchived",
table: "AcademicTerms",
column: "IsArchived");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_AcademicTerms_IsArchived",
table: "AcademicTerms");
migrationBuilder.DropColumn(
name: "ArchivedAt",
table: "AcademicTerms");
migrationBuilder.DropColumn(
name: "IsArchived",
table: "AcademicTerms");
}
}
@@ -29,6 +29,9 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime?>("ArchivedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(40)
@@ -43,6 +46,9 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.Property<bool>("IsCurrent")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsArchived")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsEnabled")
.HasColumnType("tinyint(1)");
@@ -70,6 +76,8 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("IsCurrent");
b.HasIndex("IsArchived");
b.HasIndex("IsEnabled", "SortOrder");
b.ToTable("AcademicTerms");
@@ -194,6 +194,7 @@ public sealed class TimetableDataService(AppDbContext db)
query = query.Where(x => x.Id == academicTermId);
else
query = query.OrderByDescending(x => x.IsCurrent)
.ThenBy(x => x.IsArchived)
.ThenByDescending(x => x.StartDate);
return await query.Select(x => new TimetableTermDto(
x.Id,