Files
competition-website/app/model/zhongzhiSubmission.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

132 lines
3.7 KiB
JavaScript
Raw 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';
/**
* 中职组答题记录模型 - ZhongzhiSubmission
*
* 存储中职组参赛选手的答题记录,包括答题数据、判分和Word文件上传。
* 表名:zhongzhi_submissions
*/
module.exports = app => {
const { STRING, INTEGER, TEXT, BOOLEAN, DATE } = app.Sequelize;
// 分数列统一走共享工厂(DECIMAL(6,2) + Number 兜底,见 app/lib/decimal_score.js)
const decimalScore = require('../lib/decimal_score')(app);
const ZhongzhiSubmission = app.model.define('zhongzhi_submission', {
// 主键 ID,自增
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '提交记录唯一标识',
},
// 外键:关联聚合报名 ID
aggregation_uid: {
type: INTEGER,
allowNull: false,
comment: '关联聚合报名 ID',
},
// 外键:关联题目 ID
question_uid: {
type: INTEGER,
allowNull: false,
comment: '关联题目 ID',
},
// 题型(choice / judge):与 question_uid 联合唯一标识一道题。
// 选择题与判断题的 id 在不同表中可能数值重叠(如 choice#5 与 judge#5),
// 仅凭 question_uid 无法区分,必须结合 question_type 存储,否则会折叠丢失答案(见 B11)。
question_type: {
type: STRING(16),
allowNull: false,
defaultValue: '',
comment: '题型:choice/judge,与 question_uid 联合区分题目',
},
// 答题数据(选手提交的答案)
answer_data: {
type: TEXT,
allowNull: true,
defaultValue: null,
comment: '答题数据',
},
// 是否判为正确
is_correct: {
type: BOOLEAN,
allowNull: true,
defaultValue: null,
comment: '是否判为正确',
},
// 得分
score: decimalScore('score', '得分(可含 2 位小数)', { defaultValue: null }),
// Word 文件存储路径
word_file_path: {
type: STRING(500),
allowNull: true,
defaultValue: null,
comment: 'Word 文件存储路径',
},
// Word 文件原始名称
word_file_name: {
type: STRING(200),
allowNull: true,
defaultValue: null,
comment: 'Word 文件原始名称',
},
// Word 文件 MD5
word_file_md5: {
type: STRING(32),
allowNull: true,
defaultValue: null,
comment: 'Word 文件 MD5',
},
// 记录创建时间
created_at: {
type: DATE,
allowNull: false,
defaultValue: app.Sequelize.literal('CURRENT_TIMESTAMP'),
comment: '创建时间',
},
// 记录更新时间
updated_at: {
type: DATE,
allowNull: false,
defaultValue: app.Sequelize.literal('CURRENT_TIMESTAMP'),
comment: '更新时间',
},
}, {
tableName: 'zhongzhi_submissions',
timestamps: false,
underscored: true,
indexes: [
{
fields: ['aggregation_uid'],
},
{
fields: ['question_uid'],
},
{
// 唯一约束:同一选手同一题(题型+题号)只保留一行,杜绝并发作答重复落库(B12)。
unique: true,
name: 'uk_zhongzhi_submission',
fields: ['aggregation_uid', 'question_uid', 'question_type'],
},
],
});
// 关联定义
ZhongzhiSubmission.associate = function() {
app.model.ZhongzhiSubmission.belongsTo(app.model.Aggregation, {
foreignKey: 'aggregation_uid',
targetKey: 'aggregation_uid',
as: 'aggregation',
});
app.model.ZhongzhiSubmission.belongsTo(app.model.ZhongzhiQuestion, {
foreignKey: 'question_uid',
targetKey: 'question_uid',
as: 'zhongzhiQuestion',
});
};
return ZhongzhiSubmission;
};