feat(admin): 成绩导出改为 Excel + 板块迁移配套接口(R29)

- service/admin.js: 抽出 buildScoreExportTable(group) 作单一数据源(CSV/XLSX 共用,消除表头漂移)
  + 新增 exportToXlsx()(SheetJS aoa_to_sheet + write buffer + !cols 列宽)
- controller/admin.js: 新增 exportExcel()(xlsx MIME + ctx.attachment 中文文件名编码)
- router.js: 新增 GET /api/admin/export-excel/:group(adminAuth)
- export-csv 路由刻意保留:verify_full_e2e.py B10/D10、verify_upload_download.py B2 依赖其非空断言
This commit is contained in:
2026-09-28 16:42:36 +08:00
parent 2ab0991310
commit e74778ee08
3 changed files with 106 additions and 31 deletions

View File

@@ -261,6 +261,34 @@ class AdminController extends Controller {
}
}
/**
* 导出提交记录为 Excel(.xlsx)(R29)
* GET /api/admin/export-excel/:group
*
* 测试人员反馈「导出 CSV 不如直接导出 Excel」:CSV 在 Excel 中打开时中文易乱码、
* 身份证号 / 手机号会被转成科学计数法。此处直接下发真正的 xlsx。
* ⚠️ 与 exportCsv 共用 service.admin.buildScoreExportTable(表头/口径单一数据源)。
*/
async exportExcel() {
const { ctx, service } = this;
if (!this._ensureAdmin()) return;
try {
const { group } = ctx.params;
if (!this._validateGroup(group, true)) return;
const buf = await service.admin.exportToXlsx(group);
const fileName = `${group}_scores_${new Date().toISOString().slice(0, 10)}.xlsx`;
ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
ctx.attachment(fileName);
ctx.body = buf;
} catch (err) {
ctx.logger.error('导出 Excel 失败:', err);
ctx.body = ctx.helper.error(500, '导出 Excel 失败');
}
}
/**
* 批量下载某组所有提交文件为 ZIP
* GET /api/admin/batch-download/:group