0. 作品介紹

作者 eve***

載具借用


31 1 0
辦公 非教學
預設封面圖 預設圖片

1. AI 提示詞

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

我想要建立一個 【工具借用表單】

幫我用Google 試算表建立 【工具資料庫】

我希望有些資料我可以直接在試算表更改首頁可以直接呈現

需求說明 【

1.要能增刪工具的種類及數量

2.要能統計同種工具借出的總數

3.要能統計各種工具借出後的剩餘數量】

4.未借用工具的人無法按下歸還按鈕

要把它變成程式碼我要發佈

請完整敘述操作步驟 越詳細越好

作業環境

Google試算表

Google Apps Script

我看不懂程式碼,請給我後端gs完整程式碼、前端index完整程式碼

2. 資料表規則

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

第1分頁: 工具資料庫 A 欄: 工具名稱 B 欄: 總數量 C 欄: 已借出數量出數量 D 欄: 剩餘數量 第2分頁: 借用紀錄 A 欄: 日期時間 B 欄: 工具名稱 C 欄: 借用人 D 欄: 數量 E 欄: 狀態

📄 解析結果(自動表格)

分頁:工具資料庫
A 欄 B 欄 C 欄 D 欄
工具名稱 總數量 已借出數量出數量 剩餘數量
分頁:借用紀錄
A 欄 B 欄 C 欄 D 欄 E 欄
日期時間 工具名稱 借用人 數量 狀態

3. 開啟 Apps Script

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

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

4. 貼上後端(Code.gs)

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

Code.gs(後端)
// ==================== 後端程式 Code.gs ====================

// 顯示網頁介面
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('工具借用管理系統')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

// 取得試算表
function getSpreadsheet() {
  return SpreadsheetApp.getActiveSpreadsheet();
}

// 取得工具資料庫工作表
function getToolSheet() {
  var ss = getSpreadsheet();
  var sheet = ss.getSheetByName('工具資料庫');
  if (!sheet) {
    sheet = ss.insertSheet('工具資料庫');
    sheet.getRange('A1:D1').setValues([['工具名稱', '總數量', '已借出數量', '剩餘數量']]);
    sheet.getRange('A1:D1').setFontWeight('bold');
    sheet.getRange('A1:D1').setBackground('#667eea');
    sheet.getRange('A1:D1').setFontColor('#ffffff');
    sheet.setFrozenRows(1);
  }
  return sheet;
}

// 取得借用記錄工作表
function getHistorySheet() {
  var ss = getSpreadsheet();
  var sheet = ss.getSheetByName('借用記錄');
  if (!sheet) {
    sheet = ss.insertSheet('借用記錄');
    sheet.getRange('A1:F1').setValues([['日期時間', '工具名稱', '借用人', '數量', '狀態', '備註']]);
    sheet.getRange('A1:F1').setFontWeight('bold');
    sheet.getRange('A1:F1').setBackground('#667eea');
    sheet.getRange('A1:F1').setFontColor('#ffffff');
    sheet.setFrozenRows(1);
  }
  return sheet;
}

// 取得所有工具清單
function getTools() {
  try {
    var sheet = getToolSheet();
    var lastRow = sheet.getLastRow();
    
    if (lastRow <= 1) {
      return [];
    }
    
    var data = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
    var tools = [];
    
    for (var i = 0; i < data.length; i++) {
      if (data[i][0]) {
        tools.push({
          name: data[i][0],
          total: Number(data[i][1]) || 0,
          borrowed: Number(data[i][2]) || 0,
          available: Number(data[i][3]) || 0
        });
      }
    }
    
    return tools;
  } catch (e) {
    throw new Error('讀取工具清單時發生錯誤:' + e.message);
  }
}

// 取得借用記錄
function getHistory() {
  try {
    var sheet = getHistorySheet();
    var lastRow = sheet.getLastRow();
    
    if (lastRow <= 1) {
      return [];
    }
    
    var data = sheet.getRange(2, 1, lastRow - 1, 6).getValues();
    var history = [];
    
    for (var i = 0; i < data.length; i++) {
      if (data[i][0]) {
        var dateValue = data[i][0];
        var dateStr = '';
        
        if (dateValue instanceof Date) {
          dateStr = Utilities.formatDate(dateValue, Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm:ss');
        } else {
          dateStr = String(dateValue);
        }
        
        history.push({
          date: dateStr,
          toolName: String(data[i][1]),
          borrower: String(data[i][2]),
          quantity: Number(data[i][3]) || 0,
          status: String(data[i][4]),
          note: String(data[i][5] || '')
        });
      }
    }
    
    return history.reverse();
  } catch (e) {
    throw new Error('讀取借用記錄時發生錯誤:' + e.message);
  }
}

// 檢查某人是否有未歸還的借用記錄(供前端呼叫)
function checkBorrowRecord(toolName, borrower) {
  try {
    if (!toolName || !borrower) {
      return { 
        hasBorrowed: false, 
        totalBorrowed: 0,
        totalReturned: 0,
        unreturned: 0
      };
    }
    
    var sheet = getHistorySheet();
    var lastRow = sheet.getLastRow();
    
    if (lastRow <= 1) {
      return { 
        hasBorrowed: false, 
        totalBorrowed: 0,
        totalReturned: 0,
        unreturned: 0
      };
    }
    
    var data = sheet.getRange(2, 1, lastRow - 1, 5).getValues();
    var totalBorrowed = 0;
    var totalReturned = 0;
    
    // 計算此人借用和歸還的數量
    for (var i = 0; i < data.length; i++) {
      if (data[i][1] === toolName && data[i][2] === borrower) {
        if (data[i][4] === 'borrowed') {
          totalBorrowed += Number(data[i][3]) || 0;
        } else if (data[i][4] === 'returned') {
          totalReturned += Number(data[i][3]) || 0;
        }
      }
    }
    
    var unreturned = totalBorrowed - totalReturned;
    
    return {
      hasBorrowed: unreturned > 0,
      totalBorrowed: totalBorrowed,
      totalReturned: totalReturned,
      unreturned: unreturned
    };
  } catch (e) {
    Logger.log('檢查借用記錄時發生錯誤:' + e.message);
    return { 
      hasBorrowed: false, 
      totalBorrowed: 0,
      totalReturned: 0,
      unreturned: 0
    };
  }
}

// 新增工具
function addTool(name, quantity) {
  try {
    if (!name || name.trim() === '') {
      throw new Error('工具名稱不能為空');
    }
    
    if (!quantity || quantity < 1) {
      throw new Error('數量必須大於 0');
    }
    
    var sheet = getToolSheet();
    var lastRow = sheet.getLastRow();
    
    // 檢查工具是否已存在
    if (lastRow > 1) {
      var existingData = sheet.getRange(2, 1, lastRow - 1, 1).getValues();
      for (var i = 0; i < existingData.length; i++) {
        if (existingData[i][0] === name) {
          throw new Error('工具「' + name + '」已存在!');
        }
      }
    }
    
    // 新增工具資料
    sheet.appendRow([name, quantity, 0, quantity]);
    
    return { success: true, message: '工具新增成功' };
  } catch (e) {
    throw new Error(e.message);
  }
}

// 借用工具
function borrowTool(toolName, borrower, quantity, driveLink) {
  try {
    if (!toolName || !borrower || !quantity) {
      throw new Error('請填寫完整資訊');
    }
    
    if (quantity < 1) {
      throw new Error('數量必須大於 0');
    }
    
    var toolSheet = getToolSheet();
    var historySheet = getHistorySheet();
    var lastRow = toolSheet.getLastRow();
    
    if (lastRow <= 1) {
      throw new Error('尚無工具資料');
    }
    
    var data = toolSheet.getRange(2, 1, lastRow - 1, 4).getValues();
    var toolFound = false;
    var rowIndex = -1;
    
    // 尋找工具
    for (var i = 0; i < data.length; i++) {
      if (data[i][0] === toolName) {
        toolFound = true;
        rowIndex = i + 2;
        
        var total = Number(data[i][1]) || 0;
        var borrowed = Number(data[i][2]) || 0;
        var available = total - borrowed;
        
        // 檢查數量是否足夠
        if (quantity > available) {
          throw new Error('可借用數量不足!目前僅剩 ' + available + ' 個');
        }
        
        // 更新已借出數量和剩餘數量
        var newBorrowed = borrowed + quantity;
        var newAvailable = total - newBorrowed;
        
        toolSheet.getRange(rowIndex, 3).setValue(newBorrowed);
        toolSheet.getRange(rowIndex, 4).setValue(newAvailable);
        
        break;
      }
    }
    
    if (!toolFound) {
      throw new Error('找不到工具「' + toolName + '」');
    }
    
    // 記錄借用歷史(包含雲端硬碟連結)
    var now = new Date();
    var dateStr = Utilities.formatDate(now, Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm:ss');
    var note = driveLink ? '雲端連結: ' + driveLink : '';
    historySheet.appendRow([dateStr, toolName, borrower, quantity, 'borrowed', note]);
    
    return { success: true, message: '借用登記成功' };
  } catch (e) {
    throw new Error(e.message);
  }
}

// 歸還工具(加入借用記錄檢查)
function returnTool(toolName, borrower, quantity, driveLink) {
  try {
    if (!toolName || !borrower || !quantity) {
      throw new Error('請填寫完整資訊');
    }
    
    if (quantity < 1) {
      throw new Error('數量必須大於 0');
    }
    
    // 【重點】檢查此人是否有未歸還的借用記錄
    var borrowRecord = checkBorrowRecord(toolName, borrower);
    
    if (!borrowRecord.hasBorrowed) {
      throw new Error('此人沒有「' + toolName + '」的借用記錄,無法歸還!');
    }
    
    if (quantity > borrowRecord.unreturned) {
      throw new Error('歸還數量超過未歸還數量!此人尚有 ' + borrowRecord.unreturned + ' 個未歸還');
    }
    
    var toolSheet = getToolSheet();
    var historySheet = getHistorySheet();
    var lastRow = toolSheet.getLastRow();
    
    if (lastRow <= 1) {
      throw new Error('尚無工具資料');
    }
    
    var data = toolSheet.getRange(2, 1, lastRow - 1, 4).getValues();
    var toolFound = false;
    var rowIndex = -1;
    
    // 尋找工具
    for (var i = 0; i < data.length; i++) {
      if (data[i][0] === toolName) {
        toolFound = true;
        rowIndex = i + 2;
        
        var total = Number(data[i][1]) || 0;
        var borrowed = Number(data[i][2]) || 0;
        
        // 更新已借出數量和剩餘數量
        var newBorrowed = borrowed - quantity;
        var newAvailable = total - newBorrowed;
        
        toolSheet.getRange(rowIndex, 3).setValue(newBorrowed);
        toolSheet.getRange(rowIndex, 4).setValue(newAvailable);
        
        break;
      }
    }
    
    if (!toolFound) {
      throw new Error('找不到工具「' + toolName + '」');
    }
    
    // 記錄歸還歷史(包含雲端硬碟連結)
    var now = new Date();
    var dateStr = Utilities.formatDate(now, Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm:ss');
    var note = driveLink ? '雲端連結: ' + driveLink : '';
    historySheet.appendRow([dateStr, toolName, borrower, quantity, 'returned', note]);
    
    return { success: true, message: '歸還登記成功' };
  } catch (e) {
    throw new Error(e.message);
  }
}

// 刪除工具
function deleteTool(toolName) {
  try {
    var sheet = getToolSheet();
    var lastRow = sheet.getLastRow();
    
    if (lastRow <= 1) {
      throw new Error('尚無工具資料');
    }
    
    var data = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
    
    for (var i = 0; i < data.length; i++) {
      if (data[i][0] === toolName) {
        var borrowed = Number(data[i][2]) || 0;
        
        if (borrowed > 0) {
          throw new Error('此工具尚有 ' + borrowed + ' 個借出中,無法刪除!');
        }
        
        sheet.deleteRow(i + 2);
        return { success: true, message: '工具刪除成功' };
      }
    }
    
    throw new Error('找不到工具「' + toolName + '」');
  } catch (e) {
    throw new Error(e.message);
  }
}

// 更新工具數量
function updateToolQuantity(toolName, newQuantity) {
  try {
    if (newQuantity < 1) {
      throw new Error('數量必須大於 0');
    }
    
    var sheet = getToolSheet();
    var lastRow = sheet.getLastRow();
    
    if (lastRow <= 1) {
      throw new Error('尚無工具資料');
    }
    
    var data = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
    
    for (var i = 0; i < data.length; i++) {
      if (data[i][0] === toolName) {
        var borrowed = Number(data[i][2]) || 0;
        
        if (newQuantity < borrowed) {
          throw new Error('新數量不能小於已借出數量(' + borrowed + ')!');
        }
        
        var newAvailable = newQuantity - borrowed;
        var rowIndex = i + 2;
        
        sheet.getRange(rowIndex, 2).setValue(newQuantity);
        sheet.getRange(rowIndex, 4).setValue(newAvailable);
        
        return { success: true, message: '數量更新成功' };
      }
    }
    
    throw new Error('找不到工具「' + toolName + '」');
  } catch (e) {
    throw new Error(e.message);
  }
}
貼到 Apps Script 的 Code.gs 檔案

5. 貼上前端(index.html)

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

index.html(前端)
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700&display=swap');
    
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body { 
      font-family: 'Noto Sans TC', sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      padding: 20px;
      min-height: 100vh;
    }
    
    .container {
      max-width: 1200px;
      margin: 0 auto;
      background: white;
      border-radius: 20px;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3);
      overflow: hidden;
    }
    
    .header {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 30px;
      text-align: center;
    }
    
    .header h1 {
      font-size: 32px;
      font-weight: 700;
      margin-bottom: 10px;
    }
    
    .header p {
      font-size: 16px;
      opacity: 0.9;
    }
    
    .content {
      padding: 30px;
    }
    
    .section {
      margin-bottom: 30px;
    }
    
    .section-title {
      font-size: 20px;
      font-weight: 700;
      color: #333;
      margin-bottom: 15px;
      padding-bottom: 10px;
      border-bottom: 3px solid #667eea;
    }
    
    .button-group {
      display: flex;
      gap: 10px;
      flex-wrap: wrap;
      margin-bottom: 20px;
    }
    
    button {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      border: none;
      padding: 12px 24px;
      border-radius: 8px;
      cursor: pointer;
      font-size: 16px;
      font-weight: 500;
      transition: all 0.3s ease;
      box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
    }
    
    button:hover:not(:disabled) {
      transform: translateY(-2px);
      box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
    }
    
    button:active:not(:disabled) {
      transform: translateY(0);
    }
    
    button:disabled {
      opacity: 0.5;
      cursor: not-allowed;
      transform: none;
    }
    
    .btn-add {
      background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
    }
    
    .btn-delete {
      background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
    }
    
    .btn-borrow {
      background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
    }
    
    .btn-return {
      background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
    }
    
    .form-group {
      background: #f8f9fa;
      padding: 20px;
      border-radius: 10px;
      margin-bottom: 20px;
    }
    
    .form-row {
      display: flex;
      gap: 15px;
      margin-bottom: 15px;
      flex-wrap: wrap;
    }
    
    .form-field {
      flex: 1;
      min-width: 200px;
    }
    
    label {
      display: block;
      font-weight: 500;
      color: #555;
      margin-bottom: 5px;
    }
    
    input, select {
      width: 100%;
      padding: 10px;
      border: 2px solid #ddd;
      border-radius: 6px;
      font-size: 14px;
      transition: border-color 0.3s;
    }
    
    input:focus, select:focus {
      outline: none;
      border-color: #667eea;
    }
    
    .tool-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
      gap: 20px;
      margin-top: 20px;
    }
    
    .tool-card {
      background: white;
      border: 2px solid #e0e0e0;
      border-radius: 12px;
      padding: 20px;
      transition: all 0.3s ease;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    }
    
    .tool-card:hover {
      transform: translateY(-5px);
      box-shadow: 0 5px 20px rgba(0,0,0,0.15);
      border-color: #667eea;
    }
    
    .tool-name {
      font-size: 18px;
      font-weight: 700;
      color: #333;
      margin-bottom: 10px;
    }
    
    .tool-info {
      display: flex;
      justify-content: space-between;
      margin-bottom: 8px;
      font-size: 14px;
    }
    
    .tool-info .label {
      color: #666;
    }
    
    .tool-info .value {
      font-weight: 600;
      color: #333;
    }
    
    .tool-info .value.available {
      color: #11998e;
    }
    
    .tool-info .value.borrowed {
      color: #f5576c;
    }
    
    .status-bar {
      height: 8px;
      background: #e0e0e0;
      border-radius: 4px;
      overflow: hidden;
      margin-top: 10px;
    }
    
    .status-fill {
      height: 100%;
      background: linear-gradient(90deg, #11998e 0%, #38ef7d 100%);
      transition: width 0.3s ease;
    }
    
    .history-table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 20px;
      background: white;
      border-radius: 10px;
      overflow: hidden;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    }
    
    .history-table th {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 15px;
      text-align: left;
      font-weight: 600;
    }
    
    .history-table td {
      padding: 12px 15px;
      border-bottom: 1px solid #e0e0e0;
    }
    
    .history-table tr:last-child td {
      border-bottom: none;
    }
    
    .history-table tr:hover {
      background: #f8f9fa;
    }
    
    .status-badge {
      display: inline-block;
      padding: 4px 12px;
      border-radius: 12px;
      font-size: 12px;
      font-weight: 600;
    }
    
    .status-borrowed {
      background: #ffe0e0;
      color: #d32f2f;
    }
    
    .status-returned {
      background: #e0f7e0;
      color: #2e7d32;
    }
    
    .loading {
      text-align: center;
      padding: 40px;
      color: #666;
    }
    
    .empty-state {
      text-align: center;
      padding: 60px 20px;
      color: #999;
    }
    
    .empty-state-icon {
      font-size: 64px;
      margin-bottom: 20px;
      opacity: 0.5;
    }
    
    .alert {
      padding: 15px;
      border-radius: 8px;
      margin-bottom: 15px;
      font-weight: 500;
    }
    
    .alert-warning {
      background: #fff3cd;
      color: #856404;
      border: 1px solid #ffeaa7;
    }
    
    .alert-info {
      background: #d1ecf1;
      color: #0c5460;
      border: 1px solid #bee5eb;
    }
    
    .drive-link {
      color: #667eea;
      text-decoration: none;
      font-size: 12px;
    }
    
    .drive-link:hover {
      text-decoration: underline;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>🔧 工具借用管理系統</h1>
      <p>輕鬆管理您的工具庫存與借用記錄</p>
    </div>
    
    <div class="content">
      <!-- 新增工具區 -->
      <div class="section">
        <div class="section-title">➕ 新增工具</div>
        <div class="form-group">
          <div class="form-row">
            <div class="form-field">
              <label>工具名稱</label>
              <input type="text" id="newToolName" placeholder="例如:電鑽、螺絲起子">
            </div>
            <div class="form-field">
              <label>總數量</label>
              <input type="number" id="newToolQuantity" min="1" value="1">
            </div>
          </div>
          <button class="btn-add" onclick="addTool()">新增工具</button>
        </div>
      </div>
      
      <!-- 借用/歸還區 -->
      <div class="section">
        <div class="section-title">📝 借用 / 歸還工具</div>
        <div class="form-group">
          <div id="returnWarning" class="alert alert-warning" style="display: none;">
            ⚠️ 此人沒有此工具的借用記錄,無法歸還!
          </div>
          <div id="returnInfo" class="alert alert-info" style="display: none;"></div>
          
          <div class="form-row">
            <div class="form-field">
              <label>選擇工具</label>
              <select id="toolSelect" onchange="checkReturnEligibility()">
                <option value="">請選擇工具</option>
              </select>
            </div>
            <div class="form-field">
              <label>借用人姓名</label>
              <input type="text" id="borrowerName" placeholder="請輸入姓名" onblur="checkReturnEligibility()">
            </div>
            <div class="form-field">
              <label>數量</label>
              <input type="number" id="borrowQuantity" min="1" value="1">
            </div>
          </div>
          <div class="form-row">
            <div class="form-field">
              <label>雲端硬碟連結(選填)</label>
              <input type="text" id="driveLink" placeholder="貼上 Google 雲端硬碟連結">
            </div>
          </div>
          <div class="button-group">
            <button class="btn-borrow" onclick="borrowTool()">借用工具</button>
            <button class="btn-return" id="returnBtn" onclick="returnTool()" disabled>歸還工具</button>
          </div>
        </div>
      </div>
      
      <!-- 工具庫存總覽 -->
      <div class="section">
        <div class="section-title">📦 工具庫存總覽</div>
        <div id="toolGrid" class="tool-grid">
          <div class="loading">載入中...</div>
        </div>
      </div>
      
      <!-- 借用記錄 -->
      <div class="section">
        <div class="section-title">📋 借用記錄</div>
        <div style="overflow-x: auto;">
          <table class="history-table" id="historyTable">
            <thead>
              <tr>
                <th>日期時間</th>
                <th>工具名稱</th>
                <th>借用人</th>
                <th>數量</th>
                <th>狀態</th>
                <th>備註</th>
              </tr>
            </thead>
            <tbody id="historyBody">
              <tr><td colspan="6" class="loading">載入中...</td></tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>

  <script>
    let tools = [];
    let history = [];
    
    // 初始化
    function init() {
      loadTools();
      loadHistory();
    }
    
    // 載入工具清單
    function loadTools() {
      google.script.run
        .withSuccessHandler(function(data) {
          tools = data || [];
          renderTools();
          updateToolSelect();
        })
        .withFailureHandler(function(error) {
          alert('載入工具清單失敗:' + error);
        })
        .getTools();
    }
    
    // 載入借用記錄
    function loadHistory() {
      google.script.run
        .withSuccessHandler(function(data) {
          history = data || [];
          renderHistory();
        })
        .withFailureHandler(function(error) {
          alert('載入記錄失敗:' + error);
        })
        .getHistory();
    }
    
    // 檢查是否可以歸還(呼叫後端確認)
    function checkReturnEligibility() {
      const toolIndex = document.getElementById('toolSelect').value;
      const borrower = document.getElementById('borrowerName').value.trim();
      const returnBtn = document.getElementById('returnBtn');
      const returnWarning = document.getElementById('returnWarning');
      const returnInfo = document.getElementById('returnInfo');
      
      returnWarning.style.display = 'none';
      returnInfo.style.display = 'none';
      
      if (!toolIndex || !borrower) {
        returnBtn.disabled = true;
        returnBtn.style.opacity = '0.5';
        returnBtn.style.cursor = 'not-allowed';
        return;
      }
      
      const tool = tools[toolIndex];
      
      // 呼叫後端檢查借用記錄
      google.script.run
        .withSuccessHandler(function(result) {
          if (!result.hasBorrowed || result.unreturned <= 0) {
            returnWarning.style.display = 'block';
            returnWarning.textContent = '⚠️ 此人沒有「' + tool.name + '」的借用記錄或已全部歸還,無法操作歸還!';
            returnBtn.disabled = true;
            returnBtn.style.opacity = '0.5';
            returnBtn.style.cursor = 'not-allowed';
          } else {
            returnInfo.textContent = '✓ 此人目前有 ' + result.unreturned + ' 個「' + tool.name + '」未歸還';
            returnInfo.style.display = 'block';
            returnBtn.disabled = false;
            returnBtn.style.opacity = '1';
            returnBtn.style.cursor = 'pointer';
          }
        })
        .withFailureHandler(function(error) {
          console.error('檢查失敗:', error);
          returnBtn.disabled = true;
          returnBtn.style.opacity = '0.5';
          returnBtn.style.cursor = 'not-allowed';
        })
        .checkBorrowRecord(tool.name, borrower);
    }
    
    // 渲染工具卡片
    function renderTools() {
      const grid = document.getElementById('toolGrid');
      
      if (tools.length === 0) {
        grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📭</div><p>尚無工具資料,請先新增工具</p></div>';
        return;
      }
      
      grid.innerHTML = tools.map(tool => {
        const borrowed = tool.borrowed || 0;
        const available = tool.total - borrowed;
        const percentage = (available / tool.total) * 100;
        
        return `
          <div class="tool-card">
            <div class="tool-name">${tool.name}</div>
            <div class="tool-info">
              <span class="label">總數量:</span>
              <span class="value">${tool.total}</span>
            </div>
            <div class="tool-info">
              <span class="label">已借出:</span>
              <span class="value borrowed">${borrowed}</span>
            </div>
            <div class="tool-info">
              <span class="label">可借用:</span>
              <span class="value available">${available}</span>
            </div>
            <div class="status-bar">
              <div class="status-fill" style="width: ${percentage}%"></div>
            </div>
          </div>
        `;
      }).join('');
    }
    
    // 更新工具選單
    function updateToolSelect() {
      const select = document.getElementById('toolSelect');
      select.innerHTML = '<option value="">請選擇工具</option>' +
        tools.map((tool, index) => 
          `<option value="${index}">${tool.name} (可借用: ${tool.total - (tool.borrowed || 0)})</option>`
        ).join('');
    }
    
    // 渲染借用記錄
    function renderHistory() {
      const tbody = document.getElementById('historyBody');
      
      if (history.length === 0) {
        tbody.innerHTML = '<tr><td colspan="6" class="empty-state" style="padding: 40px;">尚無借用記錄</td></tr>';
        return;
      }
      
      tbody.innerHTML = history.map(record => {
        const noteHtml = record.note && record.note.includes('雲端連結:') 
          ? '<a href="' + record.note.replace('雲端連結: ', '') + '" target="_blank" class="drive-link">📁 查看檔案</a>'
          : (record.note || '');
        
        return `
          <tr>
            <td>${record.date}</td>
            <td>${record.toolName}</td>
            <td>${record.borrower}</td>
            <td>${record.quantity}</td>
            <td><span class="status-badge status-${record.status}">${record.status === 'borrowed' ? '借用中' : '已歸還'}</span></td>
            <td>${noteHtml}</td>
          </tr>
        `;
      }).join('');
    }
    
    // 新增工具
    function addTool() {
      const name = document.getElementById('newToolName').value.trim();
      const quantity = parseInt(document.getElementById('newToolQuantity').value);
      
      if (!name) {
        alert('請輸入工具名稱');
        return;
      }
      
      if (!quantity || quantity < 1) {
        alert('請輸入有效的數量');
        return;
      }
      
      google.script.run
        .withSuccessHandler(function() {
          alert('工具新增成功!');
          document.getElementById('newToolName').value = '';
          document.getElementById('newToolQuantity').value = '1';
          loadTools();
        })
        .withFailureHandler(function(error) {
          alert('新增失敗:' + error);
        })
        .addTool(name, quantity);
    }
    
    // 借用工具
    function borrowTool() {
      const toolIndex = document.getElementById('toolSelect').value;
      const borrower = document.getElementById('borrowerName').value.trim();
      const quantity = parseInt(document.getElementById('borrowQuantity').value);
      const driveLink = document.getElementById('driveLink').value.trim();
      
      if (!toolIndex) {
        alert('請選擇工具');
        return;
      }
      
      if (!borrower) {
        alert('請輸入借用人姓名');
        return;
      }
      
      if (!quantity || quantity < 1) {
        alert('請輸入有效的數量');
        return;
      }
      
      const tool = tools[toolIndex];
      const available = tool.total - (tool.borrowed || 0);
      
      if (quantity > available) {
        alert(`可借用數量不足!目前僅剩 ${available} 個`);
        return;
      }
      
      google.script.run
        .withSuccessHandler(function() {
          alert('借用登記成功!');
          document.getElementById('borrowerName').value = '';
          document.getElementById('borrowQuantity').value = '1';
          document.getElementById('driveLink').value = '';
          loadTools();
          loadHistory();
        })
        .withFailureHandler(function(error) {
          alert('借用失敗:' + error);
        })
        .borrowTool(tool.name, borrower, quantity, driveLink);
    }
    
    // 歸還工具
    function returnTool() {
      const toolIndex = document.getElementById('toolSelect').value;
      const borrower = document.getElementById('borrowerName').value.trim();
      const quantity = parseInt(document.getElementById('borrowQuantity').value);
      const driveLink = document.getElementById('driveLink').value.trim();
      
      if (!toolIndex) {
        alert('請選擇工具');
        return;
      }
      
      if (!borrower) {
        alert('請輸入借用人姓名');
        return;
      }
      
      if (!quantity || quantity < 1) {
        alert('請輸入有效的數量');
        return;
      }
      
      const tool = tools[toolIndex];
      
      google.script.run
        .withSuccessHandler(function() {
          alert('歸還登記成功!');
          document.getElementById('borrowerName').value = '';
          document.getElementById('borrowQuantity').value = '1';
          document.getElementById('driveLink').value = '';
          document.getElementById('returnWarning').style.display = 'none';
          document.getElementById('returnInfo').style.display = 'none';
          document.getElementById('returnBtn').disabled = true;
          loadTools();
          loadHistory();
        })
        .withFailureHandler(function(error) {
          alert('歸還失敗:' + error);
        })
        .returnTool(tool.name, borrower, quantity, driveLink);
    }
    
    // 頁面載入完成後初始化
    window.onload = init;
  </script>
</body>
</html>
貼到 Apps Script 的 index.html 檔案

6. 部署

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

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

💬 評論區

老師們的交流與提問

載入評論中...