0. 作品介紹
封面
1. AI 提示詞
把這段指令貼給 AI,讓它幫你生成程式碼。
我想要建立一個 【教師軟體成果收集與展示平台】
幫我用Google 試算表建立 【教師軟體成果資料庫】
我希望有些資料我可以直接在試算表更改首頁可以直接呈現
需求說明 【
1.資料與名單管理:
我有一個名單,包含「教師姓名」與「需繳交的軟體項目」。
後端需與 Google 試算表連動,即時讀取名單設定。
照片需儲存在 Google 雲端硬碟,且自動建立資料夾階層:第一層為「軟體名稱」、第二層為「教師姓名」、第三層存放照片。
2.使用者介面 :
必須支援手機版操作,方便老師使用手機拍照後直接上傳。
老師選擇自己的姓名後,系統自動列出該位老師需繳交的軟體項目。
每個項目需顯示「未繳交」或「已完成」的狀態燈號。
支援一次選取多張照片進行批次上傳。
3.彈性擴充:
若教師不在名單內,可選擇「其他」並手動輸入姓名。
若軟體不在清單內,可選擇「其他」並手動輸入軟體名稱。
上傳成功後,介面需即時更新為「已完成」狀態,不需重新整理網頁。
4.成果展示區:
在網頁最下方顯示所有上傳的照片,並依「軟體名稱」分類。
展示區僅顯示照片,不顯示教師姓名與上傳時間。
每個軟體類別只需隨機顯示 3~4 張照片即可。
點擊照片後,需彈出視窗 (Modal/Lightbox) 顯示大圖】
要把它變成程式碼我要發佈
請完整敘述操作步驟 越詳細越好
作業環境
Google試算表
Google Apps Script
我看不懂程式碼,請給我後端gs完整程式碼、前端index完整程式碼
補充:我有使用雲端硬碟回收照片或PDF或文件【雲端硬碟連結】
幫我用Google 試算表建立 【教師軟體成果資料庫】
我希望有些資料我可以直接在試算表更改首頁可以直接呈現
需求說明 【
1.資料與名單管理:
我有一個名單,包含「教師姓名」與「需繳交的軟體項目」。
後端需與 Google 試算表連動,即時讀取名單設定。
照片需儲存在 Google 雲端硬碟,且自動建立資料夾階層:第一層為「軟體名稱」、第二層為「教師姓名」、第三層存放照片。
2.使用者介面 :
必須支援手機版操作,方便老師使用手機拍照後直接上傳。
老師選擇自己的姓名後,系統自動列出該位老師需繳交的軟體項目。
每個項目需顯示「未繳交」或「已完成」的狀態燈號。
支援一次選取多張照片進行批次上傳。
3.彈性擴充:
若教師不在名單內,可選擇「其他」並手動輸入姓名。
若軟體不在清單內,可選擇「其他」並手動輸入軟體名稱。
上傳成功後,介面需即時更新為「已完成」狀態,不需重新整理網頁。
4.成果展示區:
在網頁最下方顯示所有上傳的照片,並依「軟體名稱」分類。
展示區僅顯示照片,不顯示教師姓名與上傳時間。
每個軟體類別只需隨機顯示 3~4 張照片即可。
點擊照片後,需彈出視窗 (Modal/Lightbox) 顯示大圖】
要把它變成程式碼我要發佈
請完整敘述操作步驟 越詳細越好
作業環境
Google試算表
Google Apps Script
我看不懂程式碼,請給我後端gs完整程式碼、前端index完整程式碼
補充:我有使用雲端硬碟回收照片或PDF或文件【雲端硬碟連結】
2. 資料表規則
請照下面規則建立分頁與欄位。
第1分頁: 名單設定
A 欄: 教師姓名
B 欄: 需繳交軟體
第2分頁: 繳交紀錄
A 欄: 時間戳記
B 欄: 教師姓名
C 欄: 軟體名稱
D 欄: 圖片連結
E 欄: 檔案ID
📄 解析結果(自動表格)
分頁:名單設定
| A 欄 | B 欄 |
|---|---|
| 教師姓名 | 需繳交軟體 |
分頁:繳交紀錄
| A 欄 | B 欄 | C 欄 | D 欄 | E 欄 |
|---|---|---|---|---|
| 時間戳記 | 教師姓名 | 軟體名稱 | 圖片連結 | 檔案ID |
3. 開啟 Apps Script
從試算表開 Apps Script,才能直接用 SpreadsheetApp 讀寫資料。
- 試算表上方選單:擴充功能 → Apps Script
- 刪掉預設的程式碼(或全部選取覆蓋)
-
保留/建立檔案:
- Code.gs
- index.html
4. 貼上後端(Code.gs)
把後端程式貼到 Apps Script 的 Code.gs。
/**
* 設定區:請將下方的 ID 換成你第一步建立的 Google Drive 資料夾 ID
* 這將作為「根目錄」,程式會在其下自動建立「軟體資料夾」與「教師資料夾」
*/
const UPLOAD_FOLDER_ID = "你的資料夾ID貼在這裡"; // 例如 "1abcDEfg..."
/* -------------------------------------------------- */
// 1. 取得網頁介面
function doGet() {
return HtmlService.createTemplateFromFile('index')
.evaluate()
.setTitle('教師軟體成果收集平台')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
// 2. 取得所有初始資料 (名單、軟體清單、目前繳交狀態、成果展照片)
function getInitialData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const configSheet = ss.getSheetByName("名單設定");
const recordSheet = ss.getSheetByName("繳交紀錄");
// 讀取名單設定 (假設 A欄是姓名, B欄是需繳交軟體)
const lastRow = configSheet.getLastRow();
const configData = lastRow > 1 ? configSheet.getRange(2, 1, lastRow - 1, 2).getValues() : [];
// 整理老師名單與需求
let teachers = {};
let allSoftwareSet = new Set();
configData.forEach(row => {
let name = row[0];
let softStr = row[1];
if (name && softStr) {
let softwareList = softStr.toString().split(/[,,]/).map(s => s.trim()).filter(s => s !== "");
teachers[name] = softwareList;
softwareList.forEach(s => allSoftwareSet.add(s));
}
});
// 讀取繳交紀錄
let records = [];
if (recordSheet.getLastRow() > 1) {
records = recordSheet.getRange(2, 1, recordSheet.getLastRow() - 1, 5).getValues();
}
let completedMap = {};
let galleryData = {};
records.forEach(row => {
let rName = row[1];
let rSoft = row[2];
let rUrl = row[3];
// 標記完成狀態
completedMap[`${rName}_${rSoft}`] = true;
// 收集成果展照片
if (rUrl) {
if (!galleryData[rSoft]) {
galleryData[rSoft] = [];
}
galleryData[rSoft].unshift({
teacher: rName,
url: rUrl,
time: new Date(row[0]).toLocaleDateString()
});
}
});
return {
teachers: teachers,
completedMap: completedMap,
allSoftware: Array.from(allSoftwareSet),
gallery: galleryData
};
}
// 3. 輔助函式:取得或建立資料夾
function getOrCreateFolder(parentFolder, folderName) {
const folders = parentFolder.getFoldersByName(folderName);
if (folders.hasNext()) {
return folders.next();
} else {
const newFolder = parentFolder.createFolder(folderName);
newFolder.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
return newFolder;
}
}
// 4. 處理檔案上傳
function uploadPhoto(data) {
try {
// A. 將 Base64 轉為 Blob
const contentType = data.fileData.substring(5, data.fileData.indexOf(';'));
const bytes = Utilities.base64Decode(data.fileData.substring(data.fileData.indexOf('base64,') + 7));
const blob = Utilities.newBlob(bytes, contentType, data.fileName);
// B. 處理資料夾階層
const rootFolder = DriveApp.getFolderById(UPLOAD_FOLDER_ID);
const softwareFolder = getOrCreateFolder(rootFolder, data.softwareName);
const teacherFolder = getOrCreateFolder(softwareFolder, data.teacherName);
// C. 存入最終資料夾
const file = teacherFolder.createFile(blob);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
const displayUrl = `https://drive.google.com/uc?export=view&id=${file.getId()}`;
// D. 寫入試算表
const ss = SpreadsheetApp.getActiveSpreadsheet();
const recordSheet = ss.getSheetByName("繳交紀錄");
recordSheet.appendRow([
new Date(),
data.teacherName,
data.softwareName,
displayUrl,
file.getId()
]);
// [更新] 回傳 fileName 讓前端知道哪個檔案傳完了
return { success: true, url: displayUrl, fileName: data.fileName };
} catch (e) {
return { success: false, error: e.toString() };
}
} 貼到 Apps Script 的 Code.gs 檔案
5. 貼上前端(index.html)
把前端程式貼到 Apps Script 的 index.html。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 使用 Bootstrap 5 讓介面美觀 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body { background-color: #f8f9fa; font-family: "Microsoft JhengHei", sans-serif; }
.container { max-width: 900px; padding-top: 30px; padding-bottom: 50px; }
.card { border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); border: none; margin-bottom: 20px; }
.header-box { background: linear-gradient(135deg, #0d6efd, #0dcaf0); color: white; padding: 30px; border-radius: 12px; margin-bottom: 30px; text-align: center; }
.status-badge { font-size: 0.9em; padding: 6px 12px; border-radius: 20px; }
.gallery-img { width: 100%; height: 200px; object-fit: cover; border-radius: 8px; cursor: pointer; transition: transform 0.2s; }
.gallery-img:hover { transform: scale(1.03); }
.loading-overlay { position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(255,255,255,0.8); z-index: 9999; display: flex; justify-content: center; align-items: center; flex-direction: column; }
.software-section-title { border-left: 5px solid #0d6efd; padding-left: 10px; margin-top: 30px; margin-bottom: 15px; }
/* [新增] Modal 圖片樣式 */
#modalImage {
max-height: 80vh; /* 限制高度,避免超出螢幕 */
width: auto;
max-width: 100%;
display: block;
margin: 0 auto;
object-fit: contain;
}
</style>
</head>
<body>
<!-- Loading 動畫 -->
<div id="loader" class="loading-overlay">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;"></div>
<div class="mt-3 fw-bold">系統載入中,請稍候...</div>
</div>
<div class="container">
<div class="header-box">
<h1><i class="fas fa-camera"></i> 教師軟體成果收集平台</h1>
<p class="mb-0">請選擇您的姓名並上傳指定軟體的使用照片</p>
</div>
<!-- 步驟 1: 選擇身份 -->
<div class="card p-4">
<h4 class="mb-3"><i class="fas fa-user-circle"></i> 步驟 1:請選擇教師姓名</h4>
<select id="teacherSelect" class="form-select form-select-lg" onchange="handleTeacherChange()">
<option value="" selected disabled>-- 請選擇 --</option>
<!-- 選單由 JavaScript 動態產生 -->
</select>
<!-- 若選擇其他,顯示手動輸入框 -->
<div id="otherTeacherInputDiv" class="mt-3" style="display: none;">
<label class="form-label">請輸入您的姓名:</label>
<input type="text" id="customTeacherName" class="form-control" placeholder="例如:張代課">
<div class="mt-2 text-muted small">選擇「其他」的老師,您可以從下方自由選擇要繳交的軟體。</div>
</div>
</div>
<!-- 步驟 2: 任務列表 (動態顯示) -->
<div id="taskSection" class="card p-4" style="display: none;">
<h4 class="mb-3"><i class="fas fa-list-check"></i> 步驟 2:上傳軟體使用照片</h4>
<p class="text-muted small"><i class="fas fa-info-circle"></i> 支援多張照片同時選取上傳。</p>
<div id="taskList">
<!-- 任務卡片會插在這裡 -->
</div>
<!-- 給「其他」老師或是想額外上傳的按鈕 -->
<div id="extraUploadSection" class="mt-4 pt-3 border-top">
<h5><i class="fas fa-plus-circle"></i> 上傳其他軟體照片</h5>
<div class="row g-2 align-items-center">
<div class="col-md-6">
<select id="extraSoftwareSelect" class="form-select" onchange="handleExtraSoftwareChange()">
<option value="" selected>-- 選擇軟體 --</option>
</select>
<!-- 自訂軟體名稱輸入框 -->
<input type="text" id="customSoftwareInput" class="form-control mt-2" placeholder="請輸入軟體名稱 (例如: T++)" style="display: none;">
</div>
<div class="col-md-2">
<button class="btn btn-outline-primary w-100" onclick="addManualTask()">新增上傳</button>
</div>
</div>
</div>
</div>
<!-- 步驟 3: 成果展示區 -->
<div class="card p-4 mt-5">
<h4 class="mb-4 text-center"><i class="fas fa-images"></i> 成果展示區</h4>
<p class="text-center text-muted">目前所有老師繳交的成果將顯示於此</p>
<div id="galleryContainer">
<!-- 照片會分類插在這裡 -->
</div>
</div>
</div>
<!-- [新增] 圖片預覽 Modal (燈箱) -->
<div class="modal fade" id="imagePreviewModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">成果預覽</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center bg-dark">
<img id="modalImage" src="" alt="預覽圖片">
</div>
</div>
</div>
</div>
<!-- JavaScript 邏輯 -->
<!-- 引入 Bootstrap JS 以支援 Modal -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
let globalData = {}; // 儲存後端傳來的資料
let imageModal; // Modal 實例
window.onload = function() {
// 初始化 Modal
imageModal = new bootstrap.Modal(document.getElementById('imagePreviewModal'));
// [FIX] 檢查 google 是否定義,若無則載入模擬資料 (防止在預覽環境報錯)
if (typeof google === 'undefined' || typeof google.script === 'undefined') {
console.warn('未偵測到 Google Apps Script 環境,載入模擬資料供預覽。');
loadMockData();
} else {
google.script.run.withSuccessHandler(initPage).getInitialData();
}
};
// [NEW] 模擬資料函式
function loadMockData() {
const mockData = {
teachers: {
"王大明 (預覽)": ["Canva", "Word"],
"林小美 (預覽)": ["Excel"]
},
completedMap: {},
allSoftware: ["Canva", "Word", "Excel", "PowerPoint"],
gallery: {
"Canva": [
{ url: "https://via.placeholder.com/600x400?text=Preview+Image", teacher: "預覽", time: "2023/1/1" }
]
}
};
// 模擬一點延遲增加真實感
setTimeout(() => initPage(mockData), 500);
}
function initPage(data) {
globalData = data;
const select = document.getElementById('teacherSelect');
const extraSelect = document.getElementById('extraSoftwareSelect');
// 1. 填入老師選單
Object.keys(data.teachers).forEach(name => {
let option = document.createElement('option');
option.value = name;
option.text = name;
select.add(option);
});
let otherOpt = document.createElement('option');
otherOpt.value = "Other";
otherOpt.text = "其他 (名單外教師)";
select.add(otherOpt);
// 2. 填入所有軟體選單
data.allSoftware.forEach(soft => {
let opt = document.createElement('option');
opt.value = soft;
opt.text = soft;
extraSelect.add(opt);
});
let customSoftOpt = document.createElement('option');
customSoftOpt.value = "_CUSTOM_";
customSoftOpt.text = "其他 (自行輸入)";
extraSelect.add(customSoftOpt);
renderGallery();
document.getElementById('loader').style.display = 'none';
}
function handleTeacherChange() {
const selectedName = document.getElementById('teacherSelect').value;
const otherInputDiv = document.getElementById('otherTeacherInputDiv');
const taskSection = document.getElementById('taskSection');
const taskList = document.getElementById('taskList');
taskList.innerHTML = '';
taskSection.style.display = 'block';
if (selectedName === 'Other') {
otherInputDiv.style.display = 'block';
taskList.innerHTML = '<p class="text-muted">請使用下方「上傳其他軟體照片」區塊來進行繳交。</p>';
} else {
otherInputDiv.style.display = 'none';
const softwares = globalData.teachers[selectedName] || [];
if(softwares.length === 0) {
taskList.innerHTML = '<div class="alert alert-warning">此教師目前沒有設定需繳交的軟體。</div>';
}
softwares.forEach(soft => {
createTaskRow(selectedName, soft);
});
}
}
function handleExtraSoftwareChange() {
const select = document.getElementById('extraSoftwareSelect');
const input = document.getElementById('customSoftwareInput');
if (select.value === '_CUSTOM_') {
input.style.display = 'block';
input.focus();
} else {
input.style.display = 'none';
}
}
// 建立單一任務列 (UI)
function createTaskRow(teacherName, softwareName) {
const taskList = document.getElementById('taskList');
const isCompleted = globalData.completedMap[`${teacherName}_${softwareName}`];
const div = document.createElement('div');
div.className = 'card p-3 mb-2 shadow-sm';
div.style.borderLeft = isCompleted ? '5px solid #198754' : '5px solid #dc3545';
let statusHtml = isCompleted
? '<span class="badge bg-success status-badge"><i class="fas fa-check"></i> 已完成</span>'
: '<span class="badge bg-danger status-badge"><i class="fas fa-times"></i> 未繳交</span>';
div.innerHTML = `
<div class="row align-items-center">
<div class="col-md-4">
<h5 class="mb-0 fw-bold">${softwareName}</h5>
</div>
<div class="col-md-3">
${statusHtml}
</div>
<div class="col-md-5 text-end">
<input type="file" class="form-control d-none" id="file_${softwareName}" accept="image/*" multiple onchange="uploadFile(this, '${softwareName}')">
<button class="btn btn-outline-primary" onclick="document.getElementById('file_${softwareName}').click()">
<i class="fas fa-cloud-upload-alt"></i> ${isCompleted ? '繼續上傳' : '上傳照片'}
</button>
</div>
</div>
`;
taskList.appendChild(div);
}
function addManualTask() {
let teacherName = getCurrentTeacherName();
if (!teacherName) {
Swal.fire('提示', '請先輸入或選擇教師姓名', 'warning');
return;
}
const select = document.getElementById('extraSoftwareSelect');
let softName = select.value;
if (softName === '_CUSTOM_') {
const input = document.getElementById('customSoftwareInput');
softName = input.value.trim();
if (!softName) {
Swal.fire('提示', '請輸入軟體名稱', 'warning');
return;
}
} else if (!softName) {
Swal.fire('提示', '請選擇要新增的軟體', 'warning');
return;
}
createTaskRow(teacherName, softName);
if (select.value === '_CUSTOM_') {
document.getElementById('customSoftwareInput').value = '';
}
}
function getCurrentTeacherName() {
const select = document.getElementById('teacherSelect');
if (select.value === 'Other') {
return document.getElementById('customTeacherName').value.trim();
}
return select.value;
}
async function uploadFile(inputElement, softwareName) {
const files = inputElement.files;
if (files.length === 0) return;
const teacherName = getCurrentTeacherName();
if (!teacherName) {
Swal.fire('錯誤', '請先填寫姓名', 'error');
inputElement.value = '';
return;
}
let successCount = 0;
let failCount = 0;
Swal.fire({
title: '準備上傳中...',
html: `共有 ${files.length} 張照片,請勿關閉視窗。`,
allowOutsideClick: false,
didOpen: () => { Swal.showLoading(); }
});
for (let i = 0; i < files.length; i++) {
const file = files[i];
Swal.update({
html: `正在上傳第 <b>${i + 1}</b> / <b>${files.length}</b> 張照片<br><small class="text-muted">${file.name}</small>`
});
try {
const result = await uploadSingleFile(file, teacherName, softwareName);
if (result.success) {
successCount++;
updateGalleryLocal(softwareName, teacherName, result.url);
globalData.completedMap[`${teacherName}_${softwareName}`] = true;
} else {
failCount++;
console.error("Upload failed for " + file.name + ": " + result.error);
}
} catch (err) {
failCount++;
console.error("System error for " + file.name + ": " + err);
}
}
inputElement.value = '';
handleTeacherChange();
if (failCount === 0) {
Swal.fire('全部完成', `成功上傳 ${successCount} 張照片!`, 'success');
} else {
Swal.fire('完成但有錯誤', `成功: ${successCount} 張<br>失敗: ${failCount} 張`, 'warning');
}
}
function uploadSingleFile(file, teacherName, softwareName) {
return new Promise((resolve, reject) => {
// [FIX] 增加對預覽環境的判斷,如果是預覽則回傳假成功
if (typeof google === 'undefined' || typeof google.script === 'undefined') {
console.log("模擬環境:假裝上傳成功");
setTimeout(() => {
resolve({
success: true,
url: "https://via.placeholder.com/600x400?text=Simulated+Upload",
fileName: file.name
});
}, 800);
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const fileData = e.target.result;
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
.uploadPhoto({
fileData: fileData,
fileName: file.name,
mimeType: file.type,
teacherName: teacherName,
softwareName: softwareName
});
};
reader.readAsDataURL(file);
});
}
// [新增] 開啟圖片預覽視窗
function openImageModal(url) {
const modalImg = document.getElementById('modalImage');
// 先顯示載入中或預設圖,避免上一張殘留
modalImg.src = "";
modalImg.src = url;
imageModal.show();
}
// [核心修改] 渲染成果展示區
function renderGallery() {
const container = document.getElementById('galleryContainer');
container.innerHTML = '';
const gallery = globalData.gallery;
if (Object.keys(gallery).length === 0) {
container.innerHTML = '<div class="text-center p-5 text-muted">目前尚無任何照片</div>';
return;
}
for (const [soft, allImages] of Object.entries(gallery)) {
// 1. 隨機選取 4 張照片邏輯
let displayImages = [...allImages]; // 複製一份,不影響原始資料
if (displayImages.length > 4) {
// 簡單的隨機排序後取前4張
displayImages = displayImages.sort(() => 0.5 - Math.random()).slice(0, 4);
}
let section = document.createElement('div');
section.className = 'mb-5';
let imgHtml = '';
displayImages.forEach(img => {
// 2. 轉換為 Thumbnail 連結
let thumbUrl = img.url;
if (thumbUrl.includes('drive.google.com') && thumbUrl.includes('id=')) {
// 預覽圖使用 w600 (縮圖)
thumbUrl = thumbUrl.replace('uc?export=view&', 'thumbnail?sz=w600&');
}
// 3. [關鍵修改] 產生高畫質大圖連結 (sz=s2000 代表長邊 2000px),並綁定 Modal 事件
let largeUrl = thumbUrl.replace('sz=w600', 'sz=s2000');
// 如果原始網址沒有經過 replace (例如模擬資料),直接用原網址
if(largeUrl === thumbUrl && !thumbUrl.includes('thumbnail?')) {
largeUrl = thumbUrl;
}
// 移除 window.open,改用 openImageModal
imgHtml += `
<div class="col-6 col-md-4 col-lg-3 mb-3">
<div class="card h-100">
<img src="${thumbUrl}" class="gallery-img" onclick="openImageModal('${largeUrl}')" referrerpolicy="no-referrer" loading="lazy">
</div>
</div>
`;
});
section.innerHTML = `
<h5 class="software-section-title">${soft}</h5>
<div class="row">
${imgHtml}
</div>
`;
container.appendChild(section);
}
}
function updateGalleryLocal(soft, teacher, url) {
if (!globalData.gallery[soft]) {
globalData.gallery[soft] = [];
}
globalData.gallery[soft].unshift({
teacher: teacher,
url: url,
time: new Date().toLocaleDateString()
});
renderGallery();
}
</script>
</body>
</html>
貼到 Apps Script 的 index.html 檔案
6. 部署
部署成 Web App 後,就會拿到可分享網址。
- Apps Script 右上:部署 → 新部署
- 類型選:Web app
- 執行身分:你
- 存取權限:任何人
- 完成後複製網址
💬 評論區
老師們的交流與提問
登入 後即可發表評論