Files
competition-website/src/api/exam.js
qubiaobiao 1bfe007149 feat: 综合大题改造(替代 Word 上传)+ MockExam 按组分支
中职/高职组以「综合大题」替代原 Word 文件上传:管理员在后台录入题干
(wangEditor 富文本,含表格),系统按组随机抽题,考生在 3 个小问内作答,
每小题 10 分、大题合计 30 分。

- api/admin.js / api/exam.js / stores/exam.js:大题题库 CRUD、抽题、作答与评分链路
- Competition.vue:Word 上传区块替换为大题作答区(3 小问 + 字数上限 + 富文本题干渲染)
- Admin.vue:大题题库管理、考生作答查看与人工评分
- MockExam.vue:按组分支——中职/高职显示综合大题,其余组保持 Word 答题;侧栏与提交提示同步
- RichEditor.vue + utils/richtext.js:题干富文本渲染前做白名单消毒(零依赖,纵深防御)

说明:big_questions / big_question_answers 建表迁移 023/024 随后端仓库提交。
2026-09-21 11:17:59 +08:00

100 lines
3.6 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.
import request from './request'
// ==================== 考试/知识竞答 API ====================
/**
* 获取考试状态
* @param {string} group - 组别标识
* @returns {Promise} { isActive, startTime, endTime, countdown, status, submitted }
*/
export function getExamStatus(group) {
return request.get(`/exam/${group}/status`)
}
/**
* 获取考试题目列表
* 抽题数量以后台 competition_times 配置为准(choice_count / judge_count),
* 题目数量不足时自动按题库实际数量封顶,题号随实际数量自适应。
* @param {string} group - 组别标识
* @returns {Promise} 题目数组
*/
export function getQuestions(group) {
return request.get(`/exam/${group}/questions`)
}
/**
* 提交单个答案
* @param {string} group - 组别标识
* @param {array} answers - 答案数组 [{ question_uid, answer_data }]
* @returns {Promise}
*/
export function submitAnswers(group, answers) {
return request.post(`/exam/${group}/submit-answers`, { answers }, { silent: true })
}
// 注:原 getWordTemplate / uploadWord / deleteWord 三个封装已随「中职/高职取消 Word
// 上传题」一并移除(2026-09-21)。后端对应路由亦已对中职/高职封堵;本科/教师组的
// 文档上传走分片通道 @/api/upload,不经此处。
// ==================== 综合大题(中职/高职) ====================
/**
* 获取综合大题(首次调用即抽题并落库快照,后续调用复用同一题)
*
* 服务端保证「一考生一行、一题一快照」:抽题幂等(reused=true 表示复用),
* 题干以抽题时刻的快照为准(管理员后续改题干不影响已抽考生,防纠纷)。
* 响应体不含任何分数字段(成绩发布门控)。
* @param {string} group - 组别标识(仅 zhongzhi / gaozhi)
* @returns {Promise} {
* question: { big_question_uid, title, description, sub_prompts, score_per_sub, total_score, snapshot_at },
* answers: { answer_1, answer_2, answer_3 },
* submitTime, maxChars, subCount, reused
* }
*/
export function getBigQuestion(group) {
// silent:本接口的失败(如 4004 该组暂无大题)由答题区自行展示 + 提供重试,
// 避免与页面其他提示重复弹窗。
return request.get(`/exam/${group}/big-question`, { silent: true })
}
/**
* 保存综合大题三小问答案(整卷覆盖保存)
*
* 超长由前端 maxlength 与服务端双重拦截;服务端超限返回 400 且整批拒绝(不静默截断)。
* @param {string} group - 组别标识
* @param {{answer_1: string, answer_2: string, answer_3: string, forceSubmit?: boolean}} data
* @returns {Promise} { filled, subCount, maxChars, savedAt }
*/
export function saveBigAnswer(group, data) {
return request.post(`/exam/${group}/big-answer`, data, { silent: true })
}
/**
* 提交全部答案(结束考试)
* @param {string} group - 组别标识
* @param {object} data - 提交数据
* @returns {Promise}
*/
export function submitAll(group, data) {
return request.post(`/exam/${group}/submit-all`, data, { silent: true })
}
/**
* 获取已有答案(考试重新进入时恢复状态)
* @param {string} group - 组别标识
* @returns {Promise} { answers: [...] }
*/
export function getAnswers(group) {
return request.get(`/exam/${group}/answers`)
}
/**
* 获取当前选手个人成绩(后端按认证用户取,group 仅作透传参数)
* GET /score
* @param {string} group - 组别标识
* @returns {Promise} 成绩详情(含答题得分/作品评分/总分)
*/
export function getScore(group) {
return request.get('/score', { params: { group } })
}