訂單狀態機思維實作開發筆記
概述
本文記錄了我們在電商後台管理系統中採用「狀態機思維」改進訂單狀態管理的設計決策、實作方法和 Trade-off 分析。
背景與問題
原始問題
- 管理界面不夠嚴謹:直接點擊下拉選單選擇任意狀態,缺乏業務規則驗證
- 缺乏操作指引:管理員不清楚當前狀態的下個合理步驟
- 狀態轉換混亂:沒有明確的狀態轉換規則和限制
- 錯誤處理不完善:狀態轉換失敗時缺乏友善的錯誤提示
考慮的解決方案
方案一:導入完整狀態機框架 (如 XState)
優點:
- 完整的狀態機功能
- 視覺化狀態圖
- 完善的事件處理
- 強大的調試工具
缺點:
- 學習曲線陡峭
- 增加 bundle size
- 對簡單業務邏輯來說過度複雜
- 維護成本高
方案二:狀態機思維 + 輕量級實作 ✅ (最終選擇)
優點:
- 概念清晰,易於理解
- 最小化複雜度
- 與現有架構無縫整合
- 維護成本低
- 按需擴展
缺點:
- 需要手動管理狀態轉換邏輯
- 缺乏視覺化工具
Trade-off 分析
為什麼不用完整的狀態機框架?
業務複雜度評估
- 訂單狀態相對簡單(9個狀態,15個轉換)
- 狀態轉換規則已經在後端明確定義
- 前端主要負責 UI 呈現和驗證
技術債務考量
- 現有系統運作良好
- 團隊熟悉 Vue 3 + Composables 架構
- 引入新框架需要額外學習成本
ROI 分析
- 狀態機框架的核心價值在於複雜狀態管理
- 我們的場景更偏向於 UI 增強而非業務邏輯控制
- 輕量級方案能達到 80% 的效果,只需 20% 的複雜度
最終決策邏輯
狀態機概念很有用,但完整的狀態機框架是過殺
採用「狀態機思維」來指導設計,但用輕量級的實作方式,在複雜度和功能性之間找到最佳平衡點。
實作架構
1. 核心狀態管理 (types/order.ts)
typescript
// 完整的狀態定義
export enum OrderStatus {
PENDING = 'pending',
CONFIRMED = 'confirmed',
PAID = 'paid',
PROCESSING = 'processing',
SHIPPED = 'shipped',
DELIVERED = 'delivered',
COMPLETED = 'completed',
CANCELLED = 'cancelled',
REFUNDED = 'refunded',
}
// 狀態轉換規則 - 基於資料庫觸發器邏輯
export const ORDER_TRANSITIONS = {
[OrderStatus.PENDING]: [OrderStatus.CONFIRMED, OrderStatus.CANCELLED],
[OrderStatus.CONFIRMED]: [OrderStatus.PAID, OrderStatus.CANCELLED],
// ... 其他轉換規則
} as const
// 狀態轉換所需欄位
export const TRANSITION_REQUIRED_FIELDS = {
[`${OrderStatus.PROCESSING}_${OrderStatus.SHIPPED}`]: ['trackingNumber'],
} as const2. 狀態轉換邏輯 (composables/useOrder.ts)
typescript
// 狀態機思維的核心函數
export function getValidNextStates(currentStatus: OrderStatus): readonly OrderStatus[]
export function canTransitionTo(from: OrderStatus, to: OrderStatus): boolean
export function validateTransition(order: Order, newStatus: OrderStatus)
export function validateBulkTransition(orders: Order[], targetStatus: OrderStatus)3. UI 組件增強
單一訂單操作 (DataTableRowActions.vue)
- 智能狀態選擇:只顯示有效的下個狀態
- 視覺化提示:當前狀態、可用轉換、錯誤訊息
- 確認對話框:詳細的轉換預覽和驗證
批量操作 (BatchStatusTransitionDialog.vue)
- 批量驗證:分析哪些訂單可以執行轉換
- 統計報告:顯示可執行/無法執行的數量和原因
- 智能過濾:只對有效的訂單執行操作
4. 錯誤處理與用戶體驗
typescript
// 友善的錯誤訊息
const validation = validateTransition(order, newStatus)
if (!validation.valid) {
showErrorDialog(validation.error, validation.missingFields)
}關鍵設計決策
1. 資料來源的真理性 (Single Source of Truth)
決策:資料庫觸發器是狀態轉換的最終仲裁者 原因:
- 確保資料一致性
- 前端只負責 UI 驗證和使用者體驗
- 避免前後端邏輯不一致的問題
2. 狀態轉換規則的定義位置
決策:在前端 TypeScript 中定義常數 原因:
- 提供 TypeScript 類型安全
- 便於 UI 組件使用
- 與後端資料庫觸發器保持同步
3. 對話框確認機制
決策:每次狀態轉換都顯示確認對話框 原因:
- 防止誤操作
- 提供業務邏輯說明
- 收集必要的額外資訊(如物流單號)
4. 批量操作的智能處理
決策:預先驗證並只對有效訂單執行操作 原因:
- 避免部分失敗的困擾
- 提供清晰的操作預覽
- 減少 API 調用次數
實作細節
狀態轉換驗證流程
- UI 層驗證:檢查轉換是否符合業務規則
- 必填欄位檢查:確保所需資料完整
- 確認對話框:顯示轉換詳情和影響
- API 調用:執行實際的狀態變更
- 錯誤處理:友善的錯誤訊息和恢復建議
批量操作處理邏輯
typescript
// 批量驗證示例
const bulkValidation = validateBulkTransition(selectedOrders, targetStatus)
// 結果:{ validOrders: [], invalidOrders: [], canProceed: boolean }
// 只對有效訂單執行操作
if (bulkValidation.canProceed) {
await batchUpdateStatus(bulkValidation.validOrders, targetStatus)
}性能考量
1. 計算快取
- 狀態轉換規則使用
computed快取 - 避免重複的驗證計算
2. 組件優化
- 使用
readonly類型避免不必要的響應式監聽 - 按需載入對話框組件
3. API 調用優化
- 批量操作減少網路請求
- 樂觀更新提升使用者體驗
維護指南
新增狀態
- 更新
OrderStatusenum - 修改
ORDER_TRANSITIONS規則 - 更新
field-config.ts中的顯示設定 - 測試所有相關的 UI 組件
新增轉換規則
- 更新
ORDER_TRANSITIONS常數 - 如需額外欄位,更新
TRANSITION_REQUIRED_FIELDS - 更新確認對話框的業務邏輯說明
- 確保與後端觸發器邏輯一致
除錯指南
- 檢查瀏覽器控制台的驗證錯誤
- 確認資料庫觸發器的執行結果
- 使用 Vue DevTools 查看狀態變化
- 驗證 API 調用的參數和回應
測試策略
單元測試
- 狀態轉換函數的邏輯測試
- 邊界條件和錯誤情況測試
整合測試
- 完整的狀態轉換流程測試
- 批量操作的各種情境測試
E2E 測試
- 管理員操作流程測試
- 錯誤處理和恢復流程測試
未來擴展可能性
短期改進
- 狀態變更歷史:實作
order_status_logs表 - 自動化轉換:基於時間或事件的自動狀態轉換
- 通知機制:狀態變更時的即時通知
中期改進
- 工作流設計器:視覺化的狀態流程編輯
- 條件轉換:基於訂單屬性的動態轉換規則
- 角色權限:不同角色的狀態轉換權限控制
長期考量
- 完整狀態機:如果業務複雜度增加,考慮導入 XState
- 機器學習:預測最佳的狀態轉換路徑
- 多租戶支援:不同商戶的自定義狀態流程
結論
透過「狀態機思維」的輕量級實作,我們成功改善了訂單管理的使用者體驗,同時保持了系統的簡潔性和可維護性。這個方案證明了在適當的場景下,概念導向的設計比工具導向的設計更有價值。
關鍵成功因素:
- 準確評估業務複雜度
- 選擇合適的技術方案
- 重視使用者體驗設計
- 保持與現有架構的一致性
這個實作為未來的狀態管理提供了一個可擴展的基礎,可以根據業務需求的變化靈活調整和增強。