LESSON-27 · 零基礎學生教材
固定 Sheet schema、伺服器權威計價、去重與完整狀態
完成三張 Sheet 的固定欄序、後端權威計價、訂單編號與初始狀態、訂單/聯絡去重,以及 timeout 後沿用 submissionId 的安全重試。
先理解:這堂課解決什麼問題?
API 契約已接通;本堂由 GAS 以 Products Sheet 的有效價格重新計算商品總額、運費與訂單總額,再以 LockService 和 submissionId 防止重送,並讓前端呈現 idle/loading/success/error/timeout。
本堂要完成:完成三張 Sheet 的固定欄序、後端權威計價、訂單編號與初始狀態、訂單/聯絡去重,以及 timeout 後沿用 submissionId 的安全重試。
學習邊界:先完成「固定 Sheet schema、伺服器權威計價、去重與完整狀態」,不提前引入尚未教過的框架或建置工具。
先備知識與操作環境
延續上一章的鐵刻運動電商成果。本章只增加 固定 Sheet schema、伺服器權威計價、去重與完整狀態 的責任;先開啟 examples/minimal.html,再依 Starter → Checkpoint → Solution 留下檔案、操作、預期與實際證據。
- 檔案位置:先找到
project/e-commerce/apps-script/Code.gs、apps-script/SETUP.md、js/api.js、js/checkout.js、js/contact.js、evidence/sheet-contract.md,再從本頁的最小實驗開始。 - 瀏覽器:開啟最小實驗與階段頁;每次修改後儲存並重新整理。
- DevTools:依本堂內容使用 Elements、Styles、Computed、Console 或 Network,記下第一個可觀察證據。
- 操作習慣:每次只修改一個檔案或一個責任,先預測、再操作、最後驗收。
先做最小實驗:先看見結果
這不是期末網站,而是一個可以直接看到本堂核心結果的最小實驗。先完成一次操作,再回到檔案和名詞卡對照。
- 入口:開啟
examples/minimal.html。 - 操作:開啟本章最小實驗,按下執行按鈕,再比對畫面、Elements、Console 與 390px;不得把 local mock 當成真實 Sheet 成功。
- 預期:只呈現 L27 在鐵刻運動連續專案新增的責任,且能指出上一章輸入與下一章交付。
- 重設:重新整理
examples/minimal.html;本章最小實驗不送正式資料、不部署。
學生練習流程
依序完成每一張卡;目前只聚焦一個步驟,先操作,再用可觀察證據確認結果。
-
1
最小實驗
先閱讀本頁的問題、檔案地圖、名詞卡與最小例子。 -
2
Step 2
自己回答互動問答 4 題,再展開解析。 -
3
最小實驗 2
開啟最小實驗,實際操作一次並記下證據。 -
4
Starter
把 Starter 複製到自己的工作資料夾,照 3 張微步驟卡逐一完成;每一步都要看到結果再繼續。 -
5
Checkpoint
前往 Checkpoint,只處理 1 個本堂核心缺口;先預測,再用指定證據驗收。 -
6
Solution
前往 Solution,先看逐檔差異與修改原因,再決定要不要把完整內容帶回自己的檔案。 -
7
變體驗收
完成情境變體與獨立挑戰,留下檔案位置、操作、結果與驗收證據,並閱讀下一堂銜接:交付產物:下一堂將同一專案整理到dist/project/e-commerce並只做本機 smoke;正式部署待另行取得人工授權。
檔案地圖:每個位置負責什麼?
先不要急著背檔名;請把檔案位置和責任連起來。你在階段頁會看到同一份檔案的可執行版本。
apps-script/Code.gsGAS 測試後端:展示商品 GET、訂單/聯絡 POST、Sheet schema、權威計價與去重。apps-script/SETUP.mdGAS 測試後端:展示商品 GET、訂單/聯絡 POST、Sheet schema、權威計價與去重。js/api.js原生 JavaScript:負責商品、購物車、表單、local mock 或 GAS 測試契約。js/checkout.js原生 JavaScript:負責商品、購物車、表單、local mock 或 GAS 測試契約。js/contact.js原生 JavaScript:負責商品、購物車、表單、local mock 或 GAS 測試契約。
先學會這些詞
先從名詞、英文與白話意思開始;每次只展開一張名詞卡,再對照正式定義、範例與驗收證據。專業術語、語法、檔案路徑會使用不同樣式。第一次遇到術語時,先回到這裡,不要靠猜。
三張工作表契約
3 個詞Products/Orders/Contacts headersProducts/Orders/Contacts headers 白話意思:用來理解「Products/Orders/Contacts headers」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 Products/Orders/Contacts headers 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 Products/Orders/Contacts headers 或排查相關問題時。
- 最小範例
- Products/Orders/Contacts headers
- 可觀察證據
- 能在專章範例指出 Products/Orders/Contacts headers 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
itemsJson snapshotitemsJson snapshot 白話意思:用來理解「itemsJson snapshot」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 itemsJson snapshot 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 itemsJson snapshot 或排查相關問題時。
- 最小範例
- itemsJson snapshot
- 可觀察證據
- 能在專章範例指出 itemsJson snapshot 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
timestamp/timezonetimestamp/timezone 白話意思:用來理解「timestamp/timezone」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 timestamp/timezone 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 timestamp/timezone 或排查相關問題時。
- 最小範例
- timestamp/timezone
- 可觀察證據
- 能在專章範例指出 timestamp/timezone 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
伺服器權威計價
5 個詞有效售價有效售價 白話意思:用來理解「有效售價」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 有效售價 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 有效售價 或排查相關問題時。
- 最小範例
- 有效售價
- 可觀察證據
- 能在專章範例指出 有效售價 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
subtotalsubtotal 白話意思:用來理解「subtotal」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 subtotal 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 subtotal 或排查相關問題時。
- 最小範例
- subtotal
- 可觀察證據
- 能在專章範例指出 subtotal 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
shippingshipping 白話意思:用來理解「shipping」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 shipping 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 shipping 或排查相關問題時。
- 最小範例
- shipping
- 可觀察證據
- 能在專章範例指出 shipping 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
totaltotal 白話意思:用來理解「total」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 total 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 total 或排查相關問題時。
- 最小範例
- total
- 可觀察證據
- 能在專章範例指出 total 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
orderNo/statusorderNo/status 白話意思:用來理解「orderNo/status」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 orderNo/status 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 orderNo/status 或排查相關問題時。
- 最小範例
- orderNo/status
- 可觀察證據
- 能在專章範例指出 orderNo/status 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
去重與狀態
4 個詞LockServiceLockService 白話意思:用來理解「LockService」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 LockService 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 LockService 或排查相關問題時。
- 最小範例
- LockService
- 可觀察證據
- 能在專章範例指出 LockService 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
submissionId 去重submissionId 去重 白話意思:用來理解「submissionId 去重」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 submissionId 去重 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 submissionId 去重 或排查相關問題時。
- 最小範例
- submissionId 去重
- 可觀察證據
- 能在專章範例指出 submissionId 去重 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
idle/loading/success/error/timeoutidle/loading/success/error/timeout
白話意思:用來理解「idle/loading/success/error/timeout」的實務概念。
展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將
idle/loading/success/error/timeout定義為可在檔案、畫面或 DevTools 中驗證的技術責任。 - 什麼時候用
- 實作
idle/loading/success/error/timeout或排查相關問題時。 - 最小範例
- idle/loading/success/error/timeout
- 可觀察證據
- 能在專章範例指出
idle/loading/success/error/timeout的操作結果。 - 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
同 id retry同 id retry 白話意思:用來理解「同 id retry」的實務概念。 展開查看正式定義、範例與驗收證據
- 正式定義
- 本章將 同 id retry 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
- 什麼時候用
- 實作 同 id retry 或排查相關問題時。
- 最小範例
- 同 id retry
- 可觀察證據
- 能在專章範例指出 同 id retry 的操作結果。
- 容易混淆
- 只記名稱卻沒有實際操作或驗收證據。
最小例子:先看懂,再複製
這段只保留本堂第一個微成果。先預測結果,再逐行讀;完整檔案請開啟下方最小實驗。
Products: productId,name,image,price,salePrice,active
Orders: orderNo,submissionId,timestamp,buyer,gender,phone,address,email,subtotal,shipping,total,status,itemsJson
Contacts: contactNo,submissionId,timestamp,name,gender,phone,email,message,statusLive Lab:逐段讀碼與驗證
每一步只回答五件事:為什麼做、改哪裡、會看到什麼、用什麼證據確認、卡住先查什麼。
-
Step 1
固定 Products、Orders、Contacts 三張工作表 headers 與資料型別
- 目的
- 欄序是 API、計價、去重與人工查帳的共同契約,必須先固定再 append。
- 修改位置
apps-script/SETUP.md、apps-script/Code.gs
固定 Products、Orders、Contacts 三張工作表 headers 與資料型別- 預期結果
- Products 為 6 欄、Orders 為 13 欄、Contacts 為 9 欄,header 名稱、順序與 row builder 完全一致。
- 驗收證據
SETUP.mdheaders、Code.gsrow builders 與測試 Sheet 第一列逐欄對照。- 卡住先查
- 先逐欄比較三張 header 與各 row builder;Orders 必須從 orderNo 到 itemsJson,Contacts 必須從 contactNo 到 status。
-
Step 2
依 Products Sheet 的 active 商品與有效售價重建 items snapshot,計算 subtotal、shipping、total 並產生
orderNo/status- 目的
- 前端金額可被修改,訂單保存前必須由 GAS 重新查價與計算。
- 修改位置
apps-script/Code.gs
依 Products Sheet 的 active 商品與有效售價重建 items snapshot,計算 subtotal、shipping、total 並產生orderNo/status- 預期結果
- 竄改前端三個金額不影響保存結果;未知/停用商品被拒絕;total=subtotal+shipping。
- 驗收證據
- GAS 計算結果、Orders row、竄改 payload 對照與算式。
- 卡住先查
- 先把前端 total 改成 1,再確認 Orders row 仍由 Products 價格算出。
-
Step 3
以 LockService 將 submissionId 去重與 append 包在同一臨界區,timeout 重試沿用同 id,UI 完成
idle/loading/success/error/timeout- 目的
- 網路結果不確定時不能重複訂單,也不能讓按鈕永久停用或清空輸入。
- 修改位置
apps-script/Code.gs、js/api.js、js/checkout.js、js/contact.js
以 LockService 將 submissionId 去重與 append 包在同一臨界區,timeout 重試沿用同 id,UI 完成idle/loading/success/error/timeout- 預期結果
- 相同
order/contactsubmissionId 各只寫一列;五種狀態可重現;所有完成分支解除 disabled,timeout 後可安全重試。 - 驗收證據
- 兩次 request、Sheet row count、response deduplicated、
data-state/aria-busy/disabled與保留輸入。 - 卡住先查
- 先送出同一 submissionId 兩次並數 Sheet 列,再逐一觸發五種 UI 狀態。
-
Step 4
把 submissionId 去重移到 appendRow 之前且保持在同一把 lock 內
- 目的
- Checkpoint 只修正寫入順序,避免 timeout 重試產生重複訂單。
- 修改位置
apps-script/Code.gs
把 submissionId 去重移到 appendRow 之前且保持在同一把 lock 內- 預期結果
- 相同 submissionId 連送兩次,第二次回 deduplicated 且 Orders 只新增一列。
- 驗收證據
- GAS 執行順序、兩次 response 與 Sheet row count。
- 卡住先查
- 只檢查 lock try 區內 findBySubmissionId_ 是否位於 appendRow 之前。
-
Step 5
專章驗收:Products/Orders/Contacts Schema
- 目的
- 將概念轉成可重現的實作證據。
- 修改位置
examples/minimal.html與對應 stage files
操作 Products/Orders/Contacts Schema 並記錄結果。- 預期結果
- 能展示 Products/Orders/Contacts Schema 的畫面、DOM、Console 或 Network 證據。
- 驗收證據
- 檔案位置、操作、預期、實際與重新驗證。
- 卡住先查
- 只重做一個最小操作並查看第一個錯誤。
-
Step 6
專章驗收:伺服器權威計價
- 目的
- 將概念轉成可重現的實作證據。
- 修改位置
examples/minimal.html與對應 stage files
操作 伺服器權威計價 並記錄結果。- 預期結果
- 能展示 伺服器權威計價 的畫面、DOM、Console 或 Network 證據。
- 驗收證據
- 檔案位置、操作、預期、實際與重新驗證。
- 卡住先查
- 只重做一個最小操作並查看第一個錯誤。
讀碼順序:先找入口,再找被引用的檔案,最後用畫面、DOM、Console、Network 或資料結果驗證。
互動問答
先回答目前這一題,再展開解析;完成後按下一題,讓每一次回答都回到檔案或瀏覽器完成驗證。
Q1概念理解 「Products/Orders/Contacts headers」在本堂要解決什麼問題? 本題線索:Products/Orders/Contacts headers;回到名詞卡與 能在專章範例指出 Products/Orders/Contacts headers 的操作結果。 展開解析收合解析
白話解析:
用來理解「Products/Orders/Contacts headers」的實務概念。 正式來說,本章將 Products/Orders/Contacts headers 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
對照位置:
回到名詞卡「Products/Orders/Contacts headers」,再看 能在專章範例指出 Products/Orders/Contacts headers 的操作結果。
預期觀察:
你應該能用自己的話說出:實作 Products/Orders/Contacts headers 或排查相關問題時。
常見錯誤:
不要只回答「它是Products/Orders/Contacts headers」;那是重複名詞,不是說明責任。
立即驗證:
開啟 examples/minimal.html,依序做「開啟本章最小實驗,按下執行按鈕,再比對畫面、Elements、Console 與 390px;不得把 local mock 當成真實 Sheet 成功。」並記下畫面或工具證據。
Q2程式碼閱讀
修改指定規則後,你預測畫面會看到什麼?
本題線索:apps-script/SETUP.md、apps-script/Code.gs;要修改:固定 Products、Orders、Contacts 三張工作表 headers 與資料型別;觀察:SETUP.md headers、Code.gs row builders 與測試 Sheet 第一列逐欄對照。
展開解析收合解析
白話解析:
先看檔案責任,再預測結果。這一步的目的:欄序是 API、計價、去重與人工查帳的共同契約,必須先固定再 append。
對照位置:
對照 Live Lab Step 1,位置是 apps-script/SETUP.md、apps-script/Code.gs;範例內容:Products: productId,name,image,price,salePrice,active
Orders: orderNo,submissionId,timestamp,buyer,gender,phone,address,email,subtotal,shipping,total,status,itemsJson
Contacts: contactNo,submissionId,timestamp,name,gender,phone,email,message,status
預期觀察:
Products 為 6 欄、Orders 為 13 欄、Contacts 為 9 欄,header 名稱、順序與 row builder 完全一致。
常見錯誤:
如果只改了檔案但沒有結果,先不要重寫全部;先逐欄比較三張 header 與各 row builder;Orders 必須從 orderNo 到 itemsJson,Contacts 必須從 contactNo 到 status。
立即驗證:
實際操作後檢查 SETUP.md headers、Code.gs row builders 與測試 Sheet 第一列逐欄對照。。
Q3概念比較
「Products/Orders/Contacts headers」和「itemsJson snapshot」在本堂的責任有什麼不同?
本題線索:比較 Products/Orders/Contacts headers 與 itemsJson snapshot;操作位置:apps-script/Code.gs
展開解析收合解析
白話解析:
Products/Orders/Contacts headers:用來理解「Products/Orders/Contacts headers」的實務概念。;itemsJson snapshot:用來理解「itemsJson snapshot」的實務概念。
對照位置:
對照兩張名詞卡的正式定義:本章將 Products/Orders/Contacts headers 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。/本章將 itemsJson snapshot 定義為可在檔案、畫面或 DevTools 中驗證的技術責任。
預期觀察:
你應該能指出兩者分別出現在哪個檔案或工具,以及哪一個結果會改變。
常見錯誤:
不要把「改變畫面」當成所有技術的責任;先說清楚誰負責結構、呈現、行為或驗收。
立即驗證:
在 apps-script/Code.gs 做「依 Products Sheet 的 active 商品與有效售價重建 items snapshot,計算 subtotal、shipping、total 並產生 orderNo/status」,再用 GAS 計算結果、Orders row、竄改 payload 對照與算式。 比較前後差異。
Q4除錯驗收
如果完成操作後結果不對,你會先從哪一個證據開始查?
本題線索:examples/minimal.html 與對應 stage files;第一個檢查位置:檔案位置、操作、預期、實際與重新驗證。
展開解析收合解析
白話解析:
先描述症狀,再提出一個最小假設。本堂常見錯誤是:只口頭說明完成。
對照位置:
對照最後一段 Live Lab:examples/minimal.html 與對應 stage files;檢查方式:只重做一個最小操作並查看第一個錯誤。
預期觀察:
你要能指出一個具體位置,而不是一次修改很多檔案。
常見錯誤:
不要先清快取、重裝工具或複製 Solution;先查看第一個可觀察錯誤。
立即驗證:
重新操作並留下「症狀、證據、假設、單一修正、結果」五項紀錄。
常見誤解
- 看到畫面沒有變化,不代表程式沒執行;先確認檔案、路徑、元素與狀態證據。
- 能複製 Solution 不代表理解;請先說出修改哪個檔案、預期哪個結果、用什麼證據確認。
- 本堂只使用既定的 HTML、CSS、JavaScript、Vue CDN、Apps Script mock 與 Cloudflare Pages 驗收範圍。
本堂任務與驗收
完成三張 Sheet 的固定欄序、後端權威計價、訂單編號與初始狀態、訂單/聯絡去重,以及 timeout 後沿用 submissionId 的安全重試。
- 能用
Products/Orders/Contactsexact schema 留下可重現證據。 - 能用 GAS 重算
subtotal/shipping/total留下可重現證據。 - 能用
orderNo/status留下可重現證據。 - 能用 LockService 去重 留下可重現證據。
- 能用 五狀態與同 id 重試 留下可重現證據。
- Starter、Checkpoint、Solution 的入口都能開啟並知道三者差異。
- 遇到問題時能先寫出症狀,再檢查一個最小假設。
官方延伸閱讀
正式延伸|自主學習
把「Sheet Schema、權威計價、去重與完整狀態」拆成 4 個可獨立完成、可重設、可驗證的自主學習 module。
開啟自主學習入口|4 個獨立 topic
- 先選一個 topic,讀懂成果、檔案與定位。
- 在自己的 Starter 檔案修改一小段。
- 重新載入,用畫面、Elements、Computed、Console 或文件留下證據。
建議第一個 topic:Products/Orders/Contacts Schema。
Learning map
- Products/Orders/Contacts Schema|code-edit|files/starter/apps-script/SETUP.md|count=3、uniqueCount=3、valid=true
- 伺服器權威計價|code-edit|files/starter/apps-script/Code.gs|count=3、uniqueCount=3、valid=true
- LockService 與 submissionId 去重|code-edit|files/starter/apps-script/Code.gs|operation=dedupe、inputCount=3、outputCount=2、remoteRequests=0
- 五狀態與安全 Retry|code-edit|files/starter/apps-script/Code.gs|狀態順序、aria-busy、disabled 與同 id retry 紀錄