0. 作品介紹
封面
1. AI 提示詞
把這段指令貼給 AI,讓它幫你生成程式碼。
我想要建立一個 【研習報到表】
Google 試算表建立 【資料庫】
需求說明 【老師報到成功可指定位置、也可隨機】
要把它變成程式碼我要發佈
請給我完整程式碼
請完整敘述操作步驟 越詳細越好
作業環境
Google試算表
Google Apps Script
Google 試算表建立 【資料庫】
需求說明 【老師報到成功可指定位置、也可隨機】
要把它變成程式碼我要發佈
請給我完整程式碼
請完整敘述操作步驟 越詳細越好
作業環境
Google試算表
Google Apps Script
2. 資料表規則
請照下面規則建立分頁與欄位。
第1分頁: 分頁1
A 欄: 報到代碼
B 欄: 姓名
C 欄: 預排座位
D 欄: 報到狀態
E 欄: 分配座位
F 欄: 報到時間
📄 解析結果(自動表格)
分頁:分頁1
| A 欄 | B 欄 | C 欄 | D 欄 | E 欄 | F 欄 |
|---|---|---|---|---|---|
| 報到代碼 | 姓名 | 預排座位 | 報到狀態 | 分配座位 | 報到時間 |
3. 開啟 Apps Script
從試算表開 Apps Script,才能直接用 SpreadsheetApp 讀寫資料。
- 試算表上方選單:擴充功能 → Apps Script
- 刪掉預設的程式碼(或全部選取覆蓋)
-
保留/建立檔案:
- Code.gs
- index.html
4. 貼上後端(Code.gs)
把後端程式貼到 Apps Script 的 Code.gs。
/* 檔名:Code.gs */
// 修改:配合 8x10 座位表,設定總座位數為 80
const TOTAL_SEATS_COUNT = 80;
function doGet() {
return HtmlService.createTemplateFromFile('index')
.evaluate()
.setTitle('研習報到系統 (含座位表)')
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
function processCheckIn(userKey) {
var lock = LockService.getScriptLock();
try {
lock.waitLock(10000);
} catch (e) {
return { status: 'error', message: '系統繁忙,請稍後再試' };
}
try {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("名單");
var data = sheet.getDataRange().getValues();
var foundIndex = -1;
var userData = null;
// 1. 搜尋使用者 (A欄)
for (var i = 1; i < data.length; i++) {
if (String(data[i][0]).trim() == String(userKey).trim()) {
foundIndex = i;
userData = data[i];
break;
}
}
if (foundIndex === -1) {
return { status: 'error', message: '找不到此報到代碼,請確認後重試。' };
}
// 2. 檢查是否已報到 (D欄)
if (userData[3] === "已報到") {
return {
status: 'success',
name: userData[1],
seat: userData[4],
message: '您已經完成報到了!'
};
}
// 3. 分配座位邏輯
var assignedSeat = "";
var preAssignedSeat = userData[2]; // C欄預排
if (preAssignedSeat && String(preAssignedSeat).trim() !== "") {
assignedSeat = preAssignedSeat;
} else {
// 找出已佔用座位
var takenSeats = [];
for (var j = 1; j < data.length; j++) {
if (data[j][4]) takenSeats.push(String(data[j][4])); // E欄分配
else if (data[j][2]) takenSeats.push(String(data[j][2])); // C欄預排
}
// 產生可用座位池 (1 ~ 80)
var availableSeats = [];
for (var k = 1; k <= TOTAL_SEATS_COUNT; k++) {
// 如果座位號碼不在已佔用名單中
if (takenSeats.indexOf(String(k)) === -1) {
availableSeats.push(k);
}
}
if (availableSeats.length === 0) {
return { status: 'error', message: '抱歉,目前已無空位。' };
}
var randomIndex = Math.floor(Math.random() * availableSeats.length);
assignedSeat = availableSeats[randomIndex];
}
// 4. 寫入資料庫
var timestamp = new Date();
sheet.getRange(foundIndex + 1, 4).setValue("已報到");
sheet.getRange(foundIndex + 1, 5).setValue(assignedSeat);
sheet.getRange(foundIndex + 1, 6).setValue(timestamp);
return {
status: 'success',
name: userData[1],
seat: assignedSeat,
message: '報到成功!'
};
} catch (err) {
return { status: 'error', message: '系統錯誤: ' + err.toString() };
} finally {
lock.releaseLock();
}
} 貼到 Apps Script 的 Code.gs 檔案
5. 貼上前端(index.html)
把前端程式貼到 Apps Script 的 index.html。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background-color: #f0f2f5; padding-bottom: 50px; }
.main-container { max-width: 600px; margin: 30px auto; padding: 0 15px; }
.card { border: none; border-radius: 15px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden; }
.card-header { background: #4285f4; color: white; text-align: center; padding: 20px; }
.seat-big-display { font-size: 2.5rem; font-weight: 800; color: #4285f4; }
/* --- 座位表樣式 --- */
.stage-area {
background-color: #6c757d; color: white; text-align: center;
padding: 5px; margin-bottom: 15px; border-radius: 5px; font-weight: bold;
}
.seating-chart {
display: grid;
/* 設定 8 欄,每欄等寬 */
grid-template-columns: repeat(8, 1fr);
gap: 6px; /* 格子間距 */
justify-content: center;
margin-top: 10px;
}
.seat-box {
aspect-ratio: 1 / 1; /* 保持正方形 */
background-color: #e9ecef;
border: 1px solid #dee2e6;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
color: #666;
cursor: default;
transition: all 0.3s;
}
/* 自己的座位樣式 (被選中時) */
.seat-box.my-seat {
background-color: #dc3545 !important; /* 紅色高亮 */
color: white !important;
font-weight: bold;
border-color: #dc3545;
transform: scale(1.1);
box-shadow: 0 0 10px rgba(220, 53, 69, 0.5);
z-index: 10;
}
/* 手機版微調字體 */
@media (max-width: 400px) {
.seat-box { font-size: 0.6rem; }
}
.loader { display: none; width: 30px; height: 30px; border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.hidden { display: none; }
</style>
</head>
<body>
<div class="main-container">
<div class="card">
<div class="card-header">
<h3 class="mb-0">研習報到系統</h3>
</div>
<div class="card-body p-4">
<div id="inputSection">
<div class="mb-3 text-center">
<label class="form-label text-muted">請輸入報到代碼 / 手機</label>
<input type="text" class="form-control form-control-lg text-center" id="userKey" placeholder="例如: 1001" onkeypress="handleEnter(event)">
</div>
<button class="btn btn-primary w-100 btn-lg" onclick="submitCheckIn()">確認報到</button>
</div>
<div id="loadingSection" class="text-center mt-4 hidden">
<div class="loader"></div>
<p class="mt-2 text-muted">查詢座位中...</p>
</div>
<div id="resultSection" class="text-center mt-3 hidden">
<div class="alert alert-success fw-bold" id="statusMessage">報到成功</div>
<h4 id="userName">老師姓名</h4>
<p class="text-muted mb-0">您的座位號碼</p>
<div class="seat-big-display" id="seatNumber">--</div>
<hr>
<h6 class="text-start text-muted">座位位置圖 (紅框為您的位置)</h6>
<div class="stage-area">講 台 (前方)</div>
<div class="seating-chart" id="seatingGrid">
</div>
<button class="btn btn-outline-secondary w-100 mt-4" onclick="resetForm()">返回 / 下一位</button>
</div>
<div id="errorSection" class="alert alert-danger mt-3 hidden text-center"></div>
</div>
</div>
</div>
<script>
// 初始化:產生 8x10 的格子
document.addEventListener("DOMContentLoaded", function() {
generateSeatingGrid(80); // 80個座位
});
function generateSeatingGrid(total) {
const grid = document.getElementById('seatingGrid');
grid.innerHTML = ''; // 清空
for (let i = 1; i <= total; i++) {
let div = document.createElement('div');
div.className = 'seat-box';
div.id = 'seat-' + i; // 設定 ID 以便後續搜尋
div.innerText = i;
grid.appendChild(div);
}
}
function handleEnter(e) {
if(e.key === 'Enter') submitCheckIn();
}
function submitCheckIn() {
var key = document.getElementById('userKey').value;
if (!key) { showError('請輸入代碼!'); return; }
toggleSection('loading');
google.script.run
.withSuccessHandler(onSuccess)
.withFailureHandler(onFailure)
.processCheckIn(key);
}
function onSuccess(response) {
if (response.status === 'success') {
toggleSection('result');
document.getElementById('userName').innerText = response.name + " 老師";
document.getElementById('seatNumber').innerText = response.seat;
document.getElementById('statusMessage').innerText = response.message;
// --- 視覺化核心邏輯 ---
highlightSeat(response.seat);
} else {
toggleSection('input');
showError(response.message);
}
}
function highlightSeat(seatNum) {
// 先移除所有亮起狀態
document.querySelectorAll('.seat-box').forEach(el => el.classList.remove('my-seat'));
// 嘗試將回傳的座位轉為字串並去空白
let targetId = 'seat-' + String(seatNum).trim();
let targetDiv = document.getElementById(targetId);
// 如果找到了對應的格子 (代表是 1~80 數字),就亮起
if (targetDiv) {
targetDiv.classList.add('my-seat');
} else {
// 如果座位是 "VIP-1" 等特殊文字,無法在地圖上顯示,就不亮起,但上方文字仍會顯示
console.log("非標準數字座位,無法在地圖上標示");
}
}
function onFailure(error) {
toggleSection('input');
showError('連線錯誤,請重試。');
}
function resetForm() {
document.getElementById('userKey').value = '';
toggleSection('input');
// 清除亮起
document.querySelectorAll('.seat-box').forEach(el => el.classList.remove('my-seat'));
}
function showError(msg) {
var errDiv = document.getElementById('errorSection');
errDiv.innerText = msg;
errDiv.classList.remove('hidden');
}
function toggleSection(section) {
document.getElementById('inputSection').classList.add('hidden');
document.getElementById('loadingSection').classList.add('hidden');
document.getElementById('resultSection').classList.add('hidden');
document.getElementById('errorSection').classList.add('hidden');
if(section === 'input') document.getElementById('inputSection').classList.remove('hidden');
if(section === 'loading') document.getElementById('loadingSection').classList.remove('hidden');
if(section === 'result') document.getElementById('resultSection').classList.remove('hidden');
}
</script>
</body>
</html>
貼到 Apps Script 的 index.html 檔案
6. 部署
部署成 Web App 後,就會拿到可分享網址。
- Apps Script 右上:部署 → 新部署
- 類型選:Web app
- 執行身分:你
- 存取權限:任何人
- 完成後複製網址
💬 評論區
老師們的交流與提問
登入 後即可發表評論