HarmonyOS APP<玩转React>开源教程十四:进度管理服务

HarmonyOS APP<玩转React>开源教程十四:进度管理服务 第14次进度管理服务学习进度追踪是教育类应用的核心功能。本次课程将开发 ProgressService实现课程完成标记、连续学习天数计算、徽章奖励等功能。进度效果学习目标掌握进度数据的存储与读取实现课程和模块完成标记学会连续学习天数计算理解徽章奖励机制完成 ProgressService 的完整开发14.1 进度数据模型UserProgress 结构interfaceUserProgress{completedLessons:string[];// 已完成课程 ID 列表completedModules:string[];// 已完成模块 ID 列表currentLesson:string|null;// 当前学习的课程learningStreak:number;// 连续学习天数lastStudyDate:string|null;// 最后学习日期totalStudyTime:number;// 总学习时长分钟badges:Badge[];// 获得的徽章}默认进度值constDEFAULT_USER_PROGRESS:UserProgress{completedLessons:[],completedModules:[],currentLesson:null,learningStreak:0,lastStudyDate:null,totalStudyTime:0,badges:[]};Badge 徽章结构interfaceBadge{id:string;name:string;description:string;icon:string;earnedDate:string;}14.2 进度数据持久化加载进度staticasyncloadProgress():PromiseUserProgress{try{constprogressawaitStorageUtil.getObjectUserProgress(StorageKeys.USER_PROGRESS,DEFAULT_USER_PROGRESS);ProgressService.cachedProgressprogress;returnprogress;}catch(error){console.error([ProgressService] Failed to load progress:,error);returnDEFAULT_USER_PROGRESS;}}保存进度staticasyncsaveProgress(progress:UserProgress):Promisevoid{try{awaitStorageUtil.setObject(StorageKeys.USER_PROGRESS,progress);ProgressService.cachedProgressprogress;}catch(error){console.error([ProgressService] Failed to save progress:,error);}}缓存机制为了提高性能使用内存缓存exportclassProgressService{privatestaticcachedProgress:UserProgress|nullnull;// 同步获取缓存的进度staticgetCachedProgress():UserProgress{returnProgressService.cachedProgress??DEFAULT_USER_PROGRESS;}}14.3 课程完成标记标记课程完成staticasyncmarkLessonComplete(lessonId:string,moduleId:string):Promisevoid{constprogressawaitProgressService.loadProgress();// 添加到已完成列表避免重复if(!progress.completedLessons.includes(lessonId)){progress.completedLessons.push(lessonId);}// 更新当前课程progress.currentLessonlessonId;// 更新学习时间progress.totalStudyTime1;// 保存进度awaitProgressService.saveProgress(progress);// 更新连续学习天数awaitProgressService.updateStreak();}检查课程是否完成staticisLessonCompleted(lessonId:string,progress:UserProgress):boolean{returnprogress.completedLessons.includes(lessonId);}在页面中使用// LessonDetail.etsButton(完成学习).onClick(async(){awaitProgressService.markLessonComplete(this.lessonId,this.moduleId);// 检查模块是否完成constmoduleCompletedawaitProgressService.checkModuleCompletion(this.module);if(moduleCompleted){// 显示模块完成提示this.showModuleCompletedDialog();}// 返回上一页router.back();})14.4 模块完成检测检查模块完成状态staticasynccheckModuleCompletion(module:LearningModule):Promiseboolean{constprogressawaitProgressService.loadProgress();// 检查模块的所有课程是否都已完成constallLessonsCompletedmodule.lessons.every(lessonprogress.completedLessons.includes(lesson.id));// 如果全部完成且未记录添加到已完成模块if(allLessonsCompleted!progress.completedModules.includes(module.id)){progress.completedModules.push(module.id);awaitProgressService.saveProgress(progress);returntrue;// 返回 true 表示刚刚完成}returnallLessonsCompleted;}计算模块完成百分比staticgetCompletionPercentage(module:LearningModule,progress:UserProgress):number{if(module.lessons.length0){return0;}constcompletedCountmodule.lessons.filter(lessonprogress.completedLessons.includes(lesson.id)).length;returnMath.round((completedCount/module.lessons.length)*100);}检查模块是否解锁staticisModuleUnlocked(moduleId:string,prerequisites:string[],progress:UserProgress):boolean{// 没有前置条件直接解锁if(prerequisites.length0){returntrue;}// 检查所有前置模块是否完成returnprerequisites.every(prereqIdprogress.completedModules.includes(prereqId));}14.5 连续学习天数计算更新连续天数staticasyncupdateStreak():Promisenumber{constprogressawaitProgressService.loadProgress();consttodaynewDate().toISOString().split(T)[0];// YYYY-MM-DDconstlastDateprogress.lastStudyDate;if(!lastDate){// 首次学习progress.learningStreak1;}elseif(lastDatetoday){// 今天已经学习过不更新returnprogress.learningStreak;}else{// 计算日期差constlastDateObjnewDate(lastDate);consttodayObjnewDate(today);constdiffDaysMath.floor((todayObj.getTime()-lastDateObj.getTime())/(1000*60*60*24));if(diffDays1){// 连续学习天数 1progress.learningStreak1;}else{// 中断超过一天重置为 1progress.learningStreak1;}}// 更新最后学习日期progress.lastStudyDatetoday;awaitProgressService.saveProgress(progress);returnprogress.learningStreak;}连续学习逻辑图解昨天学习 → 今天学习 → streak 1 前天学习 → 今天学习 → streak 1中断 今天学习 → 今天再学 → streak 不变 首次学习 → streak 114.6 徽章奖励机制添加徽章staticasyncaddBadge(badge:Badge):Promisevoid{constprogressawaitProgressService.loadProgress();// 避免重复添加if(!progress.badges.some(bb.idbadge.id)){progress.badges.push(badge);awaitProgressService.saveProgress(progress);}}徽章类型设计// 预定义徽章constBADGES{FIRST_LESSON:{id:first-lesson,name:初学者,description:完成第一节课程,icon:},STREAK_7:{id:streak-7,name:坚持一周,description:连续学习 7 天,icon:},MODULE_COMPLETE:{id:module-complete,name:模块达人,description:完成一个完整模块,icon:},ALL_BEGINNER:{id:all-beginner,name:入门毕业,description:完成所有入门课程,icon:}};徽章检查逻辑// BadgeService.etsstaticasynccheckAndAwardBadges(progress:UserProgress):PromiseBadge[]{constnewBadges:Badge[][];// 检查首次完成课程if(progress.completedLessons.length1){constbadgeawaitBadgeService.awardBadge(first-lesson);if(badge)newBadges.push(badge);}// 检查连续学习 7 天if(progress.learningStreak7){constbadgeawaitBadgeService.awardBadge(streak-7);if(badge)newBadges.push(badge);}// 检查完成模块if(progress.completedModules.length0){constbadgeawaitBadgeService.awardBadge(module-complete);if(badge)newBadges.push(badge);}returnnewBadges;}14.7 实操完成 ProgressService.ets步骤一创建服务文件在entry/src/main/ets/services/目录下创建ProgressService.ets文件。步骤二编写完整服务代码/** * 进度管理服务 * 管理用户学习进度、连续学习天数等 */import{StorageUtil}from../common/StorageUtil;import{StorageKeys}from../common/Constants;import{UserProgress,Badge,DEFAULT_USER_PROGRESS,LearningModule}from../models/Models;/** * 进度服务类 */exportclassProgressService{// 内存缓存privatestaticcachedProgress:UserProgress|nullnull;/** * 加载用户进度 */staticasyncloadProgress():PromiseUserProgress{try{constprogressawaitStorageUtil.getObjectUserProgress(StorageKeys.USER_PROGRESS,DEFAULT_USER_PROGRESS);ProgressService.cachedProgressprogress;returnprogress;}catch(error){console.error([ProgressService] Failed to load progress:,error);returnDEFAULT_USER_PROGRESS;}}/** * 保存用户进度 */staticasyncsaveProgress(progress:UserProgress):Promisevoid{try{awaitStorageUtil.setObject(StorageKeys.USER_PROGRESS,progress);ProgressService.cachedProgressprogress;}catch(error){console.error([ProgressService] Failed to save progress:,error);}}/** * 获取缓存的进度同步方法 */staticgetCachedProgress():UserProgress{returnProgressService.cachedProgress??DEFAULT_USER_PROGRESS;}/** * 标记课程完成 */staticasyncmarkLessonComplete(lessonId:string,moduleId:string):Promisevoid{constprogressawaitProgressService.loadProgress();if(!progress.completedLessons.includes(lessonId)){progress.completedLessons.push(lessonId);}progress.currentLessonlessonId;progress.totalStudyTime1;awaitProgressService.saveProgress(progress);awaitProgressService.updateStreak();}/** * 检查并更新模块完成状态 */staticasynccheckModuleCompletion(module:LearningModule):Promiseboolean{constprogressawaitProgressService.loadProgress();constallLessonsCompletedmodule.lessons.every(lessonprogress.completedLessons.includes(lesson.id));if(allLessonsCompleted!progress.completedModules.includes(module.id)){progress.completedModules.push(module.id);awaitProgressService.saveProgress(progress);returntrue;}returnallLessonsCompleted;}/** * 更新连续学习天数 */staticasyncupdateStreak():Promisenumber{constprogressawaitProgressService.loadProgress();consttodaynewDate().toISOString().split(T)[0];constlastDateprogress.lastStudyDate;if(!lastDate){progress.learningStreak1;}elseif(lastDatetoday){returnprogress.learningStreak;}else{constlastDateObjnewDate(lastDate);consttodayObjnewDate(today);constdiffDaysMath.floor((todayObj.getTime()-lastDateObj.getTime())/(1000*60*60*24));if(diffDays1){progress.learningStreak1;}else{progress.learningStreak1;}}progress.lastStudyDatetoday;awaitProgressService.saveProgress(progress);returnprogress.learningStreak;}/** * 计算模块完成百分比 */staticgetCompletionPercentage(module:LearningModule,progress:UserProgress):number{if(module.lessons.length0){return0;}constcompletedCountmodule.lessons.filter(lessonprogress.completedLessons.includes(lesson.id)).length;returnMath.round((completedCount/module.lessons.length)*100);}/** * 检查模块是否解锁 */staticisModuleUnlocked(moduleId:string,prerequisites:string[],progress:UserProgress):boolean{if(prerequisites.length0){returntrue;}returnprerequisites.every(prereqIdprogress.completedModules.includes(prereqId));}/** * 检查课程是否完成 */staticisLessonCompleted(lessonId:string,progress:UserProgress):boolean{returnprogress.completedLessons.includes(lessonId);}/** * 检查模块是否完成 */staticisModuleCompleted(moduleId:string,progress:UserProgress):boolean{returnprogress.completedModules.includes(moduleId);}/** * 添加徽章 */staticasyncaddBadge(badge:Badge):Promisevoid{constprogressawaitProgressService.loadProgress();if(!progress.badges.some(bb.idbadge.id)){progress.badges.push(badge);awaitProgressService.saveProgress(progress);}}/** * 重置进度 */staticasyncresetProgress():Promisevoid{awaitProgressService.saveProgress(DEFAULT_USER_PROGRESS);ProgressService.cachedProgressnull;}}步骤三在页面中集成// Index.etsimport{ProgressService}from../services/ProgressService;EntryComponentstruct Index{Stateprogress:UserProgressDEFAULT_USER_PROGRESS;asyncaboutToAppear():Promisevoid{// 加载进度this.progressawaitProgressService.loadProgress();}// 页面显示时刷新进度onPageShow():void{this.refreshProgress();}privateasyncrefreshProgress():Promisevoid{this.progressawaitProgressService.loadProgress();}}本次课程小结通过本次课程你已经✅ 掌握了进度数据的存储与读取✅ 实现了课程和模块完成标记✅ 学会了连续学习天数计算✅ 理解了徽章奖励机制✅ 完成了 ProgressService 的完整开发课后练习添加学习时长统计记录每节课的实际学习时长实现进度导出将学习进度导出为 JSON 文件添加更多徽章设计并实现更多类型的徽章下次预告第15次首页完整实现我们将整合所有组件和服务完成首页的完整功能首页数据加载流程继续学习区域推荐模块横向滚动页面刷新机制完成首页的最终实现