第24次:错题本功能

这一篇我们继续沿着题库系统往下走,讲清楚 WrongAnswerService.etsWrongAnswerBookPage.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. 错题本只能看,不能练

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


本篇小结

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

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

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

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


跟着真实源码继续往下看

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

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;
  }
}

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

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

按这个顺序动手

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

课后练习

  1. 思考如果你要增加“按模块筛选错题”,服务层和页面层分别要改哪里。
  2. 观察 WrongAnswerService 为什么要记录 lastAttemptAt,想一想它未来还能支持哪些排序方式。
  3. 设想一个“只练最近 7 天的错题”功能,写出你会新增的过滤逻辑。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐