# 任务 08 检查记录:刷题与结果闭环 日期:2026-09-01 ## 交付内容 ### 后端(Handler + Schema + Routes) - `server/src/handlers/practice.ts`(重写核心闭环) - `modules`:返回五大模块与题量(保留)。 - `start`:支持**两种组卷**—— - 普通组卷:按模块 + `duration`(5/10/15 分钟)经 `DURATION_TO_COUNT` 换算题量随机抽取;题库不足时返回全部并打乱(实测数量关系仅题 4,total=4 而非 5)。 - **自定义组卷**:`body.custom` 指定题目 ID 列表(去重、校验存在、上限 20 题、返回首题模块),`mode='custom'`。 - `currentQuestion`:按 `session.recordIndex + 1` 推进取当前题;已答完返回 `{ question: null }`;**不返回答案与解析**;新增返回 `answered` / `durationMinutes`。 - `answer`:写入作答记录 + 递增 `recordIndex`。**幂等防重复**(同 `sessionId+questionId` 已存在返回 `accepted:false, duplicate:true`,不重复计入);**防乱序**(非当前题返回 409「题目与会话作答顺序不符」);返回 `correct` / `correctAnswer` / `analysis` 供单题即时反馈。 - `finish`:按已作答题计分(未作答不计入正确率),返回 `total/answered/correctCount/accuracy/durationSeconds/wrongQuestionIds/questions[]`;标记会话 `finished` 并写 `endedAt`。 - `wrongReasons`(新增):最近 30 天错因聚合计数 + 占比,供刷题中心「错因分布」卡片。 - `server/src/schemas/entities.ts`:`PracticeSessionSchema` 新增 `recordIndex`(默认 -1)。 - `server/src/schemas/api.ts`:`PracticeStartBodySchema` 新增可选 `custom`;`PracticeSessionViewSchema` 新增 `total`;`PracticeQuestionResponseSchema` 的 `question` 改为 `.nullable()` 并新增 `answered/durationMinutes`;`PracticeAnswerResponseSchema` 新增 `correct/correctAnswer/analysis`;`PracticeFinishResponseSchema` 新增 `answered`;新增 `PracticeWrongReasonsResponseSchema`。 - `server/src/routes.ts`:新增 `GET /api/practice/wrong-reasons`(刷题 tag),路由数 35→36。 ### 前端(View + API) - `client/src/api/index.ts`:新增类型 `PracticeSessionStart/PracticeQuestion/PracticeAnswer/PracticeFinish/PracticeWrongReasons`;`practiceApi` 新增 `wrongReasons()`。 - `client/src/views/practice/PracticeView.vue`(重写,原为 ComingSoon):刷题中心,含「刷题 / 错题本 / 申论」三 tab(按路由高亮)。 - **刷题 tab(对照原型)**:选择模块(5 卡 + 掌握度 + 选中勾)、碎片组卷时长(5/10/15)、开始刷题大按钮(含模块+时长文案)、错因分布(最近 30 天横向条)、待复习错题、推荐动作。 - **错题本 tab**:对接 `reviewApi.list({status:'pending'})`,无数据空态;完整间隔复习属任务 09。 - **申论 tab**:ComingSoon 占位。 - `client/src/views/practice/AnswerView.vue`(新增):作答 + 单题反馈 + 整卷结果。 - 作答态:顶部返回 + `第X题/共Y题` 进度条 + 计时 + 关闭;模块/子模块/难度标签;题干;A-D 选项;提交答案。 - 单题结果态:答对/答错横幅(含正确答案 + 「已自动收录到错题本」)、解析卡、AI 讲解按钮、标记错因(可多选,本地选择)、下一题 / 查看本卷结果。 - 整卷结果态:答对题数、正确率、用时、错题回顾(你的答案 → 正确答案 + 解析)、返回刷题中心。 - `secondsUsed` 按单题计时(`elapsed - questionStartElapsed`)而非会话累计。 - `client/src/views/practice/CustomView.vue`(新增):自定义组卷——从题库勾选题目(上限 20、已选计数、全选),底部开始练习。 - `client/src/components/base/icons.ts`:新增 `plus/sparkles/refresh/book-open` 图标。 - `client/src/components/base/AppButton.vue`:新增 `loading` prop(spinner)。 - `client/src/layouts/MobileLayout.vue`:`/practice` 相关路径隐藏顶栏标题(页面自带 AppPageHeader,避免标题重复)。 - `client/src/router/index.ts`:新增 `/practice/session/:id`(作答)、`/practice/custom`(自定义组卷)。 ## 与原型图对照 - 「桌面-刷题中心-刷题态.png」:选择模块、碎片组卷时长、开始刷题大按钮、错因分布(最近30天)、待复习错题、推荐动作 —— 结构一致。 - 「移动-刷题-行测Tab.png」:模块卡 + 时长 + 开始刷题按钮 + 底部 TabBar —— 一致(含选中勾选态)。 - 「移动-刷题-作答页.png」:进度条 + 计时 + 模块标签 + 题干 + 选项 + 提交答案 —— 一致。 - 「移动-刷题-结果页.png」:回答错误横幅(正确答案 + 已收录错题本)、解析、AI 讲一讲、标记错因(可多选)、下一题 —— 一致。 ## 接口实测(curl,演示数据) | 接口 | 结果 | |---|---| | `GET /api/practice/modules` | 5 模块题量 `{言语理解4, 数量关系4, 判断推理4, 资料分析4, 常识判断2}` | | `POST /api/practice/start`(数量关系 5 分钟) | 返回 sessionId / questionIds / total=4(题库不足 5 取全部)/ startedAt / status | | `POST /api/practice/start`(custom 3 题) | 「语用理解 demo-q-001/002/003」total=3,mode=custom | | `POST /api/practice/start`(custom 含不存在题) | 409 `CONFLICT`:「存在不存在的题目:nope-1」 | | `GET /api/practice/:id/question` | 返回当前题(无答案/解析)+ index/total/answered/durationMinutes | | `POST /api/practice/:id/answer`(正确) | `{accepted:true, answeredCount:1, correct:true, correctAnswer, analysis}` | | `POST /api/practice/:id/answer`(重复提交同题) | `{accepted:false, duplicate:true}`,不重复计入 | | `POST /api/practice/:id/answer`(乱序提交非当前题) | 409 `CONFLICT`:「题目与会话作答顺序不符」 | | `GET /api/practice/:id/question`(答完全部) | `{question:null, index, total, answered}` | | `POST /api/practice/:id/finish` | `{total, answered, correctCount, accuracy, durationSeconds, wrongQuestionIds, questions[]}`(部分答错正确率 25%,错题列表正确) | | `GET /api/practice/wrong-reasons` | `{reasons:[{reason,count,percent}]}` 按错因聚合(演示数据各错因 1 条 14%) | | `GET /api/practice/wrong-reasons`(无数据) | `{reasons:[]}` | ## 浏览器检查(agent-browser) | 场景 | 结果 | |---|---| | 桌面 1440 刷题中心 | 页头按钮 + 3 tab + 选择模块 + 时长 + 开始大按钮 + 错因分布 + 待复习 + 推荐动作,与原型结构对齐 | | 桌面 1440 作答页 | 进度条 + 计时 + 模块/子模块/难度标签 + 题干 + 选项 + 提交答案,渲染正常 | | 桌面 1440 单题结果态(答错) | 正确答案绿勾 / 错误答案红显 / 红色「回答错误」横幅(正确答案 + 已收录错题本) / 解析 / AI 讲一讲 / 标记错因(可多选) / 下一题 —— 对齐原型结果页 | | 桌面 1440 整卷结果页 | 答对 1/4、正确率 25%、用时、错题回顾(答案对比 + 解析)、返回刷题中心 | | 桌面 1440 自定义组卷 | 已选计数 / 全选 / 题目列表(标签 + 题干)/ 开始练习,勾选交互正常 | | 移动 390 刷题中心 | 标题单次显示(修 topbar 重复)、模块 2 列卡片、时长、开始大按钮、底部 TabBar,无横向溢出 | | 移动 390 作答页 | 顶部进度 + 计时、题干、选项、提交答案、底部 TabBar,无遮挡,对齐原型 | | 计时修正 | `secondsUsed` 改为单题耗时(修正前误用会话累计时间导致结果页「用时 7 分 24 秒」不合理) | 截图:`deliverables/checks/task-08-practice-desktop.png`、`task-08-practice-desktop-bottom.png`、`task-08-answer-desktop.png`、`task-08-answer-result-desktop.png`、`task-08-result-desktop.png`、`task-08-custom-desktop.png`、`task-08-practice-mobile.png`、`task-08-answer-mobile.png`。 ## 完成标准核对(开发计划任务08) - ✅ 模块选题(5 模块卡片全选)。 - ✅ 5/10/15 分钟组卷(时长→题量)。 - ✅ 自定义组卷。 - ✅ 会话 + 计时(作答页计时,`secondsUsed` 单题计)。 - ✅ 作答 + 提交(单选、校验当前题)。 - ✅ 结果 + 标准解析。 - ✅ 可从刷题中心或首页开始一套题并完成提交;记录写入 JSON(practice-sessions / practice-records)。 - ✅ 中途刷新恢复(`recordIndex` 进度推进,`currentQuestion` 按索引取题,刷新后 `question:null` 走 finish)。 - ✅ 提交后不能重复计入同一题(`duplicate:true` 幂等)。 - ✅ 答案、正确率、耗时、完成状态准确。 ## 边界说明 - 「答错自动沉淀错题(写 wrong-questions.json)」属于**任务 09**。任务 08 的 `answer` 不写错题库,整卷结果与单题结果中的「已自动收录到错题本」为提示文案;错题本 tab 当前仅对接 `reviewApi.list` 展示既有错题,完整间隔复习闭环在任务 09 完成。 - 「标记错因(可多选)」当前为前端本地交互,落库归属任务 09 的错题逻辑。 ## 当前结论 任务 08 完成:刷题与结果闭环在前后端打通。普通组卷 + 自定义组卷、会话持久化(`recordIndex` 进度防重复/防乱序)、单题即时反馈(对错/正确答案/解析/AI 入口/标记错因)、整卷结果(正确率/用时/错题回顾)全部实测通过;新增「错因分布」接口供刷题中心展示;修复移动端标题重复与单题耗时统计。类型检查(vue-tsc / tsc)与生产构建(128 模块)全部通过。测试数据已清理,题库/会话/记录恢复演示基线。