2024年10月25日 星期五

運用 Google Apps Script 製作代課簽到單合併列印

學校今年大約有 10 幾位代課老師需要代課簽到單,
看到教學組都會花時間在手動修改日期和列印這個簽到單,
於是想說有沒有比較好的方法可以稍微節省一下教學組的時間。
於是就出現了這個啦!



不過這次的問題,
在於設計好剛好 Googel 文件一頁的表格,
想不到套印複製到新檔案後,就變成預設邊界,
導致表格就跑版無法在一個頁面上。
雖然 Gemini 也有提供相關的設定方式,
但一直卡在新檔案的邊界無法如願設定。
所以腦海中就換了另一個想法,如果我先設定好一個目標檔案的邊界,
再將套印的資料複製過去不知是否可行, Yes ! 想不到就搞定了!
一起來看成果吧!!





🎯 程式使用方法:
需準備一個 Google試算表:Google試算表ID

 Google試算表檔案下載(套印名字、日期函數)

=TEXT(WEEKDAY(A1),"ddd")

=CHOOSE(WEEKDAY(A1),"日","一","二","三","四","五","六")


二個Google文件檔案:Google文件範本ID套印目標Google文件ID
(🎯 範本檔案下載🎯 請自行設定好一個設定好邊界的空白套印目標檔案)


// --- 設定全域變數 ---
var spreadsheetId = 'Google試算表ID';
var templateDocumentId = 'Google文件範本ID';
var targetDocumentId = '套印目標Google文件ID';

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('🚀自訂選單')
    .addItem('📄開啟範本文件', 'openTemplateDocumentLink')
    .addItem('📆顯示假日', 'showWeekends')
    .addItem('📅隱藏假日', 'hideWeekends')
    .addSeparator()
    .addItem('🎯合併所有資料', 'mergeDataToPages')
    .addItem('🗑️刪除文件內容', 'clearTargetDocument')
    .addToUi();
}

function showWeekends() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getActiveSheet();
  sheet.showRows(1, sheet.getMaxRows());
}

function hideWeekends() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getActiveSheet();
  const lastRow = sheet.getLastRow();
  const dateColumn = 1; // 假設日期在 A 欄

  for (let i = 1; i <= lastRow; i++) {
    const date = sheet.getRange(i, dateColumn).getValue();
    const dayOfWeek = new Date(date).getDay(); // 0 是星期日,6 是星期六
    if (dayOfWeek === 0 || dayOfWeek === 6) {
      sheet.hideRows(i);
    }
  }
}

function mergeDataToPages() {
  var placeholder = '{{姓名}}';

  // --- 錯誤處理:檢查檔案是否存在 ---
  try {
    SpreadsheetApp.openById(spreadsheetId);
    DriveApp.getFileById(templateDocumentId);
    DocumentApp.openById(targetDocumentId);
  } catch (e) {
    SpreadsheetApp.getActiveSpreadsheet().toast('檔案不存在,請確認 ID 是否正確', '錯誤');
    return;
  }

  // --- 取得 Google 試算表資料 ---
  var ss = SpreadsheetApp.openById(spreadsheetId);
  var sheet = ss.getSheetByName('工作表1');
  var data = sheet.getRange('A2:A').getValues().filter(row => row[0] !== "");

  // --- 取得目標文件 ---
  var targetDoc = DocumentApp.openById(targetDocumentId);
  var targetBody = targetDoc.getBody();

  // --- 取得 Google 文件範本檔案 ---
  var templateFile = DriveApp.getFileById(templateDocumentId);
  var templateDoc = DocumentApp.openById(templateFile.getId());
  var templateBody = templateDoc.getBody();

  // --- 移除範本文件最上方的空白段落 ---
  var firstElement = templateBody.getChild(0);
  if (firstElement.getType() === DocumentApp.ElementType.PARAGRAPH && firstElement.getText().trim() === "") {
    templateBody.removeChild(firstElement);
  }

  // --- 顯示合併中視窗 ---
  var htmlOutput = HtmlService.createHtmlOutput('<p>資料合併中,請稍候...</p>');
  var ui = SpreadsheetApp.getUi();
  var dialog = ui.showModelessDialog(htmlOutput, '合併中');

  // --- 顯示合併進度 ---
  SpreadsheetApp.getActiveSpreadsheet().toast('開始合併...', '合併進度');

  // --- 套印資料 ---
  for (var i = 0; i < data.length; i++) {
    if (data[i][0] !== "") {
      // --- 複製範本內容到目標文件 ---  
      var templateElements = templateBody.getNumChildren();
      for (var j = 0; j < templateElements; j++) {
        var element = templateBody.getChild(j).copy();
        // --- 立即將資料複製到目標文件 ---
        if (element.getType() === DocumentApp.ElementType.TABLE) {
          var table = element;
          for (var r = 0; r < table.getNumRows(); r++) {
            for (var c = 0; c < table.getRow(r).getNumCells(); c++) {
              var cell = table.getRow(r).getCell(c);
              cell.replaceText(placeholder, data[i][0]);
            }
          }
          targetBody.appendTable(table); // 使用 appendTable() 附加表格
        } else if (element.getType() === DocumentApp.ElementType.PARAGRAPH) {
          targetBody.appendParagraph(element); // 使用 appendParagraph() 附加段落
        } else if (element.getType() === DocumentApp.ElementType.LIST_ITEM) {
          targetBody.appendListItem(element); // 使用 appendListItem() 附加清單項目
        } else {
          // 如果是其他未處理的元素類型,則顯示警告訊息
          Logger.log('未處理的元素類型:' + element.getType());
        }
      }

      // --- 從第二次執行開始,且非最後一次執行時插入分頁符號 ---
      if (i > 0 && i < data.length - 1) {
        targetBody.appendPageBreak();
      }

      // --- 更新合併進度 ---
      SpreadsheetApp.getActiveSpreadsheet().toast(`已合併 ${i + 1}/${data.length}`, '合併進度');
    }
  }

  // --- 移除文件最後的空白頁 ---
  var lastChild = targetBody.getChild(targetBody.getNumChildren() - 1);
  if (lastChild.getType() == DocumentApp.ElementType.PAGE_BREAK) {
    targetBody.removeChild(lastChild);
  }

  // --- 合併完成訊息 ---
  SpreadsheetApp.getActiveSpreadsheet().toast('合併完成!', '合併進度');

  // --- 關閉「合併中」視窗 ---
  ui.alert('合併完成');

  // --- 開啟目標檔案的連結 ---
  var targetFileLink = `https://docs.google.com/document/d/${targetDocumentId}/edit`;

  // --- 建立 HTML 輸出並開啟目標檔案視窗 ---
  var htmlOutput = HtmlService.createHtmlOutput(`
    <script>
      window.open('${targetFileLink}', '_blank');
      google.script.host.close();
    </script>
  `);

  // --- 顯示開啟目標檔案的對話方塊 ---
  ui.showModalDialog(htmlOutput, '開啟目標檔案');
}

function openTemplateDocumentLink() {
  var templateFileLink = `https://docs.google.com/document/d/${templateDocumentId}/edit`;

  var htmlOutput = HtmlService.createHtmlOutput(`
    <script>
      window.open('${templateFileLink}', '_blank');
      google.script.host.close();
    </script>
  `);
  SpreadsheetApp.getUi().showModalDialog(htmlOutput, '開啟範本檔案');
}

function clearTargetDocument() {
  // --- 錯誤處理:檢查檔案是否存在 ---
  try {
    DocumentApp.openById(targetDocumentId);
  } catch (e) {
    SpreadsheetApp.getActiveSpreadsheet().toast('檔案不存在,請確認 ID 是否正確', '錯誤');
    return;
  }
  var targetDoc = DocumentApp.openById(targetDocumentId);
  var targetBody = targetDoc.getBody();
  targetBody.clear();
  SpreadsheetApp.getActiveSpreadsheet().toast('目標文件已清空!', '完成');
  Browser.msgBox('目標文件已清空!');
}



快來試試吧~~


沒有留言:

張貼留言

歡迎大家一起留言討論!