HarmonyOS应用实战-启示散页-07-收藏功能别只存文本:用去重、来源和刷新时间串起来
HarmonyOS应用实战-启示散页-07-收藏功能别只存文本用去重、来源和刷新时间串起来收藏功能很容易被写成“把当前答案文本放进数组”。这在第一天能用但很快会遇到问题同一句答案来自不同题库怎么办用户改了题库名后收藏来源怎么显示重复点收藏要不要出现两条收藏后列表为什么不刷新答案之书的收藏应该存来源、去重和时间而不是只存一段字符串。这篇文章会把问题拆成四个可落地的点收藏记录保存 answerId、deckId、answerText 和创建时间。用组合 key 去重避免同一答案重复收藏。收藏写入后发出刷新信号列表从 Service 重读。处理题库被删除、答案被改名后的来源展示。1. 只存文本会丢掉来源答案文本本身不是稳定身份。不同题库可能都有“先等等”同一题库的答案也可能被用户编辑。收藏如果只存文本后续无法跳回来源题库也无法区分用户收藏的是哪一次抽取结果。interfaceFavoriteRecord{id:string;deckId:string;answerId:string;answerText:string;question?:string;createdAt:number;}收藏记录保存快照文本同时保存来源 id。快照用于展示来源 id 用于追踪和去重。2. 收藏入口使用 DrawAnswerResult收藏按钮通常在抽取结果页。这个页面不应该自己拼来源字段而是直接使用DrawAnswerResult。这样收藏、历史和分享都从同一个领域结果出发。privateasyncaddFavorite():Promisevoid{if(!this.result){return;}awaitFavoriteService.addFromDraw(this.result);promptAction.showToast({message:已收藏});}页面负责触发收藏FavoriteService 负责判断能不能收藏以及怎么保存。3. 组合 key 去重更符合业务如果只按 answerText 去重不同题库里的同一句会被误认为同一个收藏如果只按 answerId 去重导入或迁移后也可能冲突。答案之书可以用 deckId answerId 作为主要去重键必要时再用文本兜底。functionfavoriteKey(item:FavoriteRecord):string{return${item.deckId}:${item.answerId};}constexists:booleanrecords.some((item:FavoriteRecord):boolean{returnfavoriteKey(item)${result.deckId}:${result.answer.id};});if(exists){return;}去重规则要体现产品语义用户收藏的是某个题库中的某条答案而不是全应用唯一的一段文字。4. 保存前生成快照展示时不依赖来源仍存在收藏记录里保留answerText是必要的。即使用户后来删除题库收藏列表仍应该能显示当时收藏的答案而不是整条记录变成空白。来源信息可以显示为“原题库已删除”。constrecord:FavoriteRecord{id:newId(fav),deckId:result.deckId,answerId:result.answer.id,answerText:result.answer.text,question:result.question,createdAt:result.drawnAt};快照和来源不是二选一。快照保证展示稳定来源保证追踪能力。5. 收藏列表从 Service 组装展示模型收藏页不要直接读 FavoriteRepository 后自己查题库名。Service 可以一次性读取收藏、题库索引和题库摘要组装成页面需要的展示模型。这样题库被删除、重命名、数据损坏时兜底口径只有一处。interfaceFavoriteViewItem{id:string;answerText:string;deckName:string;sourceState:active|missing;createdAtText:string;}asynclistViewItems():PromiseFavoriteViewItem[]{constfavorites:FavoriteRecord[]awaitFavoriteRepository.loadAll();constdecks:Mapstring,DeckSummaryawaitDeckService.loadSummaryMap();returnfavorites.map((item:FavoriteRecord):FavoriteViewItemthis.toViewItem(item,decks));}页面拿到的是可展示对象不需要知道来源题库怎么查也不需要重复处理 missing 状态。6. 写入后刷新信号要独立收藏列表和题库列表不是同一类数据。答案之书应该为收藏单独准备LastFavoriteUpdateAt避免收藏变化误触发题库列表重载也避免题库变化时收藏页不更新。awaitFavoriteRepository.saveAll(nextRecords);AppStorage.setOrCreate(AppStorageKey.LastFavoriteUpdateAt,Date.now());StorageLink(lastFavoriteUpdateAt)Watch(reloadFavorites)lastFavoriteUpdateAt:number0;刷新信号按数据域拆开页面才能按需重读。一个全局 updateAt 后期会变成性能和排障负担。7. 取消收藏也要走同一条链路删除收藏不能只在页面数组里 splice。页面本地移除看起来很快但重启后又回来或者其他页面仍显示已收藏。取消收藏要更新 Repository并发出同一条刷新信号。asyncremoveFavorite(id:string):Promisevoid{constrecords:FavoriteRecord[]awaitFavoriteRepository.loadAll();constnext:FavoriteRecord[]records.filter((item:FavoriteRecord):booleanitem.id!id);awaitFavoriteRepository.saveAll(next);AppStorage.setOrCreate(AppStorageKey.LastFavoriteUpdateAt,Date.now());}收藏的增删都要走持久化闭环。页面数组只是当前渲染结果不是数据源。8. 数量和空态从真实记录计算首页或个人页如果展示收藏数量不要手动 1 或 -1。更稳的做法是在页面出现时从 FavoriteService 读取或者监听收藏刷新信号后重新计算。这样跨页面返回、删除和恢复都不会让数字漂移。privateasyncreloadCount():Promisevoid{constrecords:FavoriteRecord[]awaitFavoriteRepository.loadAll();this.favoriteCountrecords.length;}本地计数也要尊重单一数据源。手动增减在多入口场景里迟早会错。9. 验证与排障收藏验证要覆盖重复收藏、删除来源题库、编辑答案文本、取消收藏后重启、收藏列表和首页数量同步。重点不是按钮能点而是每个入口看到的是同一份数据。验证点 1. 同一答案连续点收藏只出现一条 2. 删除来源题库后收藏仍显示 answerText 3. 取消收藏后重启应用不再出现 4. 收藏数量和列表条数一致 5. LastFavoriteUpdateAt 只在收藏变化时更新如果收藏页不刷新优先检查刷新信号和页面重读不要先改列表渲染。如果后续收藏列表要支持按题库筛选也不要把筛选条件下沉到 Repository。Repository 返回原始收藏Service 根据deckId、来源状态和时间组装展示模型页面只切换筛选条件。这样题库被删除、重命名或迁移后筛选结果仍然从同一个模型推出来不会每个页面各写一套来源兜底。验证清单清应用数据后从冷启动进入确认默认数据、页面状态和日志分支符合预期。对本文涉及的写路径准备正常、空值、重复、越界四类输入确认错误停在 Service 或 Repository。页面返回、重新进入、切换题库、收藏、历史或删除后确认对应刷新信号触发重新读取。修改资源或模块归属后重新构建确认 HAP、HAR、HSP 的依赖方向没有反转。涉及真机体验、备份恢复、发布素材的内容单独记录是否已经在设备或平台侧验证。常见问题与处理现象先看哪里处理方式重复收藏出现多条去重 key 是否存在使用 deckId answerId 去重来源题库删除后收藏空白是否保存 answerText 快照收藏记录保留展示文本取消收藏后重启又回来是否只改页面数组删除必须写回 Repository首页收藏数不准是否手动加减计数从 Service 重读真实数量小结收藏不是文本数组而是带来源的用户选择记录。保存快照、保留来源、按组合 key 去重再用独立刷新信号联动页面收藏功能才经得起后续编辑和删除。