羽毛球教程 HarmonyOS 学习应用(05):测验题、答题结果与错题反馈

羽毛球教程 HarmonyOS 学习应用(05):测验题、答题结果与错题反馈
一、测验链路不能只计算一个分数知识测验至少包含题目加载、单选、立即判定、答案解释、下一题、结果汇总、历史最高分和重新答题。用户答错时如果只显示红色叉号没有正确选项和原因测验就失去了学习价值。应用按模块组织题库。分类页只在当前模块存在题目时显示测验入口答题页管理本轮临时状态结果服务保存已经完成的轮次结果页再展示百分比和历史最高分。二、QuizQuestion 把答案与解释放在同一对象每道题有稳定 id、所属模块、题干、四个选项、正确标签和解释。正确答案不依赖选项数组下标调整展示顺序时仍能准确判定。export interface QuizOption { label: string text: string } export interface QuizQuestion { id: string moduleId: string question: string options: QuizOption[] correctLabel: string explanation: string } export function quizByModule(moduleId: string): QuizQuestion[] { return QUIZ_BANK.filter(question question.moduleId moduleId) }字段作用约束id列表键与题目身份模块内唯一moduleId按学习模块取题必须对应模块options渲染可选答案标签不可重复correctLabel判定正确项必须存在于选项中explanation错因与知识点不能为空三、答题页是一组明确的临时状态本轮题目、题号、已选标签、是否已回答、当前得分都保存在页面状态中。离开测验后无需把这些中间字段长期持久化。State questions: QuizQuestion[] [] State currentIndex: number 0 State selectedLabel: string State answered: boolean false State score: number 0 State moduleId: string aboutToAppear(): void { const params router.getParams() as QuizParams this.moduleId params?.moduleId ?? this.questions quizByModule(this.moduleId) }题库为空时页面显示“该模块暂无测验题目”不会继续访问questions[0]。模块参数缺失也会得到空题库的安全结果。这些题内字段都属于组件本地状态生命周期和刷新规则可对照华为官方 State 状态管理说明。四、一次点击只允许完成一次判定选项点击首先检查answered。首次点击写入选择、锁定本题并在正确时增加得分之后点击其他选项不再改变结果。.onClick(() { if (!this.answered) { this.selectedLabel option.label this.answered true if (option.label this.questions[this.currentIndex].correctLabel) { this.score } } })判定后正确选项始终显示绿色若用户选错所选项显示红色同时正确项仍可见。解释文本紧跟在选项后面用户无需等到全部答完才知道错因。五、下一题动作重置题内状态点击“下一题”只推进索引并重置选择与锁定状态最后一题则用replaceUrl进入结果页避免返回后落到已经回答完的旧题状态。private goNext(): void { if (this.currentIndex this.questions.length - 1) { this.currentIndex this.selectedLabel this.answered false return } router.replaceUrl({ url: Routes.QUIZ_RESULT, params: { moduleId: this.moduleId, score: this.score, total: this.questions.length } }) }这里没有重置总分因为它属于整轮测验只重置当前题的临时字段。状态层级清楚后不会出现答对上一题后下一题自动选中相同标签的串题。六、结果服务保存完整轮次而非单个最高分结果记录包含模块、得分、总题数和时间。历史最高分通过遍历同模块记录计算完成模块数通过 Set 去重。export interface QuizResult { moduleId: string score: number total: number ts: number } static submitResult(moduleId: string, score: number, total: number): void { const results: QuizResult[] QuizService.allResults() results.push({ moduleId, score, total, ts: Date.now() }) AppStorage.setOrCreatestring(quiz_results, JSON.stringify(results)) } static bestScore(moduleId: string): number { return QuizService.allResults() .filter(result result.moduleId moduleId) .reduce((best, result) Math.max(best, result.score), 0) }保留完整轮次能继续扩展正确率趋势、最近一次成绩和练习次数。只保存最高分虽然更省空间却会丢失学习过程。七、结果页用 saved 阻止生命周期重复提交结果页aboutToAppear()可能因页面重新显示而再次执行。页面用本地saved守卫确保同一个组件实例只提交一次。State saved: boolean false aboutToAppear(): void { const params router.getParams() as QuizResultParams this.moduleId params.moduleId this.score params.score this.total params.total if (!this.saved) { QuizService.submitResult(this.moduleId, this.score, this.total) this.saved true } }这能拦住普通的重复显示但它不是跨进程幂等。若未来结果需要上传服务端应增加本轮attemptId由持久化层或服务端拒绝重复提交。八、结果反馈与失败边界结果页按正确率选择绿色、橙色或红色并展示分数、百分比、鼓励文案、历史最高、重新答题和返回模块。边界页面处理数据处理题库为空显示无题提示不生成结果题目总数为 0百分比返回 0不除以 0连续点击选项锁定首次答案分数最多加 1结果 JSON 损坏返回空数组最高分降级为 0再答一次替换为新答题页新增一轮结果返回模块回到课程上下文历史结果保留现有运行界面显示 1/8、13%、分段反馈、历史最高和两个后续动作。验收还应覆盖一次全对、一次全错、重复点击选项、最后一题进入结果页、重新答题和返回模块。题库自身也要做静态一致性检查题目 id 不重复模块 id 有效选项标签不重复correctLabel必须存在解释文本不能为空。运行时再检查题号与进度条同步、回答后禁止二次加分、下一题清空题内状态、最后一题只进入一次结果页。重新答题应产生独立轮次但页面恢复或重复显示结果页不能在同一轮里插入多条记录。九、错题反馈决定测验是否真正服务学习题库模型将正确答案和解释与题目放在一起答题页用锁定状态保证一次判定结果服务保留完整轮次结果页再提供重试与回到课程的出口。这条链路让“答错”成为可理解、可继续的学习状态而不是一个没有上下文的低分数字。进一步扩展错题本时不必复制整道题到用户数据只保存题目 id、选择标签、时间和题库版本。展示时再用 id 回读当前题目题库版本变化导致 id 失效时将记录标记为已下线并保留历史成绩。这样既能追踪易错知识点也不会因为题干修订把旧答案错误地解释成新题结果。成绩展示还应保留总题数因为不同模块的题量并不相同单独比较得分没有意义。排行榜或学习建议应优先使用正确率并在题量过少时避免给出过度结论历史最高分也应同时显示对应总题数防止题库扩容后把旧的 8/8 与新的 8/12 误认为同一水平。