
- Specimen
- QR Code 行動點餐共用核心
- Category
- Frontend Systems / Ordering Flow / Shared Architecture
- Location
- Taiwan
- Field Span
- 10 months to first launch, then 2.5 years of maintenance
- Collector
- Daco
- Role
- Sole Frontend Engineer / System Design / Integration
30 秒看懂這個專案
這是一套服務不同品牌與場域的雲端點餐系統。我參與操作流程、UI/UX 與前端開發,整合內用、外帶、院內配送、套餐選擇、多品牌菜單及 QR Code 參數等複雜情境。系統已實際應用於桃園機場、臺北榮總、南門市場、馬修嚴選與 POPEYES 等場域。
我如何建立系統
在智維任職期間,我負責建立並持續維護一套行動點餐前端系統。使用者從 QR Code 進入後,網址會帶入品牌、店家與場域分區等參數,再依設定取得對應的店家、菜單、付款方式與服務流程。
這套系統後來正式導入桃園機場、榮總美食街、南門市場、馬修嚴選與 POPEYES,另有四至五個場域處於測試階段。它不是只支援一家店,而是要支援多品牌、多場域、多付款、多語系與不同取餐邏輯。
從第一版開發到首個場域正式上線,歷時約十個月。之後兩年半,我持續負責新場域導入、功能擴充與共用核心維護。這個 case 對我來說,不只是「做出點餐頁面」,而是把真實商業世界裡大量例外與限制,整理成一套可以重複部署的前端系統。
我是專案中唯一的前端工程師,負責前端架構、API 串接、套餐與加價購規則、購物車與即時計價、多語系、多種電子支付、訂單送出與查詢,以及多專案共用核心的維護。UI 視覺雖由設計端提供,但多半只有靜態圖,互動規格並不完整,因此我實際上需要把靜態畫面轉化成能被人正確操作的系統。
這個專案最核心的挑戰,不是把菜單顯示出來,而是把複雜餐飲規則轉成使用者不會出錯的流程。
一份套餐可能同時包含必選項目、數量上下限、加價購、多層子選項、互斥條件、特定品牌規則,以及不同數量與價格組合。API 會一次回傳完整的 JSON 規則資料,但不會在使用者每一步操作時重新幫你驗證,所以所有流程判斷都必須由前端完成。
我會逐層解析 API 回傳的規則,並把驗證放進每一個操作階段,而不是等到加入購物車或結帳時才告訴使用者訂單不合法。像是飲料選滿兩杯後就不能再選第三杯;想修改時,也可以只清除飲料區段重新選擇,而不需要把整份套餐整個刪掉重來。
我的設計原則是:使用者不必理解所有規則,也能自然完成一筆合法訂單。這其實是很典型的系統設計問題。你不是消滅複雜度,而是把複雜度藏到正確的位置,讓人不需要直接面對它。
為了避免未完成的套餐直接污染正式訂單,我把系統中的狀態拆成兩層:第一層是尚未加入購物車、仍在編輯中的商品物件;第二層才是正式的購物車物件。只有當商品完成所有必要選項後,才會被加入購物車,加入完成後編輯中的物件就會清空。
相同商品加入購物車時,系統也不能只比對商品 ID。我會逐層比較商品與所有客製選項:商品與客製完全相同才合併數量;商品相同但客製不同,就保留為兩筆獨立商品。這種拆法讓使用者可以在各步驟間來回修改,而不會破壞已加入購物車的內容。
金額計算也是同樣的思路。總金額從點餐開始就一路顯示在畫面下方,因此每一個操作都必須立即反映正確價格。可能影響金額的,不只商品數量,還包含套餐內容、加價購、固定折抵、百分比折扣、折價券、返回前一步重新選擇,以及特定品牌優惠。
我沒有直接在既有金額上做加減,而是每次狀態改變後,都依完整商品與購物車內容重新計算。這樣可以避免使用者多次修改、返回與取消後,因為增量運算而累積誤差。像 POPEYES 的「自備飲料杯折 5 元」這類無法完全由共用資料描述的需求,則會保留在品牌客製邏輯中。
另一個關鍵工作,是把前端互動狀態轉成零容錯的 API 訂單格式。訂單 API 的 JSON 結構非常複雜,套餐與客製內容可能形成二十層以上的巢狀資料,而且 API 對 request 幾乎沒有容錯。即使某個商品沒有客製內容,也不能省略欄位,必須保留 key 並明確傳入 null。
因此,我另外建立了訂單標準化與檢查函式,專門負責判斷商品是單點或套餐、逐層整理客製資料、補齊必要欄位、檢查型別、轉換空值格式、移除前端用但 API 不接受的欄位,最後再產生標準訂單 JSON。整個資料流程可以整理成:使用者操作 → 編輯中商品 → 購物車訂單 → 訂單標準化 → API request。
前端負責維持順暢互動與即時金額;訂單送出後,後端會重新驗證商品規則與價格,並回傳訂單編號。前端再以訂單編號重新查詢一次正式訂單,取得後端確認的價格後,才進入金流流程。也就是說:前端負責即時體驗,後端保有最終訂單與金額的決定權。
這套系統雖然共用相同核心,但會依不同場域需求載入不同功能。像桃園機場會依 QR Code 中的分區參數顯示不同店家與菜單;榮總除了內用與外帶,也支援院內配送與指定送達時間;其他測試中的場域則包含外送員媒合機制。桃園機場與南門市場也支援多語系,語系會保存在 localStorage,下次進入時沿用上次選擇。
系統支援 Apple Pay、Google Pay、LINE Pay、街口支付與臨櫃付款。這些流程並不是打一支 API 就結束,而是需要前端、後端與金流服務多次交換資料。也因此,前端不只是「接畫面」,而是整條交易流程中的狀態協調者。
在場域導入層面,我把共用邏輯集中維護在 Git submodule 中,包含 API 串接、商品與套餐處理、購物車、即時計價、訂單格式化、訂單查詢、頁面管理、多語系、金流流程,以及共用事件與介面元件。各場域有自己的專案,但核心邏輯不需要重寫。
當共用核心更新時,各場域只需要更新 submodule,就能同步取得最新版功能,而不需要逐案複製與修改。若新場域後台資料與基礎參數都已設定完成,只需要帶入品牌、店家與場域參數,系統在十分鐘內就能呈現基本版本;只有遇到特殊流程或品牌需求時,才需要再進行前端調整。
這是這個專案最重要的成果之一:從逐案開發,逐步建立成一套以參數驅動、可快速複製的前端系統。它讓新需求不再從零開始,也讓維護成本不會隨場域數量線性成長。
有趣的是,雖然這個專案沒有使用 React,而是基於 JavaScript 與 jQuery 開發,我仍然用元件化的方式拆分功能,透過 .load() 動態載入頁面模組。每當使用者進入新的操作階段,就建立一個新的內容層,再用 layer 管理目前的 z-index,讓人可以前進、返回,又不必整頁重載。
由於瀏覽器原生上一頁可能造成資料遺失,我也加入攔截與警告機制,讓使用者可以選擇停留在目前頁面。這其實是一種在舊技術限制下,盡量建立單頁操作體驗的做法。
在開發與測試上,我也沒有每次都從掃描 QR Code 開始,而是會直接在 Chrome DevTools 中注入特定購物車物件,或加入暫時的自動執行流程,讓系統直接進入套餐特定階段、購物車、結帳、金流或訂單查詢。這讓我能快速重現問題,專注驗證金額、JSON 結構與付款流程。
如果今天重新開發,我會改用 React,並加入 TypeScript、明確的狀態管理與自動化測試,讓複雜套餐、金額與訂單資料更容易維護。但我仍會保留當時幾個重要原則:讓 API 規則驅動畫面、把編輯中狀態與正式購物車分開、每次異動後重新計算價格、在每一步阻止錯誤,以及把 API request 轉換集中在單一標準化出口。
