feat: support composite practice questions

This commit is contained in:
Codex
2026-06-29 15:57:03 +08:00
parent 07a6edeea2
commit e54efbc294
14 changed files with 893 additions and 43 deletions

View File

@@ -254,6 +254,11 @@ GET /api/learning/practice-sessions/detail?practiceSessionId=<sessionId>
"<questionId>": {
"selectedOptions": ["1"],
"answerText": null,
"answerPayload": {
"mode": "composite",
"subAnswers": [],
"subResults": []
},
"isCorrect": true,
"answeredAt": "..."
}
@@ -268,6 +273,7 @@ GET /api/learning/practice-sessions/detail?practiceSessionId=<sessionId>
- 继续练习入口优先从 `GET /api/learning/practice-sessions/history?status=active` 获取未完成 session再带 `practiceSessionId` 进入练习页。
- 练习页如果 URL 有 `practiceSessionId`,先调用 detail 恢复后端题目快照和最新答案,不要新建 session。
- `answersByQuestion` 是同一题的最新答题记录,答题卡、正确/错误统计和解析展示以它为准。
- 阅读理解、案例分析等复合题会在 `answerPayload.subAnswers/subResults` 中返回子题作答、判分、解析和分值;继续练习时按该结构恢复每个子题状态。
- 倒计时以 `expiresAt` 计算剩余时间;不要用本地启动时间重新生成考试时长。
- detail 只返回当前用户自己的 session跨用户或跨租户读取会返回 `PRACTICE_SESSION_NOT_FOUND`
@@ -310,6 +316,61 @@ POST /api/learning/answers
}
```
阅读理解、案例分析、组合题等带 `subQuestions` 的复合题必须使用 `subAnswers`,不能混用顶层 `selectedOptions/answerText/selfJudgedCorrect`
```json
{
"practiceSessionId": "...",
"questionId": "...",
"subAnswers": [
{
"subQuestionId": "main-idea",
"selectedOptions": ["1"]
},
{
"subQuestionId": "reason",
"answerText": "学生自己的作答或备注",
"selfJudgedCorrect": true
}
]
}
```
复合题响应会额外返回:
```json
{
"item": {
"isCorrect": true,
"answerPayload": {
"mode": "composite",
"subAnswers": [
{ "subQuestionId": "main-idea", "selectedOptions": ["1"], "answerText": null },
{ "subQuestionId": "reason", "selectedOptions": [], "answerText": "学生自己的作答或备注", "selfJudgedCorrect": true }
],
"subResults": [
{
"subQuestionId": "main-idea",
"order": 1,
"type": "choice",
"selectedOptions": ["1"],
"isCorrect": true,
"correctOptionIndices": [1],
"explanation": "..."
}
],
"summary": {
"answeredCount": 2,
"correctCount": 2,
"wrongCount": 0,
"unansweredCount": 0
}
},
"subResults": []
}
}
```
响应关键字段:
```json
@@ -329,6 +390,8 @@ POST /api/learning/answers
前端处理规则:
- 客观题不要传 `selfJudgedCorrect`。后端会用题库标准答案判分,传了会返回 `SELF_JUDGMENT_NOT_ALLOWED`
- 客观子题同样不要传 `selfJudgedCorrect`;主观子题可以传 `selfJudgedCorrect`
- 复合题如果缺少 `subAnswers` 会返回 `SUB_ANSWERS_REQUIRED`;空提交会返回 `SUB_ANSWERS_EMPTY`;未知子题 id 会返回 `UNKNOWN_SUB_ANSWER`
- 主观题自评也由后端落库为 `answer_records.is_correct`,错题本、练习统计、模考报告都以后端返回为准。
- 前端可以在本地缓存当前 session 的答题卡和当前题号,用于刷新恢复体验;但交卷报告只以后端 `answer_records` 和 session 快照计算。
- `answerText` 只保存学生作答或备注,不要为了让后端判对而把参考答案塞进去。
@@ -430,6 +493,13 @@ POST /api/tenant-admin/badge-grants
}
```
复合题报告规则:
- `totalQuestions` 仍按顶层大题计数,阅读理解/案例分析不会按子题拆成多题。
- `questionResults[].subResults` 返回每个子题的 `selectedOptions/answerText/isCorrect/explanation/score/totalScore`
- 若导入数据没有给子题分值,后端默认把该大题分值平均分给所有子题;如果后续导入模板提供子题 `score`,报告会按子题分值再缩放到大题配置分。
- 顶层 `isCorrect=true` 表示所有子题都判为正确;若部分正确,顶层为 `false`,但 `score` 会保留部分得分。
前端处理规则:
- 重复交卷是幂等的,后端会返回同一份报告。