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

107 lines
2.7 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';
/**
* 作品提交模型 - WorkSubmission
*
* 存储选手上传的作品文件信息,包括文件类型、名称、路径等。
* 支持 plan(计划书)和 ppt(演示文稿)两种文件类型。
* 表名:work_submissions
*/
module.exports = app => {
const { STRING, INTEGER, DATE } = app.Sequelize;
// 分数列统一走共享工厂(DECIMAL(6,2) + Number 兜底,见 app/lib/decimal_score.js)
const decimalScore = require('../lib/decimal_score')(app);
const WorkSubmission = app.model.define('work_submission', {
// 主键 ID,自增
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
comment: '作品提交唯一标识',
},
// 选手 ID,外键关联 contestants 表
contestant_id: {
type: INTEGER,
allowNull: false,
comment: '选手 ID',
references: {
model: 'contestants',
key: 'id',
},
},
// 文件类型:plan(计划书)/ ppt(演示文稿)
file_type: {
type: STRING(20),
allowNull: false,
comment: '文件类型',
validate: {
isIn: [['plan', 'ppt']],
},
},
// 文件名(原始文件名)
file_name: {
type: STRING(200),
allowNull: false,
comment: '原始文件名',
},
// 文件存储路径(服务器上的相对路径)
file_path: {
type: STRING(500),
allowNull: false,
comment: '文件存储路径',
},
// 文件大小(字节)
file_size: {
type: INTEGER,
allowNull: false,
comment: '文件大小(字节)',
},
// 提交时间
submit_time: {
type: DATE,
allowNull: false,
comment: '提交时间',
},
// 得分(管理员评分后更新,NULL 表示尚未评分)
score: decimalScore('score', '作品得分', { defaultValue: null }),
// 创建时间
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: 'work_submissions',
timestamps: false,
underscored: true,
indexes: [
{
fields: ['contestant_id'],
},
{
fields: ['file_type'],
},
],
});
// 关联关系:一条作品提交记录属于一名选手
WorkSubmission.associate = () => {
app.model.WorkSubmission.belongsTo(app.model.Contestant, {
foreignKey: 'contestant_id',
as: 'contestant',
});
};
return WorkSubmission;
};