【OpenHarmony/HarmonyOs 】错题本功能从 0 到 1:答题记录、去重收录与复习闭环
错题本是学习 App 里非常实用的功能。用户不是只需要“做题”,更需要知道自己错在哪里、后面怎么复习。本文结合我的「化学视界」项目,记录我是如何开发错题本功能的。📝
一、为什么要做错题本?
化学学习有大量记忆和理解内容,比如元素性质、方程式现象、实验操作、有机化学概念等。用户答错题后,如果没有复习入口,很容易“错过就忘”。
所以我在挑战模块里加入了错题本:
- 答错时自动收录。
- 答对后可以移除。
- 错题列表可反复练习。
- 设置页能看到错题数量。
- 用户可以清空错题。
这让题库不只是一次性练习,而是形成“做题 → 出错 → 复习 → 掌握”的闭环。
二、错题本的数据应该怎么存?
我没有保存完整题目内容,而是只保存题目 id:
const STORAGE_KEY='quiz_wrong_question_ids';原因很简单:题库本身已经在本地数据中,错题本只需要记录“哪些题错过”。这样数据更轻,也避免重复保存题干、选项和解析。
读取逻辑如下:
functionreadIds():number[] {try{constraw =AppStorage.get<string>(STORAGE_KEY);if(!raw || raw.length===0) {return[]; }constparsed =JSON.parse(raw)asnumber[];if(!Array.isArray(parsed)) {return[]; }returnparsed.filter((n:number) =>typeofn ==='number'); }catch{return[]; } }这里做了容错处理:如果本地数据为空、格式异常或解析失败,就返回空数组,避免页面崩掉。
三、答错时自动加入错题集
加入错题时要去重,否则用户同一道题答错多次,错题本会出现重复项:
exportfunctionaddWrongQuestionId(id:number): void { const ids = readIds();if(!ids.includes(id)) { ids.push(id); writeIds(ids); } }这个逻辑虽然简单,但很关键。错题本应该反映“哪些题没掌握”,而不是“错了多少次”。如果要统计错误次数,可以额外做字段,但不应该让列表重复。
四、答对后移除错题
当用户在错题复习里答对某道题后,可以从错题集中移除:
exportfunctionremoveWrongQuestionId(id:number):void{writeIds(readIds().filter((x:number) =>x !== id)); }我开发时把“移除”设计为明确动作:只有复习答对后才移除,而不是用户随便进入题目就清掉。这样更符合学习逻辑。
五、把错题 id 还原成完整题目
错题页面展示时,需要根据 id 找回完整题目:
exportfunctiongetWrongQuestions():QuizQuestion[] {constlist:QuizQuestion[] = [];readIds().forEach((id: number) =>{constq = quizData.find((x: QuizQuestion) =>x.id=== id);if(q) { list.push(q); } });returnlist; }这里没有直接map,而是手动判断q是否存在。这样即使后续题库调整,某个旧 id 找不到,也不会影响整个错题本。
六、挑战页中的错题状态
在QuizView中,我为错题模式单独维护了一组状态:
@StatewrongQuestions: QuizQuestion[] = [];@StatewrongIndex: number =0;@StatewrongAnswer: string ='';@StatewrongAnswered: boolean = false;@StatewrongCorrect: boolean = false;@StatewrongResultAppear: boolean = false;@StateexplanationWrongQ: QuizQuestion | null = null;为什么不复用练习模式的状态?因为错题模式有自己的特殊逻辑:
- 答对后可能移除当前题。
- 移除后列表长度会变化。
- 解析区仍然要展示刚答过的题。
- 当前索引需要防止越界。
所以我把错题模式独立出来,避免和普通练习互相影响。
七、防止答对后列表错位
项目里有一个很细的状态:
@StateexplanationWrongQ: QuizQuestion |null=null;它的作用是固定展示刚作答的那道题。因为如果用户答对后,这道题会从错题列表移除,列表索引可能变化。如果解析区直接依赖wrongQuestions[wrongIndex],就可能显示到下一道题。
这是我开发错题本时遇到的一个真实问题:数据变化后 UI 绑定对象也跟着变了。解决办法就是把“当前解析题”单独保存一份。
八、设置页展示错题数量
错题本不只在挑战页出现,设置页也会展示入口和数量:
loadBadge() {this.wrongCount = getWrongQuestions().length;constfavs = getAllFavorites();this.favoriteCount = favs.length;this.unlockCount =this.achievementManager.getUnlockedCount(); }设置项中展示徽标:
{icon:'📚',title:'我的错题',subtitle:'答错的题目自动收录',action:'wrongbook',badge: this.wrongCount >0? String(this.wrongCount) :'',badgeColor:'#F44336'}这样用户不用进入挑战页,也能知道自己有没有待复习内容。
九、清空错题:给用户数据控制权
设置页中提供了清空错题的逻辑:
handleClearConfirm() {if(this.clearActionType ==='wrong') { clearWrongBook();this.wrongCount =0; }else{ clearFavorites();this.favoriteCount =0; }this.showClearConfirm =false; }学习数据应该由用户控制。错题本是帮助复习的工具,不应该变成无法清理的负担。
十、错题本和收藏夹的区别
开发时我也思考过一个问题:错题本和收藏夹都能保存内容,它们有什么区别?
我的理解是:
- 收藏夹是用户主动选择的内容。
- 错题本是系统根据学习结果自动生成的内容。
所以收藏夹更像“我觉得重要”,错题本更像“我还没掌握”。这两个入口在学习闭环里承担的角色不同,不能混在一起。
收藏夹适合放:
- 常查元素。
- 重点方程式。
- 重要知识点。
错题本适合放:
- 答错的题目。
- 需要反复练习的知识点。
- 用户短期薄弱内容。
正因为这个区别,我在代码里也把它们拆成两个工具类:收藏由FavoritesManager管理,错题由WrongBookStore管理。这样后续维护时不会互相影响。
十一、错题本后续可以怎么优化?
当前错题本已经实现了基础闭环,但还可以继续增强:
- 给错题增加“错误次数”,区分偶然错误和高频错误。
- 给错题增加“最近答错时间”,优先复习近期错题。
- 按题目分类统计薄弱模块。
- 在学习统计页展示错题趋势。
- 根据错题自动推荐知识点。
不过我没有一开始就把这些都做进去,因为基础功能要先稳定。先保证收录、复习、移除、清空这些主流程可靠,再做增强会更稳。
十二、我开发错题本的总结
错题本的开发重点不在 UI,而在学习流程:
- ❌ 答错自动收录。
- ✅ 答对可以移除。
- 🔁 支持反复复习。
- 🧠 解析区固定当前题。
- 🧹 支持用户清空。
从代码上看,错题本只保存 id,非常轻量;从体验上看,它把挑战模块从“做完就结束”变成了“持续复习”。这就是学习工具真正有价值的地方。🌱