
9. GAS連接多個Web App檔
code.gs
function doGet(e){
// if ... else ... statement 來確認是否有參數來決定開啟哪一個網頁
//如果沒有參數
if (!e.parameter.page){
var file=HtmlService.createTemplateFromFile("index");
var evaluate = file.evaluate();
var html=evaluate.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return html;
}
//如果有參數
else if (e.parameter.page){
var file=HtmlService.createTemplateFromFile(e.parameter.page);
var evaluate = file.evaluate();
var html=evaluate.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return html;
}
}
// 取得URL
function getUrl(){
var url=ScriptApp.getService().getUrl();
return url;
}
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h2>Welcome to My Website! </h2>
<p>點選下方連結來跳到特定網頁 </p>
<!-- create a list for links-->
<!-- import get url function from GAS -->
<?var url=getUrl();?>
<ol>
<li>
<a href="<?= url ?>">首頁</a>
</li>
<li>
<a href="<?= url ?>?page=profile">個人檔案</a>
</li>
<li>
<a href="<?= url ?>?page=contact">聯絡我 </a>
</li>
</ol>
</body>
</html>
profile.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<!-- create a list for links-->
<!-- import get url function from GAS -->
<?var url=getUrl();?>
<ol>
<li>
<a href="<?= url ?>">回到首頁</a>
</li>
<li>
<a href="<?= url ?>?page=contact">聯絡我 </a>
</li>
</ol>
<h4>個人檔案 <h4>
</body>
</html>
contact.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<!-- create a list for links-->
<!-- import get url function from GAS -->
<?var url=getUrl();?>
<ol>
<li>
<a href="<?= url ?>?page=profile">個人檔案 </a>
</li>
<li>
<a href="<?= url ?>">回到首頁 </a>
</li>
</ol>
<h4>聯絡資訊 <h4>
</body>
</html>
11. 表單上傳系統
11.3.3 Code.js程式輸入
// 連接到 Web App
function doGet() {
var file = HtmlService.createTemplateFromFile("form"); // 從名為 "form" 的 HTML 檔案建立模板
var evaluate = file.evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1'); // 添加 meta 標籤,設定視窗寬度自適應
var html = evaluate.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); // 設置允許框架內嵌的模式
return html; // 回傳 HTML 內容
}
// 取得 URL 的函式
function getUrl() {
var url = ScriptApp.getService().getUrl(); // 取得目前 ScriptApp 的 Web App URL
return url; // 回傳 URL
}
// 引入 CSS 和 JavaScript 的函式
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent(); // 從指定檔案名稱引入內容
}
// 新增資料到試算表
function addData(rowData) {
// 獲取當前時間
var currentDate = new Date();
// 獲取當前啟用的試算表
var ss = SpreadsheetApp.getActiveSpreadsheet();
// 獲取名為 "Sheet1" 的試算表
var ws = ss.getSheetByName("Sheet1");
// 格式化日期為純文字
var formattedDate = Utilities.formatDate(currentDate, ss.getSpreadsheetTimeZone(), "MM/dd/yyyy");
var formattedInputDate = Utilities.formatDate(new Date(rowData.date), ss.getSpreadsheetTimeZone(), "MM/dd/yyyy");
// 插入資料到試算表
ws.appendRow([
new Date().getTime().toString(), // 插入目前的時間戳
formattedDate, // 插入格式化後的當前日期
rowData.name, // 插入名稱
rowData.email, // 插入電子郵件
formattedInputDate, // 插入格式化後的輸入日期
rowData.level, // 插入等級
rowData.gender, // 插入性別
rowData.comments, // 插入評論
rowData.subscribe // 插入訂閱狀態
]);
}
如果你想要有表單送出後有確認信功能,請把addData(rowData) 的函式改成如下:
//插入資料以及確認信功能
function addData(rowData) {
var currentDate = new Date();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getSheetByName("Sheet1");
var ownerEmail = ss.getOwner().getEmail(); // 獲取試算表擁有者的電子郵件
var formattedDate = Utilities.formatDate(currentDate, ss.getSpreadsheetTimeZone(), "MM/dd/yyyy");
var formattedInputDate = Utilities.formatDate(new Date(rowData.date), ss.getSpreadsheetTimeZone(), "MM/dd/yyyy");
ws.appendRow([
new Date().getTime().toString(),
formattedDate,
rowData.name,
rowData.email,
formattedInputDate,
rowData.level,
rowData.gender,
rowData.comments,
rowData.subscribe
]);
var subject = "✅ 表單上傳成功";
var body = "<!DOCTYPE html><html><head><meta charset='UTF-8'></head><body style='font-family: Arial, sans-serif; color: #333;'>" +
"<h2 style='color: #4CAF50;'>🎉 恭喜!您的表單已成功上傳!</h2>" +
"<div style='border: 2px solid #4CAF50; padding: 15px; border-radius: 10px; background-color: lightgray; max-width: 400px;'>" +
"<p><strong>📌 姓名:</strong> " + rowData.name + "</p>" +
"<p><strong>📧 電子郵件:</strong> " + rowData.email + "</p>" +
"<p><strong>📅 日期:</strong> " + formattedInputDate + "</p>" +
"<p><strong>📊 等級:</strong> " + rowData.level + "</p>" +
"<p><strong>🚻 性別:</strong> " + rowData.gender + "</p>" +
"<p><strong>💬 評論:</strong> " + rowData.comments + "</p>" +
"<p><strong>🔔 訂閱狀態:</strong> " + rowData.subscribe + "</p>" +
"</div>" +
"<hr style='border: 1px solid #ddd;'>" +
"<p>📩 有任何問題,請聯絡 <a href='mailto:cwcchannel@icloud.com'>cwcchannel@icloud.com</a></p>" +
"</body></html>";
MailApp.sendEmail({
to: rowData.email,
cc: ownerEmail,
subject: subject,
htmlBody: body
});
}
11.3.5 html, CSS, JavaScript 程式輸入
form.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<!-- 連結 Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script>
<?!= include("css"); ?>
</head>
<body>
<!-- 建立連結清單 -->
<!-- 從 GAS 匯入取得 URL 的函式 -->
<?var url = getUrl(); ?>
<div class="container-contact" id="profile">
<h3>個人資料</h3>
<div id="msg"></div>
<div class="row">
<!-- 第一欄位 -->
<div class="col-sm-2"></div>
<!-- 第二欄位 -->
<div class="col-sm-8">
<form id="myForm">
<div class="row">
<!-- 第一個輸入 - 名字 -->
<div class="col-sm-6 form-group">
<label for="level">姓名</label>
<input class="form-control" id="name" name="name" placeholder="輸入姓名…" type="text" required>
</div>
<!-- 第二個輸入 - Email -->
<div class="col-sm-6 form-group">
<label for="level">Email</label>
<input class="form-control" id="email" name="email" placeholder="輸入 Email..." type="email" required>
</div>
</div>
<!-- 第三個輸入 - 生日 -->
<div class="row">
<div class="col-sm-6 form-group">
<label for="level">生日</label>
<input class="form-control" id="dateOfBirth" name="dateofbirth" placeholder="生日" type="date">
</div>
<!-- 第四個輸入 - 下拉式選單 -->
<div class="col-sm-6 form-group">
<label for="level">程式語言能力</label>
<br>
<select style="width:100%; margin-top:10px;" name="level" id="level">
<option value="">請選擇</option>
<option value="人才">人才</option>
<option value="專業">專業</option>
<option value="高手">高手</option>
</select>
</div>
</div>
<!-- 第五個輸入 - 性別 -->
<div class="col-sm-12 form-group">
<div style="margin:20px;" class="radio">
<label>性別</label>
<input type="radio" name="gender" id="male" value="男">
<label for="male">男</label>
<input type="radio" name="gender" id="female" value="女">
<label for="female">女</label>
</div>
</div>
<!-- 第六個輸入 - 留言 -->
<textarea class="form-control" id="comments" name="comments" placeholder="評論" rows="5"></textarea>
<!-- 第七個輸入 - 勾選條款 -->
<div class="col-sm-3 form-group">
<label style="display: flex; align-items: center" for="subscribe">
<input style="width: 20px; height: 20px; margin-right: 5px;" type="checkbox" id="subscribe" value="Agree"> 條約同意
</label>
</div>
<div class="row">
<!-- 提示訊息區 -->
<div id="warningMessage" style="display: none; background-color: pink; color: red;"></div>
<div id="successMessage" style="display: none; color: green;">上傳成功!</div>
<!-- 提交按鈕 -->
<div class="col-sm-12 form-group">
<input type="button" value="送出" onclick="getValues()">
</div>
</div>
</form>
</div>
<!-- 第三欄位 -->
<div class="col-sm-2"></div>
</div>
</div>
<h2 id="show"></h2>
<!-- JavaScript 程式 -->
<?!= include("js"); ?>
</body>
</html>css.html
<!-- CSS 格式修改 -->
<style>
.container-contact {
padding: 20px;
text-align: center;
color: white;
background-color: rgb(42, 165, 159);
width: 100%;
}
.container-contact h3 {
font-size: 30px;
text-align: center;
padding: 20px;
}
.btn {
width: 100%;
font-size: 20px;
margin: 20px 0px 20px 0px;
background-color: rgb(6, 117, 119);
color: rgb(255, 255, 255);
border-radius: 20px;
transition: 1s;
}
.btn:hover {
background-color: rgb(178, 224, 84);
color: black;
}
input {
margin-top: 10px;
}
a{
text-decoration:none;
}
/* 增加單選按鈕的大小 */
.radio input[type="radio"] {
transform: scale(2); /* 調整這裡的數值以增加單選按鈕的大小 */
}
/* 增加單選按鈕與標籤之間的間距 */
.radio label {
margin: 10px;
}
</style>js.html
<script>
// 抓輸入的值
function getValues() {
// 驗證姓名
if (document.getElementById("name").value === "") {
alert("Please enter name");
return;
}
// 驗證郵件
if (document.getElementById("email").value === "") {
alert("Please enter email");
return;
}
// 驗證生日
if (document.getElementById("dateOfBirth").value === "") {
alert("Please enter Date of Birth");
return;
}
// 驗證層級
if (document.getElementById("level").value === "") {
alert("Please enter level");
return;
}
// 驗證性別
if (!(document.getElementById('male').checked || document.getElementById('female').checked)) {
alert('Gender is not checked');
return;
}
// 驗證評論
if (document.getElementById("comments").value === "") {
alert("Please enter comment");
return;
}
// 驗證是否同意條款
if (!document.getElementById('subscribe').checked) {
alert('Checkbox not checked');
return;
}
// 獲取性別值
var gender = document.getElementById('male').checked ? document.getElementById("male") : document.getElementById("female");
// 獲取表單的值
var name = document.getElementById("name");
var email = document.getElementById("email");
var date = document.getElementById("dateOfBirth");
var level = document.getElementById("level");
var comments = document.getElementById("comments");
var subscribe = document.getElementById('subscribe').checked ? document.getElementById("subscribe") : document.getElementById("nosubscribe");
// 準備要上傳的資料
var rowData = {
name: name.value,
email: email.value,
date: date.value,
level: level.value,
gender: gender.value,
comments: comments.value,
subscribe: subscribe.value
};
var username = name.value;
// 上傳資料
google.script.run.addData(rowData);
// 清除表單上的資料
document.getElementById("myForm").reset();
// 顯示成功提交的訊息
// document.getElementById("show").innerHTML = "Hello!" + username + " <br>You submitted it successfully! <br><button><a href='<?= url ?>''>Go Back</a></button>";
// 完成後的訊息
alert("資料上傳成功");
// 將焦點設置回"Full name"字段以便進行下一個輸入
document.getElementById("name").focus();
}
</script> 12. 階層式登入系統
12.3.3 Code.js程式輸入
code.gs
// 連接到 Web 應用程式
function doGet() {
var file = HtmlService.createTemplateFromFile("index");
var evaluate = file.evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1');
var html = evaluate.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return html;
}
// 登入系統
function checkLogin(username, password) {
var app = SpreadsheetApp.getActiveSpreadsheet();
var sheetName = app.getSheetByName("Login"); // 請改成您自己的試算表名稱
var data = sheetName.getDataRange().getValues();
var userRole = '';
// var userPic = '';
for (var i = 1; i < data.length; i++) {
var row = data[i];
if (row[0].toString() === username && row[1].toString() === password) {
userRole = row[2].toString();
// userPic = row[5].toString();
userFirstName = row[3].toString();
break;
}
}
if (userRole !== '') {
return { role: userRole, username: username, userFirstName: userFirstName };
} else {
return null;
}
}
// 引入檔案
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
12.3.5 html, CSS, JavaScript 程式輸入
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<!-- Bootstrap 的樣式與腳本 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<style>
.container {
margin-top: 30px;
width: 50%;
border: 2px solid lightgray;
padding: 20px;
border-radius: 20px;
}
.hidden {
display: none;
}
</style>
</head>
<body onload="onload">
<!-- 登入表單 -->
<?!= include('login'); ?>
<!-- 管理者內容 -->
<?!= include('admin'); ?>
<!-- 訪客內容 -->
<?!= include('guest'); ?>
<!-- JavaScript 程式碼 -->
<?!= include('js'); ?>
</body>
</html>login.html
<!-- 登入容器 -->
<div id="loginContainer" class="container mt-5">
<h1 class="mb-4" style="text-align:center;">登入系統</h1>
<form onsubmit="submitForm(event)">
<div class="form-group">
<label for="username">使用者名稱</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<br>
<button type="submit" class="btn btn-primary">登入</button>
</form>
<!-- 載入圖示 -->
<div id="progressIcon" class="progress-icon d-none">
<img style="width:40px; height:40px;" src="https://media.tenor.com/On7kvXhzml4AAAAj/loading-gif.gif" alt="載入中...">
</div>
<!-- 錯誤訊息 -->
<p id="errorMessage" class="text-danger"></p>
</div>
admin.html
<!-- Admin Container -->
<div id="adminContentContainer" class="container-inside hidden">
<div class="row justify-content-center">
<div class="col-12 col-md-6 col-lg-4">
<h1><span id="adminUsername"></span></h1>
<h2>管理者登入</h2>
<!-- Logout button -->
<button id="logoutButton" class="btn btn-danger" onclick="logout()">登出</button>
</div>
</div>
</div>
guest.html
<!-- Guest Container -->
<div id="guestContentContainer" class="container-inside hidden">
<div class="row justify-content-center">
<div class="col-12 col-md-6 col-lg-4">
<h1><span id="guestUsername"></span></h1>
<h2>會員登入</h2>
<!-- Logout button -->
<button id="logoutButton" class="btn btn-danger" onclick="logout()">登出</button>
</div>
</div>
</div>
</div>js.html
<script>
/////////////////登入系統
function submitForm(event) {
event.preventDefault(); // 防止表單的預設提交行為
var username = document.getElementById("username").value; // 獲取使用者名稱的值
var password = document.getElementById("password").value; // 獲取密碼的值
// 顯示載入圖示,在等待登入時
showProgressIcon();
// 使用 Google Apps Script 執行登入驗證,並設置成功處理程序
google.script.run.withSuccessHandler(loginSuccess).checkLogin(username, password);
}
function loginSuccess(data) {
// 登入檢查完成後隱藏載入圖示
hideProgressIcon();
if (data) {
var userRole = data.role; // 使用者角色
var username = data.username; // 使用者名稱
var userFirstName = data.userFirstName; // 使用者的名字
// 登入成功後隱藏登入容器
document.getElementById("loginContainer").style.display = "none";
// 登入成功後清空使用者名稱和密碼欄位
document.getElementById("username").value = "";
document.getElementById("password").value = "";
if (userRole === "admin") { // 如果使用者角色是管理者
document.getElementById("adminContentContainer").classList.remove("hidden"); // 顯示管理者內容容器
document.getElementById("adminUsername").textContent = "歡迎, " + userFirstName + "(管理者身份)!";
} else if (userRole === "guest") { // 如果使用者角色是訪客
document.getElementById("guestContentContainer").classList.remove("hidden"); // 顯示訪客內容容器
document.getElementById("guestUsername").textContent = "歡迎, " + username + " (會員身份)!";
}
} else {
// 顯示錯誤訊息
document.getElementById("errorMessage").textContent = "使用者名稱或密碼錯誤.";
}
}
function showProgressIcon() {
document.getElementById("progressIcon").classList.remove("d-none"); // 顯示載入圖示
}
function hideProgressIcon() {
document.getElementById("progressIcon").classList.add("d-none"); // 隱藏載入圖示
}
// 登出功能
function logout() {
// 隱藏管理者或訪客內容,顯示登入容器
document.getElementById("adminContentContainer").classList.add("hidden"); // 隱藏管理者內容
document.getElementById("guestContentContainer").classList.add("hidden"); // 隱藏訪客內容
document.getElementById("loginContainer").style.display = "block"; // 確保登入容器可見
}
/////////////////登入系統
</script>13. 模糊資料搜尋系統 – SQL
13.3.3 Code.js程式輸入
code.gs
function doGet() {
var file = HtmlService.createTemplateFromFile("search");
var evaluate = file.evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1');
var html = evaluate.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return html;
}
// 包含(include)檔案
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
// 將資料插入試算表
function insertDataToSheet(sheetName, cell, data) {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(sheetName);
if (sheet) {
const cellRange = sheet.getRange(cell);
cellRange.setValue(data);
return "資料成功插入!";
} else {
return "找不到試算表!";
}
} catch (error) {
return "插入資料時出錯: " + error;
}
}
// 從試算表取得資料
function getDataFromSheet(sheetName) {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(sheetName);
if (sheet) {
// 從L到T欄(假設資料在第2行到最後一行)取得資料
const data = sheet.getRange("L2:T" + sheet.getLastRow()).getValues();
// 修改B和E欄的日期格式為純文字
const modifiedData = data.map(row => {
const modifiedRow = row.slice(); // 複製該行資料以避免修改原始資料
// 將B欄(索引1)和E欄(索引4)的日期值轉換為日期物件
if (modifiedRow[1] instanceof Date) {
modifiedRow[1] = Utilities.formatDate(modifiedRow[1], ss.getSpreadsheetTimeZone(), "MM/dd/yyyy");
}
if (modifiedRow[4] instanceof Date) {
modifiedRow[4] = Utilities.formatDate(modifiedRow[4], ss.getSpreadsheetTimeZone(), "MM/dd/yyyy");
}
return modifiedRow;
});
return modifiedData;
} else {
return "找不到試算表!";
}
} catch (error) {
return "取得資料時出錯: " + error;
}
}
13.3.5 html, CSS, JavaScript 程式輸入
search.html
<!DOCTYPE html>
<html>
<head>
<title>搜尋引撆</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap JS (Place this at the end of the body) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js"></script>
<?!= include('css'); ?>
</head>
<body>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-6">
<form class="d-flex" onsubmit="submitForm(event)">
<input id="searchInput" class="form-control me-2" type="search" placeholder="請輸入…" aria-label="Search">
<input class="btn btn-outline-primary" type="submit" value="搜尋">
</form>
</div>
</div>
</div>
<div id="loadingIcon" class="d-none">
<div class="spinner-border" role="status">
<span class="visually-hidden">讀取中...</span>
</div>
</div>
<div id="searchResultText" class="d-none mt-2"></div>
<div id="tableContainer">
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-12">
<table class="table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">輸入時間</th>
<th scope="col">姓名</th>
<th scope="col">Email</th>
<th scope="col">生日</th>
<th scope="col">程式能力</th>
<th scope="col">性別</th>
<th scope="col">評論</th>
<th scope="col">同意</th>
<!-- Add more columns as needed up to Column T -->
</tr>
</thead>
<tbody id="tableBody">
<!-- Table body content will be dynamically filled by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<?!= include('js'); ?>
</body>
</html>js.html
<script>
// 處理表單提交的函數
function submitForm(event) {
event.preventDefault(); // 防止表單的預設提交行為
const searchInput = document.getElementById("searchInput");
const searchTerm = searchInput.value;
// 在獲取資料時顯示載入圖示
const loadingIcon = document.getElementById("loadingIcon");
loadingIcon.classList.remove("d-none");
// 檢查搜尋關鍵字是否為空
if (!searchTerm.trim()) {
// 顯示提示訊息並提前返回
const searchResultText = document.getElementById("searchResultText");
searchResultText.textContent = "請輸入搜尋關鍵字";
searchResultText.classList.remove("d-none");
loadingIcon.classList.add("d-none"); // 隱藏載入圖示
return;
}
// 使用 google.script.run 呼叫伺服器端的函數來插入資料到試算表
google.script.run.withSuccessHandler(function() {
// 資料插入後,從伺服器檢索並顯示資料
google.script.run.withSuccessHandler(function(data) {
const tableContainer = document.getElementById("tableContainer");
tableContainer.style.display = "block"; // 顯示表格容器
const tableBody = document.getElementById("tableBody");
tableBody.innerHTML = ""; // 清除表格中現有的資料
// 遍歷資料並在表格中創建新行
data.forEach(rowData => {
const row = document.createElement("tr");
rowData.forEach(cellData => {
const cell = document.createElement("td");
cell.textContent = cellData;
row.appendChild(cell);
});
tableBody.appendChild(row);
});
// 當資料接收並顯示後,隱藏載入圖示
loadingIcon.classList.add("d-none");
}).getDataFromSheet("Customers");
}).insertDataToSheet("Customers", "K2", searchTerm);
// 在頁面上顯示搜尋結果文字
const searchResultText = document.getElementById("searchResultText");
searchResultText.textContent = "您搜尋:" + searchTerm; // 在搜尋結果文字中加入 "您搜尋:"
searchResultText.classList.remove("d-none");
searchInput.value = ''; // 提交後清除輸入框的內容
}
</script>
css.html
<style>
#loadingIcon {
display: flex;
justify-content: center;
align-items: center;
height: 100px;
}
#loadingIcon .spinner-border {
color: lightgray;
}
#searchResultText {
margin-top: 10px;
font-size: 16px;
color: gray;
text-align: center;
}
#tableContainer{
display: none;
}
</style>14. 資訊發布系統CRUD系統
14.3.3 Code.js程式輸入
code.gs
// 連接到 Web App
function doGet() {
var file = HtmlService.createTemplateFromFile("index"); // 載入前端 HTML 檔案
var evaluate = file.evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1'); // 設定響應式設計
var html = evaluate.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); // 允許 Web App 嵌入
return html;
}
// 包含
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
// 新增資料
function addData(rowData) {
var currentDate = new Date();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getSheetByName("data");
// 產生隨機6個字元的ID
var id = generateRandomID();
// 將生日字串轉換為日期物件
var birthdayDate = new Date(rowData.birthday);
var formattedBirthday = Utilities.formatDate(birthdayDate, ss.getSpreadsheetTimeZone(), "MM/dd/yyyy");
// 新增資料到試算表
ws.appendRow([id, rowData.name, rowData.email, rowData.phone, formattedBirthday]);
var updatedData = getData();
return JSON.stringify(updatedData); // 返回更新後的資料
}
// 產生隨機6個字母數字ID的函數
function generateRandomID() {
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var result = '';
var length = 6;
for (var i = 0; i < length; i++) {
var randomIndex = Math.floor(Math.random() * characters.length);
result += characters[randomIndex];
}
return result; // 返回生成的ID
}
// 根據ID獲取資料
function getCustomerDataById(id) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getSheetByName("data");
var data = ws.getDataRange().getValues();
// 遍歷資料查找匹配的ID
for (var i = 0; i < data.length; i++) {
if (data[i][0] === id) { // ID位於A欄(索引0)
var birthday = data[i][4]; // 生日數據
if (birthday instanceof Date) {
// 如果 birthday 是 Date,則格式化
birthday = Utilities.formatDate(birthday, ss.getSpreadsheetTimeZone(), "yyyy-MM-dd");
} else {
// 否則,保留原始值(可能是字符串)
Logger.log("Invalid date format detected: " + birthday);
}
return {
name: data[i][1],
email: data[i][2],
phone: data[i][3],
birthday: birthday
};
}
}
return null; // 如果未找到,返回 null
}
// 獲取資料
function getData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getSheetByName("data");
var data = ws.getDataRange().getValues();
// 移除表頭
data.shift();
// 將日期格式轉換為純文本(MM/DD/YYYY)格式
data = data.map(function (row) {
if (row[4] instanceof Date) {
row[4] = Utilities.formatDate(row[4], ss.getSpreadsheetTimeZone(), "MM/dd/yyyy");
} else {
Logger.log("Skipping invalid date: " + row[4]); // Debugging log
}
return row;
});
Logger.log(data);
return data; // 返回資料
}
// 更新資料
function updateCustomerData(data) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getSheetByName("data");
var dataRange = ws.getDataRange();
var values = dataRange.getValues();
// Iterate over the rows and find the row with the matching ID
for (var i = 0; i < values.length; i++) {
if (values[i][0] === data.id) { // ID is in the first column (index 0)
// Update the row with the new data
ws.getRange(i + 1, 2).setValue(data.name); // Column B for name
ws.getRange(i + 1, 3).setValue(data.email); // Column C for email
ws.getRange(i + 1, 4).setValue(data.phone); // Column D for phone
ws.getRange(i + 1, 5).setValue(data.birthday); // Column E for birthday
// Exit once the row is updated
return;
}
}
// If the ID wasn't found, you can optionally handle this (e.g., alert or log)
Logger.log('ID not found: ' + data.id);
}
// 刪除資料
function deleteCustomer(id) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getSheetByName("data");
var data = ws.getDataRange().getValues();
// 記錄傳入的ID以進行除錯
Logger.log('Incoming ID to delete: ' + id);
// 查找並刪除指定ID的資料行
var rowDeleted = false;
for (var i = 0; i < data.length; i++) {
// 記錄每一行的ID以進行除錯
Logger.log('Row ID: ' + data[i][0]);
// 比對ID並刪除相符的行
if (data[i][0] == id) {
Logger.log('Deleting row: ' + (i + 1)); // 記錄刪除的行數
ws.deleteRow(i + 1); // 試算表中的行數是1基的
rowDeleted = true;
break;
}
}
if (!rowDeleted) {
Logger.log('No matching row found to delete.');
}
// 刪除後取得更新的資料
var updatedData = getData();
Logger.log('Updated Data: ' + JSON.stringify(updatedData));
return updatedData; // 返回更新後的資料
}
14.3.5 html, CSS, JavaScript 程式輸入
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<!-- 匯入所需的外部 JavaScript 和 CSS 函式庫 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.datatables.net/1.10.23/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.23/js/dataTables.bootstrap4.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.23/css/dataTables.bootstrap4.min.css">
<?!= include('css'); ?>
</head>
<body>
<h1 style="text-align:center;padding-top:10px;">即時發布、編輯、刪除、顯示系統</h1>
<div class="container">
<div class="row">
<!-- 左邊欄位用於表單 -->
<div class="col-md-6">
<form id="upload-form" class="mt-4">
<!-- 表單欄位(姓名、Email、電話、生日) -->
<div class="form-group">
<!-- 隱藏的 ID 輸入框 -->
<input type="hidden" id="id" name="id">
</div>
<div class="form-group">
<label for="name">姓名</label>
<input type="text" class="form-control" id="name" name="name">
</div>
<div class="form-group">
<label for="email">電子郵件</label>
<input type="email" class="form-control" id="email" name="email">
</div>
<div class="form-group">
<label for="phone">電話</label>
<input type="tel" class="form-control" id="phone" name="phone">
</div>
<div class="form-group">
<label for="birthday">生日</label>
<input type="date" class="form-control" id="birthday" name="birthday">
</div>
<!-- 使用 input 標籤作為提交按鈕 -->
<input type="button" class="btn btn-primary uploadData" value="提交" onclick="submitForm()">
<input type="button" class="btn btn-success updateData" value="更新" id="update-button" style="display: none;" onclick="updateForm()">
<div id="success-message" class="mt-3" style="display: none; color: green;"></div>
</form>
</div>
<!-- 右邊欄位用於顯示表格 -->
<div class="col-md-6">
<br>
<!-- 顯示客戶資料的區域 -->
<div id="customer-list"></div>
</div>
</div>
</div>
<!-- 加入以下 JavaScript 程式碼來處理表單提交 -->
<?!= include('js'); ?>
</body>
</html>
js.html
<script>
function displayCustomers(customers) {
var customerListDiv = document.getElementById("customer-list");
// 清除先前的資料
customerListDiv.innerHTML = "";
// 遍歷資料並為每個創建一個框
customers.forEach(function (customer) {
var customerBox = document.createElement("div");
customerBox.className = "customer-box";
customerBox.innerHTML = `
<p><strong>ID:</strong> ${customer[0]}</p>
<p><strong>姓名:</strong> ${customer[1]}</p>
<p><strong>電話:</strong> ${customer[3]}</p>
<p><strong>Email:</strong> ${customer[2]}</p>
<p><strong>生日:</strong> ${customer[4]}</p>
<button class="btn btn-warning edit-btn" data-id="${customer[0]}">編輯</button>
<button class="btn btn-danger delete-btn" data-id="${customer[0]}">刪除</button>
`;
// 為編輯和刪除按鈕添加事件監聽器
customerBox.querySelector(".edit-btn").addEventListener("click", function() {
var id = this.getAttribute("data-id");
editCustomer(id);
});
customerBox.querySelector(".delete-btn").addEventListener("click", function() {
var id = this.getAttribute("data-id");
deleteCustomer(id);
});
customerListDiv.appendChild(customerBox);
});
}
// 編輯資料的功能
function updateForm() {
var id = document.getElementById('id').value;
var name = document.getElementById('name').value;
var email = document.getElementById('email').value;
var phone = document.getElementById('phone').value;
var birthday = document.getElementById('birthday').value;
// Convert the 'yyyy-MM-dd' format to 'MM/dd/yyyy' for the birthday
if (birthday) {
var dateParts = birthday.split('-');
if (dateParts.length === 3) {
// Format it as 'MM/dd/yyyy'
birthday = `${dateParts[1]}/${dateParts[2]}/${dateParts[0]}`;
}
}
// Send the updated data to Google Apps Script
google.script.run.withSuccessHandler(function(response) {
alert('資料已成功更新!');
// 隱藏更新按鈕
document.getElementById('update-button').style.display = 'none';
// 重新載入更新後的資料列表
google.script.run.withSuccessHandler(displayCustomers).getData();
// 更新成功後清空表單欄位
document.getElementById('id').value = '';
document.getElementById('name').value = '';
document.getElementById('email').value = '';
document.getElementById('phone').value = '';
document.getElementById('birthday').value = '';
}).withFailureHandler(function(error) {
alert('更新資料時出錯: ' + error.message);
}).updateCustomerData({ id, name, email, phone, birthday });
}
function editCustomer(id) {
google.script.run.withSuccessHandler(function(customerData) {
if (customerData) {
// 用資料填充表單欄位
document.getElementById('id').value = id;
document.getElementById('name').value = customerData.name;
document.getElementById('email').value = customerData.email;
document.getElementById('phone').value = customerData.phone;
// Convert birthday to 'yyyy-MM-dd' format for the input field
var birthday = customerData.birthday;
if (birthday) {
// Convert 'MM/dd/yyyy' to 'yyyy-MM-dd'
var dateParts = birthday.split('/');
if (dateParts.length === 3) {
birthday = `${dateParts[2]}-${dateParts[0]}-${dateParts[1]}`;
}
}
document.getElementById('birthday').value = birthday;
// 顯示更新按鈕
document.getElementById('update-button').style.display = 'inline-block';
} else {
alert('找不到資料!');
}
}).withFailureHandler(function(error) {
alert('獲取資料時出錯: ' + error.message);
}).getCustomerDataById(id);
}
// 刪除資料的功能
function deleteCustomer(id) {
if (confirm("確定要刪除嗎?")) {
google.script.run.withSuccessHandler(function() {
// 刪除後重新載入資料列表
google.script.run.withSuccessHandler(displayCustomers).getData();
}).deleteCustomer(id);
}
}
// 載入資料並顯示
google.script.run.withSuccessHandler(displayCustomers).getData();
// 驗證並提交表單的功能
function submitForm() {
// 驗證表單欄位
var name = document.getElementById("name").value;
var email = document.getElementById("email").value;
var phone = document.getElementById("phone").value;
var birthday = document.getElementById("birthday").value;
if (name.trim() === "") {
alert("請輸入姓名");
return;
}
if (email.trim() === "") {
alert("請輸入電子郵件");
return;
}
if (phone.trim() === "") {
alert("請輸入電話");
return;
}
if (birthday.trim() === "") {
alert("請輸入生日");
return;
}
// 準備表單資料
var formData = {
name: name,
email: email,
phone: phone,
birthday: birthday
};
// 將表單資料提交至 Google Apps Script
google.script.run.withSuccessHandler(function () {
// 提交成功後,重新載入資料並更新顯示
google.script.run.withSuccessHandler(displayCustomers).getData();
}).addData(formData);
// 清空表單欄位
document.getElementById("name").value = "";
document.getElementById("email").value = "";
document.getElementById("phone").value = "";
document.getElementById("birthday").value = "";
// 顯示成功訊息
var successMessage = document.getElementById("success-message");
successMessage.innerHTML = "資料上傳成功!";
successMessage.style.display = "block";
// 3秒後隱藏成功訊息
setTimeout(function () {
successMessage.style.display = "none";
}, 3000);
// 將焦點設置到姓名欄位,準備輸入下一筆資料
document.getElementById("name").focus();
}
// 載入資料並顯示
google.script.run.withSuccessHandler(displayCustomers).getData();
</script>
css.html
<style>
/* 自訂資料表和包裝器的樣式 */
.custom-table-wrapper {
width: 100%;
}
#data-table td, #data-table th {
white-space: nowrap;
}
.container {
max-width: 80%;
}
.col-md-6 {
width: 90%;
}
#id, #name, #phone, #birthday, #email {
width: 90%;
}
#upload-form {
border: 1px solid lightgray;
border-radius: 15px;
padding: 30px;
padding-left: 40px;
width: 90%;
}
.customer-box {
border: 1px solid lightgray; /* 加入一個淺灰色邊框 */
border-radius: 15px;
padding: 20px;
margin-bottom: 10px;
}
</style>15. 待辦事項-CRUD系統
15.3.3 Code.gs程式輸入
code.gs
// doGet 函數,處理 HTTP GET 請求
function doGet() {
return HtmlService.createTemplateFromFile('index')
.evaluate()
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
// 連結 CSS 和 JavaScript 檔案
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
// 新增 Create
function addItemToSheet(item) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("items");
if (!sheet) {
// 如果工作表不存在,創建一個新的工作表,命名為 "items"
sheet = ss.insertSheet("items");
// 添加標題行
sheet.appendRow(["Item", "Status"]);
}
// 將項目添加到工作表,並設置默認狀態為 0(圓形)
sheet.appendRow([item, 0]);
return item; // 返回添加的項目到客戶端 JavaScript
}
// 讀取 Read
function getAllItems() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("items");
if (!sheet) {
return []; // 如果 "items" 工作表不存在,返回空陣列
}
var dataRange = sheet.getDataRange();
var data = dataRange.getValues();
// 跳過標題行(如果存在)
if (data.length > 1) {
data.shift();
}
return data; // 返回所有數據,包括狀態
}
// 更新 Update
function updateItemInSheet(item, updatedItem) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("items");
if (!sheet) {
return; // 如果 "items" 工作表不存在,返回
}
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
// 搜索工作表中的項目並使用新值進行更新
for (var i = 0; i < values.length; i++) {
if (values[i][0] === item) {
values[i][0] = updatedItem;
break;
}
}
// 清除現有數據並將更新後的數據寫入工作表
sheet.clearContents();
sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
}
// 更新圖標狀態
function updateIconStatus(item, status) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("items");
if (!sheet) {
return; // 如果 "items" 工作表不存在,返回
}
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
// 搜索工作表中的項目並更新其狀態
for (var i = 0; i < values.length; i++) {
if (values[i][0] === item) {
values[i][1] = status; // 更新狀態欄
break;
}
}
// 清除現有數據並將更新後的數據寫入工作表
sheet.clearContents();
sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
}
// 刪除 Delete
function deleteItemFromSheet(item) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("items");
if (!sheet) {
return; // 如果 "items" 工作表不存在,返回
}
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
// 搜索工作表中的項目並從數據陣列中移除
for (var i = values.length - 1; i >= 0; i--) {
if (values[i][0] === item) {
values.splice(i, 1);
}
}
// 清除現有數據並將更新後的數據寫入工作表
sheet.clearContents();
sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
}
15.3.5 html, CSS, JavaScript 程式輸入
index.html
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>新增按鈕與資料上傳</title>
<!-- 加入 Bootstrap CSS 連結 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
<!-- 加入 Font Awesome CSS 連結 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<?!= include("css"); ?>
</head>
<body>
<div class="container mt-5">
<div class="row">
<div class="col-md-12">
<div class="input-group">
<!-- 輸入欄位 -->
<input type="text" style="font-size:20px;" id="itemInput" class="form-control" placeholder="請輸入待辦事項...">
<!-- 按鈕(使用 input 標籤來代替 button) -->
<input type="button" id="addItemButton" class="btn btn-primary" value="新增">
</div>
</div>
</div>
</div>
<!-- 顯示已新增項目的區塊 -->
<div class="container mt-5">
<div class="row">
<div class="col-md-12">
<h3>待辦事項:</h3>
<div id="addedItems"></div>
</div>
</div>
</div>
<!-- 顯示訊息的區塊 -->
<div class="container mt-3">
<div class="row">
<div class="col-md-12">
<div id="messages"></div>
</div>
</div>
</div>
<?!= include("js"); ?>
</body>
</html>
js.html
<!-- 加入 Bootstrap JS 腳本(這是 Bootstrap 功能所必須的) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js"></script>
<!-- 加入自定義 JavaScript -->
<script>
// 顯示新增訊息,5秒後自動關閉
function displayMessage(message, messageType) {
var msgDiv = document.createElement("div");
msgDiv.className = "alert alert-" + messageType + " mt-2";
msgDiv.innerHTML = message;
document.getElementById("messages").appendChild(msgDiv);
// 5秒後移除訊息
setTimeout(function() {
msgDiv.remove();
}, 5000);
}
// 點擊時切換圖示
function toggleIcon(event, item) {
var icon = event.currentTarget;
var status = 0; // 預設為圓形圖示
if (icon.classList.contains("fa-square")) {
icon.classList.remove("fa-square");
icon.classList.add("fa-check-square");
icon.style.color = "#559af2"; // 設定打勾圖示顏色為藍色
status = 1; // 將狀態設為 1(勾選)
} else {
icon.classList.remove("fa-check-square");
icon.classList.add("fa-square");
icon.style.color = ""; // 重設圓形圖示顏色
status = 0; // 將狀態設為 0(未勾選)
}
// 更新 Google Sheets 中圖示的狀態
google.script.run.updateIconStatus(item, status);
}
// 更新 HTML 中的新增項目
function updateAddedItem(item, status) {
var itemDiv = document.createElement("div");
itemDiv.className = "item-container"; // 為項目容器添加類別
// 建立圖示元素
var icon = document.createElement("i");
icon.className = status === 1 ? "fas fa-check-square" : "fas fa-square";
icon.style.marginRight = "10px"; // 圖示後方增加空間
icon.addEventListener("click", function(event) {
event.stopPropagation();
toggleIcon(event, item);
});
itemDiv.appendChild(icon);
// 建立項目文字的 span 元素
var itemTextSpan = document.createElement("span");
itemTextSpan.className = "item-text"; // 為文字添加類別
itemTextSpan.innerHTML = item;
itemDiv.appendChild(itemTextSpan);
// 建立勾選圖示
var checkIcon = document.createElement("i");
checkIcon.className = "fas fa-check check-icon"; // 使用 check-icon 類別將勾選圖示移至左側
checkIcon.style.display = "none"; // 初始隱藏勾選圖示
checkIcon.addEventListener("click", function(event) {
// 防止點擊事件冒泡至項目容器
event.stopPropagation();
// 更新項目文字為輸入框的內容
itemTextSpan.textContent = editInput.value;
// 切換輸入框和勾選圖示的顯示狀態
editInput.style.display = "none";
checkIcon.style.display = "none";
itemTextSpan.style.display = "inline";
// 呼叫 Google Apps Script 函式來更新項目
google.script.run.withSuccessHandler(loadAllItems).updateItemInSheet(item, itemTextSpan.textContent);
});
itemDiv.appendChild(checkIcon);
// 建立編輯圖示
var editIcon = document.createElement("i");
editIcon.className = "fas fa-edit edit-icon"; // 使用 edit-icon 類別將編輯圖示移至右側
editIcon.addEventListener("click", function(event) {
// 防止點擊事件冒泡至項目容器
event.stopPropagation();
// 顯示勾選圖示並隱藏文字
checkIcon.style.display = "inline";
itemTextSpan.style.display = "none";
// 設定輸入框的內容為目前的項目文字
editInput.value = itemTextSpan.textContent;
// 顯示輸入框並聚焦,方便立即編輯
editInput.style.display = "inline";
editInput.focus();
});
itemDiv.appendChild(editIcon);
// 建立刪除圖示
var deleteIcon = document.createElement("i");
deleteIcon.className = "fas fa-trash-alt delete-icon"; // 使用 delete-icon 類別將刪除圖示移至右側
deleteIcon.addEventListener("click", function(event) {
// 防止點擊事件冒泡至項目容器
event.stopPropagation();
// 顯示確認刪除的提示
if (confirm("確定要刪除嗎?")) {
// 呼叫 Google Apps Script 函式來刪除項目
google.script.run.withSuccessHandler(function() {
// 從 HTML 中移除該項目
itemDiv.remove();
}).deleteItemFromSheet(item);
}
});
itemDiv.appendChild(deleteIcon);
// 建立編輯輸入框
var editInput = document.createElement("input");
editInput.type = "text";
editInput.style.display = "none"; // 初始隱藏輸入框
editInput.classList.add("edit-input"); // 添加樣式類別
itemDiv.insertBefore(editInput, itemTextSpan); // 在項目文字之前插入輸入框
document.getElementById("addedItems").appendChild(itemDiv);
}
// 從 Google Sheets 載入所有項目並更新 HTML
function loadAllItems() {
google.script.run.withSuccessHandler(function(items) {
var addedItemsContainer = document.getElementById("addedItems");
addedItemsContainer.innerHTML = ""; // 清空現有項目
// 使用 Set 來避免重複項目
const uniqueItems = new Set();
items.forEach(function(item) {
if (!uniqueItems.has(item[0])) {
uniqueItems.add(item[0]);
updateAddedItem(item[0], item[1]); // 更新項目文字和狀態
}
});
}).getAllItems();
}
// 頁面載入時初始化載入項目
document.addEventListener("DOMContentLoaded", loadAllItems);
document.getElementById("addItemButton").addEventListener("click", function() {
var item = document.getElementById("itemInput").value;
if (item.trim() === "") {
// 如果輸入空白,顯示錯誤訊息
var errorShown = document.querySelector(".alert-danger");
if (!errorShown) {
displayMessage("不能空白", "danger");
}
return; // 如果輸入為空,停止執行
}
google.script.run.withSuccessHandler(function(item) {
// 顯示成功訊息
var successShown = document.querySelector(".alert-success");
if (!successShown) {
displayMessage("新增成功", "success");
}
// 更新 HTML 顯示新增的項目
updateAddedItem(item);
}).addItemToSheet(item);
// 新增後清空輸入框
document.getElementById("itemInput").value = "";css.html
<style>
.item-container {
border: 1px solid #90a6fd;
background-color: #dcf2f9;
padding: 10px;
padding-left:20px;
margin-bottom: 5px;
border-radius:10px;
color:#559af2;
font-size:20px;
display: flex;
align-items: right;
justify-content: space-between; /* Move icons to the right */
}
.item-container:hover {
border: 1px solid #90a6fd;
background-color: blue;
color:lightblue;
transition:0.4s;
}
.edit-icon {
color: #8fce8f;
margin-left: auto;
padding-right: 10px;
}
.item-crossed {
text-decoration: line-through;
text-decoration-thickness: 2px;
text-decoration-color: red;
text-decoration-skip-ink: none;
}
.check-icon{
margin-left:10px;
font-size:30px;
color:
}
</style>
新增章節 第19章. Google試算表-功能列按鈕 - 跳出CRUD系統
以下為程式分享
code.gs
//加入功能列按鈕
//加入功能列按鈕
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('ℹ️更新管理')
.addItem('🆙更新', 'showUpdateModal')
.addItem('📁瀏覽', 'showBrowseModal')
.addToUi();
}
//顯示更新視窗
function showUpdateModal() {
const html = HtmlService.createHtmlOutputFromFile('UpdateModal')
.setWidth(800)
.setHeight(550);
SpreadsheetApp.getUi().showModalDialog(html, '更新');
}
//顯示瀏覽視窗
function showBrowseModal() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('更新');
const data = sheet.getDataRange().getValues();
// Format and sort data in descending order by date
const formattedData = data
.slice(1) // Exclude header row
.map(row => [formatDate(row[0]), row[1] || '']) // Ensure text format
.sort((a, b) => new Date(b[0]) - new Date(a[0])); // Sort by date (newest first)
const template = HtmlService.createTemplateFromFile('BrowseModal');
template.data = formattedData;
const html = template.evaluate().setWidth(800).setHeight(650);
SpreadsheetApp.getUi().showModalDialog(html, '瀏覽');
}
// 格式化日期
function formatDate(date) {
if (date instanceof Date) {
return Utilities.formatDate(date, Session.getScriptTimeZone(), "yyyy/MM/dd");
}
return date; // Return as is if not a valid date
}
//儲存紀錄
function saveData(date, content) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('更新');
// Get the last row with data
const lastRow = sheet.getLastRow();
// Fix timezone issue by adding time offset
let selectedDate = new Date(date);
selectedDate.setHours(selectedDate.getHours() + 8); // Adjust for time zone differences (UTC+8 for Taiwan/HK)
// Convert to yyyy/MM/dd format
let formattedDate = Utilities.formatDate(selectedDate, Session.getTimeZone(), "yyyy/MM/dd");
// Ensure content is treated as text
if (typeof content === 'number') {
content = "'" + content.toString(); // Prefix with a single quote to force text format
}
// Insert the formatted date and content in the next empty row
sheet.getRange(lastRow + 1, 1).setValue(formattedDate);
sheet.getRange(lastRow + 1, 1).setNumberFormat("yyyy/MM/dd"); // Ensure format
sheet.getRange(lastRow + 1, 2).setValue(content);
sheet.getRange(lastRow + 1, 2).setNumberFormat("@"); // Set format to text
}
//刪除紀錄
function deleteRow(date, content) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('更新');
const data = sheet.getDataRange().getValues();
for (let i = data.length - 1; i >= 1; i--) { // Skip header row
let sheetDate = Utilities.formatDate(new Date(data[i][0]), Session.getTimeZone(), "yyyy/MM/dd");
let inputDate = date.replace(/-/g, "/"); // Ensure format matches
// Ensure content is treated as a string
let sheetContent = data[i][1].toString();
let inputContent = content.toString();
if (sheetDate === inputDate && sheetContent === inputContent) {
sheet.deleteRow(i + 1);
break;
}
}
}
//更新紀錄
function updateRow(originalDate, originalContent, newDate, newContent) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('更新');
const data = sheet.getDataRange().getValues();
for (let i = 1; i < data.length; i++) { // Skip header row
let sheetDate = Utilities.formatDate(new Date(data[i][0]), Session.getTimeZone(), "yyyy/MM/dd");
let inputOriginalDate = originalDate.replace(/-/g, "/");
// Ensure content is treated as a string
let sheetContent = data[i][1].toString();
let inputOriginalContent = originalContent.toString();
// Find the row to update
if (sheetDate === inputOriginalDate && sheetContent === inputOriginalContent) {
const rowNum = i + 1; // Row index in Google Sheets (1-based index)
// Fix timezone issue by adding time offset
let selectedDate = new Date(newDate);
selectedDate.setHours(selectedDate.getHours() + 8); // Adjust for UTC+8 (Taiwan/HK)
// Convert to yyyy/MM/dd format
let formattedDate = Utilities.formatDate(selectedDate, Session.getTimeZone(), "yyyy/MM/dd");
// Ensure newContent is treated as text
if (typeof newContent === 'number') {
newContent = "'" + newContent.toString(); // Prefix with a single quote to force text format
}
// Update date (Column A) and content (Column B)
sheet.getRange(rowNum, 1).setValue(formattedDate);
sheet.getRange(rowNum, 1).setNumberFormat("yyyy/MM/dd"); // Ensure format
sheet.getRange(rowNum, 2).setValue(newContent);
sheet.getRange(rowNum, 2).setNumberFormat("@"); // Set format to text
break;
}
}
}
UpdateModal.html
<!DOCTYPE html>
<html>
<head>
<style>
/* General Styling */
body {
font-family: 'Poppins', sans-serif;
background-color: #e3e6ed;
color: #2a3f5f;
padding: 20px;
margin: 0;
}
.container {
max-width: 100%;
margin: auto;
background: linear-gradient(135deg, #d1d6e0, #b0b8c7);
padding: 45px;
border-radius: 12px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.2);
border: 1px solid #9aa3b5;
}
h3 {
text-align: center;
margin-bottom: 20px;
font-size: 22px;
color: #1c2e4a;
font-weight: 600;
}
label {
font-size: 14px;
font-weight: 500;
margin-bottom: 5px;
display: block;
color: #324b6d;
}
input[type="date"], textarea {
width: 100%;
padding: 12px;
margin-bottom: 20px;
border-radius: 8px;
border: 1px solid #7f8fa6;
background-color: #e7ebf2;
color: #1c2e4a;
transition: all 0.3s ease-in-out;
font-size: 14px;
}
input[type="date"]:hover, textarea:hover {
border-color: #0056b3;
background-color: #dce2eb;
}
textarea {
resize: vertical;
min-height: 120px;
}
.button-container {
display: flex;
justify-content: space-between;
}
button {
width: 48%;
padding: 12px;
font-size: 15px;
border-radius: 8px;
border: none;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease-in-out;
}
.save-button {
background: linear-gradient(135deg, #0056b3, #003d80);
color: white;
box-shadow: 0px 3px 6px rgba(0, 86, 179, 0.4);
}
.save-button:hover {
background: linear-gradient(135deg, #004494, #002e66);
box-shadow: 0px 5px 10px rgba(0, 86, 179, 0.6);
transform: scale(1.05);
}
.cancel-button {
background: linear-gradient(135deg, #6c757d, #495057);
color: white;
}
.cancel-button:hover {
background: linear-gradient(135deg, #495057, #343a40);
transform: scale(1.05);
}
</style>
<script>
// Function to set today's date as the default value in the date picker
function setDefaultDate() {
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0'); // Months start from 0
const dd = String(today.getDate()).padStart(2, '0');
document.getElementById("date").value = `${yyyy}-${mm}-${dd}`;
}
function saveData() {
const date = document.getElementById("date").value;
const content = document.getElementById("content").value;
if (!date || !content) {
alert("請輸入日期與內容!");
return;
}
google.script.run.saveData(date, content);
google.script.host.close();
}
// Set default date when the modal loads
window.onload = setDefaultDate;
</script>
</head>
<body>
<div class="container">
<h3>新增更新</h3>
<div class="form-group">
<label for="date">日期:</label>
<input type="date" id="date" class="form-control" required>
</div>
<div class="form-group">
<label for="content">內容:</label>
<textarea id="content" class="form-control" rows="4" required></textarea>
</div>
<div class="button-container">
<button class="save-button" onclick="saveData()">儲存</button>
<button class="cancel-button" onclick="google.script.host.close()">取消</button>
</div>
</div>
</body>
</html>
BrowseModal.html
<!DOCTYPE html>
<html>
<head>
<!-- Include Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Include DataTables CSS and JS -->
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.css">
<script type="text/javascript" charset="utf8" src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.js"></script>
<script>
$(document).ready(function() {
$('#dataTable').DataTable({
paging: true,
searching: true,
info: false,
order: [[0, "desc"]],
language: {
"processing": "處理中...",
"search": "搜尋:",
"lengthMenu": "顯示 _MENU_ 筆資料",
"info": "顯示第 _START_ 至 _END_ 筆,共 _TOTAL_ 筆",
"infoEmpty": "沒有資料可顯示",
"infoFiltered": "(從 _MAX_ 筆資料篩選)",
"loadingRecords": "載入中...",
"zeroRecords": "沒有符合的資料",
"paginate": {
"first": "首頁",
"last": "末頁",
"next": "下一頁",
"previous": "上一頁"
}
}
});
// Edit function
$('.edit-icon').click(function() {
const row = $(this).closest('tr');
const dateCell = row.find('td:first');
const contentCell = row.find('td:nth-child(2)');
const editButton = $(this);
const checkButton = row.find('.check-icon');
// Store original values
row.data('originalDate', dateCell.text());
row.data('originalContent', contentCell.text());
// Make fields editable
dateCell.html(`<input type="date" class="form-control" value="${dateCell.text().replace(/\//g, '-')}">`);
contentCell.html(`<input type="text" class="form-control" value="${contentCell.text()}">`);
editButton.hide();
checkButton.show();
});
// Save edited data
$('.check-icon').click(function() {
const row = $(this).closest('tr');
const originalDate = row.data('originalDate');
const originalContent = row.data('originalContent');
const dateInput = row.find('td:first input').val();
const contentInput = row.find('td:nth-child(2) input').val();
const checkButton = $(this);
const editButton = row.find('.edit-icon');
if (!dateInput || !contentInput) {
alert("日期與內容不能為空!");
return;
}
google.script.run.withSuccessHandler(() => {
row.find('td:first').text(dateInput.replace(/-/g, '/'));
row.find('td:nth-child(2)').text(contentInput);
checkButton.hide();
editButton.show();
}).updateRow(originalDate, originalContent, dateInput, contentInput);
});
// Delete function
$('.delete-icon').click(function() {
const row = $(this).closest('tr');
const date = row.find('td:first').text();
const content = row.find('td:nth-child(2)').text();
if (confirm("確定要刪除此記錄嗎?")) {
google.script.run.withSuccessHandler(() => {
row.fadeOut(300, function() { $(this).remove(); });
}).deleteRow(date, content);
}
});
});
</script>
<style>
.icon {
cursor: pointer;
font-size: 16px;
transition: 0.3s;
margin-right: 8px;
}
.edit-icon { color: blue; }
.edit-icon:hover { color: darkblue; transform: scale(1.2); }
.check-icon { color: green; display: none; }
.check-icon:hover { color: darkgreen; transform: scale(1.2); }
.delete-icon { color: red; }
.delete-icon:hover { color: darkred; transform: scale(1.2); }
</style>
</head>
<body>
<div class="container mt-3">
<h3>更新記錄</h3>
<table id="dataTable" class="table table-striped">
<thead>
<tr>
<th>日期</th>
<th>內容</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<? for (var i = data.length - 1; i >= 0; i--) { ?>
<tr>
<td><?= data[i][0] ?></td>
<td><?= data[i][1] ?></td>
<td>
<span class="icon edit-icon">✏️</span>
<span class="icon check-icon">✔️</span>
<span class="icon delete-icon">🗑</span>
</td>
</tr>
<? } ?>
</tbody>
</table>
<button class="btn btn-secondary" onclick="google.script.host.close()">關閉</button>
</div>
</body>
</html>
