前言
在企業內部工具開發中,Google Sheets 結合 Apps Script 是一個低成本、高效率的解決方案。本文將從實際專案出發,講解如何建立一套完整的商品資料管理系統,並進階部署成獨立的 Web App。
目標讀者
- 有基礎 JavaScript 經驗的前端開發者
- 需要快速建立內部工具的 PM 或開發者
- 想了解 Apps Script 部署機制的人
你將學到
- Apps Script 與 Sheets 的互動機制
- Trigger(觸發器)的種類與使用時機
- 側邊欄、Modal 等 UI 元件開發
- 權限與授權的設計考量
- Web App 部署與常見問題排解
一、專案架構概覽
1.1 系統組成
┌─────────────────────────────────────────────────────┐│ Google Sheets ││ ┌─────────────────────────────────────────────────┐││ │ 資料儲存(多分頁支援) │││ │ A: 編號 | B: 名稱 | C: 圖片 | ... | I: 操作 │││ └─────────────────────────────────────────────────┘│└───────────────────────┬─────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────┐│ Apps Script ││ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ ││ │ 程式碼.gs │ │ UpdateModal │ │ BrowseModal│ ││ │ (後端邏輯) │ │ (側邊欄) │ │ (彈窗) │ ││ └──────────────┘ └──────────────┘ └────────────┘ │└───────────────────────┬─────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────┐│ Web App ││ (獨立網址,不需開啟 Sheet) │└─────────────────────────────────────────────────────┘1.2 檔案結構
| 檔案名稱 | 類型 | 用途 |
|---|---|---|
| 程式碼.gs | Script | 後端邏輯、API、觸發器 |
| UpdateModal.html | HTML | 側邊欄編輯介面 |
| BrowseModal.html | HTML | 資料總覽彈窗 |
| WebApp.html | HTML | 獨立 Web App 介面 |
二、基礎概念
2.1 Apps Script 與 Sheets 的關係
Apps Script 可以「綁定」在特定的 Google 文件上,這種稱為 Container-bound Script。
// 在綁定的 Script 中,可以直接取得試算表const ss = SpreadsheetApp.getActiveSpreadsheet();const sheet = ss.getActiveSheet();
// 讀取資料const value = sheet.getRange('A1').getValue();
// 寫入資料sheet.getRange('A2').setValue('Hello World');2.2 兩種觸發器(Trigger)
| 類型 | 執行身份 | 權限需求 | 使用場景 |
|---|---|---|---|
| Simple Trigger | 使用者本人 | 受限(無法存取需授權的服務) | onOpen, onEdit |
| Installable Trigger | 安裝者(管理員) | 完整權限 | 需要執行敏感操作 |
Simple Trigger 範例
// 自動執行,但權限受限function onOpen() { SpreadsheetApp.getUi() .createMenu('我的選單') .addItem('功能一', 'myFunction') .addToUi();}Installable Trigger 範例
// 需手動安裝,但有完整權限function installTrigger() { ScriptApp.newTrigger('onEditTrigger') .forSpreadsheet(SpreadsheetApp.getActive()) .onEdit() .create();}
function onEditTrigger(e) { // 這裡可以執行需要權限的操作 // 例如:開啟側邊欄、修改受保護的儲存格}2.3 為什麼選擇 Installable Trigger?
使用者在 H 欄選擇「📝 編輯」 ↓ 觸發 Installable Trigger ↓ 用「安裝者」的權限執行 ↓ 即使分頁有保護也能修改資料!重點:Installable Trigger 讓一般使用者可以透過預設的操作流程來修改資料,而不需要直接的編輯權限。
三、核心功能實作
3.1 選單與觸發器設定
const EDIT_COL = 8; // H 欄
function onOpen() { SpreadsheetApp.getUi() .createMenu('更新管理') .addItem('🔑 啟用功能(首次使用)', 'firstTimeAuth') .addSeparator() .addItem('➕ 新增資料', 'openBlankSidebar') .addItem('📋 查看當前分頁資料', 'openBrowseModal') .addSeparator() .addSubMenu(SpreadsheetApp.getUi().createMenu('🔧 管理員功能') .addItem('⚙️ 安裝觸發器', 'installTrigger')) .addToUi();}
function installTrigger() { // 先移除舊的,避免重複 const triggers = ScriptApp.getProjectTriggers(); triggers.forEach(function(trigger) { if (trigger.getHandlerFunction() === 'onEditTrigger') { ScriptApp.deleteTrigger(trigger); } });
// 安裝新的 ScriptApp.newTrigger('onEditTrigger') .forSpreadsheet(SpreadsheetApp.getActive()) .onEdit() .create();
SpreadsheetApp.getActiveSpreadsheet().toast("觸發器安裝完成!", "✅", 3);}3.2 下拉選單觸發編輯/刪除
function onEditTrigger(e) { const range = e.range; const sheet = range.getSheet(); const sheetName = sheet.getName();
// 檢查是否為 H 欄且非標題列 if (range.getColumn() === EDIT_COL && range.getRow() > 1) { const currentRow = range.getRow(); const value = e.value;
// 檢查 A 欄有沒有資料 const id = sheet.getRange(currentRow, 1).getValue(); if (!id) { range.setValue('-'); return; }
// 📝 編輯 if (value === '📝 編輯') { const rowValues = sheet.getRange(currentRow, 1, 1, 7).getValues()[0];
const productData = { sheetName: sheetName, id: rowValues[0], name: rowValues[1], image: rowValues[2], description: rowValues[3], price: rowValues[4], note: rowValues[5], date: rowValues[6], rowNumber: currentRow };
showSidebar(productData); range.setValue('-'); }
// 🗑️ 刪除 if (value === '🗑️ 刪除') { const ui = SpreadsheetApp.getUi(); const response = ui.alert( '⚠️ 確認刪除', '確定要刪除這筆資料嗎?', ui.ButtonSet.YES_NO );
if (response === ui.Button.YES) { sheet.deleteRow(currentRow); } else { range.setValue('-'); } } }}3.3 側邊欄與 Modal
// 開啟側邊欄function showSidebar(data) { const template = HtmlService.createTemplateFromFile('UpdateModal'); template.initialData = data ? JSON.stringify(data) : 'null';
const html = template.evaluate() .setTitle('商品資料編輯') .setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showSidebar(html);}
// 開啟 Modalfunction openBrowseModal() { const template = HtmlService.createTemplateFromFile('BrowseModal'); const sheetName = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName();
template.data = getAllProducts(sheetName); template.sheetName = sheetName;
const html = template.evaluate() .setWidth(750) .setHeight(550);
SpreadsheetApp.getUi().showModalDialog(html, '📋 資料總覽');}3.4 HTML 模板傳值
在 HTML 中使用 <?!= ?> 語法接收後端資料:
<script> // 從後端接收資料 const initialData = <?!= initialData ?>; const sheetName = '<?!= sheetName ?>';
// Vue 3 使用範例 const { createApp, ref, onMounted } = Vue;
createApp({ setup() { const form = ref({});
onMounted(function() { if (initialData) { form.value = initialData; } });
return { form }; } }).mount('#app');</script>四、權限與授權設計
4.1 授權流程
新用戶首次使用 ↓點選任何選單功能 ↓Google 彈出授權視窗 ↓用戶勾選同意權限 ↓完成授權,可使用所有功能4.2 常見授權問題
| 問題 | 原因 | 解決方式 |
|---|---|---|
| 功能無法使用 | 未完成授權 | 點選「首次授權」功能 |
| 授權後仍無法用 | 漏勾權限 | 去 Google 帳戶撤銷後重新授權 |
| Modal 編輯按鈕失效 | 該用戶未授權 | 提示用戶先完成授權 |
4.3 授權引導頁面
function showResetAuthInstructions() { const html = HtmlService.createHtmlOutput(` <h3>🔄 重置授權步驟</h3> <ol> <li>前往 <a href="https://myaccount.google.com/permissions" target="_blank">Google 權限設定</a></li> <li>找到此試算表名稱</li> <li>點擊「移除存取權」</li> <li>回到試算表按 F5 重新整理</li> <li>重新執行「首次授權」</li> </ol> `) .setWidth(400) .setHeight(300);
SpreadsheetApp.getUi().showModalDialog(html, '授權說明');}五、進階:Web App 部署
5.1 什麼是 Web App?
| 項目 | 綁定在 Sheet | Web App |
|---|---|---|
| 網址 | 無 | 有獨立網址 |
| 開啟方式 | 從 Sheet 選單 | 直接開網址 |
| 看到 Sheet? | 看得到 | 看不到 |
| 程式碼 | 有權限的人看得到 | 完全隱藏 |
| 適合 | 內部團隊 | 給外部人用 |
5.2 費用說明
完全免費! 🎉
| 項目 | 費用 | 限制 |
|---|---|---|
| Apps Script | 免費 | 每日執行時間 6 小時 |
| Web App 部署 | 免費 | 無流量限制 |
| 網址 | 免費 | Google 提供的子網域 |
| SSL 憑證 | 免費 | 自動 HTTPS |
5.3 Web App 入口函數
function doGet(e) { const template = HtmlService.createTemplateFromFile('WebApp');
// 取得所有分頁名稱 const ss = getSpreadsheet(); const sheets = ss.getSheets(); const sheetNames = sheets.map(function(s) { return s.getName(); });
template.sheetNames = JSON.stringify(sheetNames);
return template.evaluate() .setTitle('商品資料管理系統') .addMetaTag('viewport', 'width=device-width, initial-scale=1') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);}5.4 ⚠️ 關鍵注意事項:SPREADSHEET_ID
這是最常見的錯誤來源!
問題:在 Web App 環境中,SpreadsheetApp.getActiveSpreadsheet() 會返回 null。
原因:Web App 是獨立運行的,沒有「當前開啟的試算表」概念。
解決方案:
// ❌ 錯誤寫法const SPREADSHEET_ID = SpreadsheetApp.getActiveSpreadsheet().getId();
// ✅ 正確寫法:硬編碼試算表 IDconst SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID_HERE';
// 取得試算表的通用函數function getSpreadsheet() { try { // 先嘗試取得綁定的試算表(在 Sheet 內執行時) const ss = SpreadsheetApp.getActiveSpreadsheet(); if (ss) return ss; } catch (e) {}
// Web App 環境用 ID 開啟 return SpreadsheetApp.openById(SPREADSHEET_ID);}如何取得 ID?
從試算表網址:
https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/edit#gid=0 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 這一段就是 ID(約 44 個字元)5.5 部署步驟
-
開啟 Apps Script 編輯器
-
點選「部署」→「新增部署作業」
-
設定部署選項
| 設定項目 | 選項 | 說明 |
|---|---|---|
| 類型 | 網頁應用程式 | - |
| 執行身分 | 我 | 用你的權限執行(推薦) |
| 誰可以存取 | 任何人 | 依需求選擇 |
- 點「部署」取得網址
5.6 ⚠️ 更新部署的注意事項
每次修改程式碼後:
- 部署 → 管理部署作業
- 點右上角 鉛筆圖示(編輯)
- 版本必須選「新版本」
- 點「部署」
⚠️ 如果選擇舊版本號,修改不會生效!5.7 API 設計建議
為 Web App 設計專用的 API 函數:
// API: 取得資料function getSheetData(sheetName) { const emptyResult = { data: [], headers: [], total: 0 };
try { const ss = getSpreadsheet(); const sheet = ss.getSheetByName(sheetName);
if (!sheet) return emptyResult;
const lastRow = sheet.getLastRow(); if (lastRow <= 1) return emptyResult;
const data = sheet.getRange(2, 1, lastRow - 1, 7).getValues();
const result = data.map(function(row, index) { return { rowNum: index + 2, id: row[0], name: row[1], // ... }; });
return { data: result, total: result.length };
} catch (e) { console.log('錯誤:' + e.message); return emptyResult; }}
// API: 儲存資料function saveDataFromWeb(formData) { const ss = getSpreadsheet(); const sheet = ss.getSheetByName(formData.sheetName);
// ... 儲存邏輯
return { success: true, message: "儲存成功" };}
// API: 刪除資料function deleteDataFromWeb(sheetName, rowNum) { const ss = getSpreadsheet(); const sheet = ss.getSheetByName(sheetName);
sheet.deleteRow(rowNum);
return { success: true, message: "刪除成功" };}六、前端呼叫 Apps Script
6.1 基本語法
// 呼叫後端函數google.script.run .withSuccessHandler(function(result) { console.log('成功:', result); }) .withFailureHandler(function(error) { console.log('失敗:', error); }) .yourFunctionName(arg1, arg2);6.2 完整範例
function loadData() { isLoading.value = true;
google.script.run .withSuccessHandler(function(result) { if (result && result.data) { items.value = result.data; } else { items.value = []; } isLoading.value = false; }) .withFailureHandler(function(err) { showToast('載入失敗:' + err, 'error'); items.value = []; isLoading.value = false; }) .getSheetData(currentSheet.value);}
function saveForm() { if (!form.value.id) return;
isSaving.value = true; const payload = { ...form.value, sheetName: currentSheet.value };
google.script.run .withSuccessHandler(function(result) { isSaving.value = false; if (result.success) { showToast(result.message, 'success'); loadData(); // 重新載入 } }) .withFailureHandler(function(err) { isSaving.value = false; showToast('儲存失敗', 'error'); }) .saveDataFromWeb(payload);}七、部署權限深入解析(進階必讀)
這是許多開發者會踩到的坑,尤其是公司帳號(Google Workspace)與一般 Google 帳號的行為差異。
7.1 部署設定的兩個關鍵選項
| 設定項目 | 選項 | 影響 |
|---|---|---|
| 執行身分 | 我 (Me) | 用開發者權限執行,使用者不需要 Sheet 權限 |
| 存取應用程式的使用者 | 用使用者自己的權限,需要給 Sheet 權限 | |
| 誰可以存取 | 僅限自己 | 只有你能用 |
| 任何人 | 不需登入就能用 | |
| 任何擁有 Google 帳戶的人 | 需要登入 Google 才能用 |
7.2 ⚠️ 常見問題:抓不到使用者 Email
症狀:Session.getActiveUser().getEmail() 回傳空字串
原因:部署設定為「任何人 (Anyone)」時,Google 為了隱私不會傳遞訪客的 Email。
function doGet(e) { const userEmail = Session.getActiveUser().getEmail(); console.log(userEmail); // 輸出:""(空字串)}解決方案:
將「誰可以存取」改為 「任何擁有 Google 帳戶的人」
部署 → 管理部署作業 → 編輯 ↓誰可以存取: ❌ 任何人 (Anyone) → 抓不到 Email ✅ 任何擁有 Google 帳戶的人 → 可以抓到 Email ↓版本:選擇「新版本」 ↓部署設定後的行為變化:
使用者開啟 Web App 網址 ↓Google 先攔截,要求登入 ↓登入成功後才執行 doGet ↓Session.getActiveUser().getEmail() 可正常取得 Email7.3 ⚠️ 執行身分的權限陷阱
情境比喻
「執行身分:我」的運作方式:
使用者:「幫我修改這筆資料」 ↓Web App(代理人)拿著「你的識別證」進入 Sheet ↓Google Sheet 記錄:「開發者 修改了資料」兩種設定的比較
| 執行身分 | Sheet 權限需求 | 版本紀錄顯示 | 安全性 |
|---|---|---|---|
| 我 (Me) | 使用者不需要權限 | 只顯示開發者 | ✅ 安全 |
| 使用者 | 使用者需要編輯權限 | 顯示實際操作者 | ❌ 危險 |
為什麼不建議「執行身分:使用者」?
❌ 問題:1. 必須把 Sheet 開放「編輯權限」給每位使用者2. 使用者可以繞過 Web App,直接開 Sheet 亂改3. 資料安全性歸零7.4 ✅ 最佳實踐:自己記錄修改人
既然系統紀錄無法區分,就在資料中加入「修改人」欄位:
修改資料結構
原本:A ~ G 欄(資料)+ H 欄(操作選單)修改後:A ~ G 欄(資料)+ H 欄(修改人 Email)+ I 欄(操作選單)// 操作欄從第 8 欄移到第 9 欄const EDIT_COL = 9;修改儲存函數
function saveDataFromWeb(formData) { const ss = getSpreadsheet(); const sheet = ss.getSheetByName(formData.sheetName);
// 取得當前使用者 Email const currentUserEmail = Session.getActiveUser().getEmail();
// 資料陣列(1~8 欄) const rowData = [ formData.id, formData.name, formData.image, formData.description, formData.price, formData.note, formData.date, currentUserEmail // H 欄:修改人 ];
// ... 儲存邏輯(第 9 欄是操作選單)}效果
| id | name | … | date | 修改人 | 操作 |
|---|---|---|---|---|---|
| 001 | 商品A | … | 2025-12-15 | [email protected] | - |
| 002 | 商品B | … | 2025-12-15 | [email protected] | - |
7.5 公司帳號 vs 個人帳號差異
Google Workspace(公司帳號)有額外的安全限制:
| 項目 | 個人帳號 | 公司帳號 (Workspace) |
|---|---|---|
| 外部 App 存取 | 通常允許 | 可能被管理員限制 |
| 第三方授權 | 自由 | 可能需要管理員核准 |
| 跨網域分享 | 自由 | 可能被限制 |
常見錯誤訊息
「這個應用程式遭到封鎖」「您的機構不允許存取這個應用程式」解決方式
- 聯繫公司 IT 管理員開放權限
- 或將 Apps Script 專案發布為「內部應用程式」
7.6 推薦的部署設定組合
內部團隊使用(推薦)
執行身分:我 (Me)誰可以存取:任何擁有 Google 帳戶的人- ✅ 使用者不需要 Sheet 權限
- ✅ 可以取得使用者 Email
- ✅ 資料安全性高
完全公開(表單類)
執行身分:我 (Me)誰可以存取:任何人- ✅ 訪客不需登入
- ⚠️ 無法追蹤是誰提交的
- ⚠️ 可能被濫用
高安全性需求
執行身分:我 (Me)誰可以存取:任何擁有 Google 帳戶的人+ 白名單驗證 + 管理員分層// ========================================// 設定區// ========================================
// 一般使用者白名單(留空 [] = 公開模式,允許所有 Google 帳戶)const ALLOWED_USERS = [ // 留空代表允許所有人];
// 管理員白名單(可看到進階功能)const ADMIN_USERS = [];
// ========================================// 統一的權限檢查函數// ========================================function getUserInfo() { const email = Session.getActiveUser().getEmail(); const lowerEmail = email.toLowerCase();
// 公開模式:白名單為空時,允許所有 Google 帳戶 const isPublic = ALLOWED_USERS.length === 0;
return { email: email, // 允許進入:公開模式 OR 在白名單內 isAllowed: isPublic || ALLOWED_USERS.some(u => u.toLowerCase() === lowerEmail), // 管理員:必須在管理員名單內 isAdmin: ADMIN_USERS.some(u => u.toLowerCase() === lowerEmail) };}
// ========================================// doGet 入口的權限檢查// ========================================function doGet(e) { const userInfo = getUserInfo();
// 1. 檢查是否能取得 Email if (!userInfo.email) { return HtmlService.createHtmlOutput(` <h2>⚠️ 無法識別您的身分</h2> <p>請確認部署設定為「任何擁有 Google 帳戶的人」</p> `); }
// 2. 權限檢查 if (!userInfo.isAllowed) { return HtmlService.createHtmlOutput(` <h2>🚫 存取被拒</h2> <p>您的帳號 (${userInfo.email}) 沒有權限。</p> `); }
// 3. 正常載入,傳遞使用者資訊給前端 const template = HtmlService.createTemplateFromFile('WebApp'); template.currentUser = JSON.stringify(userInfo);
return template.evaluate();}前端可根據 isAdmin 顯示/隱藏管理功能:
// 在 WebApp.html 中const currentUser = <?!= currentUser ?>;
// 根據權限顯示按鈕if (currentUser.isAdmin) { // 顯示管理員專用按鈕}八、常見問題與解決方案
8.1 問題排查清單
| 問題 | 可能原因 | 解決方式 |
|---|---|---|
| Web App 載入失敗 | SPREADSHEET_ID 未設定 | 硬編碼正確的 ID |
| 修改沒生效 | 未選「新版本」部署 | 重新部署為新版本 |
| 權限錯誤 | 用戶未授權 | 引導用戶完成授權 |
| 觸發器不運作 | 未安裝或重複安裝 | 檢查並重新安裝 |
| Modal 編輯失敗 | 呼叫者無權限 | 改用觸發器方式 |
| Email 抓不到 | 部署設定為「任何人」 | 改為「任何擁有 Google 帳戶的人」 |
| 公司帳號被擋 | Workspace 安全限制 | 聯繫 IT 管理員 |
| 版本紀錄只顯示開發者 | 執行身分設為「我」 | 自己記錄修改人欄位 |
8.2 Debug 技巧
// 在後端加入 logfunction getSheetData(sheetName) { console.log('開始取得資料,分頁:' + sheetName);
const ss = getSpreadsheet(); console.log('試算表:' + (ss ? ss.getName() : 'null'));
// ...}
// 在 Apps Script 編輯器執行測試function testGetSheetData() { const result = getSheetData('Sheet1'); console.log('結果:' + JSON.stringify(result));}8.3 效能優化建議
// ❌ 效能差:逐格讀取for (let i = 2; i <= lastRow; i++) { const value = sheet.getRange(i, 1).getValue();}
// ✅ 效能好:批次讀取const values = sheet.getRange(2, 1, lastRow - 1, 7).getValues();values.forEach(function(row) { // 處理資料});8.4 ⚠️ 重要:併發寫入的鎖定機制(LockService)
當多人同時操作時,可能發生資料覆蓋或重複寫入的問題。使用 LockService 可以確保同一時間只有一個人能寫入。
function saveDataFromWeb(formData) { // 1. 取得鎖定 const lock = LockService.getScriptLock();
try { // 等待取得鎖定(最多等 10 秒) lock.waitLock(10000); } catch (e) { // 等不到鎖定,代表有其他人正在寫入 return { success: false, message: "系統忙碌中,請稍後再試" }; }
try { // 2. 執行寫入操作 const ss = getSpreadsheet(); const sheet = ss.getSheetByName(formData.sheetName);
// ... 寫入邏輯 ...
return { success: true, message: "儲存成功" };
} catch (e) { return { success: false, message: "錯誤:" + e.message };
} finally { // 3. 一定要釋放鎖定! lock.releaseLock(); }}LockService 的三種類型
| 類型 | 範圍 | 使用場景 |
|---|---|---|
getScriptLock() | 整個 Script | 最常用,防止任何人同時執行 |
getUserLock() | 單一使用者 | 防止同一人重複點擊 |
getDocumentLock() | 單一文件 | 綁定特定 Sheet 的操作 |
流程示意
使用者 A 開始儲存 ↓取得 Lock ✅ ↓執行寫入中... │ │ ← 使用者 B 也要儲存 │ ↓ │ 等待 Lock... ⏳ │ │寫入完成 │ ↓ │釋放 Lock │ ↓ 取得 Lock ✅ ↓ 執行寫入最佳實踐
// ⚠️ 一定要用 try-finally 確保釋放鎖定const lock = LockService.getScriptLock();
try { lock.waitLock(10000); // 寫入操作} finally { lock.releaseLock(); // 無論成功失敗都要釋放!}九、完整程式碼結構
9.1 檔案結構
📁 Apps Script 專案├── 程式碼.gs # 後端邏輯│ ├── 設定區│ │ ├── EDIT_COL = 9│ │ ├── SPREADSHEET_ID│ │ ├── ALLOWED_USERS[]│ │ └── ADMIN_USERS[]│ ├── 工具函數│ │ ├── getUserInfo()│ │ └── getSpreadsheet()│ ├── Web App│ │ └── doGet()│ ├── API│ │ ├── getSheetNames()│ │ ├── getSheetData()│ │ ├── saveDataFromWeb() # 含 LockService│ │ └── deleteDataFromWeb()│ ├── Sheet 觸發器│ │ ├── onEditTrigger()│ │ ├── showSidebar()│ │ └── installTrigger()│ └── 選單與初始化│ ├── onOpen()│ ├── initEditColumn()│ └── initAllSheetsEditColumn()│├── UpdateModal.html # 側邊欄編輯介面├── BrowseModal.html # 資料總覽彈窗└── WebApp.html # 獨立 Web App9.2 資料欄位對照
| 欄位 | 位置 | 欄位名稱 | 說明 |
|---|---|---|---|
| A | 1 | id | 編號(主鍵) |
| B | 2 | name | 名稱 |
| C | 3 | image | 圖片連結 |
| D | 4 | description | 描述 |
| E | 5 | price | 價格 |
| F | 6 | note | 備註 |
| G | 7 | date | 日期時間 |
| H | 8 | 修改人 Email(自動記錄) | |
| I | 9 | 操作 | 下拉選單(-/編輯/刪除) |
9.3 設定區範例
// ========================================// 設定區(根據需求修改)// ========================================
// 操作欄位置(第 8 欄存 Email,所以操作欄在第 9 欄)const EDIT_COL = 9;
// 試算表 ID(從網址取得)const SPREADSHEET_ID = '你的試算表ID';
// 一般使用者白名單(空陣列 = 公開模式)const ALLOWED_USERS = [ // '[email protected]',];
// 管理員白名單const ADMIN_USERS = [];結語
Google Apps Script 雖然有其限制,但對於快速建立內部工具來說是非常好的選擇:
優點
- ✅ 完全免費
- ✅ 不需要另外架設伺服器
- ✅ 與 Google 生態系無縫整合
- ✅ 部署簡單,一鍵上線
限制
- ⚠️ 每日執行時間限制(6 小時)
- ⚠️ 程式碼對編輯者可見(除非用 Web App)
- ⚠️ 效能不如專業後端
適用場景
- 內部資料管理系統
- 表單提交後處理
- 自動化報表生成
- 快速原型開發
希望這份指南能幫助你快速上手 Google Apps Script 開發!
回報錯字、失效連結,或告訴我你想看的延伸主題。