# 前提: 在 Google 試算表裡建立欄位(像資料庫的欄位)- 先記錄未測試 #### ✅ Google Sheets 資料表(User Table)建議格式 **📄 建議建立一個 Sheet:命名為 users** | id | email | password | name | phone | address | created_at | updated_at | | ---- | ------------------------------------- | --------------- | ---- | ----------- | ------- | ---------- | ---------- | | U001 | [xxx@gmail.com](mailto:xxx@gmail.com) | hashed_password | 王小明 | 0912-000000 | 台北市中山區 | 2025-01-01 | 2025-01-05 | **👉 欄位順序一定要固定** **👉 第 1 列一定要有欄位名稱** **👉 將來查詢與更新會很穩定** --- #### 🔥 各欄位用途解釋 **1. id(使用者 ID)** 唯一身份,用來做後續更新資料用 例如: * U001 * U002 * U003 ##### ✅可以用 timestamp + 隨機字串。也可以用 row(行數)產生 ID(最穩定) **假設試算表上面是:** | id | email | ... | | ---- | ----- | --- | | U001 | ... | | | U002 | ... | | **Apps Script 可以做到:** ``` function generateUserId(sheet) { const lastRow = sheet.getLastRow(); // 包含標題列 const newNumber = lastRow; // 第2列 -> 1, 第3列 -> 2... return "U" + String(newNumber).padStart(3, "0"); } ``` **如果你新增一筆 → 自動變成:** * U001 * U002 * U003 … **優點:** * 絕對不重複 * 不會跟其他人衝突 * 不需前端參與 一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一 #### 🟢 用在註冊 API 的 Apps Script 完整範例: ``` js. function doPost(e) { const body = JSON.parse(e.postData.contents); const action = body.action; const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("users"); if (action === "register") { // 產生 ID const id = generateUserId(sheet); sheet.appendRow([ id, body.email, body.password, body.name, body.phone, body.address, body.created_at, body.updated_at ]); return ContentService.createTextOutput(JSON.stringify({ status: "success", id: id })).setMimeType(ContentService.MimeType.JSON); } } function generateUserId(sheet) { const lastRow = sheet.getLastRow(); // 包含標題 const userCount = lastRow - 1; // 扣掉第一列欄位標題 return "U" + String(userCount + 1).padStart(3, "0"); } ``` -- **2. email(帳號)** * 登入主要 key -- **3. password(密碼)** **⚠ 建議存「雜湊後的密碼」: 雖然 Google Sheet 不是專業 DB,但至少不要存明碼。** 範例: ``` perl. $2b$10$Jdsf7232sdfsf.... ``` -- **4. name(姓名)** * 註冊時填。 -- **5. phone(電話)** * 登入後可更新。 -- **6. address(地址)** * 登入後用於填寫 / 修改。 -- **7. created_at(建立時間)** * 註冊日期。 -- **8. updated_at(最後修改時間)** * 當他修改個人資料時更新。 --- ### 📌 Google Sheets 資料庫 CRUD 流程 #### 🟩 1. 註冊 API(POST) **前端(Vue):** ``` js. axios.post(API_URL, { action: "register", email: email, password: hashedPassword, name, phone, address, created_at: new Date().toISOString(), updated_at: new Date().toISOString() }) ``` Apps Script: * 檢查 email 有沒有重複 * 如果無 → appendRow 寫入資料 -- #### 🟦 2. 登入 API(POST) **前端:** ``` js. axios.post(API_URL, { action: "login", email, password: hashedPassword }) ``` Apps Script: * 用 email 搜尋該列 * 比對密碼是否一致 * 回傳該使用者資訊(不含密碼) -- #### 🟪 3. 更新個人資料 API(POST / PUT) **前端:** ``` js. axios.post(API_URL, { action: "update", id: userId, phone, address, updated_at: new Date().toISOString() }) ``` Apps Script: * 找到 id 對應的 row * 更新 phone / address 欄位 --- # 前提: 如何讓 Google 試算表(Google Sheets)變成資料庫並且可以用 Axios 進行 API 串接讀取 / 寫入資料。 ### 🧩 Google Sheets 當成資料庫,有三種主流方法。但這次只先介紹一種 **依照「你能不能接受建立 Apps Script」來決定:** -- #### ✅ 方法 1:用 Apps Script 建自己專屬 API(推薦、可 GET/POST) 這是最完整也最常用的方法。 你可以把 Google Sheet 當成一個小型資料庫, 然後用 Google Apps Script 建立: * GET API(讀資料) * POST API(寫資料) * PUT / DELETE 也可以 ### **一、資料前提(你的試算表)** 假設: * 試算表內 * 工作表名稱:工作表1 * 第 1 列是標題列 * 欄位順序如下(A → O): ``` id name email tel employmentStatus consultationTopic expectExpert mainChallenges desiredGoal preferredContactMethod preferredContactTime makeAnAppointment findOutThroughAChannel tellUsOther createdAt ``` ⚠️ Apps Script 會「依欄位順序寫入」,請確保順序一致 --- #### 🚀 Step 1 — 開啟試算表 → Apps Script **Google 試算表 → Extensions(擴充功能) → Apps Script** #### 🚀 Step 2 — 建立 Web API 程式 **把下面程式貼進 `Code.gs`:** 範例如下 ``` js. function doPost(e) { try { // 先 log 前端送的資料,方便除錯 Logger.log(JSON.stringify(e.parameter)); const sheet = SpreadsheetApp .getActiveSpreadsheet() .getSheetByName("工作表1"); const data = e.parameter; // ✅ 使用 e.parameter 取得 form 資料 // 取得最後一列 const lastRow = sheet.getLastRow(); // 自動 ID const id = lastRow; // 建立時間 const createdAt = new Date(); // 寫入資料(依欄位順序,跟第一次程式碼一致) sheet.appendRow([ id, data.name || "", data.email || "", data.tel || "", data.employmentStatus || "", data.consultationTopic || "", data.expectExpert || "", data.mainChallenges || "", data.desiredGoal || "", data.preferredContactMethod || "", data.preferredContactTime || "", data.makeAnAppointment || "", data.findOutThroughAChannel || "", data.tellUsOther || "", createdAt ]); return ContentService .createTextOutput(JSON.stringify({ success: true, message: "資料新增成功", id: id, received: data // ✅ 回傳收到的資料,方便前端檢查 })) .setMimeType(ContentService.MimeType.JSON); } catch (error) { return ContentService .createTextOutput(JSON.stringify({ success: false, message: error.toString() })) .setMimeType(ContentService.MimeType.JSON); } } ``` --- #### 🚀 Step 3 — 部署成 Web App **點擊:** **1️⃣ Deploy(部署) → New Deployment(新增部署) → 選取類型 : Web app(Web 應用程式)** **2️⃣ 右側方設定如下:** | 項目 | 設定 | | ---- | ------- | | 說明 | API v1 | | 執行身分 | **我** | | 存取權限 | **任何人** | #### **👉設定完成後,點「部署」** -- #### 🚀 Step 4 — 彈跳出視窗以及為什麼會出現這個畫面? ``` Google hasn’t verified this app The app is requesting access to sensitive info in your Google Account. Until the developer (xxxxxxxx@gmail.com) verifies this app with Google, you shouldn't use it. 選擇 Advanced 還是 back to safety? ``` 因為你的 Apps Script: * 使用了 Google 帳號權限 * 又被部屬成 Web App * 但「不是 Google 官方 App」 * 也「還沒送 Google 審核」 👉 所有自建 Apps Script 都一定會看到這個畫面 ✔️ 99% 的內部系統、表單後端、公司工具 ✔️ 都是這樣使用 ✔️ 完全正常 -- **正確、安全的通過方式(你現在就該這樣做):** 🔹 畫面 1:警告頁 點擊: **Advanced(進階)** 🔹 畫面 2:隱藏選項出現 點擊: **Go to「你的專案名稱」(unsafe)** ⚠️ 這個 unsafe 只是表示「未經 Google 公開驗證」 不是病毒、不是危險程式 🔹 畫面 3:權限清單 **確認內容後 → Allow(允許)** ✅ 完成 之後 同一個帳號不會再看到這個警告 **什麼情況「不需要」送 Google 驗證?(你目前就是)** 你現在的用途是: * 當試算表後端 API * 自己用 / 團隊用 * 表單資料寫入 * CRM / 預約系統 👉 完全不需要做 Google 驗證 Google 官方也明確說: 只要不是公開給「不特定大量一般使用者登入」 就不用驗證 -- **什麼情況「才需要」Google 驗證?(多數人永遠用不到)** 只有在以下情況才需要: 🔴 App 給「大量外部使用者」 🔴 使用 Google OAuth 登入(如「用 Google 登入」) 🔴 要上架 Marketplace 🔴 商業 SaaS 產品 而且驗證流程會: * 填寫隱私權政策 * 錄影操作流程 * 等 2~6 週 ### 給你一個安心判斷口訣 : **|「自己寫的 Apps Script → 一律 Advanced」** -- #### 🚀 Step 5 — 快速測試(瀏覽器) ✅ 方法一(最簡單,推薦) 1️⃣ 複製你的 API 連結 就是這一個(完整包含 /exec): ``` https://script.google.com/macros/s/AKfycbz_bpIfyQQk939_ceHoqJV07H2x_tuZfopWceE1uXRDFJjpLo7iJlyv8c4D2fMHPjte/exec ``` -- 2️⃣ 打開瀏覽器 用 Chrome / Edge / Safari 都可以 -- 3️⃣ 貼到網址列(上面那條) * 點一下瀏覽器最上面的 網址列 * 貼上剛剛的連結 * 按 Enter 📍 注意: 不是貼在 Google 搜尋框 是貼在「顯示網址的地方」 -- 4️⃣ 你會看到什麼? 情況 A(你目前最可能看到) 畫面顯示錯誤或空白,例如: * Script function not found: doGet * 或白畫面 👉 這代表 API 有在跑,但你還沒寫 doGet ✔️ 這其實是「成功的一種」 -- 情況 B(我們加了 doGet 之後) 加到你現有程式碼最下面: ``` function doGet(e) { return ContentService .createTextOutput(JSON.stringify({ status: "API is running" })) .setMimeType(ContentService.MimeType.JSON); } ``` ### 👉 只要有新增程式碼,就一定要重新部署 **🔁 接下來一定要做的事(超重要)** **重新部署** 1. Apps Script 右上角 → 部署 2. 管理部署作業 3. 點選「進行中」下面你想更新的版本(通常就是最上面的 API v1) 4. 點該部署右邊的「鉛筆 ✏️(編輯)」 5. 設定不變,直接更新 6. 👉 點 "部屬" **👉 不重新部署 = 瀏覽器永遠打到舊程式** -- 5️⃣ 完成後會跳出彈跳視窗訊息 ``` 已成功更新部署作業。 3 版 (2026年1月12日下午3:46) 部署作業 ID AKfycbyN7wBHtdB19DPAPbiKpEe-MB8IKe5-CoZqcuXT4HJqPnhXvEwm7RcK8uEJO98FFlGZ 網頁應用程式 網址 https://script.google.com/macros/s/AKfycbyN7wBHtdB19DPAPbiKpEe-MB8IKe5-CoZqcuXT4HJqPnhXvEwm7RcK8uEJO98FFlGZ/exec 資料庫 網址 https://script.google.com/macros/library/d/1iEq5NBPiWU0twYxFe4nGDRd77M8178AdCdC2f5K4h58_9v-IKc9xJn5l/3 如要讓其他使用者和群組將這項專案當做資料庫使用,請與對方共用這項專案。 ``` 重點: * 部署版本 3 → 你剛更新的版本 * 網頁應用程式網址 → 這就是新的 API * 資料庫網址 → 這是「以 Apps Script Library 形式引用的專案」,跟 API 呼叫無關,你目前不用理它 🔁 接下來將新的API 連結貼上瀏覽器測試 : 瀏覽器應該會看到: ``` { "status": "API is running" } ``` 這樣你一眼就知道: 👉 API 正常、部署成功、連結正確 -- #### 🚀 Step 6 — 其他彈跳視窗 ``` Untitled project (Unverified) The untitled project requires your permission to access data in your Google account. Google Apps Script When reviewing permissions, please consider whether you trust this unverified project. 未命名的專案 (未經證實) 未命名的專案需要您的許可才能存取您在 Google 上的資料。 Google Apps Script 審核權限時,請考慮您是否信任他們未命名的專案。 ``` **出現時機:** 👉當你點瀏覽器打開 API 連結,或者部署後第一次測試 doGet/doPost 時,Google 會跳出這個授權畫面。 這個畫面 完全正常,而且你現在做的是「正確流程」 👍 我直接告訴你 要按什麼、為什麼可以按、按完會發生什麼事。 -- **✅ 你現在該怎麼做(直接照按)** 🔹 畫面看到: 「未命名的專案(未經證實)」 需要您的許可才能存取您在 Google 上的資料 👉 請照這個順序點: 1️⃣ 點 「進階(Advanced)」 2️⃣ 點 「前往『未命名的專案』(不安全)」 3️⃣ 下一頁 → 點 「允許(Allow)」 ✔️ 請放心點「允許」 -- **為什麼這是安全的?(重點)** 因為: ✅ 這個 Apps Script 是你自己建立的 ✅ 開發者顯示的是 你的帳號 ✅ 只存取你自己的 Google 試算表 ✅ 沒有讀 Gmail、Drive 全部檔案、或密碼 Google 只是提醒: 「這個 App 還沒送官方驗證」 不是在說它有風險。 -- **為什麼會顯示「未命名的專案」?** 因為你還沒幫 Apps Script 取名字 😂 這完全不影響功能,但可以順便處理一下。 **(選擇性)幫專案改名** 在 Apps Script 左上角: 1. 把「未命名的專案」 2. 改成例如: * Spreadsheet API Backend * Consultation Form API 改完之後,警告畫面也會顯示新名字,看起來比較安心。 -- **按「允許」之後會發生什麼?** ✅ 授權完成 ✅ Web App 正常運作 ✅ 同一個帳號 之後不會再跳出這個畫面 ✅ 你可以正常用瀏覽器 / Postman / 前端呼叫 API -- **如果你現在「不按允許」會怎樣?** 如果你按: * ❌ Back to safety * ❌ 關閉視窗 結果就是: * API 無法執行 * doGet / doPost 不會動 * 試算表不會寫入資料 **總結一句話(記住這句就好)** **|自己寫的 Apps Script → 一律 Advanced → 允許** --- ### 🚀 用 Axios 串接(前端) **✔ POST 寫入資料** ``` js. (async()=>{ try{ const params = new URLSearchParams(); params.append("name", this.defaultGuest.name); const res = await axios({ method:'POST', url:`${this.apiA1}`, data:params }); console.log(res.data); // 可以看到後端回傳的 received }catch(err){ console.warn(err); } })(); ``` * #### 不要包成物件,直接放 URLSearchParams * #### 不要手動加 Content-Type header,axios 自動設定成 application/x-www-form-urlencoded --- ## 📌 QA: 為什麼要用這樣的寫法去寫入資料呢?? ``` const params = new URLSearchParams(); params.append("name", this.defaultGuest.name); params.append("email", this.defaultGuest.email); ``` * ### 這是 Google Apps Script + axios 前端整合的核心關鍵。 -- ### 我來仔細解釋一下為什麼我們要用這種寫法,還有它背後的原理!! #### 1️⃣ URLSearchParams 是什麼? URLSearchParams 是 瀏覽器原生的工具,可以把資料自動轉成: ``` name=xxx&email=yyy ``` 這就是 application/x-www-form-urlencoded 的格式,也就是 HTML 表單提交時的標準格式。 -- **範例:** ``` const params = new URLSearchParams(); params.append("name", "Alice"); params.append("email", "alice@gmail.com"); console.log(params.toString()); ``` **輸出:** ``` name=Alice&email=alice%40gmail.com ``` * 空格會自動轉成 %20 * @ 會自動編碼成 %40 * 完全符合傳統 form 的格式 -- #### 2️⃣ 為什麼不用 JSON? **如果你寫:** ``` js. axios.post(this.apiA1, { name: this.defaultGuest.name, email: this.defaultGuest.email }); ``` * axios 會自動加 Content-Type: application/json * 然後傳送 JSON 字串: ``` json. {"name":"Alice","email":"alice@gmail.com"} ``` * Google Apps Script 的 e.parameter 讀不到 JSON * 如果你用 JSON.parse(e.postData.contents),就必須小心 CORS 與預檢請求(OPTIONS) * JSON 方式容易 觸發 CORS 預檢,導致前端出現 ERR_NETWORK 或 blocked -- #### 3️⃣ 為什麼 URLSearchParams + axios 可以直接成功? * 送出的是 x-www-form-urlencoded * 這種請求被瀏覽器視為 simple request → 不會觸發 CORS 預檢 * Google Apps Script doPost(e) 可以直接用: ``` js. const data = e.parameter; ``` * data.name、data.email 就能拿到資料 * 不需要 JSON.parse * 也不需要改 headers * ✅ CORS 和 GAS 都很友善 -- #### 4️⃣ append 的好處 * 一個一個 append,方便動態生成欄位 * 可對應到試算表的多欄位寫入 * 例子: ``` js. params.append("tel", this.defaultGuest.tel); params.append("consultationTopic", this.defaultGuest.consultationTopic); ``` * 最終 `params.toString()` 會自動生成: ``` ini. name=Alice&email=alice@gmail.com&tel=0912345678&consultationTopic=Marketing ``` * GAS `e.parameter` 就會變成: ``` json. { "name": "Alice", "email": "alice@gmail.com", "tel": "0912345678", "consultationTopic": "Marketing" } ``` -- #### 5️⃣ 總結 1. GAS 接收資料只能直接讀 form (x-www-form-urlencoded) 或 query string 2. 用 JSON 會炸,還會觸發 CORS 預檢 3. URLSearchParams + axios → 自動轉 form,簡單、穩定、不需要額外設定 header 4. 方便對應多欄位寫入試算表 **💡 小結公式:** ### **| 前端送表單資料 → URLSearchParams → axios POST → GAS doPost(e) → e.parameter → appendRow** 這就是 Google Apps Script + axios 最穩定的「全流程寫法」。 ---