
上傳生成看起來只有兩個按鈕選擇圖片、生成圖紙。真正運行時卻至少包含空閑、打開圖庫、已選擇、轉換中、成功和失敗六種狀態。若只依靠pickedImageUri、一條createStatus字符串和若干布爾值拼判斷很容易出現舊預覽沒有清空、轉換中還能重復點擊、錯誤后按鈕文案與數據不一致。這篇文章把《拼豆制圖》的創建頁改造成一個顯式狀態機讓 Harmony os 頁面渲染、按鈕可用性和異步回調都由同一個狀態事實源驅動。一、先列出真實狀態而不是控件狀態exporttypeCreatePhaseidle|picking|picked|converting|ready|failed;exportinterfaceCreateState{phase:CreatePhase;imageUri:string;result:Pattern|null;message:string;requestId:number;}requestId用來識別過期異步結果。它不是展示字段卻是防止“后發請求先完成”覆蓋當前選擇的關鍵。二、定義允許發生的狀態遷移當前狀態事件下一狀態idle點擊選擇pickingpicking選擇成功pickedpicking取消idlepicked點擊生成convertingconverting轉換成功readyconverting轉換失敗failedready / failed重新選擇picking未列出的遷移應被拒絕。例如converting期間再次點擊生成不應該再創建第二個任務。三、用一個State保存完整快照StatecreateState:CreateState{phase:idle,imageUri:,result:null,message:請選擇一張圖片,requestId:0};privatereplaceCreateState(next:CreateState):void{this.createStatenext;}整對象替換能讓 ArkUI 明確收到狀態變化也避免只改了消息、忘記同步 URI 或結果。四、選擇圖片時區分取消與失敗privateasyncpickImage():Promisevoid{if(this.createState.phasepicking||this.createState.phaseconverting){return;}constrequestIdthis.createState.requestId1;this.replaceCreateState({phase:picking,imageUri:,result:null,message:正在打開系統圖庫...,requestId});try{consturiawaitImagePickerService.pickSingleImage();if(requestId!this.createState.requestId){return;}if(uri.length0){this.replaceCreateState({phase:idle,imageUri:,result:null,message:未選擇圖片,requestId});return;}this.replaceCreateState({phase:picked,imageUri:uri,result:null,message:圖片已選擇可以生成編號圖,requestId});}catch(error){this.failCreate(requestId,errorasError);}}取消不是失敗頁面不應顯示紅色錯誤也不應保留上一次圖片冒充新選擇。五、生成任務要拒絕過期結果privateasyncconvertPickedImage():Promisevoid{if(this.createState.phase!picked){return;}constrequestIdthis.createState.requestId1;consturithis.createState.imageUri;this.replaceCreateState({phase:converting,imageUri:uri,result:null,message:正在讀取像素并生成 70×70 圖紙...,requestId});try{constoutputawaitImageConvertService.convertFromPickedImage(uri,this.getSelectedPattern());if(requestId!this.createState.requestId){return;}this.replaceCreateState({phase:ready,imageUri:uri,result:output.pattern,message:output.message,requestId});}catch(error){this.failCreate(requestId,errorasError);}}即使用戶快速重新選擇舊任務完成后也無法覆蓋新任務因為它攜帶的requestId已過期。六、按鈕文案與可用性由狀態派生privatecreateButtonText():string{if(this.createState.phasepicking){return打開圖庫中...;}if(this.createState.phaseconverting){return生成中...;}if(this.createState.phaseready){return重新生成;}return生成圖紙;}privatecanConvert():boolean{returnthis.createState.phasepicked;}頁面不再解析消息文本來判斷業務狀態。消息只負責展示phase才負責決策。七、失敗狀態保留可恢復輸入privatefailCreate(requestId:number,error:Error):void{if(requestId!this.createState.requestId){return;}this.replaceCreateState({phase:failed,imageUri:this.createState.imageUri,result:null,message:error.message.length0?error.message:生成失敗請重新選擇圖片,requestId});}是否允許直接重試要根據錯誤類型決定。若 URI 仍可讀可以提供“重試”若訪問失效則引導重新選擇不要一律清空上下文。八、成功后再提交副作用生成結果、最近記錄、當前圖紙和頁面跳轉應按固定順序提交privatecommitGeneratedPattern(pattern:Pattern):void{this.generatedPatternpattern;this.selectedPatternIdpattern.id;this.savePatternRecord(pattern);this.exportStatus;this.activeTabnumbered;}不要在轉換開始前寫最近記錄也不要在結果尚未通過數據不變量校驗時跳到編號圖頁。九、狀態機測試比截圖更能抓住競態建議覆蓋轉換中第二次點擊不會發起服務調用。取消選擇后imageUri為空。舊requestId的成功回調不會覆蓋新狀態。失敗后按鈕可以恢復到重新選擇路徑。成功后才寫入最近記錄。十、常見問題與修復問題表現修復用狀態文案做判斷改文案后邏輯失效使用窄聯合phase轉換中可重復點擊多任務互相覆蓋入口狀態守衛重新選擇仍顯示舊結果結果字段未清空進入 picking 時重置結果舊任務晚到覆蓋新圖無請求身份使用遞增requestId復現競態時不要只快速點擊按鈕還要人為控制兩個異步任務的完成順序。讓舊請求最后返回再確認它因為requestId不一致而被丟棄這種反序用例可以證明狀態機真正處理了晚到結果而不只是依賴設備當前恰好較快。十一、驗證清單每個界面狀態都能映射到唯一phase。未列出的遷移不會執行。取消、失敗、成功使用不同語義。重復點擊不會啟動并發轉換。重新選擇后舊回調被忽略。記錄與跳轉只在成功提交時發生。每項驗證都應同時觀察頁面狀態和副作用調用次數。例如失敗后不僅要看到可重試文案還要確認最近記錄沒有增加、當前圖紙沒有替換成功后則應保證記錄只寫一次避免界面正確但存儲已經重復。十二、總結上傳生成鏈路的難點不在按鈕數量而在異步事件的先后關系。用一個類型化狀態快照承載階段、輸入、結果和請求身份Harmony os 創建頁就能把重復點擊、過期回調和錯誤恢復收口到可驗證的遷移規則里。標簽Harmony os、ArkTS、狀態機、異步任務、圖片生成