
HarmonyOS应用实战-启示散页-08-提问历史别无限长用去重、截断和 Sheet 入口做轻量记忆提问历史的价值不是把用户每一次点击都永久保存而是帮助用户找回最近问过什么、当时抽到了什么。答案之书如果无限追加历史Preferences 会越来越大列表越来越慢重复问题也会淹没有用记录。轻量应用更适合做“最近记忆”去重、截断、可清理并用 Sheet 承接查看入口。这篇文章会把问题拆成四个可落地的点历史记录保存问题、答案、题库和时间不只保存问题文本。用最近上限控制 Preferences 体积。相同问题可合并或前移避免列表噪声。Sheet 入口只展示轻量历史完整规则留在 Service。1. 历史不是日志仓库日志追求完整历史追求可用。用户在答案之书里可能连续问同一个问题也可能只是试动画。把每次抽取都永久保存会让列表快速失去阅读价值。历史服务应该定义上限比如只保留最近 50 条。constMAX_HISTORY_COUNT:number50;interfaceQuestionHistoryRecord{id:string;question:string;answerText:string;deckId:string;answerId:string;createdAt:number;}历史记录仍要保存来源字段因为它可能被用于收藏、复盘或再次提问。2. 空问题不要污染历史答案之书允许用户不输入问题直接抽答案但这类行为不一定值得进提问历史。可以只记录非空问题或者把空问题标记为“随便问问”。关键是规则要在 Service 里统一而不是由页面临时判断。functionnormalizeQuestion(question?:string):string{return(question??).trim().replace(/\s/g, );}if(!normalizeQuestion(result.question)){return;}输入规范化能合并多余空格也能避免空字符串成为历史列表里的噪声。3. 重复问题可以前移用户重复问同一个问题时历史列表里出现十条相同问题没有意义。答案之书可以按规范化问题去重新结果覆盖旧结果并移动到最前面。这样保留最近答案也保持列表清爽。constkey:stringnormalizeQuestion(result.question).toLocaleLowerCase();constrest:QuestionHistoryRecord[]records.filter((item:QuestionHistoryRecord):boolean{returnnormalizeQuestion(item.question).toLocaleLowerCase()!key;});constnext:QuestionHistoryRecord[][createHistoryRecord(result),...rest].slice(0,MAX_HISTORY_COUNT);这个策略不是丢数据而是把历史定位成最近记忆。用户最需要看到的是最后一次问这个问题得到的答案。4. 追加历史要复用抽取结果历史来源应该是DrawAnswerResult不要让 DrawingPage 再拼一遍字段。只要抽取结果已经包含 deckId、answerId、answerText、question 和 drawnAt历史服务就能稳定生成记录。functioncreateHistoryRecord(result:DrawAnswerResult):QuestionHistoryRecord{return{id:newId(history),question:normalizeQuestion(result.question),answerText:result.answer.text,deckId:result.deckId,answerId:result.answer.id,createdAt:result.drawnAt};}历史是抽取链路的下游字段必须从同一个结果对象来避免收藏和历史对不上。5. Preferences 里只放必要字段历史记录可能增长字段越多越容易拖慢读写。不要把整份 Deck、完整 Answer 对象或 UI 展示字段写进历史。展示需要的题库名、时间文案可以在 Service 读取时组装。awaitPreferencesStore.setJsonQuestionHistoryRecord[](PrefStoreName.App,AppPrefKey.QuestionHistory,next);AppStorage.setOrCreate(AppStorageKey.LastHistoryUpdateAt,Date.now());持久化字段越稳定后续迁移越轻。展示字段属于 ViewItem不属于历史原始记录。6. Sheet 适合承接轻量历史提问历史不是主流程页面放在 Sheet 里更符合轻量工具体验。首页保留抽取入口Sheet 展示最近问题、答案和时间并提供再次提问、收藏、清空。BuilderHistorySheet(){Column({space:12}){ForEach(this.historyItems,(item:HistoryViewItem){this.historyRow(item);},(item:HistoryViewItem):stringitem.id)}}Sheet 只承载展示和操作入口不直接读写 Preferences。列表数据仍从 HistoryService 来。7. 清空历史要明确范围清空历史不是清空收藏也不是清空题库。按钮文案和 Service 方法都要表达范围避免误删。对于本地离线应用用户数据边界越清楚隐私和发布说明越好写。asyncclearQuestionHistory():Promisevoid{awaitPreferencesStore.setJsonQuestionHistoryRecord[](PrefStoreName.App,AppPrefKey.QuestionHistory,[]);AppStorage.setOrCreate(AppStorageKey.LastHistoryUpdateAt,Date.now());}清空动作只触碰历史 key。收藏、题库、当前题库都不应该被顺手改掉。8. 历史刷新独立于题库刷新历史变化不一定意味着题库变化。答案之书用独立的LastHistoryUpdateAtSheet 打开或刷新信号变化时重新读取。这样历史列表不会依赖首页重建也不会因为收藏变化而误刷新。StorageLink(lastHistoryUpdateAt)Watch(reloadHistory)lastHistoryUpdateAt:number0;privateasyncreloadHistory():Promisevoid{this.historyItemsawaitHistoryService.listViewItems();}刷新信号按数据域拆分是让小应用长期可维护的基础习惯。9. 验证与排障历史验证要连续抽取、重复提问、清空、重启、删除来源题库。尤其要看 MAX_HISTORY_COUNT 是否生效以及重复问题是否前移而不是新增一条。验证点 1. 空问题不进入历史或按约定显示 2. 同一问题重复提问只保留最近一条 3. 超过 50 条后只保留最近 50 条 4. 清空历史不影响收藏和题库 5. 重启后历史仍按时间倒序显示如果 Sheet 打开很慢先看历史是否无限增长再看是否把展示字段或整份题库写进了历史记录。历史里的“再次提问”也要重新走抽取链路而不是把旧答案重新展示一遍。历史记录提供的是 question 上下文新的答案仍由AnswerDrawService从当前题库里生成。这样用户能区分“查看过去结果”和“基于同一个问题再抽一次”历史功能才不会变成隐藏的结果缓存。这个边界对收藏也有影响从历史里收藏旧答案应该收藏历史记录里的 answerId 和 answerText从历史里再次提问后收藏新答案则使用新的 DrawAnswerResult。两个动作看起来都从 Sheet 发起但数据来源不同Service 方法也应该分开。验证清单清应用数据后从冷启动进入确认默认数据、页面状态和日志分支符合预期。对本文涉及的写路径准备正常、空值、重复、越界四类输入确认错误停在 Service 或 Repository。页面返回、重新进入、切换题库、收藏、历史或删除后确认对应刷新信号触发重新读取。修改资源或模块归属后重新构建确认 HAP、HAR、HSP 的依赖方向没有反转。涉及真机体验、备份恢复、发布素材的内容单独记录是否已经在设备或平台侧验证。常见问题与处理现象先看哪里处理方式历史列表越来越慢MAX_HISTORY_COUNT 是否生效写入时截断最近记录重复问题刷屏是否按规范化问题去重重复问题前移并覆盖旧结果清空后收藏没了清理范围是否过大只清 QuestionHistory keySheet 数据不刷新LastHistoryUpdateAt 是否变化历史写入和清空后发刷新信号小结提问历史应该是轻量记忆不是无限日志。规范化问题、合并重复、限制上限、独立刷新能让历史功能长期保持有用也不会拖累本地存储。