20
26
2026
設計概念串接真實商品資料——設計師工作指南
從 Wireframe IA 到 Google Sheets 資料建構,到 HTML 建置,到評審前資料更新—— 真實商品資料貫穿整個設計概念生命週期。 掌握這份工作地圖,不管面對多複雜的設計情境(包含 tab 切換、時段資料),你都知道從哪裡開始。
閱讀前提:這篇與前三篇的關係
AI 設計協作指南 Part 1–3 聚焦在 Figma 設計階段——如何寫 Brief 讓 Claude Code 在 Figma 裡建置設計,以及如何做 QA。 Part 4 覆蓋的是另一個工作情境:HTML 設計概念(design concept)。 這不是 Figma 建置的替代方案,而是在正式建 Figma 前,用 HTML prototype 做快速視覺探索的流程。 在 SHP 電商設計系統裡,這個 HTML 環境有一套串接真實商品資料的機制——這篇就是這套機制的完整說明。
這篇文章在整個設計流程的哪個位置
FIG.00 · 產品設計流程全覽
本篇聚焦在「設計概念 HTML」這個階段——評選時讓所有人看到真實商品在不同版型下的樣子,才能做有根據的設計決策
什麼是「設計概念」?
在 SHP 設計系統的工作流程裡,設計概念(design concept)是指以 HTML + CSS 建立的可互動 prototype, 用於探索不同的頁面方向、驗證視覺構想、以及在正式 Figma 建置前讓相關人員評選。 它不是最終交付物,而是一個「快速低成本的思考工具」。
SHP 設計系統已預先建立了完整的元件庫(商品卡、導覽列、底部 Tab Bar 等)和資料串接機制——
設計師不需要從零設計,只需要組合元件、定義版型,然後讓商品資料自動填入。
product-loader.js 就是這個資料串接機制的核心。
它負責讀取每日更新的 data/products.json,
並自動把商品資料渲染成設計系統規格的商品卡。
設計師在建立 prototype 時,最常見的做法是手動填入假商品名稱、隨便寫一個價格、 用佔位圖取代實際圖片。這個習慣在靜態設計稿沒有問題, 但在可互動的設計概念裡,它會造成兩個根本性問題:
視覺評估失真
假資料遮蔽真實排版問題
商品名稱長度不一,有的 8 個字,有的 30 個字。假資料通常是精心挑選過的「剛好合適」長度。真實資料才能暴露排版的邊界情況。
維護成本爆炸
每次更新要改兩個地方
商品資料每天都在變——價格調整、優惠券更新、新商品上架。假資料讓設計概念永遠滯後於真實狀態,每次 review 前都要手動更新一遍。
解決方案是讓設計概念直接讀取 data/products.json——
這份資料每天由 Google Sheets + Apps Script 自動更新。
設計師只需要學會三條規則,就能讓自己的概念永遠顯示最新商品資料,
完全不需要手動維護。
設計概念的真實資料串接,不是做完 HTML 再回頭補的工作,而是貫穿整個設計概念生命週期的四個階段。 清楚哪些事情只需要做一次、哪些需要每天重複,是這份工作的核心分工。
四個工作階段拆解
Wireframe IA 與資料規劃 設計決策
在 Wireframe 階段決定頁面的資訊架構——包含哪些模組、每個模組的商業邏輯、資料故事要說什麼。
同步在 Wireframe 標注上為每個商品模組決定 section 標籤代碼(例如「降價」、「相機」、「秒殺10點」)。
這些代碼後續會同時出現在 Google Sheets Staging 欄值和 HTML 的 data-filter 裡——先想清楚,後面執行才不會亂。
輸出:模組清單 + section 標籤代碼對照表。完整流程見 02 — 規劃階段。
Google Sheets 資料建構 一次性設定
在 Google Sheets Staging tab 新增 section 欄位(欄位標題必須是英文 section,全小寫),
為每個商品填入對應的 section 標籤值。完成後執行 📤 匯出 JSON,更新本機 data/products.json。
關鍵原則:Apps Script 強制重新匯入不會覆蓋 section 欄——這個設定只需要做一次,之後每日更新不會被清掉。
複雜情境(Tab 切換、時段資料)的資料建構方式見 03 — Google Sheets 情境指南。
HTML 建置 一次性設定
引入 product-loader.js,為每個模組容器加上 data-api、data-filter="section:xxx"、
視需要加 data-max 和 data-label。
在瀏覽器確認每個模組都正確載入預期數量的商品,排版在真實資料下沒有破版。
HTML 設定完成後即固定,不需要每天重新設定。三條核心規則見 05,data-filter 語法速查見 06。
評審前資料更新 每日重複
每次想讓設計概念顯示最新商品資料(價格、優惠券、圖片):
Apps Script 🔄 強制重新匯入全部 → 📤 匯出 JSON → 貼入本機 products.json → 瀏覽器 Cmd+Shift+R。
整個流程約 5–7 分鐘。
⚠️ 一定要用 🔄 強制重新匯入,而不是 📥 匯入——後者只處理 name 欄空白的商品,無法更新已有名稱的商品價格。
評選完成後的下一步
設計概念通過三方評選、選定方向後,進入 Figma 正式建置階段。 建置流程請參照 AI 設計協作指南 Part 1(Brief 撰寫)、Part 2(Figma 建置)、Part 3(品質確認)。 設計概念的工作到這裡結束——不同的工具、不同的精確度標準、相輔相成的兩個階段。
大多數設計師的習慣是:先把 HTML 建好,再回頭想要放什麼資料。這個順序是倒過來的。 資料規劃應該在 Wireframe IA 階段就完成——你在動手建 HTML 時,每個模組的資料需求已經清楚, 不需要中途停下來補;在 Google Sheets 裡準備好資料後,你可以在 HTML 建置同時就看到真實商品,立即確認排版沒有破版。
2-A 每個模組在問一個資料問題
設計概念裡的每個商品模組,都在對使用者說一個故事——「這些是最便宜的」、「這些是最近有降價的」、「這些是你可能喜歡的」。 在 Wireframe 的時候,你已經決定了每個模組的商業邏輯。 資料規劃就是把這個商業邏輯翻譯成 Google Sheets 能理解的語言。
每個商品容器在 Wireframe 階段只需要回答一個問題: 「這個模組的商品,在 Google Sheets 裡應該有什麼共同特徵?」 這個共同特徵就是你的 section 代碼——它是 Google Sheets Staging 欄值和 HTML data-filter 的共同語言。
2-B 從 Wireframe 標注到 section 代碼:三步驟
在 Wireframe 上標記每個商品容器的「資料意圖」
Wireframe 完成後,為每個商品容器加一個標注,說明它的商業語意。
不需要特別工具,Figma annotation、便利貼備注、或設計規格表都行。
重點是在視覺設計開始前,先確認每個模組的資料邏輯——而不是等 HTML 建好才想。
標注範例:「最新降價商品區」→ 資料意圖:近期售價下降的商品,預計 6–8 筆
列出模組清單,決定 section 標籤代碼
為設計概念的所有商品模組列一份清單,每個模組決定一個 section 代碼。
這個代碼之後要填進 Google Sheets 的 section 欄,也要寫進 HTML 的 data-filter——兩個地方的值必須完全一致,包含大小寫。
推薦用中文代碼(如 降價、相機、秒殺10點)——可讀性好,維護直覺。英文也可以,但選一種格式統一,不要混用。
確認「這個 section,哪些商品應該有它?數量夠嗎?」
有了 section 代碼後,想一下你能找到多少商品可以被標為這個代碼。
如果「相機精選」這個 section 在 Staging 裡只有 1 件商品,版面就會塌陷——及早在 Wireframe 階段發現,調整設計或更換商品,比 HTML 建好才發現便宜得多。
特別是 tab 切換情境(如秒殺時時樂的 4 個時段 tab),每個 tab 至少需要 3–4 筆商品,共需準備 12–16 筆。
商品數量可行性要在 Wireframe 階段就確認。
2-C Wireframe 資料標注對照表(範例)
| Wireframe 模組名稱 | 資料意圖 | Section 代碼 | 預計商品數 |
|---|---|---|---|
| 最新降價 | 近期售價有調降的商品 | 降價 |
6–8 筆 |
| 相機精選 | 相機類別主力商品 | 相機 |
4 筆(+ data-max="4") |
| 你可能會喜歡 | AI 推薦 / 個人化商品 | 推薦 |
6 筆(+ data-max="6") |
| 秒殺 10:00 時段 | 早上 10 點的限時特賣商品 | 秒殺10點 |
4 筆 / tab,共 4 tab = 16 筆 |
什麼時候開始動 Google Sheets?
Wireframe 完成、模組清單確認、section 代碼決定——這三件事完成後,就可以開始動 Google Sheets。
不需要等 HTML 建好,不需要等設計稿定稿。
Google Sheets 的資料準備越早完成,你在建 HTML 時就能即時看到真實商品的排版效果,減少後期的 debug 循環。
特別是複雜情境(Tab 切換、時段資料),資料結構一定要在 Google Sheets 裡先跑通,再寫 HTML——
否則你很容易建了一整套 tab UI,最後才發現資料根本無法支撐這個設計,白費時間。
Google Sheets Staging 範本
新專案直接取用 · 不需要從空白欄位開始建
範本包含
三步驟
快速開始
下載 CSV → Google Sheets 新建試算表 → 選單「檔案 → 匯入」→ 上傳 CSV
替換 source_url 欄為你的 Yahoo 商品網址,section 欄填入你的模組代碼
安裝 Apps Script → 執行 🔄 強制重新匯入 → 商品資料自動填入 → 📤 匯出 JSON
Google Sheets + Apps Script 的組合是這套工作流程的核心。 Apps Script 負責從 Yahoo 商品頁面抓取資料、填入 Staging 工作表、以及匯出 products.json—— 這些工作完全自動完成,設計師只需要在最初做一次設定,之後每天執行幾個選單指令即可。 這個章節帶你在 15 分鐘內完成設定。
開始前確認
已完成:已下載 Staging 範本 CSV(見上方 Section 02 的下載區塊)。
需要:一個 Google 帳號,以及 Google Sheets 和 Apps Script 的基本存取權限。
不需要:寫程式的能力。整個安裝流程只需要複製貼上和點按幾個確認按鈕。
步驟 1 / 5 建立 Google Sheets 並匯入範本
新建 Google 試算表
前往 sheets.google.com → 點「+」建立空白試算表 → 把試算表名稱改成你的專案名稱(例如「SHP 電商首頁概念 2026-06」)。 試算表名稱不影響功能,只是方便你之後找到它。
匯入 CSV 範本
選單 檔案 → 匯入 → 上傳 → 選擇你下載的 shp-staging-template.csv
匯入設定請選:
• 匯入位置:取代試算表
• 分隔符號類型:逗號
• 將文字轉換為數字、日期和公式:否(避免價格被誤轉)
點「匯入資料」完成。
確認工作表名稱是「Staging」
畫面底部的工作表標籤應該顯示「Staging」。
這個名稱非常重要——Apps Script 靠它識別工作表,名稱不對就找不到資料。
如果顯示的是「工作表1」或其他名稱:右鍵點工作表標籤 → 重新命名 → 輸入 Staging(注意大寫 S)。
步驟 2 / 5 開啟 Apps Script 編輯器
開啟 Apps Script
在 Google Sheets 選單點 擴充功能 → Apps Script。
瀏覽器會在新分頁開啟 Apps Script 編輯器。
如果找不到「擴充功能」選單,確認你已登入 Google 帳號,以及試算表是「我的雲端硬碟」裡的檔案(不是他人分享的唯讀連結)。
清空預設程式碼
編輯器裡預設會有一段 function myFunction() {} 的空函數。
全選(Ctrl/Cmd+A)→ 刪除,把整個編輯區清空,
準備貼入完整的 SHP 商品管理程式碼。
步驟 3 / 5 貼入程式碼並儲存
SHP 商品管理程式碼
完整可用的 Apps Script · 複製貼入即可執行
程式碼包含
貼入
步驟
開啟下載的 .js 檔案 → Ctrl/Cmd+A 全選 → Ctrl/Cmd+C 複製全部內容
回到 Apps Script 編輯器 → 點編輯區 → Ctrl/Cmd+V 貼上
Ctrl/Cmd+S 儲存 → 確認左上角出現「已儲存變更」(可改名稱為「SHP 商品管理」)
貼入後,你應該會看到編輯器左側的函數清單出現 onOpen、
importNewProducts、
forceReimportAll、
exportProductsJson 等函數。
如果編輯器顯示紅色錯誤標示,通常是貼入時有多餘空白或 BOM 字元——重新全選刪除再貼一次即可。
步驟 4 / 5 執行授權
Apps Script 需要你的授權才能存取 Google Sheets 和外部網路(用來抓取 Yahoo 商品資料)。 這個授權只需要做一次,之後每次使用不需要重複。 以下是詳細的授權步驟——Google 的授權介面設計比較繞,請按照步驟操作:
選擇 onOpen 並執行
在編輯器頂部的函數下拉選單(預設顯示「選取函式」),
選擇 onOpen。
點選左側的 ▶ 執行 按鈕。
點「查看權限」開始授權
系統會彈出「需要授權」對話框 → 點 「查看權限」。 接著選擇你的 Google 帳號(即擁有這份試算表的帳號)。
通過「Google 尚未驗證」警告
Google 會顯示「Google 尚未驗證這個應用程式」的警告頁面。
這是正常的——你自己寫的 Script 都會出現這個畫面,不代表程式有問題。
點左下角的 「進階」 展開隱藏選項 →
點 「前往 SHP 商品管理(不安全)」(名稱取決於你的專案名稱)。
點「允許」完成授權
確認權限要求頁面(會列出 Apps Script 需要的權限:
存取試算表、以你的身份連接外部服務)→
點 「允許」。
授權完成後,畫面會回到 Apps Script 編輯器,
底部執行紀錄應顯示「執行完成」。
步驟 5 / 5 確認選單出現
回到 Google Sheets 並重新整理
切回 Google Sheets 的分頁 → 按 Cmd+R(或 F5)重新整理頁面。
授權完成後,onOpen() 函數才會在頁面載入時執行,建立選單。
確認「商品管理」選單
重新整理後,頂部選單列應該出現 「商品管理」 選項。
點開確認有三個指令:
• 📥 匯入商品資料(只補空白名稱)
• 🔄 強制重新匯入全部
• ─────────────
• 📤 匯出商品 JSON
出現這三個選項代表安裝完成。
三個選單指令:什麼時候用哪個
匯入商品資料(只補空白名稱)
掃描 Staging 工作表,只抓取 name 欄為空白的列。
用途:新專案剛貼入 source_url 後,第一次把商品資料填進來。
不適合:更新已有名稱的商品價格——那些列會被跳過,不會更新。
強制重新匯入全部
重新抓取所有有 source_url 的列,不管 name 欄有沒有值。
section 欄的值不會被覆蓋。
用途:每次想更新最新商品資料前(評審前、每日更新)一律用這個。
一次抓取多筆商品需要 1–5 分鐘,抓取中請勿關閉瀏覽器。
匯出商品 JSON
把 Staging 工作表的所有有效商品資料轉成 JSON 格式,
寫入 _JSON_Export 工作表的 A1 儲存格。
用途:每次強制重新匯入後都要執行一次,然後把 A1 的內容複製貼入本機 data/products.json。
安裝完成驗收
source_url、name、section(全小寫)
_JSON_Export 工作表 A1 出現 JSON 格式資料
常見問題
「商品管理」選單沒出現:確認授權有完成(回 Apps Script 執行 onOpen 看有無報錯) → 重新整理 Google Sheets 頁面。
授權時找不到「進階」按鈕:往下捲動警告頁面,「進階」通常在頁面底部。不同瀏覽器介面略有不同。
執行時出現「找不到 Staging 工作表」:確認工作表標籤名稱完全正確(Staging,S 大寫,不是 staging 或 STAGING)。
匯入後 name 欄仍是空白:確認 source_url 是完整的 Yahoo 商品網址(以 https://tw.buy.yahoo.com 開頭),商品尚在架。可打開 Apps Script → 查看「執行記錄」取得詳細錯誤訊息。
抓取速度很慢或中途失敗:Apps Script 每次抓取有 6 分鐘的執行時間上限。一次匯入不要超過 15–20 筆,可以分批執行。
section 欄位是設計師在 Google Sheets 裡的「分類語言」——你決定哪些商品屬於哪個模組, products.json 忠實記錄這些分類,HTML 按照分類篩選顯示。 無論面對多複雜的設計概念,都能用這個機制找到對應的資料建構方式。
新專案?從範本開始,不要從空白開始
每次新專案都要從空白 Google Sheets 手動建欄位很浪費時間。
下載 Staging 範本 CSV ↓——
匯入後欄位結構即完整,6 筆範例資料涵蓋 section 分類示範(相機、降價、秒殺時段等)。
替換 source_url、調整 section 欄值,執行 Apps Script 即可開始。
第一次建立 section 欄位的設定步驟請看下方 3-A。
3-A 第一次使用:在 Staging 建立 section 欄位
在 Staging tab 標題列找一個空白欄,填入欄位名稱
欄位標題輸入 section(全英文小寫)。
不能改成中文「模組」或其他名稱——這個名稱直接對應 products.json 的 key,系統要靠它識別欄位。
位置在哪一欄都可以。這個步驟只需要做一次。
為每件商品填入 section 標籤值
對應你的 Wireframe 模組清單,為每件商品填入對應的 section 代碼。
一件商品可同時屬於多個 section,用逗號分隔(不加空格):降價,推薦。
不屬於任何設計概念模組的商品,section 欄留空。
⚠️ Apps Script 🔄 強制重新匯入不會覆蓋 section 欄——設定一次後,每日資料更新都不需要重填。
執行 📤 匯出 JSON,更新本機 data/products.json
商品管理 → 📤 匯出商品 JSON → 開啟 _JSON_Export 工作表 → 點 A1 → 全選複製 → 貼入本機 data/products.json(完整取代整個檔案)。
section 欄的值會自動包含在每筆商品資料裡,例如 "section": "相機,降價"。
「section 欄是 Apps Script 不會碰的地方。
它是設計師的分類語言,只有設計師決定它的值。」
情境 A — 基礎多模組:同頁三個垂直商品區
最常見的設計概念場景:同一頁面有多個垂直排列的商品區塊,每個區塊顯示不同語意的商品。 這是 section 機制最直接的應用,也是所有設計師最先需要掌握的情境。
| 商品 | Staging section 欄值 | 出現在哪個模組 |
|---|---|---|
| FUJIFILM X-HF1 | 相機,推薦 |
相機精選 + 你可能會喜歡(兩個模組同時出現) |
| SONY RX100VII | 相機,降價 |
相機精選 + 最新降價 |
| Sony WH-1000XM5 | 推薦 |
你可能會喜歡 |
| LG 4K 電視 | 降價 |
最新降價 |
情境 A — HTML 結構(三模組,各自 section 篩選)
<div data-api="../data/products.json" data-filter="section:降價" data-label="最新降價"></div> <div data-api="../data/products.json" data-filter="section:相機" data-max="4" data-label="相機精選"></div> <div data-api="../data/products.json" data-filter="section:推薦" data-max="6" data-label="你可能會喜歡"></div>
情境 B — 複雜情境:秒殺時時樂(Tab 切換 × 時段資料)
電商首頁的「秒殺時時樂」模組有 4 個橫向 tab,對應一天內 4 個不同時段的特賣商品(10:00、12:00、14:00、18:00)。 使用者點擊 tab 可以瀏覽不同時段的優惠,商品內容每天早上 10 點更新。 這個複雜互動在設計概念裡要如何處理?
設計概念的「資料誠實」原則
設計概念不需要呈現真實的時間觸發邏輯(10 點時才顯示 10 點的商品,14 點後才開放 14 點 tab)。
你的目標是讓評審看到:每個 tab 的商品樣貌是什麼、tab 切換的互動感、時段標籤的視覺呈現。
真實的時間觸發是工程師在正式上線時實作的——設計概念只需要靜態地呈現「每個 tab 是什麼樣子」。
因此,你需要為每個 tab 準備一組代表性商品:每組 3–4 筆,4 個時段共需 12–16 筆商品(分別標入不同 section 代碼)。
Google Sheets 建構方式:為每個時段 tab 建立一個獨立的 section 代碼。 每件商品通常只屬於一個時段(除非同一商品確實在多個時段出現,再用逗號分隔)。
| Tab 名稱 | Section 代碼 | 所需商品數 | 設計代表的商業邏輯 |
|---|---|---|---|
| 10:00 時段 | 秒殺10點 |
4 筆 | 早間限時特賣 |
| 12:00 時段 | 秒殺12點 |
4 筆 | 午間限時特賣 |
| 14:00 時段 | 秒殺14點 |
4 筆 | 下午限時特賣 |
| 18:00 時段 | 秒殺18點 |
4 筆 | 晚間限時特賣 |
情境 B 的 Staging 設定範例
| 商品名稱 | section 欄值 | 說明 |
|---|---|---|
| Apple Watch Ultra | 秒殺10點 |
只出現在早間 tab |
| Dyson V15 吸塵器 | 秒殺12點 |
只出現在午間 tab |
| Sony WH-1000XM5 | 秒殺14點,降價 |
出現在下午 tab + 另一個降價模組 |
| Samsung Galaxy S25 | 秒殺18點 |
只出現在晚間 tab |
情境 B — HTML 結構(Tab UI + 各時段各自的 data-filter)
<!-- 秒殺時時樂:Tab 切換列 --> <div class="flash-tabs"> <button class="tab active" data-tab="tab-10">10:00</button> <button class="tab" data-tab="tab-12">12:00</button> <button class="tab" data-tab="tab-14">14:00</button> <button class="tab" data-tab="tab-18">18:00</button> </div> <!-- 各時段商品容器:每個 panel 各自用 data-filter 載入對應 section --> <div id="tab-10" class="tab-panel active"> <div data-api="../data/products.json" data-filter="section:秒殺10點" data-max="4" data-label="秒殺10點"></div> </div> <div id="tab-12" class="tab-panel" hidden> <div data-api="../data/products.json" data-filter="section:秒殺12點" data-max="4" data-label="秒殺12點"></div> </div> <!-- 14點、18點 panel 結構相同,section 代碼對應替換 -->
Tab 切換的 JavaScript:設計概念允許的例外
Tab 切換的互動邏輯(點擊哪個 tab 就顯示對應 panel)需要幾行 JavaScript——這是 UI 互動邏輯,不是商品資料邏輯,所以被允許。
區分的原則很簡單:「控制哪個 panel 顯示」的 JS 是 UI 互動,可以寫;「控制商品資料如何載入」的 JS 禁止自己寫,交給 product-loader.js。
Tab 切換範例:document.querySelectorAll('.tab').forEach(btn => btn.addEventListener('click', () => { /* show/hide panels */ }))
Do & Don't — 複雜情境資料規劃原則
✓ Do
先確認每個 tab 都有足夠商品再動手
4 個 tab × 4 筆商品 = 至少需要 16 筆商品標入各時段 section。先在 Staging 確認數量可行,再開始建 HTML tab UI。
✕ Don't
同一批商品重複標入所有時段
為了讓每個 tab 有商品,把同樣 4 件商品同時標成 秒殺10點,秒殺12點,秒殺14點,秒殺18點——4 個 tab 顯示一樣的商品,讓評審看不出 tab 切換的設計意義。
✓ Do
Tab 互動做靜態切換就夠了
設計概念的 tab 只需要「點哪個就顯示哪個 panel」。評審不需要看到「10 點前 tab 要 disabled」這種時間邏輯——那是工程師的工作,設計概念不需要這個精確度。
✕ Don't
花時間實作時間觸發邏輯
在設計概念 JS 裡寫「10 點之後才能點這個 tab」——這是正式上線的功能實作,不是設計概念的工作範圍,花時間寫這個等於在錯誤的地方投資。
✓ Do
每次評審前做一次每日更新
section 標籤設定好後,每天評審前用 🔄 強制重新匯入更新商品資料,tab 裡的商品就是最新的價格和優惠——不需要修改任何 HTML。
✕ Don't
資料不夠就在 HTML 硬編碼商品
「Staging 裡 18 點時段商品不夠,我直接在 HTML 裡補幾筆假的」——一旦硬編碼,這個模組就永遠顯示假資料,評審無法對它的商業邏輯做有效判斷。資料不夠時,調整設計或多找商品,不是補假資料。
在動手之前,先理解整個資料流。知道資料的來源和流向, 才知道設計師的工作邊界在哪裡——什麼需要你做,什麼已經有人負責。
FIG.01 · 商品資料流架構
↑ 設計師只需要知道最後兩個節點:products.json 是資料來源,HTML 是使用端
從 Yahoo 商品頁面抓取到 Google Sheets,再匯出為 JSON 存到本機——
這整個流程由 Apps Script 自動完成。設計師不需要碰這個流程,
只需要假設 data/products.json 永遠是最新的。
product-loader.js 是什麼?從哪裡來?
scripts/product-loader.js 是 SHP 設計系統的一部分,
已經預先存在於 codebase 的 scripts 目錄下。設計師不需要自己建立這個檔案,也不應該修改它。
你只需要在 HTML 裡引入它,它就會自動偵測頁面上有 data-api 屬性的容器,
讀取 products.json,並把商品卡渲染進去。
product-loader.js 內建了三個設計師不需要關心但需要知道的機制: stale-while-revalidate 快取(避免每次重整都重新 fetch)、 圖片載入失敗的 fallback(顯示 purple-50 背景 + 灰色 icon,不會出現破圖)、 以及資料格式驗證(JSON 格式錯誤時在 console 輸出警告而非靜默失敗)。
products.json 的欄位結構
| 欄位 | 說明 | 範例值 |
|---|---|---|
| id | 商品唯一識別碼 | P001 |
| name | 商品名稱(從頁面 h1 擷取) | Sony WH-1000XM5 無線降噪耳機 |
| price | 當前售價(純數字) | 8990 |
| original_price | 原價(有折扣時顯示) | 11900 |
| image_url | 商品主圖 URL | https://… |
| rating | 評分(最高 5.0) | 4.8 |
| reviews | 評論數量 | 1,243 |
| sales | 月銷量文字 | 已售出 500+ |
| tag1 / tag2 | 優惠券標籤(pink 或 red 前綴) | pink:折 $200 |
| category | 商品分類 | audio |
| section | 模組分類標籤(設計師手動維護,逗號分隔多值) | camera, price-drop |
Products 商品資料範本
對應 products.json 欄位 · 可直接匯入 Google Sheets
範本包含
使用
方式
下載 CSV → Google Sheets 新建試算表 → 「檔案 → 匯入 → 上傳」
將 source_url 欄替換為你的 Yahoo 商品網址,section 欄填入模組代碼
執行 Apps Script 🔄 強制重新匯入 → 📤 匯出 JSON → 貼入本機 data/products.json
這三條規則是這份工作流程的核心。違反任何一條, 要嘛會造成資料不更新,要嘛會讓多個概念的行為不一致,要嘛會在 code review 時被退件。
「三條規則的核心邏輯只有一個:
讓 product-loader.js 做它該做的事,不要跟它搶工作。」
一律引入 product-loader.js,禁止重新實作載入邏輯
所有設計概念一律在 <head> 或 body 底部加上這行:
<script src="../scripts/product-loader.js"></script>。
禁止自己重寫 buildCard()、快取邏輯、或 fetch 載入機制。
product-loader.js 已經處理了 stale-while-revalidate 快取、錯誤處理、圖片 fallback——
你重寫一遍只會比它差,而且每次更新都要同步修改兩個地方。
商品容器只加 data-api 屬性,不寫任何 JS
商品卡的容器元素只需要一個屬性:data-api="../data/products.json"。
product-loader.js 會自動偵測這個屬性、讀取資料、渲染商品卡。
不需要、也不應該在 HTML 裡寫任何 JavaScript 來控制商品載入。
如果你發現自己在 HTML 裡寫 fetch() 或 buildCard(),代表你走錯了路。
禁止在 HTML 內硬編碼任何商品資料
商品名稱、價格、圖片 URL、標籤文字——這些全部禁止硬寫在 HTML 裡。 一旦硬編碼,你的概念就永遠卡在那個時間點的資料。 商品下架、價格調整、圖片更新,你的 prototype 都不會知道。 評審時看到的就是一份「說謊」的設計概念。
正確的 HTML 結構
正確示範 — 只需要這樣
<!-- 1. 引入 loader(一次,通常在 body 底部)--> <script src="../scripts/product-loader.js"></script> <!-- 2. 商品容器只加 data-api 屬性 --> <div class="product-grid" data-api="../data/products.json"> <!-- product-loader.js 會自動填入商品卡 --> </div>
product-loader.js 透過兩個獨立屬性讓你控制每個模組的顯示內容:
data-filter 負責篩選哪些商品,
data-max 負責限制顯示數量。
兩個屬性分開寫、分開管——沒有 sort、category、has 等其他語法,也不要把數量寫進 data-filter。
| 使用情境 | 語法 | 說明 |
|---|---|---|
| 指定模組資料來源 | data-filter="section:price-drop" |
只顯示 section 欄位含有 price-drop 的商品 |
| 限制顯示數量 | data-max="6" |
最多顯示 6 筆(獨立屬性,不是寫在 data-filter 裡) |
| 模組篩選 + 數量限制 | data-filter="section:camera"data-max="4" |
相機模組,最多顯示 4 筆 |
| AND 條件(同時符合) | data-filter="section:camera section:price-drop" |
同時帶有兩個 section 標籤的商品(空格分隔,兩條件都要符合) |
| 不篩選(預設) | 不加 data-filter | 顯示 products.json 裡的所有商品 |
不存在的語法——請勿使用
以下語法不存在於 product-loader.js。寫了不會報錯,篩選條件會被靜默忽略,導致顯示全部商品:
data-filter="limit:4" → 數量要用獨立的 data-max="4"
data-filter="category:mobile" → 沒有 category 語法,要用 section
data-filter="has:discount" → 沒有 has: 語法
data-filter="sort:rating-desc" → 沒有 sort: 語法
data-filter + data-max 正確用法
<!-- ✅ 正確:section 篩選 + data-max 限制數量 --> <div data-api="../data/products.json" data-filter="section:camera" data-max="4"></div> <!-- ❌ 錯誤:limit 不可寫在 data-filter 內 --> <div data-filter="section:camera,limit:4"></div> <!-- AND 條件:空格分隔,同時符合兩個 section --> <div data-api="../data/products.json" data-filter="section:camera section:price-drop"></div>
section 值從哪裡來?命名規則是什麼?
data-filter="section:xxx" 裡的 xxx,
對應 Google Sheets Staging tab 的 section 欄位值。
這個欄位由設計師手動維護——Apps Script 強制重新匯入時不會覆蓋它。
命名規則:欄位標題必須是英文 section(全小寫),這個名稱會直接成為 products.json 的 key,不能改成中文或其他名稱。
但欄位的值可以是中文(例如 降價、相機、推薦)——只要 HTML 的 data-filter 值與 Staging 欄值完全一致即可。
中文值對設計師來說可讀性更好,是日常作業的推薦方式。
下一章(04)說明完整的 Staging 設定流程與多模組設計概念的建置方式。
一個設計概念頁面通常不只有一個商品區塊。 首頁可能同時有「最新降價」、「相機精選」、「你可能會喜歡」三個模組, 每個模組需要不同的商品集合。 這一章說明如何在 Google Sheets Staging 建立 section 欄位, 讓 HTML 裡的每個模組都能精準指定自己的資料來源。
先搞清楚:哪些事只做一次,哪些事每天重複
多模組資料串接有兩類工作,性質完全不同,混淆了就會做重複的事或搞錯順序:
一次性(設定好後不需重做):
→ HTML 的 data-filter 屬性 — 設計概念完成後就不用動
→ Google Sheets Staging 的 section 欄填值 — Apps Script 強制重新匯入不會覆蓋這個欄
每日重複(每次想看最新商品資料都要做):
→ Apps Script 🔄 強制重新匯入 → 📤 匯出 JSON → 貼入本機 products.json → 瀏覽器 Cmd+Shift+R
這四步是每日例行,約 5–7 分鐘。
「一份 products.json,N 個模組——
section 欄位是設計師的分類語言,HTML 用它來聆聽。」
4-A 全流程架構:從 Staging 到頁面模組
多模組資料指定的全流程由三個環節組成:
設計師在 Google Sheets Staging 裡填入 section 標籤,
匯出後 section 值進入 products.json,
HTML 的 data-filter="section:xxx" 再依此篩選。
FIG.02 · 多模組資料指定全流程
同一份 products.json,不同模組各自用不同的 section 值篩選,互不干擾
4-B Google Sheets Staging:section 欄位設定步驟
section 欄位是設計師手動維護的標籤系統, 第一次使用前需要在 Staging tab 新增這個欄位。 Apps Script 的強制重新匯入不會覆蓋 section 欄位—— 設定一次,之後每次資料更新都不會被清掉。
在 Staging tab 新增 section 欄位(一次性設定)
在 Google Sheets Staging tab 的標題列找一個空白欄,輸入欄位標題 section(全英文小寫)。
欄位標題不能改成中文或其他名稱——這個名稱會直接成為 products.json 的 key,必須跟系統對得起來。
位置在哪一欄都可以。
這個步驟只需要做一次。之後 Apps Script 強制重新匯入時,section 欄的值不會被覆蓋——這正是 section 欄的設計意圖:讓設計師有一個 Apps Script 不會碰的自訂分類空間。
為每個商品填入 section 標籤值
決定每個商品屬於哪個設計概念模組,在 section 欄填入對應的標籤值。值可以是中文,推薦用中文——可讀性好,設計師日常維護更直覺。
單一模組:直接填入,例如 相機 或 降價
屬於多個模組:逗號分隔(不加空格),例如 相機,降價
不屬於任何模組:留空即可,不影響其他商品
填入範例:FUJIFILM X-HF1 → 相機,推薦 SONY RX100VII → 相機,降價
匯出 JSON(section 值會一起帶出)
section 欄填完後,執行正常的匯出流程:
商品管理 → 📤 匯出商品 JSON → 複製 _JSON_Export A1 內容 → 貼入本機 data/products.json。
section 欄的值會自動包含在每筆商品資料裡,例如:
"section": "camera,price-drop"。
在 HTML 用 data-filter="section:xxx" 篩選
products.json 更新後,HTML 裡的 data-filter="section:camera" 就能正確篩選出有 camera 標籤的商品。
product-loader.js 使用包含比對(contains)——
section 值為 camera,price-drop 的商品,用 section:camera 篩選時也會被篩到。
| Staging section 欄值 | 會被哪些 data-filter 篩到 | 說明 |
|---|---|---|
camera |
section:camera |
只屬於 camera 模組 |
camera,price-drop |
section:camera、section:price-drop、AND 條件 section:camera section:price-drop |
同時屬於兩個模組 |
| (空白) | 任何 section 篩選都篩不到 | 沒有 section 標籤的商品只出現在無 data-filter 的全量容器 |
4-C 多模組設計概念 — 完整程式碼範例
正確示範 — 一頁三模組,各自指定 section(中文值)
<!-- ① 引入 loader(一次即可,放在 body 底部)--> <script src="../scripts/product-loader.js"></script> <!-- ② 模組 A:最新降價(Staging section 欄填「降價」)--> <section class="section-price-drop"> <h2>最新降價</h2> <div class="product-grid" data-api="../data/products.json" data-filter="section:降價" data-label="最新降價"></div> </section> <!-- ③ 模組 B:相機精選(Staging section 欄填「相機」,最多 4 筆)--> <section class="section-camera"> <h2>相機精選</h2> <div class="product-grid" data-api="../data/products.json" data-filter="section:相機" data-max="4" data-label="相機精選"></div> </section> <!-- ④ 模組 C:你可能會喜歡(Staging section 欄填「推薦」,最多 6 筆)--> <section class="section-recommended"> <h2>你可能會喜歡</h2> <div class="product-grid" data-api="../data/products.json" data-filter="section:推薦" data-max="6" data-label="你可能會喜歡"></div> </section> <!-- 📌 Staging section 欄對應填入: --> <!-- FUJIFILM X-HF1 → 相機,推薦 --> <!-- SONY RX100VII → 相機,降價 --> <!-- 某款耳機 → 推薦 -->
兩個獨立 JSON 的情境(邊界案例)
絕大多數設計概念只需要一份 products.json。
只有商品真的來自兩個完全不同的資料來源時(例如不同 BU 各自維護的 Google Sheets),才需要用兩個 data-api:
<div data-api="../data/products-camera.json" data-filter="section:camera"></div>
<div data-api="../data/products-glasses.json" data-filter="section:sunglasses"></div>
如果只是想分模組顯示,用同一份 JSON + 不同 section 標籤就夠了。
不要為了分模組而分 JSON 檔——那會讓匯出和維護的成本翻倍。
4-D 多模組頁面的建置流程
多模組頁面最常見的失敗模式:HTML 都寫好了,才發現某個模組因為 Staging 的 section 欄是空的而沒有資料。 正確的流程是先完成兩件一次性的工作(決定 section 標籤代碼、在 Staging 填入值),再寫 HTML 的 data-filter——資料先行,HTML 跟上。
定義模組與 section 標籤代碼(動手前)
列出設計概念的所有模組,為每個模組決定一個 section 標籤代碼。
例如:最新降價 → price-drop、相機精選 → camera、你可能會喜歡 → recommended。
標籤代碼建議用英文小寫加連字號,語意清晰即可。中文也可以,但 Staging 欄值和 HTML data-filter 值必須完全一致(包含大小寫)。
在 Staging 填入 section 值並匯出 JSON(動手前)
打開 Google Sheets Staging tab,確認 section 欄位已存在(第一次使用需要手動新增欄)。
為每個商品填入對應的 section 值,執行 📤 匯出 JSON,更新本機 data/products.json。
⚠️ 最常踩到的坑:忘了更新 products.json,導致 HTML 篩不到任何資料。先完成這個步驟,才能確認 data-filter 寫對了。
寫 HTML:data-filter + data-max + data-label
Staging 資料確認後,在每個模組容器加上對應的 data-filter="section:xxx"、
視需要加 data-max 限制數量,
以及 data-label 標示模組名稱(方便 console debug)。
真實資料驗證(建置後)
在 browser 開 DevTools,確認每個模組都正確載入了預期數量的商品。 如果某個模組顯示 0 筆,先檢查 Staging 的 section 欄是否已填值並重新匯出 JSON,再排查 HTML 語法。 空白模組在評審時會造成版面塌陷,及早發現及早修正。
4-E 多模組資料指定的設計原則
section 標籤代碼反映模組的語意,而非技術的方便
標籤代碼是設計師和資料之間的語言。
用 price-drop 而不是 module1;
用 recommended 而不是 test。
好的標籤代碼讓六個月後的你看到 data-filter 就知道這個模組在幹嘛,不需要翻文件。
section 欄先填,HTML 後寫——資料先行
在 Staging 填完 section 值並更新 products.json 之後,再動手寫 HTML 的 data-filter。 反過來先寫 HTML 後填 Staging,會導致在本機看不到商品、無法即時驗證, 然後花時間排查「為什麼空白」。 正確的順序節省 90% 的 debug 時間。
不同模組的 section 標籤應有明確差異
如果「今日推薦」和「你可能會喜歡」兩個模組用了相同的 section 值, 它們就會顯示一模一樣的商品——兩個模組存在的理由就消失了。 每個模組應有獨立的 section 標籤,代表獨立的商業邏輯和使用者價值。
一份 JSON,多個 section——不要為了分模組而分 JSON 檔
多模組設計概念不需要多份 JSON 檔。 section 欄位的設計目的就是讓一份 products.json 服務多個模組。 只有商品真的來自兩個完全不同資料來源時,才需要用兩個 data-api。 分 JSON 只會增加匯出次數和維護負擔。
4-F Do & Don't
✓ Do
先在 Staging 填 section 值,再寫 HTML
資料先行。確認 Staging 有值、products.json 已更新後,再寫 data-filter——每個模組都能立即看到真實商品,省下反覆 debug 的時間。
✕ Don't
data-filter 寫好了才想到 section 欄是空的
HTML 都寫完了,打開瀏覽器全部空白,才去 Staging 補 section 值。這個順序會讓你的開發驗證循環多繞一圈,浪費時間。
✓ Do
一個商品可屬於多個 section
一件商品同時屬於「降價」和「相機」兩個模組?在 section 欄填 price-drop,camera。它就會同時出現在兩個模組的篩選結果,不需要重複建資料。
✕ Don't
把 limit 寫進 data-filter
data-filter="section:camera,limit:4" 是錯的。limit 不是 data-filter 的一部分,要改用獨立的 data-max="4" 屬性。
✓ Do
多模組頁面一律加 data-label
每個容器加 data-label="模組名稱",console log 載入訊息會帶上模組名稱。多模組 debug 時,一眼就知道哪個容器出問題。
✕ Don't
為了讓評審好看而把不相關商品標入 section
真實商品數量不夠,就把不相關的商品也標成這個 section 讓畫面填滿——這讓設計概念的資料故事失去意義。真實資料不夠時,調整模組設計,不是調整資料。
以下是在 code review 和設計概念 QA 中反覆出現的錯誤。 每一條都有真實的背景——不是為了限制你,而是因為這些做法在實際運作時會出問題。
重寫 buildCard()——「我覺得我的版本更好」
最常見的錯誤。設計師複製 product-loader.js 裡的 buildCard() 邏輯,在自己的 HTML 裡修改。 問題:product-loader.js 更新時,你的版本不會自動跟著更新。商品卡的 DOM 結構、class 名稱、 fallback 邏輯一旦分叉,之後的維護就是兩條平行線。 正確做法:用 CSS 覆寫樣式,不要碰 JS 邏輯。
用 <script> 手寫商品陣列——「測試用的」
在 HTML 裡宣告一個 JavaScript 陣列存放假商品資料,然後用 for loop 渲染。 問題:「測試用」的假資料最後往往留在 prototype 裡被拿去評審。 評審看到的價格、圖片、標籤全是你寫死的,不反映真實商品狀態。 正確做法:直接用 data-api + products.json,沒有「測試版」和「正式版」之分。
把 product-loader.js 的路徑寫錯
src="scripts/product-loader.js"(少了 ../)是最常出現的 typo。
問題:載入失敗但瀏覽器不一定顯示明顯錯誤,容器會空白,設計師以為是資料問題去查 JSON,繞了一大圈。
正確路徑:確認你的 HTML 在 examples/ 目錄下,路徑應為 ../scripts/product-loader.js。
用 file:// 直接開 HTML——「雙擊就能看」
瀏覽器的安全限制讓 file:// 協議下的 fetch() 呼叫失敗,商品資料完全讀不到。
問題:設計概念看起來正常(結構在),但商品卡是空的。
正確做法:一律用 python3 -m http.server 8088 啟動本地 server,
再從 http://localhost:8088 開啟。
在 product-card 之外自定義商品卡樣式
設計師認為 product-card 元件「不夠好看」,自己設計一套商品卡 HTML + CSS, 然後用 JS 填入資料。 問題:脫離設計系統的商品卡在跨 prototype 的比較評審中造成「這個方向怎麼商品卡長不一樣」的疑惑,評審焦點從設計概念的差異移到元件差異上。 正確做法:如需調整商品卡樣式,修改 components/product-card.css,讓改動對所有概念生效。
最根本的判斷原則
如果你在設計概念的 HTML 裡寫了任何與商品資料相關的 JavaScript,幾乎可以確定你走偏了。設計概念的 HTML 應該只有結構(HTML)和樣式(CSS),商品資料的一切邏輯都在 product-loader.js 裡。
products.json 不會自己更新——需要有人執行 Google Sheets 的匯出流程。 這個流程通常由負責商品管理的人執行,但設計師需要知道整個流程, 才能在「資料看起來不對」時知道問題在哪個環節。
確認 Staging tab 的 source_url
每列的 source_url 欄位必須是有效的 Yahoo 商品網址。如果某個商品已下架,這一列要移除或更換 URL,否則重新匯入時會失敗並產生空白資料。
選擇正確的匯入指令
更新現有商品(價格/標籤)→ 🔄 強制重新匯入全部(所有有 source_url 的列都重抓)。
新增商品(name 欄位空白)→ 📥 匯入商品資料。
⚠️ 最常見錯誤:用 📥 匯入試圖更新價格——它只處理 name 為空的列,有名稱的商品會被跳過。每日更新一律用 🔄 強制重新匯入。
目視確認 Staging 資料正確
重新匯入完成後,快速掃一眼 name、price、tag1、tag2 欄位是否正確。特別注意 tag1/tag2——Apps Script 只抓未登入狀態下可見的優惠券,個人化優惠不會出現(這是正常行為,不是 bug)。
匯出 JSON 並更新本機檔案
商品管理 → 📤 匯出商品 JSON → 開啟 _JSON_Export 工作表 → 點 A1 → 全選複製 → 貼入本機 data/products.json(完整取代整個檔案內容)。
強制重新整理瀏覽器確認更新生效
到 http://localhost:8088 按 Cmd+Shift+R(強制重新整理)——這會清除 10 分鐘的 stale-while-revalidate 快取,確保看到最新資料。
普通的 Cmd+R 可能因為快取而仍顯示舊資料,如果更新後看起來沒有改變,先試試 Cmd+Shift+R。
常見問題排查
商品卡是空白的:先確認 localhost 是否啟動(不是 file://)、product-loader.js 路徑是否正確、products.json 是否存在且格式正確。
資料沒有更新:兩個常見原因——① 用了 📥 匯入而非 🔄 強制重新匯入全部;② 瀏覽器快取未清除。先試 Cmd+Shift+R,若還是舊資料再確認匯入指令是否正確。
某個模組顯示空白:Staging 的 section 欄還沒填這個模組的值,或填完後忘了重新匯出 JSON 更新本機。
tag1/tag2 是空的:該商品目前沒有公開優惠券,這是正確行為。
這份清單是今天這個工作流程的濃縮版。 建立任何包含商品資料的設計概念前後,各跑一遍——前置確認資料正確, 完成後確認實作無誤。
python3 -m http.server 8088),不是用 file:// 開啟
../scripts/product-loader.js
data-api="../data/products.json" 屬性
data-filter="section:xxx" 值與 Staging section 欄的值一致(拼寫、大小寫相符)
data-max 獨立屬性,而非 data-filter="limit:xxx"
data-label 屬性,方便 console debug
設計概念與 Figma 建置的分工
設計概念(HTML prototype)用於快速探索和評選方向,側重視覺組合的可能性,資料是真實的但實作是快速的。
Figma 建置用於精修後的正式設計,側重精確規格、元件品質、以及交付工程師的 Handoff 品質。
正確的流程是:先用設計概念探索 3–5 個方向 → 三方評選 → 選定方向後按照 Part 1(Brief Basics)的框架撰寫 Brief,進入 Figma 建置 → 建置過程中按照 Part 3(Quality Check)做品質確認。
設計概念的真實資料讓評選決策更可靠;Part 1–3 的流程讓 Figma 建置有系統可依循。兩個流程相輔相成,不互相取代。
「看完就能上手、不看就容易出錯——這是這份指南的設計目標。
真正的設計能力在於你為這些商品創造的體驗,而不是你如何取得這些商品的資料。」