当前位置: 首页 > news >正文

HarmonyOS APP<玩转React>开源教程二十:收藏功能实现

第20次:收藏功能实现

收藏功能让用户可以标记感兴趣的课程,方便后续快速访问。本次课程将完整实现收藏功能,包括服务层、状态管理和收藏页面。


项目效果





学习目标

  • 掌握 BookmarkService 设计
  • 学会收藏状态管理
  • 实现收藏列表持久化
  • 完成收藏页面开发
  • 实现收藏功能全流程

20.1 收藏数据模型

Bookmark 结构

interfaceBookmark{lessonId:string;// 课程 IDmoduleId:string;// 所属模块 IDaddedAt:string;// 添加时间(ISO 格式)}

存储键

// Constants.etsexportclassStorageKeys{staticreadonlyBOOKMARKS:string='bookmarks';}

20.2 BookmarkService 设计

服务结构

exportclassBookmarkService{// 内存缓存privatestaticcachedBookmarks:Bookmark[]=[];// 加载收藏staticasyncloadBookmarks():Promise<Bookmark[]>;// 添加收藏staticasyncaddBookmark(lessonId:string,moduleId:string):Promise<void>;// 移除收藏staticasyncremoveBookmark(lessonId:string):Promise<void>;// 切换收藏状态staticasynctoggleBookmark(lessonId:string,moduleId:string):Promise<boolean>;// 检查是否已收藏staticisBookmarked(lessonId:string):boolean;}

加载收藏

staticasyncloadBookmarks():Promise<Bookmark[]>{try{constbookmarks=awaitStorageUtil.getObject<Bookmark[]>(StorageKeys.BOOKMARKS,[]);BookmarkService.cachedBookmarks=bookmarks;returnbookmarks;}catch(error){console.error('[BookmarkService] Failed to load bookmarks:',error);return[];}}

保存收藏

privatestaticasyncsaveBookmarks(bookmarks:Bookmark[]):Promise<void>{try{awaitStorageUtil.setObject(StorageKeys.BOOKMARKS,bookmarks);BookmarkService.cachedBookmarks=bookmarks;}catch(error){console.error('[BookmarkService] Failed to save bookmarks:',error);}}

20.3 收藏操作实现

添加收藏

staticasyncaddBookmark(lessonId:string,moduleId:string):Promise<void>{constbookmarks=awaitBookmarkService.loadBookmarks();// 检查是否已收藏,避免重复if(bookmarks.some(b=>b.lessonId===lessonId)){return;}// 创建新收藏constnewBookmark:Bookmark={lessonId,moduleId,addedAt:newDate().toISOString()};bookmarks.push(newBookmark);awaitBookmarkService.saveBookmarks(bookmarks);}

移除收藏

staticasyncremoveBookmark(lessonId:string):Promise<void>{constbookmarks=awaitBookmarkService.loadBookmarks();constfiltered=bookmarks.filter(b=>b.lessonId!==lessonId);awaitBookmarkService.saveBookmarks(filtered);}

切换收藏状态

staticasynctoggleBookmark(lessonId:string,moduleId:string):Promise<boolean>{constisCurrentlyBookmarked=BookmarkService.isBookmarked(lessonId);if(isCurrentlyBookmarked){awaitBookmarkService.removeBookmark(lessonId);returnfalse;// 返回新状态:未收藏}else{awaitBookmarkService.addBookmark(lessonId,moduleId);returntrue;// 返回新状态:已收藏}}

检查收藏状态

// 同步方法,使用缓存staticisBookmarked(lessonId:string):boolean{returnBookmarkService.cachedBookmarks.some(b=>b.lessonId===lessonId);}

20.4 收藏分组功能

按模块分组

staticgetBookmarksByModule():Map<string,Bookmark[]>{constgrouped=newMap<string,Bookmark[]>();for(constbookmarkofBookmarkService.cachedBookmarks){constmoduleBookmarks=grouped.get(bookmark.moduleId)??[];moduleBookmarks.push(bookmark);grouped.set(bookmark.moduleId,moduleBookmarks);}returngrouped;}

获取收藏数量

staticgetBookmarkCount():number{returnBookmarkService.cachedBookmarks.length;}

清空收藏

staticasyncclearBookmarks():Promise<void>{awaitBookmarkService.saveBookmarks([]);}

20.5 收藏页面实现

页面结构

@Entry@Componentstruct BookmarkPage{@Statebookmarks:Bookmark[]=[];@StateisLoading:boolean=true;@StorageLink('isDarkMode')isDarkMode:boolean=false;aboutToAppear():void{this.loadBookmarks();}privateasyncloadBookmarks():Promise<void>{this.bookmarks=awaitBookmarkService.loadBookmarks();this.isLoading=false;}}

空状态展示

@BuilderEmptyState(){Column(){Text('📚').fontSize(64)Text('暂无收藏').fontSize(18).fontColor(this.isDarkMode?'#d1d5db':'#495057').margin({top:16})Text('点击课程旁的 ☆ 添加收藏').fontSize(14).fontColor(this.isDarkMode?'#9ca3af':'#6c757d').margin({top:8})}.width('100%').height('100%').justifyContent(FlexAlign.Center)}

收藏列表

@BuilderBookmarkList(){List(){ForEach(this.bookmarks,(bookmark:Bookmark)=>{ListItem(){this.BookmarkItem(bookmark)}.margin({bottom:8})},(bookmark:Bookmark)=>bookmark.lessonId)}.width('100%').layoutWeight(1).padding({left:16,right:16,top:12}).scrollBar(BarState.Off)}

收藏项组件

@BuilderBookmarkItem(bookmark:Bookmark){Row(){// 获取课程信息Column(){Text(this.getLessonTitle(bookmark)).fontSize(15).fontWeight(FontWeight.Medium).fontColor(this.isDarkMode?'#ffffff':'#1a1a2e')Text(this.getModuleTitle(bookmark)).fontSize(12).fontColor(this.isDarkMode?'#d1d5db':'#495057').margin({top:4})}.alignItems(HorizontalAlign.Start).layoutWeight(1)// 取消收藏按钮Text('★').fontSize(20).fontColor('#fcc419').onClick(()=>this.removeBookmark(bookmark))// 箭头Text('›').fontSize(18).fontColor(this.isDarkMode?'#9ca3af':'#6c757d').margin({left:8})}.width('100%').padding(16).backgroundColor(this.isDarkMode?'#282c34':'#ffffff').borderRadius(12).onClick(()=>{router.pushUrl({url:'pages/LessonDetail',params:{moduleId:bookmark.moduleId,lessonId:bookmark.lessonId}});})}// 获取课程标题privategetLessonTitle(bookmark:Bookmark):string{constlesson=TutorialService.getLessonById(bookmark.moduleId,bookmark.lessonId);returnlesson?.title??'未知课程';}// 获取模块标题privategetModuleTitle(bookmark:Bookmark):string{constmodule=TutorialService.getModuleById(bookmark.moduleId);returnmodule?.title??'未知模块';}// 移除收藏privateasyncremoveBookmark(bookmark:Bookmark):Promise<void>{awaitBookmarkService.removeBookmark(bookmark.lessonId);this.bookmarks=this.bookmarks.filter(b=>b.lessonId!==bookmark.lessonId);}

20.6 完整服务代码

/** * 收藏管理服务 */import{StorageUtil}from'../common/StorageUtil';import{StorageKeys}from'../common/Constants';import{Bookmark}from'../models/Models';exportclassBookmarkService{privatestaticcachedBookmarks:Bookmark[]=[];staticasyncloadBookmarks():Promise<Bookmark[]>{try{constbookmarks=awaitStorageUtil.getObject<Bookmark[]>(StorageKeys.BOOKMARKS,[]);BookmarkService.cachedBookmarks=bookmarks;returnbookmarks;}catch(error){console.error('[BookmarkService] Failed to load bookmarks:',error);return[];}}privatestaticasyncsaveBookmarks(bookmarks:Bookmark[]):Promise<void>{try{awaitStorageUtil.setObject(StorageKeys.BOOKMARKS,bookmarks);BookmarkService.cachedBookmarks=bookmarks;}catch(error){console.error('[BookmarkService] Failed to save bookmarks:',error);}}staticgetCachedBookmarks():Bookmark[]{returnBookmarkService.cachedBookmarks;}staticasyncaddBookmark(lessonId:string,moduleId:string):Promise<void>{constbookmarks=awaitBookmarkService.loadBookmarks();if(bookmarks.some(b=>b.lessonId===lessonId)){return;}constnewBookmark:Bookmark={lessonId,moduleId,addedAt:newDate().toISOString()};bookmarks.push(newBookmark);awaitBookmarkService.saveBookmarks(bookmarks);}staticasyncremoveBookmark(lessonId:string):Promise<void>{constbookmarks=awaitBookmarkService.loadBookmarks();constfiltered=bookmarks.filter(b=>b.lessonId!==lessonId);awaitBookmarkService.saveBookmarks(filtered);}staticasynctoggleBookmark(lessonId:string,moduleId:string):Promise<boolean>{constisCurrentlyBookmarked=BookmarkService.isBookmarked(lessonId);if(isCurrentlyBookmarked){awaitBookmarkService.removeBookmark(lessonId);returnfalse;}else{awaitBookmarkService.addBookmark(lessonId,moduleId);returntrue;}}staticisBookmarked(lessonId:string):boolean{returnBookmarkService.cachedBookmarks.some(b=>b.lessonId===lessonId);}staticgetBookmarksByModule():Map<string,Bookmark[]>{constgrouped=newMap<string,Bookmark[]>();for(constbookmarkofBookmarkService.cachedBookmarks){constmoduleBookmarks=grouped.get(bookmark.moduleId)??[];moduleBookmarks.push(bookmark);grouped.set(bookmark.moduleId,moduleBookmarks);}returngrouped;}staticgetBookmarkCount():number{returnBookmarkService.cachedBookmarks.length;}staticasyncclearBookmarks():Promise<void>{awaitBookmarkService.saveBookmarks([]);}}

20.7 在其他页面集成

课程详情页

// LessonDetail.ets@StateisBookmarked:boolean=false;aboutToAppear():void{this.isBookmarked=BookmarkService.isBookmarked(this.lessonId);}// 收藏按钮Text(this.isBookmarked?'★':'☆').fontSize(24).fontColor(this.isBookmarked?'#fcc419':'#9ca3af').onClick(async()=>{this.isBookmarked=awaitBookmarkService.toggleBookmark(this.lessonId,this.moduleId);})

模块详情页

// ModuleDetail.etsLessonItem({lesson:lesson,isBookmarked:BookmarkService.isBookmarked(lesson.id),onBookmarkTap:async()=>{awaitBookmarkService.toggleBookmark(lesson.id,module.id);this.bookmarkVersion++;// 触发刷新}})

本次课程小结

通过本次课程,你已经:

✅ 掌握了 BookmarkService 设计
✅ 学会了收藏状态管理
✅ 实现了收藏列表持久化
✅ 完成了收藏页面开发
✅ 实现了收藏功能全流程


课后练习

  1. 添加排序功能:支持按添加时间排序

  2. 添加批量操作:支持批量删除收藏

  3. 添加导出功能:导出收藏列表


下次预告

第21次:测验服务层实现

我们将开发测验功能:

  • QuizService 设计
  • 测验数据结构
  • 答案验证逻辑
  • 分数计算

进入测验功能开发!

http://www.jsqmd.com/news/519480/

相关文章:

  • 从SolarWinds事件看二进制SCA的重要性:你的供应链安全还缺这一环
  • Ubuntu20.04下微信中文输入终极解决方案:修改deepin-wine配置全记录
  • ARM64服务器上Docker跑Redis总崩溃?3种配置文件调试方案实测
  • SLAM避坑指南:为什么你的base_footprint总在Rviz里‘飘移‘?(TF树排查手册)
  • 基于虚拟阻抗重塑的构网型VSG变流器SISO序阻抗建模与宽频振荡抑制策略分析(面向高比例新能源并网场景)
  • 联发科MTK Sensor Bring Up避坑指南:以STK3321为例的常见问题解析
  • PyAV实战:如何用TCP协议稳定拉取RTSP视频流(附超时解决方案)
  • Microchip Libero SoC v12.2 Windows版:从官网下载到License激活的保姆级避坑指南
  • 保姆级教程:用FFmpeg+Nginx把监控摄像头RTSP流转成HLS网页播放
  • NRF52系列选型终极指南:从52810到52840,5个关键指标帮你省下30%成本
  • Spring AI对话记忆存储选型指南:MySQL vs Redis性能对比实测
  • LLM 大语言模型 训练的时候 batchsize 调整大导致梯度爆炸问题解决
  • 养狗管理拟参照道路交通法个人观点:计分、吊证、入刑,这些行为将被终身禁养
  • CentOS7下Zabbix5.0与MariaDB完美搭配:从零搭建到邮件告警全攻略
  • MAC和PHY到底在搞什么?用大白话拆解网卡工作原理
  • 还在用三层交换机?手把手教你用Cisco Packet Tracer搞定单臂路由,让老旧路由器也能玩转VLAN互通
  • CATIA模型导出避坑指南:为什么你的DXF文件在Cadence中显示异常?
  • 7、C语言指针专题:多级指针
  • 如果“管狗如管车”全国落地,社会将发生什么?农村学生体质会下降吗?
  • 告别龟速下载!保姆级教程:用国内镜像站5分钟搞定Ubuntu 20.04 LTS下载与VMware安装
  • 从Maya到Max:如何完美转换Bone骨骼并优化飘带动画效果
  • Wox这款开源Windows启动器,我用了十年
  • ROS2实战:如何用DDS中间件优化你的机器人通信(附Fast DDS配置指南)
  • Matrix200读码器安装调试全攻略:从接线到参数设置一步到位
  • 8、C语言指针专题:指针与字符串
  • 实测省下3小时:Gemini 3.1 Pro终结职场重复劳动,打工人提前下班
  • 救命神器!AI论文写作软件 千笔·专业论文写作工具 VS 文途AI,全行业通用首选!
  • UE5新手必看:LocalPlayer输入管理与视口配置全解析(附分屏实战代码)
  • Hardhat实战:5分钟搞定以太坊智能合约的本地测试与部署
  • 用Dify工作流给DeepSeek插上翅膀:手把手教你构建带联网能力的AI日历助手