HarmonyOS APP<玩转React>开源教程二十四:错题本功能

第24次:错题本功能

这一篇我们继续沿着题库系统往下走,讲清楚 WrongAnswerService.ets 和 WrongAnswerBookPage.ets。错题本不是一个附属页面,而是题库闭环里非常关键的复习模块。


为什么错题本不能只是"存错题"

很多初学者做错题本时,只会把答错的题塞进一个数组里。这样当然也能跑,但很快就不够用了,因为真正的错题本还要解决这些问题:

  • 同一道题多次答错时怎么处理
  • 用户答对后能不能标记为已掌握
  • 错题是否需要记录最后一次练习时间
  • 列表应该按什么顺序展示
  • 如何从错题本再次进入练习

当前项目的 WrongAnswerService 恰好把这些都考虑进去了。
QuizPracticePage 答错
WrongAnswerService.addWrongAnswer
生成或更新 WrongAnswerRecord
saveWrongAnswers 持久化
WrongAnswerBookPage 读取列表
查看详情 / 移除 / 从错题本练习
答对后 markAsMastered


一、错题记录为什么不是只存题目 ID

当前的错题记录 WrongAnswerRecord 会保存很多信息:

  • questionId
  • question
  • userAnswer
  • correctAnswer
  • recordedAt
  • attemptCount
  • isMastered
  • lastAttemptAt

这说明项目并不是把错题本当成一个简单收藏夹,而是把它当成一个"复习跟踪器"。

尤其是这几个字段很关键:

  • attemptCount:能看出这道题卡了用户几次。
  • isMastered:能看出这道题是不是已经会了。
  • lastAttemptAt:能帮助后续做复习排序或复盘分析。

这也提醒我们:错题本真正有价值的,不只是"错过",而是"后续怎么复习"。


二、为什么 addWrongAnswer() 要支持更新旧记录

如果用户第一次答错某道题,你当然要新增记录;但如果用户后面又把同一道题答错一次,显然不应该再重复新增一条相同记录。

当前 addWrongAnswer() 的做法是:

  • 先查有没有同题记录
  • 如果有,就更新用户答案、尝试次数、最后练习时间,并把 isMastered 重置为 false
  • 如果没有,再新建记录

这种设计非常合理,因为它让一条错题记录真正对应"一道题的掌握过程",而不是"答错的次数清单"。


三、为什么要区分"全部错题"和"未掌握错题"

WrongAnswerService 同时提供了:

  • getAllWrongAnswers()
  • getUnmasteredWrongAnswers()
  • getUnmasteredCount()

这说明服务层很明确地区分了两种视角:

全部错题

适合回顾历史。

未掌握错题

适合立即复习。

这个区分很重要,因为学习产品最怕的就是把所有历史错误都一股脑堆给用户。真正有效的复习,应该优先处理"还没掌握"的内容。


四、错题本页面为什么做成"列表 + 详情面板"

WrongAnswerBookPage 的页面结构很值得学:

  • 顶部导航栏
  • 统计条
  • 开始练习错题按钮
  • 错题列表
  • 底部详情弹层

列表页只负责快速浏览,点击某条错题后再弹出详情面板,展示:

  • 模块名
  • 完整题干
  • 所有选项
  • 你的答案
  • 正确答案
  • 题目解析
  • 移除按钮

这种设计很适合手机端。因为错题内容通常比较长,如果每条列表都展开,页面会非常乱;而用弹层展示详情,既保留了列表浏览效率,又保留了完整信息查看能力。


五、从错题本继续练习为什么是关键能力

错题本真正的价值,不在于"存起来",而在于"继续练"。当前页面上的"开始练习错题"按钮会跳转到:

  • QuizPracticePage
  • 并携带 isFromWrongBook: true

随后 QuizPracticePage 会切换到错题练习模式,只读取未掌握错题集合。这就形成了一个非常完整的学习闭环:

  • 做题
  • 产生错题
  • 进入错题本
  • 再次练习
  • 答对后标记已掌握

这种闭环一旦形成,题库系统才真正有"成长性"。


六、详情面板里为什么要同时高亮用户答案和正确答案

在 DetailPanel() 中,每个选项会根据状态展示不同背景:

  • 正确答案:绿色风格
  • 用户错误答案:红色风格

这种"答案对比式展示"比只告诉用户"你错了"更有效,因为它能帮助用户一眼看到:

  • 我选错的是哪一项
  • 正确答案到底是哪一项

这在复习场景里尤其重要。错题本页面不是考试现场,而是复盘现场,重点是帮助用户理解差距。


七、自己操作时最推荐的路径

  1. 从题库页进入练习。
  2. 故意答错几题。
  3. 回到题库页,进入错题本。
  4. 先看统计条和错题列表是否正确更新。
  5. 点击某道错题,打开详情面板查看解析。
  6. 再点击"开始练习错题",看题目是否只来自未掌握集合。
  7. 如果在错题模式下答对,看它是否会被标记为已掌握。

这条路径跑通后,你就会非常清楚错题本在整个题库系统中的价值。


八、本篇常见坑

1. 每次答错都新增一条重复记录

这样错题本会越来越脏,不利于复习。

2. 错题答对后不更新掌握状态

没有 isMastered,错题本就无法形成复习闭环。

3. 错题列表不做排序

当前项目按时间倒序展示,更符合最近复习场景。

4. 错题本只能看,不能练

那它就只是"历史记录",不是"学习工具"。


本篇小结

这一篇最重要的理解是:错题本不是题库的附件,而是题库系统的第二增长曲线。它把一次答错,变成了后续复习和最终掌握的机会。

当前项目的做法很值得借鉴:

  • 服务层记录错题与掌握状态
  • 页面层做浏览、查看、移除和再练习
  • 与题库练习页通过参数模式打通

这就是一个完整、实用的错题本设计。


跟着真实源码继续往下看

错题记录新增或更新的真实代码如下:

ts 复制代码
static async addWrongAnswer(question: InterviewQuestion, userAnswer: number): Promise<void> {
  const existingIndex = WrongAnswerService.wrongAnswers.findIndex(
    r => r.questionId === question.id
  );

  const now = new Date().toISOString();

  if (existingIndex >= 0) {
    const existing = WrongAnswerService.wrongAnswers[existingIndex];
    existing.userAnswer = userAnswer;
    existing.attemptCount += 1;
    existing.lastAttemptAt = now;
    existing.isMastered = false;
  }
}

错题本页从错题列表进入练习的真实代码如下:

ts 复制代码
Button() {
  Row() {
    Text('📝')
    Text('开始练习错题')
  }
}
.onClick(() => {
  router.pushUrl({
    url: 'pages/QuizPracticePage',
    params: { isFromWrongBook: true }
  });
})

按这个顺序动手

  1. 打开 WrongAnswerService.ets,看 addWrongAnswer 和 markAsMastered。
  2. 再打开 WrongAnswerBookPage.ets,搜索 isFromWrongBook。
  3. 最后去 QuizPracticePage.ets 看这个参数如何切换题目来源。

课后练习

  1. 思考如果你要增加"按模块筛选错题",服务层和页面层分别要改哪里。
  2. 观察 WrongAnswerService 为什么要记录 lastAttemptAt,想一想它未来还能支持哪些排序方式。
  3. 设想一个"只练最近 7 天的错题"功能,写出你会新增的过滤逻辑。
相关推荐
考研保研资料分享6 小时前
211公共管理保研北航行政管理:五校考核、论文追问与择校复盘
面试
考研保研资料分享6 小时前
985工科跨保上财应用统计:数学笔试、匿名面试与机试复盘
面试·职场和发展
冬奇Lab7 小时前
一天一个开源项目(第249篇):Hallmark —— 一个拒绝“AI 味”的设计 Skill,用 48 套材质主题和浏览器 Linter 约束编程助手
人工智能·开源·资讯
Aurora119 小时前
Jane Street OA 真题解析:Mastermind 猜密码与候选集筛选
面试
鸿蒙开发9 小时前
共享 Schema 与 SchemaFactory
前端·后端·harmonyos
用户5794507706110 小时前
BaoCode,你不是读不懂 Claude Code,是终端不让你读懂
开源
skytier10 小时前
昇思大模型推理:输出 logits
面试
SL_staff10 小时前
运维救火实战:钉钉API变更致薪资计算中断,如何用可视化逻辑引擎5分钟热修复
java·开源·全栈
weh96010 小时前
受够了同事之间的拌嘴,所以我做了一个多Agent协同平台
开源
丑陋小蚊子11 小时前
简历电脑上清楚,手机上字太小,先改哪里?
面试·求职招聘·大学生·简历·简历下载