feat(big-question): 综合大题 Excel 模板下载 + 批量导入(R73)
新增 GET /api/admin/big-questions/:group/template —— 生成自解释模板: 数据 sheet 排第 1(导入侧按 SheetNames[0] 解析,是承重契约)+「填写说明」sheet, 含列名含义/状态建议/常见被拒原因,让测试人员下载即会用,不依赖外部文档。 新增 POST /api/admin/big-questions/:group/import-excel —— 逐行调用 service.bigQuestion.createQuestion,标题长度/三小问/分值/泄题检测(detectAnswerLeak) 全部自动继承,与手工录入同一入口同一校验;批量导入绝不成为绕过防呆的后门。 组别以 URL :group 为准,忽略表格里的组别列;同组内标题重复即跳过并报错; 状态缺省 0(停用),批量录入的题未经人工校对不直接进抽题池。 客观题模板 buildQuestionTemplate 同步加「填写说明」sheet:数据 sheet 仍排第 1、 原 8 列不变,故 importQuestionsFromExcel 的 SheetNames[0] 解析不受影响(已离线验证)。 路由顺序硬约束:含具体段的 template / import-excel 必须排在通配段 /:id 之前。
This commit is contained in:
@@ -1638,6 +1638,30 @@ class AdminService extends Service {
|
||||
XLSX.utils.sheet_add_aoa(ws, [['判断题', '北斗系统由我国自主研发。', '', '', '', '', '对', 1]], { origin: -1 });
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, '客观题');
|
||||
// ⚠️ sheet 顺序是**契约**:导入侧(importQuestionsFromExcel)按 `SheetNames[0]` 解析,
|
||||
// 因此数据 sheet 必须排第 1,「填写说明」只能排第 2(删掉也不影响导入)。
|
||||
// R73:测试人员反馈「不知道导入模板长什么样」⇒ 让模板自解释,不必依赖外部文档。
|
||||
const notes = [
|
||||
['客观题导入模板 — 填写说明'],
|
||||
[],
|
||||
['列名', '是否必填', '说明'],
|
||||
['题型', '必填', '只能填「选择题」或「判断题」(英文 choice / judge 也能识别)'],
|
||||
['题目', '必填', '题干内容'],
|
||||
['选项A', '选填', '选项内容。选择题至少需 2 个非空选项'],
|
||||
['选项B', '选填', ''],
|
||||
['选项C', '选填', ''],
|
||||
['选项D', '选填', ''],
|
||||
['正确答案', '必填', '选择题填选项字母(如 C,多选如 AC);判断题填「对」或「错」'],
|
||||
['分值', '已废弃', '客观题分值口径为「每小题固定 1 分」,本列会被忽略,无需填写'],
|
||||
[],
|
||||
['【注意事项】'],
|
||||
['· 表头必须位于第 1 行,请勿删除或改写表头文字。'],
|
||||
['· 一行一道题;判断题请把选项列(A..D)留空。'],
|
||||
['· 导入完成后请在题库列表中核对数量是否与预期一致。'],
|
||||
];
|
||||
const notesWs = XLSX.utils.aoa_to_sheet(notes);
|
||||
notesWs['!cols'] = [{ wch: 14 }, { wch: 10 }, { wch: 66 }];
|
||||
XLSX.utils.book_append_sheet(wb, notesWs, '填写说明');
|
||||
return XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user