HarmonyOS应用实战-启示散页-66-空题库别只禁用按钮:给当前选择和抽取入口一条恢复链
HarmonyOS 应用实战 66:空题库别只禁用按钮,给当前选择和抽取入口一条恢复链
“获取答案”按钮变灰只能阻止当前点击,不能解释当前题库为什么不可用。空题库、被删除的题库、答案全被清空的题库,都需要一条恢复链:告诉用户当前状态、给出可操作入口,并保证抽取服务不会拿空数组随机。
本文解决四个问题:
- 区分无题库、空题库、当前题库失效
- 在服务层返回可恢复状态
- 页面展示恢复入口而不只禁用按钮
- 验证首页、选择器、抽取页三处一致
空题库不是一个按钮状态
按钮禁用只是 UI 结果,背后可能是三类业务状态:没有任何题库、当前题库不存在、当前题库存在但没有答案。它们对应的恢复动作不同。
| 状态 | 用户应看到什么 | |---|---| | noDeck | 创建或导入题库入口 | | missingCurrent | 回退默认题库或重新选择 | | emptyAnswers | 去编辑题库添加答案 |空题库不是一个 UI 状态,而是一组需要恢复的业务状态。没有题库、当前题库失效、题库存在但答案为空,分别需要不同入口。
HomeReadyState 描述恢复动作
首页快照应明确是否可抽取,以及不可抽取时该给哪个恢复动作。
typeRecoveryAction='createDeck'|'selectDeck'|'editCurrentDeck'|'fallbackDefault';interfaceHomeReadyState{drawable:boolean;currentDeckId:string;reason?:string;recoveryAction?:RecoveryAction;}HomeReadyState把是否可抽取和恢复动作放在同一个快照里,页面就不用在按钮、空态和选择器之间各自判断。
HomeService 负责判断题库状态
页面不遍历 decks 判断原因。服务读取当前 id 和题库列表后,返回可展示状态。
classHomeReadinessService{asyncload():Promise<HomeReadyState>{constdecks=awaitDeckRepository.loadAllDecks();if(decks.length===0){return{drawable:false,currentDeckId:'',reason:'还没有题库',recoveryAction:'createDeck'};}constcurrentId=awaitAppPreferencesRepository.loadCurrentDeckId();constcurrent=decks.find((deck)=>deck.id===currentId);if(!current){return{drawable:false,currentDeckId:DEFAULT_DECK_ID,reason:'当前题库已失效',recoveryAction:'fallbackDefault'};}returncurrent.answers.length>0?{drawable:true,currentDeckId:current.id}:{drawable:false,currentDeckId:current.id,reason:'当前题库没有答案',recoveryAction:'editCurrentDeck'};}}首页服务负责读题库、校验当前选择并给出原因。这个判断放在页面里,会让快捷入口和抽取页绕过同一条规则。
抽取服务仍然要做最后防线
首页显示可抽取不代表点击瞬间仍可抽取。AnswerPickService 必须拒绝空候选集。
classAnswerPickService{pick(deck:Deck):Answer{if(deck.answers.length===0){thrownewError('题库没有可抽取答案');}returndeck.answers[Math.floor(Math.random()*deck.answers.length)];}}抽取服务必须做最后防线。状态从首页展示到用户点击之间可能变化,空数组随机不能靠按钮禁用来避免。
页面恢复入口要指向具体动作
不要只显示“暂无数据”。用户需要知道下一步是创建、选择、编辑还是回退。
@Componentstruct HomeRecoveryPanel{@Propstate:HomeReadyState;privateprimaryAction():void{if(this.state.recoveryAction==='editCurrentDeck'){NavigationService.openDeckEdit(this.state.currentDeckId);}elseif(this.state.recoveryAction==='fallbackDefault'){DeckService.setCurrent(DEFAULT_DECK_ID);}}}恢复入口要具体到动作:创建、选择、编辑、回退。只显示“暂无数据”会让用户知道出错,却不知道怎么恢复。
空题库回归要覆盖入口差异
从首页按钮、历史再次提问、快捷入口进入抽取,都要得到同一个不可抽取原因。
验证:删除全部题库;保留一个空题库;删除当前题库;从首页和快捷入口分别抽取,确认都不会进入随机逻辑。入口差异是空题库回归的重点。首页按钮、历史再次提问、快捷推荐进入抽取,都应该得到同一个不可抽取原因。
空题库排查表
如果只是按钮灰了,说明恢复链还不完整。
| 现象 | 缺少什么 | 修复 |
|---|---|---|
| 只有按钮变灰 | 没有 reason/action | 返回 HomeReadyState |
| 快捷入口仍崩溃 | 只在首页判断 | 抽取服务最后校验 |
| 用户不知怎么办 | 空态无主动作 | 指向创建/编辑/回退 |
排查时不要先调按钮样式。先确认服务返回的reason和recoveryAction是否完整,再看页面是否正确消费。
恢复链要写到用户能继续操作
空题库文章要避免停在“禁用按钮”。真正的恢复链至少有状态解释、主动作和兜底动作。比如没有题库时主动作是创建或导入;当前题库为空时主动作是编辑;当前题库失效时主动作是重新选择或回退默认。
| 状态 | 主动作 | 兜底 |
|---|---|---|
noDeck | 创建题库 | 导入演示题库 |
missingCurrent | 重新选择 | 回退默认题库 |
emptyAnswers | 编辑当前题库 | 切换其他题库 |
这个表能直接指导 UI,而不是只指导 Service。
服务和页面各验一次
服务层验证看返回的HomeReadyState,页面层验证看用户是否有可点击恢复入口。两层只验一层都会漏问题:服务正确但页面没入口,用户仍然卡住;页面有入口但服务最后不拦截,快捷路径仍可能崩。
服务样本:noDeck / missingCurrent / emptyAnswers / ready 页面样本:首页按钮 / 快捷推荐 / 历史再次提问 / 冷启动恢复 通过标准:每个不可抽取状态都有 reason 和 recoveryAction,抽取服务拒绝空候选集。要特别检查历史再次提问和快捷推荐,因为它们最容易绕过首页按钮状态。
发布前不要让空态截图变成假证据
空态截图只能证明页面有提示,不能证明抽取服务安全。交付记录要把截图和服务样本放在一起。
| 材料 | 证明范围 | 仍需补充 |
|---|---|---|
| 空态截图 | 用户能看到恢复入口 | 抽取服务是否拦截 |
| Service 返回样本 | 状态判断正确 | 页面是否正确消费 |
| 冷启动记录 | 当前选择恢复安全 | 快捷入口是否一致 |
这样写,读者不会误以为“按钮灰了”就代表空题库链路已经闭合。
恢复动作要能跨页面复用
空题库恢复不能只服务首页。历史页的“再次提问”、结果页的“再抽一次”、快捷推荐卡片,都可能进入同一条抽取链路。因此RecoveryAction最好能被多个入口复用,而不是写成首页私有按钮逻辑。
classRecoveryNavigator{statichandle(action:RecoveryAction,deckId:string):void{if(action==='createDeck'){NavigationService.openDeckCreate();}elseif(action==='editCurrentDeck'){NavigationService.openDeckEdit(deckId);}elseif(action==='selectDeck'){NavigationService.openDeckSelector();}}}这个协调层不负责判断题库状态,只负责把服务层给出的动作连接到页面。这样恢复策略仍在HomeReadinessService,导航执行也不会散到每个按钮里。
人工评审时看提示是否能变成动作
空态文案如果只解释原因,不提供动作,用户还是会卡住。评审时不要只看文字是否友好,而要点每个恢复按钮,确认它能进入创建、导入、编辑或选择题库的真实页面。
评审样本: noDeck -> 点击创建题库 -> 进入 DeckCreatePage missingCurrent -> 点击重新选择 -> 打开 DeckSelector emptyAnswers -> 点击添加答案 -> 进入 DeckEditPage 并定位到答案区 fallbackDefault -> 点击继续 -> currentDeckId 写回默认题库这组样本能区分“空态设计完成”和“恢复链完成”。前者只让页面好看,后者才能让用户继续完成抽取任务。
小结
空题库要给恢复链,而不是只禁用按钮。HomeService 返回可抽取状态和恢复动作,页面展示具体入口,抽取服务做最后防线;这样各种入口都能安全处理空数据。