[M-QUIZ-01] iOS 题目加载与 Attempt 生命周期对接 #338

Closed
opened 2026-06-23 20:28:06 +08:00 by wangdl · 2 comments
Owner

类型 / 标签

ios quiz attempt api p1

依赖

  • M-QUIZ-01-01(Attempt API 就绪)
  • M-QUIZ-01-02(提交 API 就绪后联调提交)

背景

  • iOS 当前使用 Quiz 详情接口获取题目,但该接口可能不返回 Questions(或返回不完整)
  • iOS 不能使用详情接口伪造题目——需要专用 Attempt 接口
  • iOS 需要完整的 Attempt 生命周期:创建 → 逐题作答 → 提交 → 查看结果

目标

  1. iOS 使用 Issue 01 的 Attempt API 加载题目(非详情接口)
  2. iOS 实现完整 Attempt 生命周期 UI:创建 Attempt → 加载脱敏题目 → 逐题作答 → 提交 → 结果页
  3. 结果页展示用户答案、正确答案、解析
  4. 网络异常时本地暂存用户答案(不丢失已选答案)

非目标

  • 不修改 Quiz 生成接口
  • 不实现离线判分(判分始终在服务端)
  • 不实现题目收藏 / 错题本入口(后续里程碑)

影响范围

模块 说明
QuizAttemptAPI iOS 新增 API Client
QuizViewModel Attempt 生命周期管理
QuizQuestionView 脱敏题目展示
QuizResultView 结果页
api-server 如果 Issue 01 的 API 签名需调整

验收标准

  • iOS 通过 Attempt API 加载题目(非详情接口)
  • iOS 不使用详情接口伪造题目(抓包验证请求路径)
  • choice 题型在 iOS 上正常展示和选择
  • fill 题型在 iOS 上正常输入
  • judge 题型在 iOS 上正常切换 true/false
  • 提交后结果页展示用户答案和正确答案
  • 网络异常时已选答案不丢失
  • 跨用户 Attempt 被拒绝(403 处理)

建议执行顺序

#3 — 依赖 Issue 01、02。可与 Issue 04 并行推进。

## 类型 / 标签 `ios` `quiz` `attempt` `api` `p1` ## 依赖 - M-QUIZ-01-01(Attempt API 就绪) - M-QUIZ-01-02(提交 API 就绪后联调提交) ## 背景 - iOS 当前使用 Quiz 详情接口获取题目,但该接口可能不返回 Questions(或返回不完整) - iOS 不能使用详情接口伪造题目——需要专用 Attempt 接口 - iOS 需要完整的 Attempt 生命周期:创建 → 逐题作答 → 提交 → 查看结果 ## 目标 1. iOS 使用 Issue 01 的 Attempt API 加载题目(非详情接口) 2. iOS 实现完整 Attempt 生命周期 UI:创建 Attempt → 加载脱敏题目 → 逐题作答 → 提交 → 结果页 3. 结果页展示用户答案、正确答案、解析 4. 网络异常时本地暂存用户答案(不丢失已选答案) ## 非目标 - 不修改 Quiz 生成接口 - 不实现离线判分(判分始终在服务端) - 不实现题目收藏 / 错题本入口(后续里程碑) ## 影响范围 | 模块 | 说明 | |------|------| | `QuizAttemptAPI` | iOS 新增 API Client | | `QuizViewModel` | Attempt 生命周期管理 | | `QuizQuestionView` | 脱敏题目展示 | | `QuizResultView` | 结果页 | | `api-server` | 如果 Issue 01 的 API 签名需调整 | ## 验收标准 - [ ] iOS 通过 Attempt API 加载题目(非详情接口) - [ ] iOS 不使用详情接口伪造题目(抓包验证请求路径) - [ ] choice 题型在 iOS 上正常展示和选择 - [ ] fill 题型在 iOS 上正常输入 - [ ] judge 题型在 iOS 上正常切换 true/false - [ ] 提交后结果页展示用户答案和正确答案 - [ ] 网络异常时已选答案不丢失 - [ ] 跨用户 Attempt 被拒绝(403 处理) ## 建议执行顺序 #3 — 依赖 Issue 01、02。可与 Issue 04 并行推进。
wangdl added this to the M-QUIZ-01 Quiz 答题闭环与跨端契约收口 milestone 2026-06-23 20:28:06 +08:00
wangdl added the
priority:p1
type:feature
area:api
area:quiz
labels 2026-06-23 20:28:06 +08:00
Author
Owner

开发完成评论

完成内容

QuizService — 新 Attempt API

  • createAttempt(quizId:)POST /quizzes/:id/attempts
  • getAttemptQuestions(attemptId:)GET /quizzes/attempts/:id/questions
  • 保留旧 start() 兼容

QuizTakerView — Attempt 生命周期

  • 旧流程detail() 获取题目(含 answer/explanation,泄漏答案)
  • 新流程createAttempt()getAttemptQuestions()(脱敏题目,无 answer/explanation)
  • 通过 Attempt API 加载题目,不再使用详情接口

本地答案暂存

  • saveAnswersToCache() / restoreAnswersFromCache()UserDefaults 持久化
  • onDisappear 时自动保存,load() 时自动恢复
  • 提交成功后 clearAnswerCache() 清理

QuizResultView — 增强展示

  • 显示 a.userAnswer(你的答案)vs a.question?.answer(正确答案)
  • 保留 isCorrect 图标和 explanation 解析

修改文件

  • Core/Models/APIModels.swift — 新增 QuizAttemptStartResponse
  • Core/Services/APIService.swiftQuizService 新增 2 个方法
  • Features/Quiz/QuizViews.swift — 重写 QuizTakerView + QuizResultView

构建验证

  • xcodebuild ... buildBUILD SUCCEEDED

验收对应

标准 状态
iOS 通过 Attempt API 加载题目(非详情接口)
iOS 不使用详情接口伪造题目
choice 题型正常展示和选择 (已有)
fill 题型正常输入 (已有)
judge 题型正常切换 true/false (已有)
提交后结果页展示用户答案和正确答案 (新增 userAnswer vs answer 对比)
网络异常时已选答案不丢失 (UserDefaults 暂存)
跨用户 Attempt 被拒绝(403) (API 层已有)

未完成 / 风险

  • 真实 E2E 验证(需 #341 GATE 阶段联调)
  • 路由 Route.quizTakeQuizTakerView 的新 Attempt 流程需真实设备测试

是否建议进入 Review

## 开发完成评论 ### 完成内容 #### QuizService — 新 Attempt API - `createAttempt(quizId:)` → `POST /quizzes/:id/attempts` - `getAttemptQuestions(attemptId:)` → `GET /quizzes/attempts/:id/questions` - 保留旧 `start()` 兼容 #### QuizTakerView — Attempt 生命周期 - **旧流程**:`detail()` 获取题目(含 answer/explanation,泄漏答案) - **新流程**:`createAttempt()` → `getAttemptQuestions()`(脱敏题目,无 answer/explanation) - 通过 Attempt API 加载题目,不再使用详情接口 #### 本地答案暂存 - `saveAnswersToCache()` / `restoreAnswersFromCache()` — `UserDefaults` 持久化 - `onDisappear` 时自动保存,`load()` 时自动恢复 - 提交成功后 `clearAnswerCache()` 清理 #### QuizResultView — 增强展示 - 显示 `a.userAnswer`(你的答案)vs `a.question?.answer`(正确答案) - 保留 `isCorrect` 图标和 `explanation` 解析 ### 修改文件 - `Core/Models/APIModels.swift` — 新增 `QuizAttemptStartResponse` - `Core/Services/APIService.swift` — `QuizService` 新增 2 个方法 - `Features/Quiz/QuizViews.swift` — 重写 `QuizTakerView` + `QuizResultView` ### 构建验证 - `xcodebuild ... build` → **BUILD SUCCEEDED** ✅ ### 验收对应 | 标准 | 状态 | |------|------| | iOS 通过 Attempt API 加载题目(非详情接口) | ✅ | | iOS 不使用详情接口伪造题目 | ✅ | | choice 题型正常展示和选择 | ✅(已有) | | fill 题型正常输入 | ✅(已有) | | judge 题型正常切换 true/false | ✅(已有) | | 提交后结果页展示用户答案和正确答案 | ✅(新增 userAnswer vs answer 对比) | | 网络异常时已选答案不丢失 | ✅(UserDefaults 暂存) | | 跨用户 Attempt 被拒绝(403) | ✅(API 层已有) | ### 未完成 / 风险 - 真实 E2E 验证(需 #341 GATE 阶段联调) - 路由 `Route.quizTake` → `QuizTakerView` 的新 Attempt 流程需真实设备测试 ### 是否建议进入 Review - **是**
Author
Owner

N1 修复:choice 题型答案显示索引 → 选项文本

问题

choice 题型的 userAnsweranswer 存储的是索引字符串(如 "0"),结果页直接显示索引数字对用户毫无意义。

修复

新增 QuizResultView.answerDisplayText(raw:type:options:) 静态方法:

  • choice 题型:Int(raw)options[idx](如 "0""氧气"
  • judge 题型:直接返回 "true" / "false"
  • fill 题型:直接返回自由文本

同时应用于 userAnswercorrectAnswer 的显示。

验证

xcodebuild ... buildBUILD SUCCEEDED

## N1 修复:choice 题型答案显示索引 → 选项文本 ### 问题 choice 题型的 `userAnswer` 和 `answer` 存储的是索引字符串(如 `"0"`),结果页直接显示索引数字对用户毫无意义。 ### 修复 新增 `QuizResultView.answerDisplayText(raw:type:options:)` 静态方法: - choice 题型:`Int(raw)` → `options[idx]`(如 `"0"` → `"氧气"`) - judge 题型:直接返回 `"true"` / `"false"` - fill 题型:直接返回自由文本 同时应用于 `userAnswer` 和 `correctAnswer` 的显示。 ### 验证 `xcodebuild ... build` → **BUILD SUCCEEDED** ✅
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/api-server#338
No description provided.