私人訂購頁產生器
為任何品牌複製一套經實戰驗證的預購系統。 架構固定、內容全配置:Cloudflare Worker 承載「動態密碼閘門+訂購頁+名片與圖示 KV 儲存」, Google Apps Script 負責「寫入訂單總表+寄 HTML 通知信」。每月成本 $0。
實戰來源:曾實際用於精品品牌展場活動與工作室的「私人邀請・限量預購」場景。
這套系統能做什麼
決定要不要用這個技能時,先看功能對不對得上:
- 動態密碼閘門——每 30 秒更新的 6 位數,現場人員報給客人才進得去。 未通過閘門連 HTML 原始碼都拿不到,網址單獨外流沒有用
- 商品卡:一項多張圖左右滑動輪播、分色選購(每色可獨立定價)、固定顏色、數量加減
- 課程/體驗:日曆選場次,可設每場名額;
makeSessions(n)能自動排未來 n 個月的週末場次 - 身份折扣:專業身份(設計師、企業採購等)上傳名片,全站價格自動切換成折扣價
- 寄送:宅配/超商兩種運費,材積超限的商品自動鎖住超商選項;不需寄送的品項不問地址
- 多階段付款:百分比自動試算,最後一段吃四捨五入尾差
- 強制閱讀條款:捲到底才能勾同意
- 收單雙保險:HTML 排版通知信(公司收件+客人副本)+ Google 試算表總表
工作流程總覽
- 素材訪談 → 讀
references/intake.md,逐項收集(缺必要項不要硬做,先問) - 產出網站檔案包 → 本文件〈產出步驟〉
- 部署與驗收 → 讀
references/deployment.md(含歷史教訓,部署前必讀) - 交接 → 產出品牌專屬交接說明
第 2 步可完全離線完成;若使用者只要檔案包(自行部署),做完第 2 步即可交付。
產出步驟
工作目錄建議:<品牌代號>-preorder/。
1. 客製訂購頁:assets/template.html → site.html
模板所有 {{…}} 都是待替換欄位,全部換掉才算完成。主要區塊:
| 位置 | 改什麼 |
|---|---|
:root CSS 變數 |
品牌配色(--bg 背景、--ink 文字、--accent 輔色、--sale 強調價色等) |
<link rel="icon"> |
指向 /fav/light、/fav/dark、/fav/touch,圖檔另外上傳 KV(見部署文件) |
| hero 區 | LOGO 圖片網址、英文標語、主標題、副標兩行、三個賣點 |
const SHIPPING |
宅配/超商的名稱、運費、說明 |
const PAY_STAGES |
付款階段:{name, pct, note, short, highlight, remainder},pct 總和 100,最後一段設 remainder:true 吃尾差 |
const ROLES |
身份角色:{name, pro},pro=true 須填公司+名片 |
HINT_PRO / HINT_BASIC / HINT_NONE |
身份提示文案 |
const PRO_RATE |
專業身份折數(0.9=9 折;不打折就設 1,並把 HINT 文案改掉) |
makeSessions() |
課程場次自動排程規則(時段組合、每月開幾天) |
const PRODUCTS |
商品陣列,模板內有三種型態的示範,照著填 |
| terms-box | 法律條款全文(分節 <p>,粗體小標) |
匯款資料(成功頁 dl.bank)+ hidden("匯款資訊") |
戶名、銀行代碼、帳號 |
| LINE 按鈕與線上刷卡連結 | 官方帳號 ID+加好友連結、金流收款連結 |
| 訂單編號前綴 | 預設兩碼英文,例如 RY-260802-1234 |
| footer | 公司名、統編、信箱、LINE |
有專業折扣時,條款要加一條:名片查核不符者改以一般價計算並通知補差額, 買方不同意可於三日內取消全額退款。頁面承諾了折扣,條款就要接得住。
改完必做(自動驗收):
node scripts/check_site.js site.html
會檢查:JS 語法、{{…}} 佔位是否清空、舊品牌字樣殘留、角色/階段/商品設定完整性、
付款百分比總和=100、各種總額下的金額加總一致性、條款鎖定與送單驗證機制是否完好。
未通過就修到通過再繼續——這些都是實際踩過的坑。
2. 建置 Worker:assets/build_worker.py
複製到工作目錄,改頂部 CONFIG(SITE_HTML_PATH、GATE_ENABLED、品牌名、
閘門文案、LOGO 網址、閘門配色、SESSION_HOURS),執行後產出 worker.js。
SESSION_HOURS 是訪談時問到的放行時效,不要沿用範例預設值。
GATE_ENABLED: False 時網站直接公開、名片上傳不驗 session,其餘功能不變。
產出後跑 node --input-type=module --check < worker.js。
Worker 內建 /fav/<key> 路由,從 KV 讀網站圖示。圖示不要 base64 內嵌進 HTML——
會讓 worker.js 肥好幾十 KB,改版重傳很慢。
3. 收單程式:assets/apps-script-template.gs
改頂部 CONFIG:試算表 ID、收件信箱、品牌名、寄件顯示名、HEADERS、預設狀態。
HEADERS 的付款欄位命名必須是「階段名+pct+%」(與網頁 hidden 欄位一致),
例如 PAY_STAGES 有「訂金50%」就要有欄位 訂金50%。
HEADERS 與網頁送出的欄位名要逐一對齊。交付前用這個檢查:
把 site.html 裡所有 name="…" 與 hidden("…") 收集起來,比對 HEADERS,
兩邊都不該有對方沒有的欄位(時間、處理狀態 除外,那兩欄由程式自己填)。
sendOrderMail_() 產生 HTML 排版的通知信,分區塊呈現,不是一長串純文字。
要調整分區就改 MAIL_SECTIONS。
formatSheet() 設定試算表版面(欄寬、自動換行、狀態下拉、隔行底色),
建表時自動執行,也可手動跑一次套用到既有的表。
4. 交付前最終驗收
node scripts/check_site.js site.html # 頁面 40 項檢查
node --input-type=module --check < worker.js # Worker 語法
python3 -c "import ast;ast.parse(open('build_worker.py',encoding='utf8').read())"
全部通過才交付。剩下的 ⚠️ 警告(條款待補、圖片未接圖床)要明確告知使用者哪些還沒補齊。
5. 金鑰與 QR:scripts/gen_keys.py
python3 gen_keys.py <品牌> <網址> <輸出目錄> <QR色碼>(無閘門版跳過驗證器 QR,仍要入口 QR)。
網址之後若有變更,記得重產入口 QR。
6. 品牌交接說明
寫 專案交接說明.md:網址與帳號表、架構圖、現場怎麼用、改東西的三步驟、
常用設定對照表、上線前待補項目、緊急操作(換金鑰=全場踢人、刪 Worker=下架)、歷史教訓。
若使用者打算長期接案,再加一節「未來要幫新客戶開一個站」的步驟。
設計原則(為什麼這樣做)
- 內容伺服器端保護:訂購頁 HTML 內嵌在 Worker,未通過閘門連原始碼都拿不到—— 這是「防外流」的實質保證,不只是擋 UI。
- 確認成功才顯示成功:送單讀到後端
ok才給成功頁。第三方出事時客人看到的是明確錯誤 而非假成功,訂單不會無聲消失。 - 只依賴 Cloudflare+Google:寄信一律 Apps Script MailApp。不要用 FormSubmit 等免費表單 轉信服務(曾發生過三次事故,詳見 deployment.md 歷史教訓)。
- 雙保險收單:信箱(單筆通知+名片連結)+試算表(總覽對帳),一邊漏了另一邊還在。
- 金額只算一次:總額=商品+運費,付款階段從總額拆。置底總計列、成功頁、訂單信、 試算表必須是同一個數字——分開算過一次,結果少收運費。
- 狀態要誠實反映:沒選商品時,置底總計列不寫「已選 0 項,合計 NT$ 0」——那是一排沒有意義的零。 改成「尚未選擇商品」,金額淡化、送出鍵降對比,選了東西才亮起來。使用者一眼知道現在能不能送。
- 主要按鈕用描邊而非實心:深色底上一整塊白會像促銷 banner。細框透明底、hover 才填滿, 同樣醒目但不吵,和限量、受邀的調性比較合。
- 失敗要留退路,但重試必須是安全的:送單失敗先自動重試一次;仍失敗就給訂單編號、
請對方別重送。但前端重試一定要搭配後端去重——寄信慢會拖住回應,
前端誤判成失敗而重試,同一筆訂單就會寫進試算表兩次(實際發生過)。
doPost先上鎖,再用訂單編號查是否已寫過,寫過就回ok-dup不重複寫也不重複寄信。 - 承諾與實作要對齊:頁面上寫的(折扣、時限、運費)就是系統實際會做的事。 不確定就先問使用者,不要先寫個好聽的文案再說。
- 會出錯的欄位要在送出前擋:地址少了郵遞區號或門牌號碼就是寄不到, 超商只填「711台北濟新」工作人員也分不出是哪一家。這類錯誤事後補問很花時間, 而且客人通常已經匯完款。能用選的就不要讓對方自由填寫,必須填寫的就檢查完整性, 錯誤訊息要明講「缺什麼」並附一個正確範例,不要只說「格式錯誤」。
- 送出要快,慢的部分先做掉:Apps Script 容器閒置後會被回收,下次請求得先冷啟動。
實測同一支什麼都不做的
doGet,冷的 24~49 秒、熱的 1.7 秒——差距不在你的程式碼。 作法是客人一進站就在背景打一次無副作用的 GET(mode:"no-cors")把容器叫醒, 填表時再視互動補打(節流 60 秒)。等他按送出,容器已經熱了。 同理,autoResizeRows()這種純外觀的操作不要放在客人等待的路徑上。 - 送不出去要看得出為什麼:表單設
novalidate,改由validateOrder()一次收集 所有問題,再用置底的錯誤總覽一次列完,並捲到第一個有問題的欄位、加深該欄框線。 瀏覽器原生的驗證泡泡一次只講一個欄位、講得又籠統(「請填寫這個欄位」), 客人按了沒反應會直接放棄。錯誤總覽平常完全隱藏,按下送出才出現,版面才不會一開始就髒。
交付物清單
site.html(原始碼)、worker.js(部署產物)、apps-script.gs(已填品牌 CONFIG)、
build_worker.py(已填 CONFIG,供日後改版重建)、TOTP_SECRET.txt+兩張 QR、
專案交接說明.md。全部打包成 zip 用 present_files 交給使用者保存。
關於作者
羅巧雲 Clou Luo|入雲視覺設計工作室 負責人/創意總監
台北的平面設計工作室,做品牌識別與視覺設計。客戶專案橫跨品牌、包裝、網站與社群,同時是設計教育工作者與獎項評審。
這套系統是接案途中被客戶的難題逼出來的產物。現成方案不是沒有,但預算和時程對不上,只好自己來,最後成了一套能重複使用的工具。
- 網站:clouluo.com
- Instagram:@clouluo / 工作室 @clou.dy
- 合作洽詢:hi@clouluo.com
使用這套技能產出的網站與程式碼,內容與資料屬於使用者本人;歡迎自由修改與商業使用。