feat(export): 新增「导入格式」报名名单导出(汇总 / 分小项),与导入模板逐列一致、可直接重导;版本 dev_0.5.14

主人反馈:导出页「报名名单」卡片导出的表格与导入模板对不上,没法拿回去重导。

核对属实(旧导出是给人看的报表):列序与 importColumns 不同、缺「种子 / 队员名单 / 队伍」
三列、姓名列被拼成「姓名(号码布)」、队名拼在单位名后面、状态列写「已退赛」而
normalizeImportStatus 不认 —— 整体错位 + 状态列报错,重导必炸。

- 新增 GET /api/export/entries-import.xlsx?format=flat|sheets&lang=zh|en&<四个筛选>
  · flat(缺省)= 所有行汇总在一张工作表;表名不含 @(导入侧见到带 @ 的表名就整份按
    分工作表模板解析,汇总表定不出「小项@组别」会被拒)
  · sheets = 按「小项@组别」分工作表,表名走 entrySheetName(与模板、导入解析三处同源,
    31 字符截断与撞名去重都跟着一起走)
  · 权限与筛选参数与 /api/export/entries.xlsx 完全一致(访客可读);空结果回 200 + 只有表头
  · 列序不写常量:按 importColumns 逐列取值,表头走 entryTemplateHeader(lang),
    加列/改序时测试当场红(TestEntryImportRecordCoversImportColumns)
- 取值口径(重导的关键):团体行的「姓名」留空、队员写「队员名单」列(中文逗号分隔;
  以库里 token 为底,再补上 entry_athletes 里后加的队员号码布,只增不减)、
  队伍独立成列、状态写导入器认的四个中文说法(正常参赛/退赛/取消资格/未出场)、
  联队照旧写 A+B、未分批留空、证件号/性别/年龄来自运动员档案(团体行空)
- 新增 internal/export/entries_import.go(读库层,与报表导出同源 SQL、取值口径刻意不同)
  与 internal/httpapi/entries_import_export.go(工作簿组装 + handler)
- 原有报表导出(/api/export/entries.xlsx 与 internal/export/entries.go)一个字没动;
  导出页新增两个按钮与中文/英文表头选择(只影响这两个导入格式)

测试:往返测试把导出件直接喂回报名导入解析器,flat 与 sheets 各断言四条(解析零错误 /
行数与 13 列逐项一致 / 团体行没被变成个人报名 / 两种格式都跑);另做变异验证 —— 状态改回
「已退赛」、给团体行填姓名,守卫当场变红。四套自检 ×2 全绿(finalcheck_0514.log:
前端 996 例 / Console 96 例 / 服务端 16 包,EXIT=0)。

无数据库迁移;已有成绩、名次与榜单不变。
文档:使用手册 §11.1(并订正「不要拿导出件重导」的旧话)、报名名单导入规范 新增第九节、
升级说明 新增 dev_0.5.14、常见问题 新增 Q104。
This commit is contained in:
AskaEth committed 2026-09-17 04:03:52 +08:00
1 parent 19e5630b39
commit ee19b92182
19 files changed
+2135 -34

No files matched your search

+30 -1
View File
@@ -226,7 +226,7 @@ Console 启动的多屏窗口默认每屏锁定一页。到「大屏控制」点
## 导出与导入
**Q43:能导出哪些文件?**
「导出 → 结果导出」页有六类 Excel:**总表**(`总积分榜.xlsx`)、**全部**(`赛事成绩总表.xlsx`,多 sheet)、**全部(单页)**(`赛事成绩总表(单页).xlsx`,单张表平铺)、**分项成绩表**(`分项成绩表.xlsx`)、**单位积分明细**(`单位积分明细.xlsx`),以及可筛选的**报名名单**(`报名名单.xlsx`,见 Q87)。「积分榜」页另有四项:单位总分榜卡片头的**导出总明细**(`单位积分总明细.xlsx`)、单位奖牌榜卡片头的**导出奖牌明细**(`单位奖牌明细.xlsx`)、个人积分榜卡片头的**导出个人明细**(`个人积分总明细.xlsx`),以及展开单位后明细卡头的**导出该单位明细**(`单位积分明细.xlsx`)。另外可导出赛事项目包(zip)、**基础数据包**(xlsx,见 Q88)与规则包(YAML)。
「导出 → 结果导出」页有六类 Excel:**总表**(`总积分榜.xlsx`)、**全部**(`赛事成绩总表.xlsx`,多 sheet)、**全部(单页)**(`赛事成绩总表(单页).xlsx`,单张表平铺)、**分项成绩表**(`分项成绩表.xlsx`)、**单位积分明细**(`单位积分明细.xlsx`),以及可筛选的**报名名单**(`报名名单.xlsx`,见 Q87;同一张卡片里另有「导入格式」的两个版本 `报名名单(导入格式).xlsx` / `报名名单(导入格式·分小项).xlsx`,见 Q104)。「积分榜」页另有四项:单位总分榜卡片头的**导出总明细**(`单位积分总明细.xlsx`)、单位奖牌榜卡片头的**导出奖牌明细**(`单位奖牌明细.xlsx`)、个人积分榜卡片头的**导出个人明细**(`个人积分总明细.xlsx`),以及展开单位后明细卡头的**导出该单位明细**(`单位积分明细.xlsx`)。另外可导出赛事项目包(zip)、**基础数据包**(xlsx,见 Q88)与规则包(YAML)。
**Q44:导出文件保存在哪?**
浏览器默认下载目录。服务端生成的临时包位于 `data\exports\`,下载完成后自动删除。
@@ -543,6 +543,8 @@ Console 启动的多屏窗口默认每屏锁定一页。到「大屏控制」点
**Q87:怎么把报名名单导出来?**
到「导出 → 结果导出」页,往下找**「报名名单」**卡片:先按**小项 / 组别 / 单位 / 批次**筛选(都可以留空 = 不筛,即导全部),再点「导出报名名单」,得到 `报名名单.xlsx`。表里有 13 列:小项编码、小项名、组别、单位编码、单位名、姓名、证件号、号码布、批次、性别、年龄、状态(中文:正常参赛 / 已退赛 / 取消资格 / 未出场)、备注;号码布按文本导出,前导零不会丢。访客也能导出(只读)。
> 这份是**给人看的报表,不能导回去**。同一张卡片下面还有「**导出(汇总·导入格式)**」「**导出(分小项·导入格式)**」两个按钮,导出的 13 列与报名导入模板逐列一致、可以直接重新导入(见 Q104)。
**Q88:基础数据(单位 / 大项 / 小项 / 组别)能批量导入导出吗?**
能。到「导出 → 基础数据」页:
@@ -732,3 +734,30 @@ Console 启动的多屏窗口默认每屏锁定一页。到「大屏控制」点
- 升级前的旧版本遇到这个现象时,**挪一下盖在它上面的窗口**即可立刻恢复显示,**不需要重新发布成绩、也不需要重算**。详见《升级说明》的 `dev_0.5.13` 一节。
> 顺带分清另一件事:「**撤销发布**」**保留**成绩内容(只把分收回来,改完重新发布即可);要**连成绩内容一起清掉**,那是工具栏上另一对按钮「**清空本组 / 清空本批**」(危险操作,要勾选确认,清整组时还会一并清掉按单位录的团体名次)。两者的对照见使用手册 4.7。
**Q104:导出的报名名单为什么和导入模板不一样?能不能直接导回去?**
**先说结论:以前导出的那份不能,现在报名名单卡片里多了两个能导回去的按钮——挑对按钮就行。**
原来「导出报名名单」导出的是一张**给人看的报表**:额外有小项名称、单位名称与备注三列,**姓名列拼了队员**、状态写「已退赛」、单位名后面还拼着队名。**导入器不认这份表**(列对不上,状态那个写法也不认),拿它去「基础数据 → 报名导入」重导会被拦下——所以那份只能看、不能导回去。
现在同一个**报名名单卡片**里多了一组「**导入格式**」导出,与「基础数据 → 报名导入」里下载的模板**逐列一致**,可以直接导回去:
| 按钮 | 导出来是什么 | 文件名 |
| --- | --- | --- |
| 导出(汇总·导入格式) | 13 列汇总在**一张工作表**里 | `报名名单(导入格式).xlsx` |
| 导出(分小项·导入格式) | 13 列,按「小项名称@组别名称」**分成多张工作表** | `报名名单(导入格式·分小项).xlsx` |
- 13 列是:项目编码 / 组别 / 单位编码 / 姓名 / 证件号 / 性别 / 年龄 / 号码布 / 种子 / 批次 / 状态 / 队员名单 / 队伍(顺序就是这个,没有小项名称 / 单位名称 / 备注那三列);
- **团体报名的「姓名」列是空的**、队员写在「队员名单」列(「姓名」列填了就会被当成个人报名);「**队伍**」是**独立的一列**(报表件里是拼在单位名后面的);「状态」写的是导入器认的四个中文说法:`正常参赛` / `退赛` / `取消资格` / `未出场`;
- 筛选与权限跟报表一样:小项 / 组别 / 单位 / 批次都可留空(留空 = 不筛),**访客也能导**;
- 上方的**表头语言**(中文表头 / 英文表头)**只管这两个导出**,改它不会动上面那份报表(界面上写明了);
- **往返已经测过**:这两种导出件原样喂回导入接口,零错误、行数一致、13 列逐项一致,**团体行不会被变成个人报名**(队员关联还在)。想自己验一遍:导出 → **一个字都不改** → 在「基础数据 → 报名导入」原样上传,结果应当是「新增 0 / 更新 N / 错误 0」。
**重导前要知道的三条限制**(它不是"完全无损"):
1. **号码布可能进冲突面板**:导出件写的是**报名上登记的号码布**,导入时对个人行会拿它跟**运动员档案里的号码布**比;两边都非空且不同,这一行就进「冲突」列表等你逐条决定(库里两者一致是常态,那时是静默幂等);
2. **联队的份额会变成均分**:13 列里联队只能写 `A+B`,重导回去是**各 50%**;当初设成 `2:1` 这种非均分的会变均分——表格本身表达不了,要保权重只能在界面上改;
3. **「队员名单」只增不减**:导出时会把界面上后来补进这支队的队员一并补上,所以重导后这支队的队员**不会少**,但个别名单的**顺序 / 写法**可能与最初上传的那张表不完全一样。
**一句话**:给人看的用「导出报名名单」;**要拿回去重导,用「导出(汇总·导入格式)」或「导出(分小项·导入格式)」**,导出的文件**不要改表头、不要插删列**。详见[使用手册 §11.1](./使用手册.md)与[报名名单导入规范 · 第九节](./报名名单导入规范.md#九导出的名单怎么导回去)。