0. 作品介紹
預設圖片
1. AI 提示詞
把這段指令貼給 AI,讓它幫你生成程式碼。
我想要建立一個 【工具借用表單】
幫我用Google 試算表建立 【工具資料庫】
我希望有些資料我可以直接在試算表更改首頁可以直接呈現
需求說明 【
1.要能增刪工具的種類及數量
2.要能統計同種工具借出的總數
3.要能統計各種工具借出後的剩餘數量】
4.未借用工具的人無法按下歸還按鈕
要把它變成程式碼我要發佈
請完整敘述操作步驟 越詳細越好
作業環境
Google試算表
Google Apps Script
我看不懂程式碼,請給我後端gs完整程式碼、前端index完整程式碼
幫我用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 讀寫資料。
- 試算表上方選單:擴充功能 → Apps Script
- 刪掉預設的程式碼(或全部選取覆蓋)
-
保留/建立檔案:
- Code.gs
- index.html
4. 貼上後端(Code.gs)
把後端程式貼到 Apps Script 的 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。
<!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 後,就會拿到可分享網址。
- Apps Script 右上:部署 → 新部署
- 類型選:Web app
- 執行身分:你
- 存取權限:任何人
- 完成後複製網址
💬 評論區
老師們的交流與提問
登入 後即可發表評論