HarmonyOS 应用开发《掌上英语》第28篇:学习计划管理——LearningPlanManager 的目标设定与追踪
学习计划管理——LearningPlanManager 的目标设定与追踪引言学习计划是英语学习 App 中驱动用户持续学习的关键机制。一个好的学习计划系统不仅要支持用户设定每日目标还需要追踪完成进度、维护连续学习天数、通过成就徽章激励用户。LearningPlanManager就是这样一个集目标设定、进度追踪、连续天数维护、成就系统于一体的 Manager 模块。本文将深入分析它的设计思路与实现细节。一、数据模型设计LearningPlan接口包含了学习计划所需的全部状态字段exportinterfaceLearningPlan{// 每日目标dailyWordGoal:number// 每日单词目标dailyListeningGoal:number// 每日听力目标分钟dailyReadingGoal:number// 每日阅读目标分钟// 连续学习continuousDays:number// 连续学习天数lastStudyDate:number// 最后学习日期时间戳// 今日进度todayWordCount:number// 今日已学单词数todayListeningDuration:number// 今日听力时长todayReadingDuration:number// 今日阅读时长// 提醒设置reminderEnabled:boolean// 学习提醒是否开启reminderTime:string// 提醒时间如 08:00reminderRepeatType:number// 重复周期 0:每天 1:工作日 2:自定义// 累计数据totalWordCount:number// 总学习单词数totalStudyDuration:number// 总学习时长分钟// 成就徽章badges:string[]// 已解锁徽章列表}默认值通过常量DEFAULT_LEARNING_PLAN提供exportconstDEFAULT_LEARNING_PLAN:LearningPlan{dailyWordGoal:20,// 每日 20 词dailyListeningGoal:15,// 听力 15 分钟dailyReadingGoal:10,// 阅读 10 分钟continuousDays:0,lastStudyDate:0,todayWordCount:0,todayListeningDuration:0,todayReadingDuration:0,reminderEnabled:false,reminderTime:08:00,reminderRepeatType:0,totalWordCount:0,totalStudyDuration:0,badges:[]};二、读写与部分更新LearningPlanManager以单例模式提供读写接口exportclassLearningPlanManager{privatestaticinstance?:LearningPlanManager;privateLEARNING_PLAN_KEYLEARNING_PLAN;publicstaticgetInstance():LearningPlanManager{if(!LearningPlanManager.instance){LearningPlanManager.instancenewLearningPlanManager();}returnLearningPlanManager.instance;}// 读取publicgetLearningPlan():LearningPlan{try{letplanPreferenceUtil.getInstance().get(this.LEARNING_PLAN_KEY,undefined)asLearningPlan;if(!plan){planthis.cloneLearningPlan(DEFAULT_LEARNING_PLAN);this.saveLearningPlan(plan);}returnplan;}catch(e){Logger.error(LearningPlanManager,获取学习计划失败:${JSON.stringify(e)});returnthis.cloneLearningPlan(DEFAULT_LEARNING_PLAN);}}// 保存publicsaveLearningPlan(plan:LearningPlan):void{try{PreferenceUtil.getInstance().put(this.LEARNING_PLAN_KEY,plan);}catch(e){Logger.error(LearningPlanManager,保存学习计划失败:${JSON.stringify(e)});}}// 部分更新publicupdateLearningPlan(plan:PartialLearningPlan):LearningPlan{try{letcurrentPlanthis.getLearningPlan();letnewPlanthis.mergeLearningPlans(currentPlan,plan);this.saveLearningPlan(newPlan);returnnewPlan;}catch(e){Logger.error(LearningPlanManager,更新学习计划失败:${JSON.stringify(e)});returnthis.getLearningPlan();}}}部分更新的关键在于mergeLearningPlans方法中的??空值合并运算符privatemergeLearningPlans(currentPlan:LearningPlan,plan:PartialLearningPlan):LearningPlan{return{dailyWordGoal:plan.dailyWordGoal??currentPlan.dailyWordGoal,dailyListeningGoal:plan.dailyListeningGoal??currentPlan.dailyListeningGoal,// ... 每个字段都有 ?? 兜底badges:plan.badges?plan.badges.concat():currentPlan.badges.concat()};}这样调用方可以只传入需要修改的字段LearningPlanManager.getInstance().updateLearningPlan({dailyWordGoal:30,// 只修改单词目标reminderEnabled:true});三、连续学习天数维护连续学习天数是激励用户每日打卡的重要机制publiccheckAndUpdateContinuousDays():LearningPlan{try{letplanthis.getLearningPlan();lettodaynewDate();today.setHours(0,0,0,0);lettodayTimestamptoday.getTime();if(plan.lastStudyDate0){// 首次学习plan.continuousDays1;}else{letlastDatenewDate(plan.lastStudyDate);lastDate.setHours(0,0,0,0);letlastTimestamplastDate.getTime();letdiffDaysMath.floor((todayTimestamp-lastTimestamp)/(1000*60*60*24));if(diffDays0){// 同一天学习不更新}elseif(diffDays1){// 连续学习天数 1plan.continuousDays;}else{// 中断重置为 1plan.continuousDays1;}}plan.lastStudyDatetodayTimestamp;this.saveLearningPlan(plan);returnplan;}catch(e){Logger.error(LearningPlanManager,更新连续学习天数失败:${JSON.stringify(e)});returnthis.getLearningPlan();}}核心逻辑将today和lastStudyDate都归零到当天 00:00:00消除时间影响。计算天数差diffDays0同一天多次学习连续天数不变。1昨天也学习了连续天数 1。2中断了重置为 1。lastStudyDate使用Date.getTime()的时间戳方便存储和比较。四、学习记录与进度查询// 记录单词学习publicrecordWordStudy(count:number):LearningPlan{letplanthis.getLearningPlan();plan.todayWordCountcount;plan.totalWordCountcount;planthis.checkAndUpdateContinuousDays();planthis.checkAndUnlockBadges(plan);this.saveLearningPlan(plan);returnplan;}// 获取今日进度百分比publicgetTodayProgress():TodayProgress{letplanthis.getLearningPlan();return{wordProgress:plan.dailyWordGoal0?Math.min(100,Math.floor(plan.todayWordCount/plan.dailyWordGoal*100)):0,listeningProgress:plan.dailyListeningGoal0?Math.min(100,Math.floor(plan.todayListeningDuration/plan.dailyListeningGoal*100)):0,readingProgress:plan.dailyReadingGoal0?Math.min(100,Math.floor(plan.todayReadingDuration/plan.dailyReadingGoal*100)):0};}getTodayProgress返回三个维度的百分比0-100使用Math.min(100, ...)防止分母为 0 或进度超过 100% 的场景。五、成就徽章系统徽章系统是 gamification 的典型应用用户达成特定里程碑后自动解锁徽章// 徽章常量exportconstBADGE_DAYS_3连续学习3天exportconstBADGE_DAYS_7连续学习7天exportconstBADGE_DAYS_30连续学习30天exportconstBADGE_WORDS_100词汇达人(100词)exportconstBADGE_WORDS_500词汇大师(500词)exportconstBADGE_WORDS_1000词汇王者(1000词)exportconstBADGE_LISTENING_10听力新手(10分钟)exportconstBADGE_LISTENING_60听力高手(60分钟)privatecheckAndUnlockBadges(plan:LearningPlan):LearningPlan{letbadgesplan.badges.concat();// 连续学习徽章阶梯式解锁if(plan.continuousDays30!badges.includes(BADGE_DAYS_30)){badges.push(BADGE_DAYS_30);}elseif(plan.continuousDays7!badges.includes(BADGE_DAYS_7)){badges.push(BADGE_DAYS_7);}elseif(plan.continuousDays3!badges.includes(BADGE_DAYS_3)){badges.push(BADGE_DAYS_3);}// 词汇徽章if(plan.totalWordCount1000!badges.includes(BADGE_WORDS_1000)){badges.push(BADGE_WORDS_1000);}elseif(plan.totalWordCount500!badges.includes(BADGE_WORDS_500)){badges.push(BADGE_WORDS_500);}elseif(plan.totalWordCount100!badges.includes(BADGE_WORDS_100)){badges.push(BADGE_WORDS_100);}// 听力徽章if(plan.totalStudyDuration60!badges.includes(BADGE_LISTENING_60)){badges.push(BADGE_LISTENING_60);}elseif(plan.totalStudyDuration10!badges.includes(BADGE_LISTENING_10)){badges.push(BADGE_LISTENING_10);}plan.badgesbadges;returnplan;}设计亮点阶梯判断从高到低判断确保用户达到 30 天时不会重复获得 3 天和 7 天徽章。幂等性每次检查badges.includes()防止徽章被重复添加。可扩展新增徽章只需添加常量判断逻辑不影响现有代码。六、最佳实践6.1 使用concat()确保不可变在cloneLearningPlan和mergeLearningPlans中badges数组使用concat()复制而非直接赋值避免多个LearningPlan对象共享同一个数组引用导致意外修改。6.2 进度百分比上限保护getTodayProgress使用Math.min(100, ...)确保进度不会超过 100%避免 Progress 组件显示异常。6.3 每日数据重置resetDailyData方法在新的一天开始时调用将todayWordCount、todayListeningDuration、todayReadingDuration重置为 0。根据业务需求可以在应用启动时或每次学习记录前判断是否需要重置。七、总结LearningPlanManager是一个功能完备的学习计划管理模块它覆盖了目标设定、进度追踪、连续天数维护、成就徽章等完整的学习激励闭环。通过PartialT参数实现优雅的部分更新通过时间戳比较实现准确的连续天数计算通过阶梯式徽章检测实现渐进式的用户激励。这种设计模式可以轻松复用到其他需要目标追踪和成就系统的场景中。