0. 作品介紹
封面
1. AI 提示詞
把這段指令貼給 AI,讓它幫你生成程式碼。
這是一份濃縮成 10 句話、完全不含程式碼語法的精華版提示詞,適合直接丟給 AI 生成最終成果:
1. 請製作一個現代風格的教室座位表產生器,啟動時先讓使用者上傳一張平面圖作為背景。
2. 系統必須嚴格區分「新增」、「選取」與「拖曳」三種互斥模式,確保在拖曳時絕不會誤觸勾選,選取時絕不會移動位置。
3. 座位外觀請設計成寬長方形(雙倍寬度)以容納長姓名,且只有在「選取模式」下,座位右上角才會顯示可供勾選的視覺提示(如圓圈)。
4. 提供垂直與水平對齊功能,計算已選座位的平均位置來進行排列。
5. **關鍵邏輯:執行對齊動作後,系統必須自動清除所有選取狀態,讓使用者能乾淨地進行下一次操作。**
6. 當編輯完成進入「排課模式」時,從後端讀取班級名單顯示於側邊欄。
7. 支援將學生名牌拖曳至座位,若座位已有人則自動交換或將舊生踢回待分配區,點擊座位亦可移除學生。
8. 加入「隨機補滿」按鈕,可一鍵將剩餘學生分配至空位。
9. 實作截圖功能,需在截圖瞬間自動隱藏所有工具列與按鈕,產出乾淨的座位表圖片。
10. 整體介面請採用圓角、陰影與柔和配色的現代化設計,並確保所有互動都有清晰的視覺回饋。
1. 請製作一個現代風格的教室座位表產生器,啟動時先讓使用者上傳一張平面圖作為背景。
2. 系統必須嚴格區分「新增」、「選取」與「拖曳」三種互斥模式,確保在拖曳時絕不會誤觸勾選,選取時絕不會移動位置。
3. 座位外觀請設計成寬長方形(雙倍寬度)以容納長姓名,且只有在「選取模式」下,座位右上角才會顯示可供勾選的視覺提示(如圓圈)。
4. 提供垂直與水平對齊功能,計算已選座位的平均位置來進行排列。
5. **關鍵邏輯:執行對齊動作後,系統必須自動清除所有選取狀態,讓使用者能乾淨地進行下一次操作。**
6. 當編輯完成進入「排課模式」時,從後端讀取班級名單顯示於側邊欄。
7. 支援將學生名牌拖曳至座位,若座位已有人則自動交換或將舊生踢回待分配區,點擊座位亦可移除學生。
8. 加入「隨機補滿」按鈕,可一鍵將剩餘學生分配至空位。
9. 實作截圖功能,需在截圖瞬間自動隱藏所有工具列與按鈕,產出乾淨的座位表圖片。
10. 整體介面請採用圓角、陰影與柔和配色的現代化設計,並確保所有互動都有清晰的視覺回饋。
2. 資料表規則
請照下面規則建立分頁與欄位。
第1分頁: ClassData
A 欄: 班級名稱
B 欄: 學生名單
📄 解析結果(自動表格)
分頁:ClassData
| A 欄 | B 欄 |
|---|---|
| 班級名稱 | 學生名單 |
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.createTemplateFromFile('index')
.evaluate()
.setTitle('電腦教室座位分配系統')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
// 取得所有班級列表與學生名單
function getClassData() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('ClassData');
var data = sheet.getDataRange().getValues();
var classes = {};
// 從第二列開始讀取 (跳過標題)
for (var i = 1; i < data.length; i++) {
var className = data[i][0];
var studentString = data[i][1];
if (className && studentString) {
// 將逗號分隔的字串轉為陣列,並去除空白
var students = studentString.toString().split(/[,,、]/).map(function(s) {
return s.trim();
}).filter(function(s) { return s !== "" }); // 過濾空值
classes[className] = students;
}
}
return classes;
}
// 包含 CSS 與 JS 的輔助函式
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}// Code.gs
function doGet() {
return HtmlService.createTemplateFromFile('index')
.evaluate()
.setTitle('電腦教室座位分配系統')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
// 取得所有班級列表與學生名單
function getClassData() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('ClassData');
var data = sheet.getDataRange().getValues();
var classes = {};
// 從第二列開始讀取 (跳過標題)
for (var i = 1; i < data.length; i++) {
var className = data[i][0];
var studentString = data[i][1];
if (className && studentString) {
// 將逗號分隔的字串轉為陣列,並去除空白
var students = studentString.toString().split(/[,,、]/).map(function(s) {
return s.trim();
}).filter(function(s) { return s !== "" }); // 過濾空值
classes[className] = students;
}
}
return classes;
}
// 包含 CSS 與 JS 的輔助函式
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
} 貼到 Apps Script 的 Code.gs 檔案
5. 貼上前端(index.html)
把前端程式貼到 Apps Script 的 index.html。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link href="https://fonts.googleapis.com/css2?family=Varela+Round&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<style>
:root {
--bg-color: #2c3e50;
--seat-empty: rgba(255, 255, 255, 0.95);
--seat-border: #bdc3c7;
--seat-occupied: #ffecb3;
--accent-blue: #3498db;
--accent-green: #2ecc71;
--text-dark: #34495e;
}
body {
font-family: 'Varela Round', sans-serif;
background-color: var(--bg-color);
color: white;
margin: 0; padding: 20px;
display: flex; flex-direction: column; align-items: center;
min-height: 100vh; box-sizing: border-box;
user-select: none;
}
/* --- 設定精靈 --- */
#setupPanel {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(44, 62, 80, 0.98); z-index: 2000;
display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.step-box {
background: white; color: var(--text-dark); padding: 30px;
border-radius: 20px; text-align: center; max-width: 600px;
box-shadow: 0 10px 25px rgba(0,0,0,0.5);
}
.hidden { display: none !important; }
/* --- 控制面板 --- */
.controls {
background: rgba(255, 255, 255, 0.1); padding: 10px 20px;
border-radius: 20px; display: flex; gap: 10px; align-items: center;
margin-bottom: 15px; backdrop-filter: blur(5px); flex-wrap: wrap; justify-content: center;
z-index: 100;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
}
button, select {
padding: 8px 12px; border-radius: 8px; border: none; font-size: 0.9rem;
cursor: pointer; transition: 0.2s; display: flex; align-items: center; gap: 5px;
}
button:hover { filter: brightness(1.1); transform: translateY(-1px); }
button:active { transform: translateY(1px); }
.btn-tool { background: #546e7a; color: white; }
.btn-tool.active { background: #ff7043; box-shadow: 0 0 8px #ff7043; font-weight: bold; transform: scale(1.05); }
.btn-action { background: var(--accent-blue); color: white; }
.btn-success { background: var(--accent-green); color: white; font-weight: bold; }
.btn-danger { background: #e74c3c; color: white; }
.btn-screenshot { background: #f1c40f; color: var(--text-dark); font-weight: bold; }
/* --- 工作區 --- */
.workspace { display: flex; width: 100%; height: 85vh; gap: 20px; }
.student-pool-container {
flex: 1; background: rgba(255,255,255,0.95); border-radius: 20px; padding: 15px;
color: var(--text-dark); display: flex; flex-direction: column; max-width: 250px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.student-pool { flex: 1; overflow-y: auto; display: flex; flex-wrap: wrap; align-content: flex-start; gap: 8px; }
.student-card {
background: white; border: 2px solid var(--accent-blue); border-radius: 10px;
padding: 5px 10px; font-size: 0.9rem; cursor: grab;
}
/* 地圖區域 */
.map-container {
flex: 4; position: relative;
background: #2c3e50;
border: 4px solid #34495e;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
#mapImage {
position: absolute; top: 0; left: 0;
width: 100%; height: 100%;
object-fit: fill;
pointer-events: none;
}
/* --- 座位樣式 --- */
.seat {
position: absolute;
width: 100px; /* 寬度維持兩倍 */
height: 50px;
background: var(--seat-empty);
border: 2px solid var(--seat-border);
border-radius: 8px; display: flex; flex-direction: column;
justify-content: center; align-items: center;
transform: translate(-50%, -50%);
box-shadow: 0 4px 6px rgba(0,0,0,0.2); z-index: 10;
transition: left 0.2s, top 0.2s, transform 0.1s;
}
.seat-number { font-size: 1.1rem; font-weight: bold; color: var(--text-dark); pointer-events: none; margin-bottom: 2px; }
.seat-student {
font-size: 0.9rem;
color: #d35400;
font-weight: bold;
text-align: center;
width: 100%;
line-height: 1.1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: none;
pointer-events: none;
}
.seat.occupied { background: var(--seat-occupied); border-color: #f39c12; }
.seat.occupied .seat-number { font-size: 0.8rem; opacity: 0.6; }
.seat.occupied .seat-student { display: block; }
/* --- 勾選框 UI --- */
.checkbox-icon {
position: absolute; top: -10px; right: -10px;
width: 22px; height: 22px;
background: #fff;
border: 2px solid #bdc3c7;
border-radius: 50%;
display: none;
justify-content: center; align-items: center;
font-size: 14px; color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 25;
pointer-events: none;
}
body.tool-select .checkbox-icon { display: flex; }
/* --- 選取狀態 --- */
.seat.selected {
border: 3px solid var(--accent-green) !important;
background-color: #e8f5e9 !important;
transform: translate(-50%, -50%) scale(1.05);
z-index: 30;
}
.seat.selected .checkbox-icon {
background: var(--accent-green);
border-color: var(--accent-green);
}
.seat.selected .checkbox-icon::after {
content: '✔';
}
/* --- 游標控制 --- */
body.tool-add .map-container { cursor: crosshair; }
body.tool-select .seat { cursor: pointer; }
body.tool-select .map-container { cursor: default; }
body.tool-drag .seat { cursor: move; }
body.tool-drag .seat:active { cursor: grabbing; }
.divider { width: 1px; height: 20px; background: rgba(255,255,255,0.3); margin: 0 5px; }
</style>
</head>
<body class="edit-mode tool-add">
<div id="setupPanel">
<div class="step-box">
<h1>📸 建立教室座位表</h1>
<p>請上傳教室平面圖。</p>
<input type="file" id="fileInput" accept="image/*" onchange="handleImageUpload(event)">
<button class="btn-success" id="startBtn" disabled onclick="closeSetup()">使用此圖片</button>
</div>
</div>
<div class="controls" id="topControls">
<div id="editTools" style="display:flex; gap:10px; align-items:center;">
<button class="btn-tool active" id="toolAdd" onclick="setTool('add')">➕ 新增</button>
<button class="btn-tool" id="toolSelect" onclick="setTool('select')">✋ 選取模式</button>
<button class="btn-tool" id="toolDrag" onclick="setTool('drag')">✥ 拖曳模式</button>
<div class="divider"></div>
<button class="btn-action" onclick="alignSeats('v')">↕️ 垂直對齊(直)</button>
<button class="btn-action" onclick="alignSeats('h')">↔️ 水平對齊(橫)</button>
<button class="btn-danger" onclick="deleteSelected()">🗑️ 刪除勾選</button>
<div class="divider"></div>
<button class="btn-success" onclick="finishEditing()">✅ 完成設定</button>
</div>
<div id="assignTools" class="hidden" style="display:flex; gap:10px; align-items:center;">
<select id="classSelect" onchange="loadClassStudents()">
<option value="" disabled selected>請選擇班級</option>
</select>
<button class="btn-action" onclick="resetStudents()">重置</button>
<button class="btn-tool" onclick="randomizeRemaining()">🎲 隨機補滿</button>
<button class="btn-screenshot" onclick="downloadScreenshot()">📸 下載截圖</button>
<button class="btn-action" onclick="backToEdit()">✏️ 修改座位</button>
</div>
</div>
<div class="workspace">
<div class="student-pool-container">
<div style="font-weight:bold; margin-bottom:10px; text-align:center;">待分配 (<span id="poolCount">0</span>)</div>
<div id="studentPool" class="student-pool" ondrop="dropToPool(event)" ondragover="allowDrop(event)"></div>
</div>
<div class="map-container" id="mapContainer" onclick="handleMapClick(event)">
<img id="mapImage" src="" alt="請上傳地圖">
</div>
</div>
<script>
const DEFAULT_MAP_IMAGE = "";
let allClassData = {};
let seatLayout = [];
let assignedData = {};
let currentStudents = [];
let isEditMode = true;
let currentTool = 'add';
let selectedSeatIds = new Set();
let isDragging = false;
let dragItem = null;
let startX, startY;
let initialLeft, initialTop;
window.onload = function() {
if (DEFAULT_MAP_IMAGE && DEFAULT_MAP_IMAGE.trim() !== "") {
document.getElementById('mapImage').src = DEFAULT_MAP_IMAGE;
document.getElementById('setupPanel').classList.add('hidden');
}
google.script.run.withSuccessHandler(data => {
allClassData = data;
const select = document.getElementById('classSelect');
for (let cls in data) {
let opt = document.createElement('option');
opt.value = cls; opt.text = cls; select.add(opt);
}
}).getClassData();
setTool('add');
};
function handleImageUpload(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('mapImage').src = e.target.result;
document.getElementById('startBtn').disabled = false;
}
reader.readAsDataURL(file);
}
}
function closeSetup() { document.getElementById('setupPanel').classList.add('hidden'); }
// --- 工具切換 ---
function setTool(mode) {
currentTool = mode;
document.querySelectorAll('.btn-tool').forEach(b => b.classList.remove('active'));
if(mode === 'add') document.getElementById('toolAdd').classList.add('active');
if(mode === 'select') document.getElementById('toolSelect').classList.add('active');
if(mode === 'drag') document.getElementById('toolDrag').classList.add('active');
document.body.classList.remove('tool-add', 'tool-select', 'tool-drag');
document.body.classList.add('tool-' + mode);
clearSelection();
}
function getNextAvailableId() {
let id = 1;
while (seatLayout.find(s => s.id === id)) { id++; }
return id;
}
// --- 地圖互動 ---
function handleMapClick(e) {
if (!isEditMode) return;
const isBackground = e.target.id === 'mapContainer' || e.target.id === 'mapImage';
if (currentTool === 'add' && isBackground) {
const container = document.getElementById('mapContainer');
const rect = container.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
createSeat(getNextAvailableId(), x, y);
} else if (currentTool === 'select' && isBackground) {
clearSelection();
}
}
function createSeat(id, x, y) {
const seat = document.createElement('div');
seat.className = 'seat';
seat.id = 'seat-' + id;
seat.style.left = x + '%';
seat.style.top = y + '%';
seatLayout.push({id: id, x: x, y: y});
seat.innerHTML = `
<div class="checkbox-icon"></div>
<div class="seat-number">${id}</div>
<div class="seat-student" id="stu-seat-${id}"></div>
`;
seat.onclick = (e) => handleSeatClick(e, id);
seat.onmousedown = (e) => handleSeatMouseDown(e, id);
seat.ondragover = allowDrop;
seat.ondrop = (e) => dropToSeat(e, id);
document.getElementById('mapContainer').appendChild(seat);
}
// --- 點擊:選取/排課踢人 ---
function handleSeatClick(e, id) {
if (!isEditMode) {
if (assignedData[id]) { addStudentToPool(assignedData[id]); clearSeat(id); }
return;
}
if (currentTool === 'select') {
e.stopPropagation();
toggleSelection(id);
}
}
// --- 拖曳:移動座位 ---
function handleSeatMouseDown(e, id) {
if (!isEditMode) return;
if (currentTool !== 'drag') return;
if (e.button !== 0) return;
e.stopPropagation();
isDragging = true;
dragItem = document.getElementById('seat-' + id);
dragItem.style.transition = 'none';
startX = e.clientX;
startY = e.clientY;
initialLeft = parseFloat(dragItem.style.left);
initialTop = parseFloat(dragItem.style.top);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', (ev) => onMouseUp(ev, id));
}
function onMouseMove(e) {
if (!isDragging || !dragItem) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const container = document.getElementById('mapContainer');
const rect = container.getBoundingClientRect();
const newX = initialLeft + (dx / rect.width) * 100;
const newY = initialTop + (dy / rect.height) * 100;
dragItem.style.left = newX + '%';
dragItem.style.top = newY + '%';
}
function onMouseUp(e, id) {
document.removeEventListener('mousemove', onMouseMove);
e.target.removeEventListener('mouseup', onMouseUp);
if (isDragging && dragItem) {
dragItem.style.transition = 'left 0.2s, top 0.2s, transform 0.1s';
const seat = seatLayout.find(s => s.id === id);
if (seat) {
seat.x = parseFloat(dragItem.style.left);
seat.y = parseFloat(dragItem.style.top);
}
}
isDragging = false;
dragItem = null;
}
// --- 選取功能 ---
function toggleSelection(id) {
const el = document.getElementById('seat-' + id);
if (!el) return;
if (selectedSeatIds.has(id)) {
selectedSeatIds.delete(id);
el.classList.remove('selected');
} else {
selectedSeatIds.add(id);
el.classList.add('selected');
}
}
function clearSelection() {
selectedSeatIds.forEach(id => {
const el = document.getElementById('seat-' + id);
if(el) el.classList.remove('selected');
});
selectedSeatIds.clear();
}
// --- 對齊功能 (修正點:對齊後自動清空選取) ---
function alignSeats(direction) {
if (currentTool !== 'select') {
alert('請先切換至「✋ 選取模式」並勾選要對齊的座位');
setTool('select');
return;
}
if (selectedSeatIds.size < 2) { alert('請勾選至少兩個座位進行對齊'); return; }
let sum = 0;
let targets = [];
selectedSeatIds.forEach(id => {
const seat = seatLayout.find(s => s.id === id);
if (seat) {
sum += (direction === 'v' ? seat.x : seat.y);
targets.push(seat);
}
});
if (targets.length === 0) return;
const average = sum / targets.length;
targets.forEach(seat => {
const el = document.getElementById('seat-' + seat.id);
if (direction === 'v') {
seat.x = average;
if(el) el.style.left = average + '%';
} else {
seat.y = average;
if(el) el.style.top = average + '%';
}
});
// 關鍵修正:對齊動作完成後,自動清除選取
// 這樣使用者進行下一次對齊時,不會被舊的選取影響
clearSelection();
}
function deleteSelected() {
if (selectedSeatIds.size === 0) { alert('請切換至「選取模式」並勾選要刪除的座位'); return; }
if (!confirm(`確定刪除這 ${selectedSeatIds.size} 個座位?`)) return;
const idsToDelete = Array.from(selectedSeatIds);
idsToDelete.forEach(id => {
const el = document.getElementById('seat-' + id);
if (el) el.remove();
const idx = seatLayout.findIndex(s => s.id === id);
if (idx > -1) seatLayout.splice(idx, 1);
delete assignedData[id];
});
selectedSeatIds.clear();
}
// --- 流程控制 ---
function finishEditing() {
if (seatLayout.length === 0) { alert('請至少新增一個座位'); return; }
isEditMode = false;
clearSelection();
document.body.classList.remove('edit-mode');
document.body.classList.remove('tool-add', 'tool-select', 'tool-drag');
document.getElementById('editTools').classList.add('hidden');
document.getElementById('assignTools').classList.remove('hidden');
}
function backToEdit() {
isEditMode = true;
document.body.classList.add('edit-mode');
setTool('select');
document.getElementById('editTools').classList.remove('hidden');
document.getElementById('assignTools').classList.add('hidden');
}
// --- 排課邏輯 ---
function loadClassStudents() {
const cls = document.getElementById('classSelect').value;
currentStudents = allClassData[cls] ? allClassData[cls].slice() : [];
resetStudents();
}
function resetStudents() {
assignedData = {};
document.querySelectorAll('.seat-student').forEach(el => el.innerText = '');
document.querySelectorAll('.seat').forEach(el => el.classList.remove('occupied'));
renderPool(currentStudents);
}
function renderPool(students) {
const pool = document.getElementById('studentPool');
pool.innerHTML = '';
students.forEach(stu => {
let card = document.createElement('div');
card.className = 'student-card';
card.draggable = true;
card.id = 'card-' + stu;
card.innerText = stu;
card.ondragstart = (e) => e.dataTransfer.setData("text", stu);
pool.appendChild(card);
});
document.getElementById('poolCount').innerText = students.length;
}
function allowDrop(ev) { ev.preventDefault(); }
function dropToSeat(ev, seatId) {
ev.preventDefault();
if (isEditMode) return;
const studentName = ev.dataTransfer.getData("text");
if (assignedData[seatId]) addStudentToPool(assignedData[seatId]);
assignStudentToSeat(studentName, seatId);
removeStudentFromPoolUI(studentName);
for(let sid in assignedData) {
if(assignedData[sid] === studentName && parseInt(sid) !== seatId) clearSeat(sid);
}
assignedData[seatId] = studentName;
}
function dropToPool(ev) {
ev.preventDefault();
const studentName = ev.dataTransfer.getData("text");
let currentSeatId = null;
for(let sid in assignedData) {
if(assignedData[sid] === studentName) { currentSeatId = sid; break; }
}
if (currentSeatId) { clearSeat(currentSeatId); addStudentToPool(studentName); }
}
function assignStudentToSeat(name, seatId) {
document.getElementById('stu-seat-' + seatId).innerText = name;
document.getElementById('seat-' + seatId).classList.add('occupied');
assignedData[seatId] = name;
}
function clearSeat(seatId) {
document.getElementById('stu-seat-' + seatId).innerText = '';
document.getElementById('seat-' + seatId).classList.remove('occupied');
delete assignedData[seatId];
}
function addStudentToPool(name) {
if (document.getElementById('card-' + name)) return;
const pool = document.getElementById('studentPool');
let card = document.createElement('div');
card.className = 'student-card';
card.draggable = true;
card.id = 'card-' + name;
card.innerText = name;
card.ondragstart = (e) => e.dataTransfer.setData("text", name);
pool.appendChild(card);
document.getElementById('poolCount').innerText = pool.childElementCount;
}
function removeStudentFromPoolUI(name) {
const card = document.getElementById('card-' + name);
if(card) card.remove();
document.getElementById('poolCount').innerText = document.getElementById('studentPool').childElementCount;
}
function randomizeRemaining() {
let available = [];
seatLayout.forEach(pos => { if (!assignedData[pos.id]) available.push(pos.id); });
let pool = [];
document.querySelectorAll('#studentPool .student-card').forEach(c => pool.push(c.innerText));
if (pool.length === 0) { alert('無學生可排'); return; }
if (available.length < pool.length) { alert('座位不足'); return; }
pool.sort(() => Math.random() - 0.5);
pool.forEach((stu, i) => { assignStudentToSeat(stu, available[i]); removeStudentFromPoolUI(stu); });
}
function downloadScreenshot() {
const btn = event.target;
const controls = document.getElementById('topControls');
const originalText = btn.innerText;
btn.innerText = '📷';
controls.classList.add('hidden');
html2canvas(document.body, { backgroundColor: '#2c3e50', scale: 2, ignoreElements: (el) => el.id === 'setupPanel' }).then(canvas => {
controls.classList.remove('hidden');
btn.innerText = originalText;
const link = document.createElement('a');
link.download = `座位表_${new Date().getTime()}.png`;
link.href = canvas.toDataURL("image/png");
link.click();
});
}
</script>
</body>
</html>
貼到 Apps Script 的 index.html 檔案
6. 部署
部署成 Web App 後,就會拿到可分享網址。
- Apps Script 右上:部署 → 新部署
- 類型選:Web app
- 執行身分:你
- 存取權限:任何人
- 完成後複製網址
💬 評論區
老師們的交流與提問
登入 後即可發表評論