HarmonyOS 应用开发《掌上英语》第50篇:题本与收藏——用户数据的本地管理

📅 2026/7/27 16:09:54 👁️ 阅读次数 📝 编程学习
HarmonyOS 应用开发《掌上英语》第50篇:题本与收藏——用户数据的本地管理

错题本与收藏——用户数据的本地管理

一、功能概述

错题本和收藏是学习应用中两个重要的辅助功能。错题本自动收集用户答错的题目,供后续复习;收藏则让用户可以手动标记重点题目,建立自己的学习素材库。

两者的数据都存储在本地 Preferences 中,使用单独的 Key:

exportclassPreferConstant{staticreadonlyERROR_RECORDS:string='ERROR_RECORDS';// 错题记录staticreadonlyEXAM_PREFER_COLLECT:string='EXAM_PREFER_COLLECT';// 收藏记录}

二、错题自动记录

2.1 记录时机

错题记录在 AnswerQuestionsPage 中自动触发。当用户选择答案后系统判断:

storageWrongClick(){// 排除从错题本或收藏进入的情况if(this.sourceRouterModel?.sourceType==='我的错题'){return;}if(this.sourceRouterModel?.sourceType==='我的收藏'){return;}if(this.sourceRouterModel?.sourceType==='错题练习'){return;}if(this.currentModel){// 只有当选择答案不等于正确答案时,才记录为错题if(this.currentModel.selectQues.toString()!==this.currentModel.rightQues.toString()){letcurrentItem=this.transTopicItemType();if(currentItem){letarr:TopicItemType[]=PreferenceUtil.getInstance().get(PreferConstant.ERROR_RECORDS,[])asTopicItemType[];if(arr.length===0){arr.push(currentItem);}else{letisExit=false;arr.forEach((itemType:TopicItemType,idx:number)=>{if(itemType.keyID===currentItem?.keyID){arr[idx]=currentItem;// 更新已存在的错题isExit=true;}});if(!isExit){arr.push(currentItem);// 新增错题}}PreferenceUtil.getInstance().put(PreferConstant.ERROR_RECORDS,arr);}}}}

关键设计:

  • 自动记录:答错即记录,无需用户手动操作
  • 去重更新:同一题多次答错,只保留最新一条记录
  • 排除干扰:从错题本进入练习时不再重复记录
  • 完整数据:保存TopicItemType完整对象,包含题目、选项、解析等

2.2 收藏记录

收藏需要用户手动触发:

storageCollectClick(){if(this.currentModel){letcollectItem=this.transTopicItemType();if(collectItem){letarr:TopicItemType[]=PreferenceUtil.getInstance().get(PreferConstant.EXAM_PREFER_COLLECT,[])asTopicItemType[];if(arr.length===0){arr.push(collectItem);}else{letisExit=false;arr.forEach((itemType:TopicItemType,idx:number)=>{if(itemType.keyID===this.currentModel?.keyID){arr.splice(idx,1);// 再次点击取消收藏isExit=true;}});if(!isExit){arr.push(collectItem);}}PreferenceUtil.getInstance().put(PreferConstant.EXAM_PREFER_COLLECT,arr);}}}

收藏是"开关式"操作:点击收藏、再点击取消收藏。

三、MyWrongPage——错题展示

3.1 页面结构

build(){Column(){this.topBuilder();// 顶部导航 + 全选/删除按钮if(this.wrongQues.length<=0){Text('暂无易错词汇,加油学习吧');// 空状态}else{this.wrongListBuilder();// 错题列表this.deleteBuilder();// 批量删除按钮}}}

3.2 数据加载

asyncinitData(){try{letarr:TopicItemType[]=PreferenceUtil.getInstance().get(PreferConstant.ERROR_RECORDS,[])asTopicItemType[];arr.sort((a,b)=>b.jlStamp-a.jlStamp);// 按时间戳倒序this.wrongArr=arr;if(arr&&arr.length>0){arr.forEach((element:TopicItemType)=>{letmodel:WrongModel=newWrongModel();model.isDelete=false;model.keyID=element.keyID;model.title=element.title;this.wrongQues.push(model);});}}catch(e){this.wrongArr=[];this.wrongQues=[];}}

数据加载要点:

  • PreferConstant.ERROR_RECORDS读取
  • 按时间戳jlStamp倒序排列,最新错误在最前面
  • 转换为WrongModel列表用于 UI 显示

3.3 空状态处理

if(this.wrongQues.length<=0){Text('暂无易错词汇,加油学习吧').fontSize($r('sys.float.Body_S')).fontColor($r('sys.color.icon_secondary')).height('100%');}

当错题列表为空时,居中显示友好的提示文字。这是用户体验的基本要求——不因数据为空而显示空白页面。

3.4 错题列表

@BuilderwrongListBuilder(){List(){ForEach(this.wrongQues,(item:WrongModel,idx:number)=>{ListItem(){MyWrongItem({isSelectDelete:this.isSelectDelete,item:item,changeCollectionItem:()=>{/* 切换选中状态 */},idx:idx})}.swipeAction({end:{builder:()=>{this.itemEndComponent(idx);}}}).onClick(()=>{// 点击进入答题页面letrouterModel:QuestionsRouterModel=newQuestionsRouterModel();routerModel.datalist=[this.wrongArr[idx]];routerModel.sourceType='易错词汇';RouterModule.push({url:RouterMap.ANSWER_QUESTIONS_PAGE,param:routerModel});});});}}

列表支持两种操作:

  1. 左滑删除:通过swipeAction实现单条删除
  2. 点击进入练习:点击错题跳转到答题页面,sourceType标记为"易错词汇"

3.5 批量删除

@BuilderdeleteBuilder(){Row(){Image($r('app.media.icon_delete'));Text('删除');}.opacity(this.selectCount>0?1:0.4).visibility(this.isSelectDelete?Visibility.Visible:Visibility.None).onClick(()=>{this.deleteSelectData();});}

批量删除模式:点击顶部的删除图标进入选择模式,勾选要删除的错题,点击"删除"按钮批量清除。

deleteSelectData(){letarr:string[]=[];this.wrongQues.forEach((model:WrongModel)=>{if(model.isDelete){arr.push(model.keyID);}});this.wrongQues=this.wrongQues.filter(it=>!it.isDelete);this.wrongArr=this.wrongArr.filter(it=>!arr.includes(it.keyID));PreferenceUtil.getInstance().put(PreferConstant.ERROR_RECORDS,this.wrongArr);}

删除时同时更新内存中的wrongQueswrongArr,以及持久化存储。

四、收藏的展示与使用

收藏的数据结构和使用模式与错题本完全一致,只是存储 Key 不同(EXAM_PREFER_COLLECT)。在 AnswerQuestionsPage 中,从收藏列表进入答题时:

if(this.sourceRouterModel?.sourceType==='我的收藏'){RouterModule.pop();// 收藏模式点击返回直接退出}

收藏列表页MyCollectionPage的 UI 结构和 MyWrongPage 基本一致,同样支持删除和点击进入答题。

五、数据管理要点

5.1 存储格式

错题和收藏都使用TopicItemType[]数组存储。每个元素包含:

interfaceTopicItemType{keyID:string// 唯一标识title:string// 题目内容type:string// 题型rightQues:string[]// 正确答案selectQues:string[]// 用户选择ques:AnswerItemType[]// 选项列表parse:string// 解析jlStamp:number// 记录时间戳// ...}

5.2 数据隔离

错题和收藏使用不同的 Key,互不干扰。同一个题目可以同时出现在错题本和收藏中。

5.3 性能考虑

Preferences 适合存储较小体量的数据。对于错题和收藏这种单用户最多几百条的场景完全足够。如果将来数据量增长到数千条,可以考虑迁移到关系型数据库(如@kit.ArkData中的relationalStore)。

六、总结

错题本和收藏功能展示了 Preferences 在"用户私有数据"存储中的典型应用。自动记录 + 手动收藏的组合,覆盖了"系统推荐"和"用户自选"两种学习素材收集模式。结合空状态处理、批量操作、滑动删除等交互细节,为用户提供了完整的数据管理体验。