我們要來介紹前端的部分,我們可以用GAS一次寫在同一個.gs檔案裡,也可以分兩個檔案做。
在查詢的部分,我們可以用後端傳資料給前端,讓前端生成html表格;也可以用後端先生成html表格傳給前端並貼上。這次我們選用後者。
<body>
<div class="container">
<div class="header">
<div class="logo">📅</div>
<div>
<h1>會議室借用系統</h1>
<div class="sub">快速預約、避免重複、即時查詢</div>
</div>
</div>
<div class="grid">
<section class="card">
<h2>借用會議室</h2>
<div class="hint">填寫資料後按「借用」,系統會自動檢查是否與他人衝突。</div>
<div class="field">
<label class="label" for="room">會議室名稱</label>
<input class="input" id="room" type="text" placeholder="例如:A101 / B201" />
</div>
<div class="field">
<label class="label" for="user">借用人</label>
<input class="input" id="user" type="text" placeholder="輸入您的姓名" />
</div>
<div class="row">
<div class="field">
<label class="label" for="start">開始時間</label>
<input class="input" id="start" type="datetime-local" />
</div>
<div class="field">
<label class="label" for="end">結束時間</label>
<input class="input" id="end" type="datetime-local" />
</div>
</div>
<div class="actions">
<button id="bookBtn" class="btn btn-primary" onclick="book()">
立即借用
</button>
<button class="btn btn-secondary" onclick="resetForm()">清空</button>
</div>
<div id="result" class="msg" style="display:none;"></div>
</section>
<section class="card">
<h2>查詢會議室紀錄</h2>
<div class="hint">輸入會議室名稱,顯示所有已登記的時段與狀態。</div>
<div class="toolbar">
<input class="input" id="findRoom" type="text" placeholder="例如:A101" style="flex:1" />
<button class="btn btn-secondary" onclick="search()">查詢</button>
</div>
<div id="records" class="table-wrap" style="display:none;"></div>
<div id="empty" class="empty" style="display:none;">尚無資料,或請先輸入會議室名稱查詢。</div>
</section>
</div>
</div>
<script>
// 前端 JavaScript 函式
function showMsg(text, type){
const el = document.getElementById("result");
el.style.display = "block";
el.textContent = text;
el.className = "msg " + (type === "ok" ? "ok" : type === "err" ? "err" : "");
}
function setBookLoading(loading){
const btn = document.getElementById("bookBtn");
if(loading){
btn.disabled = true;
btn.textContent = "處理中…";
}else{
btn.disabled = false;
btn.textContent = "立即借用";
}
}
function resetForm(){
document.getElementById("room").value = "";
document.getElementById("user").value = "";
document.getElementById("start").value = "";
document.getElementById("end").value = "";
document.getElementById("result").style.display = "none";
}
function formatDate(v){
if(!v) return "";
const d = new Date(v);
if (isNaN(d.getTime())) return v;
const pad = n=>String(n).padStart(2,"0");
return \`\${d.getFullYear()}/\${pad(d.getMonth()+1)}/\${pad(d.getDate())} \${pad(d.getHours())}:\${pad(d.getMinutes())}\`;
}
function book() {
const room = document.getElementById("room").value.trim();
const user = document.getElementById("user").value.trim();
const start = document.getElementById("start").value;
const end = document.getElementById("end").value;
if (!room || !user || !start || !end) {
showMsg("⚠️ 請填寫完整資料!", "err");
return;
}
const startMs = new Date(start.replace("T", " ") + ":00").getTime();
const endMs = new Date(end.replace("T", " ") + ":00").getTime();
if (startMs >= endMs) {
showMsg("⚠️ 開始時間必須早於結束時間!", "err");
return;
}
setBookLoading(true);
google.script.run.withSuccessHandler(msg => {
const ok = msg.startsWith("✅");
showMsg(msg, ok ? "ok" : "err");
if (ok) {
resetForm();
showSuccessToast("借用成功 🎉");
}
setBookLoading(false);
}).withFailureHandler(err => {
showMsg("❌ 送出失敗:" + (err && err.message ? err.message : "未知錯誤"), "err");
setBookLoading(false);
}).bookRoom(room, user, startMs, endMs);
}
function search() {
const empty = document.getElementById("empty");
const records = document.getElementById("records");
const room = document.getElementById("findRoom").value.trim();
records.style.display = "none";
empty.style.display = "none";
records.innerHTML = "";
if (!room) {
empty.textContent = "請輸入會議室名稱後查詢。";
empty.style.display = "block";
return;
}
google.script.run.withSuccessHandler(html => {
if (html === '<p class="empty">查無資料。</p>') {
empty.textContent = "查無資料。";
empty.style.display = "block";
} else {
records.innerHTML = html;
records.style.display = "block";
}
}).withFailureHandler(err => {
empty.textContent = "查詢失敗:" + (err && err.message ? err.message : "未知錯誤");
empty.style.display = "block";
}).getBookings(room);
}
// 借用成功 toast
function showSuccessToast(text){
const toast = document.createElement("div");
toast.textContent = text;
toast.style.position = "fixed";
toast.style.bottom = "30px";
toast.style.right = "30px";
toast.style.background = "#10b981";
toast.style.color = "#fff";
toast.style.padding = "12px 18px";
toast.style.borderRadius = "12px";
toast.style.boxShadow = "0 6px 20px rgba(0,0,0,0.15)";
toast.style.fontSize = "14px";
toast.style.zIndex = "9999";
toast.style.opacity = "0";
toast.style.transition = "opacity 0.3s ease";
document.body.appendChild(toast);
setTimeout(()=>toast.style.opacity="1",50);
setTimeout(()=>{
toast.style.opacity="0";
setTimeout(()=>toast.remove(),300);
},2500);
}
</script>
</body>
這邊有兩個區塊,一個是登記的地方、另一個是查尋的地方。
showMsg(text, type):一個通用的工具函式,用於在網頁上顯示提示訊息,例如「借用成功」或「請填寫完整資料」。它會動態修改訊息框的文字內容、顯示狀態和樣式。
setBookLoading(loading):用於控制「立即借用」按鈕的狀態。當請求發送時,它會將按鈕設定為「處理中…」並禁用按鈕,防止重複點擊。請求完成後則恢復按鈕狀態。
resetForm():用於清空「借用會議室」表單中的所有輸入欄位,方便使用者重新填寫。
formatDate(v):用於將日期資料格式化為 年/月/日 時:分 的字串格式,方便在網頁表格中顯示。
book():處理使用者點擊「立即借用」按鈕的邏輯。它會先驗證所有輸入欄位是否填寫完整,並檢查結束時間是否晚於開始時間。驗證通過後,它會呼叫後端(Google Apps Script)的 bookRoom 函式來處理借用請求。
search():處理使用者點擊「查詢」按鈕的邏輯。它會從輸入框中獲取會議室名稱,然後呼叫後端(Google Apps Script)的 getBookings 函式來查詢相關紀錄。這個函式還會根據後端回傳的 HTML 內容來動態更新頁面上的表格或顯示「查無資料」的訊息。
showSuccessToast(text):一個用於顯示短暫提示訊息(toast)的工具函式,用於在借用成功時提供一個友善的視覺回饋。它會動態創建一個小彈窗並在幾秒後自動消失。
輸入資料後,按下立即借用就會存記錄在google試算表
接著查詢該會議室:a123