不用背誦程式語法、也不必申請複雜的外部 API 金鑰!在 Google Workspace(Sheets、Docs、Slides)中,直接用「內建 Gemini Spark (✦)」負責語意理解、摘要與內容生成,再請「問問 Gemini (Ask Gemini)」幫我們撰寫 Google Apps Script (GAS) 負責固定格式、批次拆表、自訂選單與跨產品自動化,兩者相得益彰!
許多人在使用 Google Workspace 時常遇到兩個極端:只用右側的「✦ 內建 Gemini Spark」,雖然能幫忙寫文案、做摘要,但遇到「要把 100 筆資料拆成 10 個分頁、套用公司標準配色、或是批次轉成 PDF 寄信」時,AI 側邊欄無法替你執行這些固定流程;反過來說,若想寫 Google Apps Script (GAS),一般行政與業務人員又覺得寫程式門檻太高。把兩者結合,剛好完美互補!
SpreadsheetApp、DocumentApp 等 API 語法;若要在 GAS 裡呼叫外部 AI 還要申請 API Key 與綁信用卡。在 Google Sheets、Docs 或 Slides 上方選單列,點擊「擴充功能 (Extensions)」➔「Apps Script」,會開出一個新分頁。
myFunction 後貼上把編輯器裡預設的幾行字全部刪掉,貼上「問問 Gemini」幫你寫好的完整程式碼,按上方💾 儲存專案(或 Ctrl+S)。
第一次執行時 Google 會跳出「需要授權」視窗,這是因為腳本是你自己寫的(尚未送交 Google 商店審核),請安心放行。
只要在 Prompt 裡請 Gemini 加上 onOpen() 自訂選單,回到試算表/文件按 F5 重整,上方就會長出你的專屬魔法按鈕!
為了讓你與學員能立即動手測試本頁所有 GAS 程式碼與內建 Gemini Spark,我們已預先製作好 4 份結構完全對齊 GAS 腳本欄位的 Office 底稿範本(故意保留未美化素色、殘留 **星號** 與急件測試資料,執行 GAS 時一鍵變身超有感!):
內含「總表(12筆含負面急件與4大部門)」+「練習區_待Gemini分析」雙工作表。
📥 下載 Tab2_Sheets.xlsx內含大量殘留 **粗體星號**、H1/H2 標題、2 張素色表格與【待辦/急件/重要】關鍵字。
第 2 頁內建可直接複製的 5 頁「標題|重點|口白」大綱,一鍵長出投影片與備忘稿!
📥 下載 Tab4_Slides.pptx內含 A~E 欄學員名單、Gemini 專屬回饋與「已寄出防呆跳過」測試列,可立即產生 Gmail 草稿。
📥 下載 Tab5_跨產品.xlsx.xlsx、.docx 或 .pptx 上傳到 Google Drive 並點開時,若檔名旁邊出現綠/藍/橘色的 .XLSX / .DOCX / .PPTX 標籤(代表處於 Office 相容檢視模式,此時不會顯示 Apps Script 選單),請點選左上角選單:想讓「問問 Gemini」一次寫出不會報錯、又好用的 GAS 程式碼,提示詞只要包含以下 4 個要素:
請幫我寫一段適用於【Google Sheets / Docs / Slides】的 Google Apps Script (GAS) 完整程式碼。 需求如下: 1. 【自訂選單】:請包含 onOpen() 函式,在頂部選單新增一個名為「🚀 AI 自動化工具箱」的選單,裡面有一個項目叫「【填入功能名稱】」。 2. 【資料結構】:我的檔案中,【描述資料位置,例如:工作表「總表」的第 1 列是標題,A 欄是日期、B 欄是客戶、C 欄是 Gemini 產出的分類標籤】。 3. 【執行動作】:當我點擊選單按鈕時,請自動幫我: - 動作一:【例如:依照 C 欄的分類標籤,自動建立對應名稱的子工作表並將資料複製過去】 - 動作二:【例如:將所有分頁的第 1 列標題設為深藍底(#1a73e8)白字粗體、凍結第 1 列,並自動調整欄寬】 4. 【防呆機制】:如果子工作表已經存在,請覆寫更新內容不要報錯;全部執行完成後,請跳出視窗提示「處理完成!」。 請提供完整可貼上執行的程式碼,並加上繁體中文註解。
.html 教材,網頁秒變專屬 AI 助教![HTML] 精準來源標記
很多人以為「問問 Gemini (Ask Gemini)」只能拿來摘要一般新聞網頁或看 PDF;其實直接把 .html 網頁檔(例如您現在正在看的這份 index.html 教材,或從系統匯出的 HTML 報表)餵給「問問 Gemini」,效率高得驚人!
因為 HTML 具備天然的語意標籤(<title>、<h1>~<h4>、<table>、<section>),AI 不必像讀 PDF 那樣猜測跨頁排版,能在 1 秒內精準讀懂 1,800 行教材架構並標註 [HTML] 引用出處!
index.html 丟給「問問 Gemini」秒梳理解析
🔍 點圖放大
▲ 實測亮點觀察:① 上傳或讀取 </> index.html 後,只輸入一句「我是AI老師,我想指導學生善用 gemini spark+ask gemini…」;② AI 瞬間識別出「謝老師規劃之 Office GAS × ✦ 問問 Gemini 雙劍合璧實戰工作台」,並附上 [HTML] 來源標籤自動梳理出核心脈絡與教學指導架構!
.html 餵給「問問 Gemini」是學員課後最強外掛?
.html 的結構樹(DOM)比 PDF 更乾淨,AI 抓表格與程式碼區塊 100% 不斷行。我是 AI 老師(或部門內部種子講師),我想指導學員善用「內建 Gemini Spark + 問問 Gemini (Ask Gemini) 寫 GAS」。請讀取這份 index.html 教材,幫我梳理出: 1. 這份教材的核心脈絡與教學亮點 2. 適合帶領零程式基礎學員實作的 3 個循序漸進步驟 3. 每個步驟容易卡關的提醒與課堂引導提問
請讀取這份 index.html 教材中「Tab 2(Google Sheets 拆表美化)」與「Tab 5(跨產品寄信)」的 GAS 範本結構。 現在請幫我改寫成適合我部門的 GAS 程式碼: - 我的總表名稱叫「115年教育訓練報名表」 - 第 1 列是標題,B 欄是「學員姓名」、D 欄是「所屬處室」、F 欄是「Email」、H 欄是「審核狀態」 - 請幫我加上頂部自訂選單:一鍵依 D 欄「所屬處室」拆成獨立分頁美化,並在 H 欄打勾時自動寄出錄取通知信!
Tab2_Sheets_客戶回饋與拆表實戰底稿.xlsx):✦ 問問 Gemini 自動填出 D~F 欄)。
真實工作痛點:當你收回 200 筆「客訴與問卷回饋」或「各部門採購申請單」時,既需要看懂文字內容並分類(語意理解),又需要把總表依部門或情緒拆成獨立分頁、標記紅色警示、鎖定表頭並美化格式(重複性苦工)。看我們如何用「內建 Gemini Spark + Ask Gemini 寫 GAS」在 2 分鐘內搞定!
✦ 問問 Gemini 側邊欄,輸入:「請幫我分析目前工作表 C 欄的『客戶回饋內容』,幫我建立表格或填入 D 欄『情緒判定(正面/中立/負面急件)』與 E 欄『建議負責部門(客服部/物流部/產品部)』及 F 欄『一句話處理建議』。」
請幫我寫一段 Google Sheets 的 Google Apps Script (GAS) 程式碼。 我的試算表第一個工作表為「總表」,第 1 列是標題列(A:日期、B:客戶名稱、C:回饋內容、D:情緒判定、E:負責部門、F:處理建議)。 請幫我寫出以下功能: 1. 建立 onOpen() 選單,名稱為「📊 AI 報表神器」,內含: - 「1. 標記急件並美化總表」 - 「2. 依負責部門自動拆出子工作表」 2. 當點選「1. 標記急件並美化總表」時: - 將第 1 列標題設為深綠底(#0d9488)白字粗體、置中,並凍結第 1 列。 - 檢查 D 欄(第 4 欄),若內容包含「負面」或「急件」,將該筆整列背景設為淺紅底(#fee2e2)、文字深紅粗體;其餘資料列設為白灰相間的視覺效果。 - 自動調整 A 到 F 欄的欄寬。 3. 當點選「2. 依負責部門自動拆出子工作表」時: - 讀取「總表」E 欄(第 5 欄)的所有不重複部門名稱。 - 為每個部門建立獨立工作表(若已存在則先清空內容再寫入),複製標題列與屬於該部門的所有資料列,並同樣套用深藍標題列(#1a73e8)、凍結首列與自動調整欄寬。 - 完成後跳出視窗告知共拆出幾個部門分頁。
/**
* Google Sheets 雙劍合璧實戰:急件標記、報表美化與自動依部門拆表
* 講師:謝老師|AI 辦公自動化實戰範例
*/
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('📊 AI 報表神器')
.addItem('1. 標記急件並美化總表', 'formatMainSheet')
.addItem('2. 依負責部門自動拆出子工作表', 'splitSheetsByDepartment')
.addToUi();
}
/**
* 功能一:美化總表並自動將「負面/急件」整列標記為淺紅底警示
*/
function formatMainSheet() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('總表') || ss.getSheets()[0];
const lastRow = sheet.getLastRow();
const lastCol = sheet.getLastColumn();
if (lastRow < 2) {
SpreadsheetApp.getUi().alert('目前沒有足夠的資料列可處理!');
return;
}
// 1. 設定第 1 列標題格式與凍結
const headerRange = sheet.getRange(1, 1, 1, lastCol);
headerRange
.setBackground('#0d9488')
.setFontColor('#ffffff')
.setFontWeight('bold')
.setHorizontalAlignment('center');
sheet.setFrozenRows(1);
// 2. 讀取資料並依 D 欄(第 4 欄)情緒判定標色
const data = sheet.getRange(2, 1, lastRow - 1, lastCol).getValues();
const bgColors = [];
const fontColors = [];
const fontWeights = [];
for (let i = 0; i < data.length; i++) {
const sentiment = String(data[i][3] || '');
const isUrgent = sentiment.includes('負面') || sentiment.includes('急件');
const rowBg = isUrgent ? '#fee2e2' : (i % 2 === 0 ? '#ffffff' : '#f8fafc');
const rowColor = isUrgent ? '#991b1b' : '#1e293b';
const rowWeight = isUrgent ? 'bold' : 'normal';
bgColors.push(new Array(lastCol).fill(rowBg));
fontColors.push(new Array(lastCol).fill(rowColor));
fontWeights.push(new Array(lastCol).fill(rowWeight));
}
const bodyRange = sheet.getRange(2, 1, lastRow - 1, lastCol);
bodyRange.setBackgrounds(bgColors)
.setFontColors(fontColors)
.setFontWeights(fontWeights)
.setVerticalAlignment('middle');
sheet.autoResizeColumns(1, lastCol);
ss.toast('總表美化與急件紅底標記完成!', '✅ 執行成功', 4);
}
/**
* 功能二:依 E 欄(第 5 欄:負責部門)自動拆分獨立工作表
*/
function splitSheetsByDepartment() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const mainSheet = ss.getSheetByName('總表') || ss.getSheets()[0];
const allValues = mainSheet.getDataRange().getValues();
if (allValues.length < 2) return;
const headers = allValues[0];
const deptMap = {};
// 依第 5 欄 (index 4) 分組
for (let i = 1; i < allValues.length; i++) {
const row = allValues[i];
const deptName = String(row[4] || '未分類').trim();
if (!deptMap[deptName]) deptMap[deptName] = [];
deptMap[deptName].push(row);
}
const deptNames = Object.keys(deptMap);
deptNames.forEach(dept => {
let targetSheet = ss.getSheetByName(dept);
if (!targetSheet) {
targetSheet = ss.insertSheet(dept);
} else {
targetSheet.clear();
}
const outputRows = [headers, ...deptMap[dept]];
targetSheet.getRange(1, 1, outputRows.length, headers.length).setValues(outputRows);
// 美化子工作表標題與欄寬
targetSheet.getRange(1, 1, 1, headers.length)
.setBackground('#1a73e8')
.setFontColor('#ffffff')
.setFontWeight('bold');
targetSheet.setFrozenRows(1);
targetSheet.autoResizeColumns(1, headers.length);
});
SpreadsheetApp.getUi().alert('🎉 拆表完成!共自動更新了 ' + deptNames.length + ' 個部門工作表:\n' + deptNames.join('、'));
}
許多同學第一次貼上 GAS 按下「執行」時,看到 Google 跳出「需要授權」與「這個應用程式未經 Google 驗證」紅色三角形警告就會嚇到不敢點!其實這只是因為這段腳本是你自己剛建立的私人工具(未上架公開商店),只要照著下方 Step 4 ~ Step 7 點選「進階 ➔ 前往未命名的專案 ➔ 繼續」(每份檔案只需授權一次),就能立刻解鎖上方專屬選單!
myFunction()
🔍 點圖放大
程式碼.gs 編輯區後,按 Ctrl + A 全選並刪除預設的空白 function myFunction() { }(避免兩個函式撞名或殘留括號報錯)。
Tab3_Docs_企劃書清除星號與企業排版底稿.docx):**粗體星號殘留**、未上色的 H1/H2 標題、2 張素色表格,以及多處「待辦/重要/急件/TODO/注意」字眼。上傳至 Google Drive 並點選「檔案 ➔ 另存為 Google 文件」後,執行下方 GAS 即可一鍵體驗瞬間變成正式品牌企劃書的快感!
真實工作痛點:我們常在 Google Docs 裡用 ✦ Help me write 生成企劃書、會議紀錄,或從外部 AI 貼上文章,結果整份文件裡到處都是殘留的 **粗體星號**、標題字型大小不一、表格沒有表頭底色、重點關鍵字也沒有醒目標記。與其手動改半小時,不如請「問問 Gemini」幫我們寫一顆「📐 文件品牌排版魔法師」按鈕!
請幫我寫一段適用於 Google Docs 的 Google Apps Script (GAS) 程式碼。 我常使用 Gemini 在 Docs 裡生成企劃書草稿,希望能一鍵完成標準化排版: 1. 請建立 onOpen() 選單,名稱為「📝 文件排版魔法師」,內含: - 「1. 清除 Markdown 星號並轉為真粗體」 - 「2. 套用企業標準標題與表格美化」 - 「3. 高亮標記所有【重要/待辦】關鍵字」 2. 「清除 Markdown 星號並轉為真粗體」功能: - 自動搜尋文件中所有形如 `**文字**` 的段落,將裡面的文字設為粗體,並把前後的 `**` 符號刪除。 3. 「套用企業標準標題與表格美化」功能: - 將所有 HEADING_1 設為深藍色(#1a73e8)、粗體;HEADING_2 設為墨綠色(#0d9488)、粗體。 - 將內文段落行距統一設為 1.5 倍。 - 將文件中所有表格(Table)的第 1 列設為深藍底(#1a73e8)白字粗體,其餘列設為淺灰邊框。 4. 「高亮標記關鍵字」功能: - 自動尋找文件中所有的「待辦」、「重要」、「急件」、「TODO」、「注意」,使用 RangeElement 的 getStartOffset() 與 getEndOffsetInclusive() 將背景標上亮黃色螢光筆(#fef08a)與紅字粗體。
/**
* Google Docs 雙劍合璧實戰:AI 草稿一鍵轉企業標準排版與清理星號
* 講師:謝老師|AI 辦公自動化實戰範例
*/
function onOpen() {
DocumentApp.getUi()
.createMenu('📝 文件排版魔法師')
.addItem('1. 清除 **星號** 並轉為真粗體', 'cleanMarkdownBold')
.addItem('2. 套用企業標準標題與表格美化', 'applyCorporateStyle')
.addItem('3. 螢光筆高亮【重要/待辦】關鍵字', 'highlightKeywords')
.addToUi();
}
/**
* 功能一:將 **文字** 轉為真正的粗體並移除 **
*/
function cleanMarkdownBold() {
const body = DocumentApp.getActiveDocument().getBody();
const paragraphs = body.getParagraphs();
let count = 0;
paragraphs.forEach(p => {
const textObj = p.editAsText();
let str = textObj.getText();
const regex = /\*\*([^*]+)\*\*/g;
let match;
const matches = [];
while ((match = regex.exec(str)) !== null) {
matches.push({ start: match.index, end: regex.lastIndex - 1 });
}
// 由後往前處理,避免索引偏移
for (let i = matches.length - 1; i >= 0; i--) {
const m = matches[i];
textObj.setBold(m.start, m.end, true);
textObj.deleteText(m.end - 1, m.end);
textObj.deleteText(m.start, m.start + 1);
count++;
}
});
DocumentApp.getUi().alert('✅ 清理完成!共將 ' + count + ' 處 **星號** 轉為正式粗體。');
}
/**
* 功能二:統一 H1/H2 標題顏色、行距與所有表格表頭美化
*/
function applyCorporateStyle() {
const body = DocumentApp.getActiveDocument().getBody();
// 1. 處理段落與標題
body.getParagraphs().forEach(p => {
p.setLineSpacing(1.5);
const heading = p.getHeading();
if (heading === DocumentApp.ParagraphHeading.HEADING1) {
p.editAsText().setForegroundColor('#1a73e8').setBold(true);
} else if (heading === DocumentApp.ParagraphHeading.HEADING2) {
p.editAsText().setForegroundColor('#0d9488').setBold(true);
}
});
// 2. 美化文件內所有表格
body.getTables().forEach(table => {
table.setBorderColor('#cbd5e1');
const numRows = table.getNumRows();
for (let r = 0; r < numRows; r++) {
const row = table.getRow(r);
const isHeader = (r === 0);
for (let c = 0; c < row.getNumCells(); c++) {
const cell = row.getCell(c);
cell.setBackgroundColor(isHeader ? '#1a73e8' : (r % 2 === 0 ? '#f8fafc' : '#ffffff'));
cell.editAsText()
.setForegroundColor(isHeader ? '#ffffff' : '#1e293b')
.setBold(isHeader);
}
}
});
DocumentApp.getUi().alert('🎉 企業標準標題與表格美化已全部套用!');
}
/**
* 功能三:自動高亮標記關鍵字
*/
function highlightKeywords() {
const body = DocumentApp.getActiveDocument().getBody();
const keywords = ['待辦', '重要', '急件', 'TODO', '注意'];
let total = 0;
keywords.forEach(kw => {
let found = body.findText(kw);
while (found) {
const elem = found.getElement().asText();
elem.setBackgroundColor(found.getStartOffset(), found.getEndOffsetInclusive(), '#fef08a');
elem.setForegroundColor(found.getStartOffset(), found.getEndOffsetInclusive(), '#b91c1c');
elem.setBold(found.getStartOffset(), found.getEndOffsetInclusive(), true);
total++;
found = body.findText(kw, found);
}
});
DocumentApp.getUi().alert('🔍 標記完成!共高亮標示了 ' + total + ' 處關鍵字。');
}
Tab4_Slides_簡報速生器與講稿自動生成底稿.pptx):真實工作痛點:在 Google Slides 裡,內建的 Gemini Spark 雖然可以幫單張投影片生成圖片或改寫文字,但當你有一份 8~15 頁的報告大綱時,一頁一頁新增投影片、貼標題、貼條列重點、還要把講稿貼進下方「講者備忘稿 (Speaker Notes)」非常耗時!透過「Ask Gemini 產結構大綱 + GAS 批次生出所有頁面與講稿 + Slides 內建 Gemini 補配圖」,簡報製作速度直接提升 10 倍!
「請將這份內容規劃成 6 頁簡報大綱,每頁一行,嚴格使用『頁面標題 | 重點1;重點2;重點3 | 100字講者上台口白』的格式輸出,不要加其他多餘符號。」
請幫我寫一段適用於 Google Slides 的 Google Apps Script (GAS) 程式碼。 功能需求: 1. 建立 onOpen() 選單,名稱為「📽️ AI 簡報速生器」,包含兩個功能: - 「1. 貼上大綱批次生成投影片(含講者備忘稿)」 - 「2. 一鍵為所有投影片加上重點頁尾標章」 2. 當點選「1. 貼上大綱批次生成投影片」時: - 跳出一個 Prompt 輸入框,讓我貼上由 Gemini 產出的多行大綱(每行格式為:`標題 | 重點1;重點2;重點3 | 講者備忘稿`)。 - 若我未輸入,請內建 3 頁預設的 AI 辦公室轉型示範大綱直接生成。 - 針對每一行使用 `pres.appendSlide()` 新增投影片並清空預設元素後,用 `slide.insertTextBox()` 建立深藍色粗體標題與條列式內文(請勿使用 PredefinedLayout.TITLE_AND_BODY,確保 PowerPoint 轉檔母片也 100% 相容),並將第 3 欄自動寫入該頁的 Speaker Notes(講者備忘稿)中! 3. 當點選「2. 一鍵為所有投影片加上頁尾標章」時: - 在每張投影片右下角自動加上一個小文字方塊「AI 辦公室自動化實戰」。
/**
* Google Slides 雙劍合璧實戰:大綱一鍵批次生成投影片 + 自動寫入講者備忘稿
* 講師:謝老師|AI 辦公自動化實戰範例
*/
function onOpen() {
SlidesApp.getUi()
.createMenu('📽️ AI 簡報速生器')
.addItem('1. 貼上大綱批次生成投影片(含講稿)', 'generateSlidesFromOutline')
.addItem('2. 一鍵為全簡報加上右下角標章', 'addFooterBadgeToAllSlides')
.addToUi();
}
/**
* 功能一:解析「標題 | 重點1;重點2 | 講者備忘稿」批次建立投影片
* (100% 相容 PowerPoint .pptx 轉檔母片與原生 Google 簡報)
*/
function generateSlidesFromOutline() {
const ui = SlidesApp.getUi();
const res = ui.prompt(
'📽️ 批次生成投影片與講者備忘稿',
'請貼上大綱(格式:標題 | 重點1;重點2 | 講稿)。\n若直接按確定,將自動生成 5 頁雙劍合璧示範投影片:',
ui.ButtonSet.OK_CANCEL
);
if (res.getSelectedButton() !== ui.Button.OK) return;
let rawText = res.getResponseText().trim();
if (!rawText) {
rawText = [
'為什麼需要 AI 雙劍合璧? | 單靠對話視窗無法批次改格式與跨檔搬移;單靠寫程式對行政人員門檻太高;內建 Gemini 動腦 + GAS 動手完美互補 | 各位主管好,這張投影片說明我們為什麼把內建 Gemini 與 GAS 結合使用。',
'痛點一:試算表分類與拆表苦工 | 過去每週花 2 小時人工篩選與複製分頁;現在用 Gemini 判斷情緒與負責部門;點一下 GAS 按鈕 1 秒拆出各部門報表 | 在試算表場景中,我們讓 AI 先做好分類,再由腳本一鍵拆出子工作表。',
'痛點二:文件草稿星號殘留與排版 | AI 生成文章常帶有雙星號與素色表格;透過 Docs GAS 一鍵轉為真粗體並刪除星號;自動將所有表格首列套用企業深藍標準色 | 在文件場景中,再也不用人工逐段調整字型與表格顏色。',
'痛點三:簡報逐頁貼字與寫講稿耗時 | 先請 Gemini 產出「標題|重點|口白」結構;執行 Slides GAS 自動建立投影片與演講稿;最後用右側 Gemini 補充視覺插圖 | 就如同您現在看到的這幾頁投影片,連同我下方的演講稿都是 2 秒自動生成的!',
'下週導入行動與預期效益 | 各部門挑選 1 個最高頻的週報或通知流程;建立共用試算表與專屬自動化按鈕;預計全公司每月可節省超過 120 小時工時 | 最後我們建議從各部門最常做的週報開始導入,謝謝大家!'
].join('\n');
}
// 若瀏覽器單行輸入框自動把多行換行轉為空白,自動在講稿句尾與下一頁標題間補回換行
rawText = rawText.replace(/([。!!])\s+(?=[^||\n]{2,45}[||])/g, '$1\n');
const pres = SlidesApp.getActivePresentation();
const lines = rawText
.split(/\r?\n/)
.map(l => l.replace(/^[•\-*\d.]+\s*/, '').trim())
.filter(l => l.includes('|') || l.includes('|'));
let createdCount = 0;
lines.forEach(line => {
const parts = line.split(/[||]/).map(s => s.trim());
const titleText = parts[0] || '未命名標題';
const bullets = (parts[1] || '').split(/[;;]/).map(b => b.trim()).filter(Boolean);
const notesText = parts[2] || '';
// 相容 .pptx 轉檔母片:直接新增投影片並清除預設框,改用 insertTextBox 精準排版
const slide = pres.appendSlide();
slide.getPageElements().forEach(el => el.remove());
const titleShape = slide.insertTextBox(titleText, 40, 28, 640, 48);
titleShape.getText().getTextStyle()
.setFontSize(22)
.setForegroundColor('#1a73e8')
.setBold(true);
if (bullets.length > 0) {
const bodyShape = slide.insertTextBox(bullets.join('\n'), 45, 92, 630, 240);
bodyShape.getText().getTextStyle()
.setFontSize(15)
.setForegroundColor('#1e293b')
.setBold(false);
bodyShape.getText().getListStyle()
.applyListPreset(SlidesApp.ListPreset.DISC_CIRCLE_SQUARE);
}
if (notesText) {
slide.getNotesPage().getSpeakerNotesShape().getText().setText(notesText);
}
createdCount++;
});
ui.alert('🎉 生成完成!已自動建立 ' + createdCount + ' 張投影片並寫入講者備忘稿。\n現在可打開右側「✦ Gemini」為重點頁面生成配圖!');
}
/**
* 功能二:批次在每張投影片右下角加上標章
*/
function addFooterBadgeToAllSlides() {
const pres = SlidesApp.getActivePresentation();
const slides = pres.getSlides();
slides.forEach(slide => {
const box = slide.insertTextBox('AI 智慧辦公實戰', 560, 370, 145, 24);
box.getText().getTextStyle().setFontSize(9).setForegroundColor('#64748b').setBold(true);
});
SlidesApp.getUi().alert('✅ 已為全部 ' + slides.length + ' 張投影片加上右下角標章!');
}
Tab5_跨產品_Sheets結合Gmail自動草稿與寄信底稿.xlsx):✅ 已寄出 示範防呆跳過機制;第 2~4 筆已預填 Gemini 個人化回覆,可將 B3 信箱改為你自己的 Email 後點選「1. 批次建立 Gmail 草稿」到 Gmail 草稿匣檢視成果;第 5 筆留白供練習用右側 ✦ 問問 Gemini 生成回覆)。
為什麼這是必學大招?單一產品內的「✦ 內建 Gemini Spark」無法跨越檔案邊界去「自動複製 Docs 範本、轉成 PDF、再用 Gmail 批次寄給 50 個人」。但當我們先在 Google Sheets 裡用內建 Gemini 為每個人寫好客製化問候或審核評語,再請 「問問 Gemini」幫我們寫一段跨產品 GAS,就能一鍵串起整個 Google Workspace 生態系!
請幫我寫一段串接 Google Sheets 與 Gmail 的 Google Apps Script (GAS) 程式碼。 我的試算表欄位如下(第 1 列為標題): - A 欄:學員/客戶姓名 - B 欄:Email 信箱 - C 欄:報名項目或提問內容 - D 欄:由內建 Gemini 產出的「個人化回覆與建議」 - E 欄:寄送狀態 請幫我設計: 1. 在頂部建立「🚀 跨產品自動化」選單,包含两个按鈕: - 「1. 批次產生 Gmail 草稿(含人工檢查)」 - 「2. 批次直接寄出信件並標記時間戳記」 2. 程式執行時,請逐列檢查: - 若 B 欄沒有 Email,或 E 欄已經包含「已」字樣,則自動跳過(防止重複寄送)。 - 將 A 欄姓名、C 欄項目與 D 欄 Gemini 個人化建議,組合成排版美觀的 HTML 信件。 - 若點選「產生草稿」,則使用 `GmailApp.createDraft()` 建立草稿並在 E 欄填入「📝 已建草稿 + 時間」並標為淺黃底。 - 若點選「直接寄出」,則先跳出確認視窗,確認後使用 `GmailApp.sendEmail()` 寄出,並在 E 欄填入「✅ 已寄出 + 時間」並標為淺綠底。
/**
* 跨產品綜合實戰:Sheets 名單 + Gemini 客製評語 ➔ 批次產生 Gmail 草稿或寄信(含防重寄機制)
* 講師:謝老師|AI 辦公自動化實戰範例
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('🚀 跨產品自動化')
.addItem('1. 批次建立 Gmail 草稿(安全人工檢查模式)', 'createPersonalizedDrafts')
.addItem('2. 批次直接寄出 Gmail 信件(含防重寄標記)', 'sendPersonalizedEmails')
.addSeparator()
.addItem('3. 📊 查詢今日剩餘免費寄信額度', 'checkRemainingEmailQuota')
.addToUi();
}
function checkRemainingEmailQuota() {
const remaining = MailApp.getRemainingDailyQuota();
SpreadsheetApp.getUi().alert(
'📊 今日 Gmail 自動寄信剩餘額度',
'您目前的 Google 帳號今天還可以透過 GAS 自動寄出:' + remaining + ' 封信!\n\n' +
'💡 額度參考:\n• 個人 @gmail.com 帳號(含付費 Google One / Gemini Pro 個人戶):GAS 皆固定為每日 100 封(若改走外部 SMTP 應用程式密碼則為 500 封/日)\n• 公司專屬網域 Workspace (@company.com 正式付費滿期):GAS 最高每日 1,500 封(SMTP / API 為 2,000 封/日)',
SpreadsheetApp.getUi().ButtonSet.OK
);
}
function createPersonalizedDrafts() {
processMailWorkflow(true);
}
function sendPersonalizedEmails() {
const ui = SpreadsheetApp.getUi();
const confirm = ui.alert(
'📧 確認批次寄信',
'即將針對 E 欄尚未處理的名單直接寄出客製化信件,是否繼續?',
ui.ButtonSet.YES_NO
);
if (confirm === ui.Button.YES) {
processMailWorkflow(false);
}
}
/**
* 核心工作流:支援「僅建草稿 (isDraft=true)」與「直接寄出 (isDraft=false)」
*/
function processMailWorkflow(isDraft) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;
const range = sheet.getRange(2, 1, lastRow - 1, 5);
const rows = range.getValues();
let count = 0;
for (let i = 0; i < rows.length; i++) {
const [name, email, topic, aiReply, status] = rows[i];
if (!email || String(status).includes('已')) continue;
const subject = '【專屬回饋通知】' + name + ' 您好,關於「' + topic + '」的 AI 建議與回覆';
const htmlBody = [
'<div style="font-family:sans-serif; line-height:1.7; color:#1e293b; max-width:600px;">',
' <h3 style="color:#1a73e8;">親愛的 ' + name + ' 您好:</h3>',
' <p>感謝您提交關於 <strong>「' + topic + '」</strong> 的資訊,以下為我們為您整理的重點建議:</p>',
' <div style="background:#f8fafc; border-left:4px solid #1a73e8; padding:12px 16px; margin:14px 0;">',
String(aiReply || '感謝您的參與!').replace(/\n/g, '<br>'),
' </div>',
' <p style="font-size:12px; color:#64748b;">本信件由 Google Sheets ✕ Gemini 智慧工作流輔助產生</p>',
'</div>'
].join('');
const timestamp = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm');
const statusCell = sheet.getRange(i + 2, 5);
if (isDraft) {
GmailApp.createDraft(email, subject, '', { htmlBody: htmlBody });
statusCell.setValue('📝 已建草稿 (' + timestamp + ')').setBackground('#fef9c3');
} else {
GmailApp.sendEmail(email, subject, '', { htmlBody: htmlBody });
statusCell.setValue('✅ 已寄出 (' + timestamp + ')').setBackground('#dcfce7');
}
count++;
}
SpreadsheetApp.getUi().alert(
isDraft
? '📝 已成功建立 ' + count + ' 封 Gmail 草稿!請打開 Gmail「草稿匣」檢查無誤後再寄出。'
: '🎉 已成功寄出 ' + count + ' 封客製化信件,並於 E 欄標記完成時間!'
);
}
「我剛剛執行這段 GAS 時跳出這行錯誤訊息:【貼上紅字錯誤】,請幫我修正並給我完整的新版程式碼。」——Gemini 通常在 5 秒內就能幫你修好!
Active User,而非固定為試算表擁有者)。@gmail.com(含付費買 Google One / Gemini Pro):只要信箱尾綴是 @gmail.com,在 GAS 底層皆視為個人 Consumer 帳號,GAS 寄信上限一律鎖定為 100 封/日!@gmail.com 改走「16 碼應用程式密碼 (SMTP)」或「Gmail API」:每日上限會提升 5 倍至 500 封/日(滾動 24 小時計算)。@company.com):需為企業網域且過試用暖機期,GAS 上限才會開放至 1,500 封/日(走外部 SMTP / API 則為 2,000 封/日)。| 比較項目 | ① GAS 內部原生寄信(本課做法 ⭐)GmailApp / MailApp |
② 外部程式走傳統 SMTP Python smtplib / Excel VBA / 舊腳本 |
③ 外部程式呼叫 Google Gmail API Python / Node.js 獨立系統串接 |
|---|---|---|---|
| 運作位置與通道 | Google 雲端內部原生執行 直接呼叫內部 GmailApp 服務,不經過外部網路 SMTP 埠號。 |
外部電腦/伺服器連線 需透過網路連線至 smtp.gmail.com(Port 465 或 587)。 |
外部電腦/伺服器呼叫 REST API 透過 HTTPS 呼叫 gmail.googleapis.com 端點。 |
| 是否需申請 16 碼 「應用程式密碼」? |
❌ 完全不用! 免開兩步驟驗證、免找 16 碼密碼。 |
⚠️ 強制必須! 必須先開啟 Google 兩步驟驗證,再申請 16 碼 App Password 寫入程式。 |
❌ 不用應用程式密碼 但須改走更複雜的 GCP OAuth 2.0 憑證交換流程。 |
| 是否需建立 GCP 專案 與下載 JSON 憑證? |
❌ 完全不用! 首次執行時於畫面點擊「審查權限 ➔ 進階 ➔ 繼續」即完成內部 OAuth 授權。 |
❌ 不用建立 GCP 專案, 但常被公司防火牆封鎖 SMTP 埠號(Port 25/465/587)導致連線失敗。 |
⚠️ 強制必須! 需進 Google Cloud Console 建立專案、啟用 Gmail API、設定 OAuth 同意畫面、下載 credentials.json 並產生 token.json。 |
| 資安風險與分享性 | 🛡️ 最高(零帳密外洩風險) 程式碼內完全不含任何帳號密碼,整份試算表直接複製給同事即可各自安全使用。 |
⚡ 高風險 16 碼密碼常被明文寫在程式碼或 .env 中,檔案外流即遭盜寄。 |
🔒 中高(需妥善保管 Token) 需自行管理 credentials.json 與 Refresh Token 更新機制。 |
| 每日寄信額度上限 (滾動 24 小時) |
• 個人 @gmail.com(含 Google One / Gemini 付費戶):100 封/日• 企業網域 Workspace( @company.com 正式期):1,500 封/日(註:Workspace 新開通試用期亦會暫時限縮於 100~500 封) |
• 個人 @gmail.com(含 Google One 付費戶):500 封/日(比 GAS 多 5 倍!)• 企業網域 Workspace:2,000 封/日(若設定 Workspace SMTP Relay 最高可達 10,000 封/日) |
• 個人 @gmail.com(含 Google One 付費戶):500 封/日• 企業網域 Workspace:2,000 封/日 (另受每分鐘 API Quota Units 併發速率限制) |
| 最佳適用場景與突破限制做法 | 辦公室日常自動化首選(100 封內):名單通知、報表派送、建立草稿。 💡 若想在 GAS 突破 100 封,可用 UrlFetchApp 串接 Brevo(免費 300 封/日)或 Resend API! |
本機 Python 腳本需寄送 100~500 封/日時,改走 16 碼應用程式密碼 + SMTP 即可突破 GAS 的 100 封限制。 | 企業大型外部網站後端、ERP/CRM 獨立伺服器系統整合。 |