feat(imports): 成绩/名单表格导入 + ClassIsland 时间配置导入 + 安装版增量迁移

服务端:
- 新增 imports 模块: 模板下载(csv/xlsx)、表格解析预览、字段映射导入(append/upsert)、
  错误行号与结果统计; ClassIsland 时间配置容错解析 + replace/merge 导入 + 失败回滚(400 不写库)
- TimeLayout 模型 + 迁移; 修复 contentBase64 空值被 422 拦截, 改由 service 返回 IMPORT_EMPTY_FILE
- merge 语义改为按开始时间槽位覆盖, 同开始时间的旧节次不再被静默丢弃
- db-bootstrap 增加 _ch_migrations 账本: 覆盖安装升级也能自动补跑新增迁移(旧库实测通过)

Web 管理端:
- TableImportDialog(成绩/名单): 模板下载、预览、字段映射、重复处理模式、结果统计与错误行
- TimeLayoutImportDialog: 粘贴/选 .json、解析预览、覆盖/合并、已导入列表与删除
- 成绩录入/学生管理/课表管理接入入口(按权限矩阵显示)

验证: verify:e2e 107/107(新增导入 22 项) verify:web 18/18(新增时间配置导入弹窗真实点击)
      verify:desktop 40/40
文档: README 新增导入章节、增量迁移说明、API 表与验收对照更新
This commit is contained in:
LoveChengke committed 2026-09-12 15:35:01 +08:00
1 parent 0dcab6f369
commit 8b85079d81
23 files changed
+2735 -108

No files matched your search

@@ -28,6 +28,12 @@ export function validate<B = unknown, Q = unknown, P = unknown>(
}
/** 读取已校验的 query(类型由调用方用 zod schema 保证) */
/** 取出校验后的请求体(与 validatedQuery/validatedParams 同一套约定) */
export function validatedBody<T>(req: Request): T {
// validate() 会把解析结果写回 req.body
return req.body as T;
}
export function validatedQuery<T>(req: Request): T {
return (req.validatedQuery ?? {}) as T;
}