0. 作品介紹

作者 徐敬嵐 南寮國小

可以跟同事一起訂午餐和好吃的美食


40 1 0
非教學
封面圖 封面

1. AI 提示詞

把這段指令貼給 AI,讓它幫你生成程式碼。

我想要建立一個團購網頁
幫我用Google 試算表建立 【食品品項與點餐名單資料庫】
我希望有些資料我可以直接在試算表更改後,首頁可以直接呈現,並且送單之後會自動更新頁面

需求說明 【1.請列出各種品項的食品與價格2.建立40位學校成員的名單3.統計各種食品品項的數量4.統計每人應支付的金錢總額5.點錯的品項要可以刪除6.希望頁面呈現美味溫馨的風格】
要把它變成程式碼我要發佈
請完整敘述操作步驟 越詳細越好
作業環境
Google試算表
Google Apps Script
請給我後端gs完整程式碼、前端index完整程式碼

2. 資料表規則

請照下面規則建立分頁與欄位。

第1分頁: 菜單 A 欄: 品項 B 欄: 價格 第2分頁: 成員名單 A 欄: 姓名 第3分頁: 訂單紀錄 A 欄: 時間戳記 B 欄: 姓名 C 欄: 品項 D 欄: 金額

📄 解析結果(自動表格)

分頁:菜單
A 欄 B 欄
品項 價格
分頁:成員名單
A 欄
姓名
分頁:訂單紀錄
A 欄 B 欄 C 欄 D 欄
時間戳記 姓名 品項 金額

3. 開啟 Apps Script

從試算表開 Apps Script,才能直接用 SpreadsheetApp 讀寫資料。

  1. 試算表上方選單:擴充功能 → Apps Script
  2. 刪掉預設的程式碼(或全部選取覆蓋)
  3. 保留/建立檔案:
    • Code.gs
    • index.html

4. 貼上後端(Code.gs)

把後端程式貼到 Apps Script 的 Code.gs。

Code.gs(後端)
// ========================================
// 午餐團購系統 - Google Apps Script 後端
// ========================================

/**
 * 顯示前端網頁
 */
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('午餐團購系統')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

/**
 * 取得所有資料(成員、菜單、訂單、統計)
 */
function getData() {
  try {
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    
    // 取得或建立工作表
    var memberSheet = getOrCreateSheet(ss, '成員名單', ['姓名']);
    var menuSheet = getOrCreateSheet(ss, '菜單', ['品項', '價格']);
    var orderSheet = getOrCreateSheet(ss, '訂單記錄', ['姓名', '品項', '價格']);
    
    // 讀取成員名單
    var members = memberSheet.getRange('A2:A').getValues()
      .flat()
      .filter(function(v) { return v !== ''; });
    
    // 讀取菜單
    var menuData = menuSheet.getRange('A2:B').getValues();
    var menu = [];
    for (var i = 0; i < menuData.length; i++) {
      if (menuData[i][0] !== '') {
        menu.push([menuData[i][0], Number(menuData[i][1])]);
      }
    }
    
    // 讀取所有訂單
    var orders = [];
    var orderData = orderSheet.getRange('A2:C').getValues();
    for (var i = 0; i < orderData.length; i++) {
      if (orderData[i][0] !== '') {
        orders.push({
          name: orderData[i][0],
          item: orderData[i][1],
          price: Number(orderData[i][2])
        });
      }
    }
    
    // 計算統計資料
    var stats = calculateStats(orders);
    
    return {
      members: members,
      menu: menu,
      orders: orders,
      stats: stats
    };
    
  } catch (error) {
    Logger.log('getData 錯誤: ' + error.toString());
    throw error;
  }
}

/**
 * 送出訂單
 */
function submitOrder(order) {
  try {
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var orderSheet = ss.getSheetByName('訂單記錄');
    
    if (!orderSheet) {
      throw new Error('找不到訂單記錄工作表');
    }
    
    // 驗證訂單資料
    if (!order.name || !order.item || !order.price) {
      throw new Error('訂單資料不完整');
    }
    
    // 新增訂單到工作表
    orderSheet.appendRow([
      order.name,
      order.item,
      Number(order.price)
    ]);
    
    // 回傳更新後的資料
    return getData();
    
  } catch (error) {
    Logger.log('submitOrder 錯誤: ' + error.toString());
    throw error;
  }
}

/**
 * 刪除訂單
 */
function deleteOrder(index) {
  try {
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var orderSheet = ss.getSheetByName('訂單記錄');
    
    if (!orderSheet) {
      throw new Error('找不到訂單記錄工作表');
    }
    
    // 計算實際列號(+2 是因為:1. 有標題列 2. 索引從0開始但列從1開始)
    var rowNumber = index + 2;
    
    // 確認列號有效
    var lastRow = orderSheet.getLastRow();
    if (rowNumber <= 1 || rowNumber > lastRow) {
      throw new Error('無效的訂單索引');
    }
    
    // 刪除該列
    orderSheet.deleteRow(rowNumber);
    
    // 回傳更新後的資料
    return getData();
    
  } catch (error) {
    Logger.log('deleteOrder 錯誤: ' + error.toString());
    throw error;
  }
}

/**
 * 計算統計資料
 */
function calculateStats(orders) {
  var itemCounts = {};    // 品項統計
  var personTotals = {};  // 個人金額統計
  
  orders.forEach(function(order) {
    // 品項數量統計
    if (!itemCounts[order.item]) {
      itemCounts[order.item] = 0;
    }
    itemCounts[order.item]++;
    
    // 個人金額統計
    if (!personTotals[order.name]) {
      personTotals[order.name] = 0;
    }
    personTotals[order.name] += Number(order.price);
  });
  
  return {
    itemCounts: itemCounts,
    personTotals: personTotals
  };
}

/**
 * 取得或建立工作表
 */
function getOrCreateSheet(ss, sheetName, headers) {
  var sheet = ss.getSheetByName(sheetName);
  
  if (!sheet) {
    // 建立新工作表
    sheet = ss.insertSheet(sheetName);
    
    // 設定標題列
    if (headers && headers.length > 0) {
      var headerRange = sheet.getRange(1, 1, 1, headers.length);
      headerRange.setValues([headers]);
      headerRange.setFontWeight('bold');
      headerRange.setBackground('#ff6b9d');
      headerRange.setFontColor('#ffffff');
    }
    
    Logger.log('已建立新工作表: ' + sheetName);
  }
  
  return sheet;
}

/**
 * 初始化範例資料(首次使用時執行)
 */
function initializeSampleData() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  
  // 建立成員名單
  var memberSheet = getOrCreateSheet(ss, '成員名單', ['姓名']);
  memberSheet.getRange('A2:A6').setValues([
    ['小明'],
    ['小華'],
    ['小美'],
    ['小強'],
    ['小芳']
  ]);
  
  // 建立菜單
  var menuSheet = getOrCreateSheet(ss, '菜單', ['品項', '價格']);
  menuSheet.getRange('A2:B11').setValues([
    ['雞腿便當', 80],
    ['排骨便當', 85],
    ['魚排便當', 90],
    ['素食便當', 75],
    ['焢肉便當', 80],
    ['咖哩雞便當', 85],
    ['宮保雞丁便當', 85],
    ['牛肉麵', 100],
    ['炒飯', 70],
    ['炒麵', 70]
  ]);
  
  // 建立訂單記錄工作表
  getOrCreateSheet(ss, '訂單記錄', ['姓名', '品項', '價格']);
  
  SpreadsheetApp.getUi().alert('範例資料初始化完成!\n\n已建立:\n- 成員名單(5人)\n- 菜單(10項)\n- 訂單記錄(空白)');
}

/**
 * 清空所有訂單
 */
function clearAllOrders() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var orderSheet = ss.getSheetByName('訂單記錄');
  
  if (!orderSheet) {
    SpreadsheetApp.getUi().alert('找不到訂單記錄工作表');
    return;
  }
  
  var ui = SpreadsheetApp.getUi();
  var response = ui.alert(
    '確認清空',
    '確定要清空所有訂單記錄嗎?此操作無法復原!',
    ui.ButtonSet.YES_NO
  );
  
  if (response == ui.Button.YES) {
    var lastRow = orderSheet.getLastRow();
    if (lastRow > 1) {
      orderSheet.deleteRows(2, lastRow - 1);
    }
    ui.alert('已清空所有訂單記錄!');
  }
}

/**
 * 建立自訂選單
 */
function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('🍱 午餐團購')
    .addItem('📱 開啟訂餐頁面', 'openWebApp')
    .addSeparator()
    .addItem('📊 初始化範例資料', 'initializeSampleData')
    .addItem('🗑️ 清空所有訂單', 'clearAllOrders')
    .addToUi();
}

/**
 * 開啟網頁應用程式
 */
function openWebApp() {
  var html = HtmlService.createHtmlOutputFromFile('index')
    .setWidth(1000)
    .setHeight(800);
  SpreadsheetApp.getUi().showModalDialog(html, '🍱 午餐團購系統');
}
貼到 Apps Script 的 Code.gs 檔案

5. 貼上前端(index.html)

把前端程式貼到 Apps Script 的 index.html。

index.html(前端)
<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet">
  <style>
    :root {
      --primary-color: #ff6b9d;
      --secondary-color: #ffd93d;
      --success-color: #6bcf7f;
      --danger-color: #ff4757;
      --bg-gradient: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
      --card-bg: #ffffff;
    }
    
    body {
      font-family: 'Microsoft JhengHei', 'Comic Sans MS', sans-serif;
      background: var(--bg-gradient);
      color: #333;
      margin: 0;
      padding: 20px;
      display: flex;
      flex-direction: column;
      align-items: center;
      min-height: 100vh;
    }
    
    .container {
      max-width: 1000px;
      width: 100%;
      background: var(--card-bg);
      padding: 40px;
      border-radius: 30px;
      box-shadow: 0 20px 60px rgba(255, 107, 157, 0.3);
      position: relative;
      overflow: hidden;
    }
    
    .container::before {
      content: '🍕🍔🍟🌮🍜🍱🥗🍰';
      position: absolute;
      top: -20px;
      left: 0;
      right: 0;
      font-size: 40px;
      text-align: center;
      opacity: 0.1;
      letter-spacing: 20px;
      animation: float 6s ease-in-out infinite;
    }
    
    @keyframes float {
      0%, 100% { transform: translateY(0px); }
      50% { transform: translateY(-10px); }
    }
    
    h1 { 
      color: var(--primary-color);
      text-align: center;
      font-size: 2.5em;
      margin-bottom: 10px;
      text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
      animation: bounce 1s ease-in-out;
    }
    
    .subtitle {
      text-align: center;
      color: #ff8fab;
      font-size: 1.1em;
      margin-bottom: 30px;
      font-weight: 500;
    }
    
    .form-group { 
      margin-bottom: 25px;
      position: relative;
    }
    
    label { 
      display: block;
      margin-bottom: 10px;
      font-weight: bold;
      color: #555;
      font-size: 1.1em;
    }
    
    label::before {
      content: '🌟 ';
      font-size: 1.2em;
    }
    
    select, button {
      width: 100%;
      padding: 15px;
      border-radius: 15px;
      border: 3px solid #ffe0e9;
      font-size: 16px;
      transition: all 0.3s ease;
      background: white;
    }
    
    select {
      cursor: pointer;
      font-weight: 500;
      color: #555;
    }
    
    select:focus {
      outline: none;
      border-color: var(--primary-color);
      box-shadow: 0 0 0 4px rgba(255, 107, 157, 0.1);
    }
    
    button {
      background: linear-gradient(135deg, var(--primary-color) 0%, #ff8fab 100%);
      color: white;
      border: none;
      cursor: pointer;
      font-weight: bold;
      font-size: 1.2em;
      margin-top: 10px;
      box-shadow: 0 8px 20px rgba(255, 107, 157, 0.4);
      position: relative;
      overflow: hidden;
    }
    
    button::before {
      content: '✨';
      position: absolute;
      left: 20px;
      font-size: 1.5em;
    }
    
    button::after {
      content: '✨';
      position: absolute;
      right: 20px;
      font-size: 1.5em;
    }
    
    button:hover {
      transform: translateY(-3px);
      box-shadow: 0 12px 30px rgba(255, 107, 157, 0.5);
    }
    
    button:active {
      transform: translateY(-1px);
    }
    
    button:disabled {
      background: #ccc;
      cursor: not-allowed;
      transform: none;
      box-shadow: none;
    }
    
    .stats-container {
      margin-top: 40px;
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      gap: 20px;
    }
    
    .stats-card {
      background: linear-gradient(135deg, #fff5f7 0%, #ffe0e9 100%);
      padding: 20px;
      border-radius: 20px;
      box-shadow: 0 8px 20px rgba(0,0,0,0.08);
      border: 3px solid #ffc2d4;
      position: relative;
      overflow: hidden;
    }
    
    .stats-card.full-width {
      grid-column: 1 / -1;
    }
    
    .stats-card::before {
      content: '';
      position: absolute;
      top: -50px;
      right: -50px;
      width: 100px;
      height: 100px;
      background: rgba(255, 255, 255, 0.5);
      border-radius: 50%;
    }
    
    .stats-card h3 {
      margin-top: 0;
      font-size: 1.3em;
      color: var(--primary-color);
      margin-bottom: 15px;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    
    .item-list {
      list-style: none;
      padding: 0;
      font-size: 1em;
    }
    
    .item-list li {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 12px;
      margin-bottom: 8px;
      background: white;
      border-radius: 10px;
      transition: all 0.3s ease;
      border-left: 4px solid var(--secondary-color);
    }
    
    .item-list li:hover {
      transform: translateX(5px);
      box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    }
    
    .item-list li span:first-child {
      font-weight: 600;
      color: #555;
      flex: 1;
    }
    
    .item-list li span:nth-child(2) {
      color: var(--primary-color);
      font-weight: bold;
      background: #ffe0e9;
      padding: 4px 12px;
      border-radius: 20px;
      font-size: 0.9em;
      margin-right: 10px;
    }
    
    .order-list {
      list-style: none;
      padding: 0;
    }
    
    .order-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 15px;
      margin-bottom: 10px;
      background: white;
      border-radius: 12px;
      border-left: 4px solid var(--success-color);
      transition: all 0.3s ease;
    }
    
    .order-item:hover {
      box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    }
    
    .order-info {
      flex: 1;
      display: flex;
      align-items: center;
      gap: 15px;
    }
    
    .order-person {
      font-weight: bold;
      color: #555;
      min-width: 80px;
    }
    
    .order-item-name {
      color: #666;
      flex: 1;
    }
    
    .order-price {
      color: var(--primary-color);
      font-weight: bold;
      background: #ffe0e9;
      padding: 6px 12px;
      border-radius: 20px;
      min-width: 60px;
      text-align: center;
    }
    
    .delete-btn {
      background: linear-gradient(135deg, var(--danger-color) 0%, #ff6b81 100%);
      color: white;
      border: none;
      padding: 8px 16px;
      border-radius: 10px;
      cursor: pointer;
      font-size: 0.9em;
      font-weight: bold;
      transition: all 0.3s ease;
      box-shadow: 0 4px 10px rgba(255, 71, 87, 0.3);
    }
    
    .delete-btn:hover {
      transform: translateY(-2px);
      box-shadow: 0 6px 15px rgba(255, 71, 87, 0.4);
    }
    
    .delete-btn:active {
      transform: translateY(0);
    }
    
    #loading {
      text-align: center;
      color: var(--primary-color);
      font-size: 1.5em;
      padding: 60px;
      animation: pulse 1.5s ease-in-out infinite;
    }
    
    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.5; }
    }
    
    .empty-state {
      text-align: center;
      color: #999;
      padding: 20px;
      font-style: italic;
    }
    
    .modal {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) scale(0);
      background: white;
      padding: 40px;
      border-radius: 30px;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3);
      z-index: 1000;
      text-align: center;
      opacity: 0;
      transition: all 0.3s ease;
      min-width: 300px;
    }
    
    .modal.show {
      transform: translate(-50%, -50%) scale(1);
      opacity: 1;
    }
    
    .modal .emoji {
      font-size: 80px;
      animation: bounceIn 0.6s ease-out;
    }
    
    @keyframes bounceIn {
      0% { transform: scale(0); }
      50% { transform: scale(1.2); }
      100% { transform: scale(1); }
    }
    
    .overlay {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0,0,0,0.5);
      z-index: 999;
      display: none;
    }
    
    .overlay.show {
      display: block;
    }
  </style>
</head>
<body>

<div class="overlay" id="overlay"></div>

<div class="modal" id="successModal">
  <div class="emoji">🎉</div>
  <h2 style="color: var(--primary-color); margin: 20px 0 10px 0;">訂單成功!</h2>
  <p style="color: #666; font-size: 1.1em;">美味即將送達~</p>
</div>

<div class="modal" id="deleteModal">
  <div class="emoji">✅</div>
  <h2 style="color: var(--success-color); margin: 20px 0 10px 0;">刪除成功!</h2>
  <p style="color: #666; font-size: 1.1em;">訂單已移除</p>
</div>

<div class="container animate__animated animate__fadeIn">
  <h1>🍽️ 超可愛午餐團購 🍽️</h1>
  <p class="subtitle">讓我們一起享用美味的午餐吧!</p>
  
  <div id="loading">🍕 載入美味菜單中... 🍔</div>
  
  <div id="main-content" style="display:none;">
    <div class="form-group">
      <label>👤 你是誰呢?</label>
      <select id="memberSelect">
        <option value="">請選擇你的名字</option>
      </select>
    </div>

    <div class="form-group">
      <label>🍴 想吃什麼美食?</label>
      <select id="menuSelect">
        <option value="">請選擇想吃的餐點</option>
      </select>
    </div>

    <button onclick="sendOrder()" id="submitBtn">送出我的美味訂單</button>

    <div class="stats-container">
      <div class="stats-card full-width animate__animated animate__fadeInUp">
        <h3>📝 所有訂單明細</h3>
        <ul id="orderList" class="order-list">
          <li class="empty-state">尚無訂單資料</li>
        </ul>
      </div>
      
      <div class="stats-card animate__animated animate__fadeInLeft">
        <h3>📊 品項統計</h3>
        <ul id="itemStats" class="item-list">
          <li class="empty-state">尚無訂單資料</li>
        </ul>
      </div>
      
      <div class="stats-card animate__animated animate__fadeInRight">
        <h3>💰 每人應付金額</h3>
        <ul id="personStats" class="item-list">
          <li class="empty-state">尚無訂單資料</li>
        </ul>
      </div>
    </div>
  </div>
</div>

<script>
  let menuData = [];
  let allOrders = [];

  // 初始化載入
  window.onload = () => {
    google.script.run.withSuccessHandler(renderPage).getData();
  };

  function renderPage(data) {
    console.log('收到的資料:', data);
    
    document.getElementById('loading').style.display = 'none';
    document.getElementById('main-content').style.display = 'block';
    
    const memberSelect = document.getElementById('memberSelect');
    const menuSelect = document.getElementById('menuSelect');
    
    // 渲染名單
    if (data.members && data.members.length > 0) {
      memberSelect.innerHTML = '<option value="">請選擇你的名字</option>' + 
        data.members.map(m => `<option value="${m}">${m}</option>`).join('');
    } else {
      memberSelect.innerHTML = '<option value="">無成員資料</option>';
    }
    
    // 渲染菜單
    menuData = data.menu || [];
    if (menuData.length > 0) {
      menuSelect.innerHTML = '<option value="">請選擇想吃的餐點</option>' +
        menuData.map(m => {
          const itemName = m[0] || '';
          const price = m[1] || 0;
          return `<option value="${itemName}">${itemName} ($${price})</option>`;
        }).join('');
    } else {
      menuSelect.innerHTML = '<option value="">無菜單資料</option>';
    }
    
    // 儲存所有訂單
    allOrders = data.orders || [];
    
    // 更新顯示
    updateAllDisplays(data.stats);
  }

  function updateAllDisplays(stats) {
    updateOrderList();
    updateStatsDisplay(stats);
  }

  function updateOrderList() {
    const orderList = document.getElementById('orderList');
    
    if (allOrders && allOrders.length > 0) {
      orderList.innerHTML = allOrders.map((order, index) => `
        <li class="order-item animate__animated animate__fadeIn">
          <div class="order-info">
            <span class="order-person">👤 ${order.name}</span>
            <span class="order-item-name">🍱 ${order.item}</span>
            <span class="order-price">$${order.price}</span>
          </div>
          <button class="delete-btn" onclick="deleteOrder(${index})">🗑️ 刪除</button>
        </li>
      `).join('');
    } else {
      orderList.innerHTML = '<li class="empty-state">尚無訂單資料</li>';
    }
  }

  function updateStatsDisplay(stats) {
    const itemStats = document.getElementById('itemStats');
    const personStats = document.getElementById('personStats');

    // 品項統計
    if (stats && stats.itemCounts && Object.keys(stats.itemCounts).length > 0) {
      itemStats.innerHTML = Object.entries(stats.itemCounts)
        .map(([name, count]) => `<li><span>🍱 ${name}</span><span>${count} 份</span></li>`)
        .join('');
    } else {
      itemStats.innerHTML = '<li class="empty-state">尚無訂單資料</li>';
    }

    // 個人金額統計
    if (stats && stats.personTotals && Object.keys(stats.personTotals).length > 0) {
      personStats.innerHTML = Object.entries(stats.personTotals)
        .map(([name, total]) => `<li><span>👤 ${name}</span><span>$${total}</span></li>`)
        .join('');
    } else {
      personStats.innerHTML = '<li class="empty-state">尚無訂單資料</li>';
    }
  }

  function sendOrder() {
    const memberSelect = document.getElementById('memberSelect');
    const menuSelect = document.getElementById('menuSelect');
    const selectedName = memberSelect.value;
    const selectedItem = menuSelect.value;
    
    // 驗證是否已選擇
    if (!selectedName) {
      alert('請先選擇你的名字!😊');
      return;
    }
    
    if (!selectedItem) {
      alert('請先選擇想吃的餐點!🍴');
      return;
    }
    
    const btn = document.getElementById('submitBtn');
    btn.disabled = true;
    btn.innerText = '🍳 處理中...';

    // 找到對應的價格
    const menuItem = menuData.find(m => m[0] === selectedItem);
    const price = menuItem ? menuItem[1] : 0;

    const order = {
      name: selectedName,
      item: selectedItem,
      price: price
    };

    console.log('送出訂單:', order);

    google.script.run
      .withSuccessHandler((newData) => {
        console.log('訂單成功回傳:', newData);
        
        // 更新所有資料
        allOrders = newData.orders || [];
        updateAllDisplays(newData.stats);
        
        // 顯示成功動畫
        showModal('successModal');
        
        // 恢復按鈕
        btn.disabled = false;
        btn.innerText = '送出我的美味訂單';
      })
      .withFailureHandler((error) => {
        console.error('訂單失敗:', error);
        alert('訂單送出失敗,請稍後再試!');
        btn.disabled = false;
        btn.innerText = '送出我的美味訂單';
      })
      .submitOrder(order);
  }
  
  function deleteOrder(index) {
    if (!confirm('確定要刪除這個訂單嗎?🤔')) {
      return;
    }
    
    console.log('刪除訂單索引:', index);
    
    google.script.run
      .withSuccessHandler((newData) => {
        console.log('刪除成功回傳:', newData);
        
        // 更新所有資料
        allOrders = newData.orders || [];
        updateAllDisplays(newData.stats);
        
        // 顯示刪除成功動畫
        showModal('deleteModal');
      })
      .withFailureHandler((error) => {
        console.error('刪除失敗:', error);
        alert('刪除失敗,請稍後再試!');
      })
      .deleteOrder(index);
  }
  
  function showModal(modalId) {
    const overlay = document.getElementById('overlay');
    const modal = document.getElementById(modalId);
    
    overlay.classList.add('show');
    modal.classList.add('show');
    
    setTimeout(() => {
      overlay.classList.remove('show');
      modal.classList.remove('show');
    }, 2000);
  }
</script>

</body>
</html>
貼到 Apps Script 的 index.html 檔案

6. 部署

部署成 Web App 後,就會拿到可分享網址。

  1. Apps Script 右上:部署 → 新部署
  2. 類型選:Web app
  3. 執行身分:你
  4. 存取權限:任何人
  5. 完成後複製網址

💬 評論區

老師們的交流與提問

載入評論中...