Files
competition-website/app/model/bigQuestionAnswer.js
qubiaobiao 2ab0991310 fix(admin): 复核页选择题选项渲染修复 + 阅卷导出 Word + 评分两位小数(R28)
针对测试人员反馈的四项:
1) 复核明细「选择题大量空白」:options 为 text 列存 JSON 字符串,原先被原样透传,
   前端 v-for 对字符串逐字符迭代(op.key/op.text 皆 undefined)⇒ 每字符渲染一行。
   新增 parseOptions()(仅 string 才 JSON.parse,失败降级 null)。
2) 选项与答案不随机:仅题目用 RAND() 抽题,normalizeQuestion 无 shuffle,字母无歧义。
3) 评分精度:DECIMAL(6,1) -> DECIMAL(6,2),migration 026 覆盖 17 列(与 025 同范围),
   lib/decimal_score.js 与 11 个 model 注释同步;汇总收敛 round2(×100/100)。
4) 阅卷导出:GET /api/admin/big-answers/:group/export?ids=
   单选考生 -> 直接 .docx;多位/全部 -> 每位考生单独 .docx 打包 .zip。
   路由为 3 段,注册在 :group 通配段之前。

验证:verify_big_question 51 PASS/1 FAIL(唯一 FAIL=测试人员新数据,非本次改动)、
verify_score_leak_all_channels 24/24、verify_question_no_answer_leak 4/3/0/1。
2026-09-28 15:50:46 +08:00

98 lines
2.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/**
* 综合大题作答模型 - BigQuestionAnswer
*
* 对应 big_question_answers 表(2026-09-21 新增)。
*
* 一对一行:UNIQUE(aggregation_uid) —— 一个考生一行数据(用户明确要求的存储形态),
* 同时与「逐题一行」的客观题表 zhongzhi_submissions / gaozhi_submissions 完全隔离。
*
* question_snapshot:抽题那一刻的题干快照(JSON 文本)。因 MariaDB 5.5 无 JSON 列类型,
* 以 MEDIUMTEXT 存储,由应用层 JSON.stringify / JSON.parse 处理。
*/
module.exports = app => {
const { STRING, INTEGER, DATE, TEXT } = app.Sequelize;
// 分数列统一走共享工厂(DECIMAL(6,2) + Number 兜底,见 app/lib/decimal_score.js)
const decimalScore = require('../lib/decimal_score')(app);
return app.model.define('BigQuestionAnswer', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '主键ID',
},
aggregation_uid: {
type: INTEGER,
allowNull: false,
comment: '聚合报名ID(中职/高职组 == contestants.id),唯一 → 一考生一行',
},
contestant_id: {
type: INTEGER,
allowNull: true,
comment: '选手ID(冗余,便于管理端查证与排障)',
},
group: {
type: STRING(20),
allowNull: false,
comment: '组别:zhongzhi/gaozhi',
},
big_question_id: {
type: INTEGER,
allowNull: true,
comment: '抽中的大题ID',
},
big_question_uid: {
type: STRING(64),
allowNull: true,
comment: '抽中的大题稳定标识',
},
question_snapshot: {
type: TEXT('medium'),
allowNull: true,
comment: '抽题时刻的题干快照 JSON(防纠纷存证)',
},
answer_1: {
type: TEXT,
allowNull: true,
comment: '第1小问作答(上限 2000 字)',
},
answer_2: {
type: TEXT,
allowNull: true,
comment: '第2小问作答(上限 2000 字)',
},
answer_3: {
type: TEXT,
allowNull: true,
comment: '第3小问作答(上限 2000 字)',
},
score_1: decimalScore('score_1', '第1小问得分(0-10,可含 2 位小数)'),
score_2: decimalScore('score_2', '第2小问得分(0-10,可含 2 位小数)'),
score_3: decimalScore('score_3', '第3小问得分(0-10,可含 2 位小数)'),
total_score: decimalScore('total_score', '三小问得分合计(可含 2 位小数)'),
submit_time: {
type: DATE,
allowNull: true,
comment: '随整卷正式提交写入(未提交为 NULL)',
},
created_at: {
type: DATE,
defaultValue: app.Sequelize.NOW,
comment: '创建时间',
},
updated_at: {
type: DATE,
defaultValue: app.Sequelize.NOW,
comment: '更新时间',
},
}, {
tableName: 'big_question_answers',
timestamps: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
});
};