0. 作品介紹
1. AI 提示詞
把這段指令貼給 AI,讓它幫你生成程式碼。
1.我想要建立一個 【期末考卷影印需求填寫網頁】
2.Google 試算表建立 【114-1課程資料】和【回覆資料庫】
3.需求說明 【老師選擇姓名之後,從114-1課程資料的試算表調出他的資料、選擇印製份數(修課人數, +1, +2)、限制只能上傳pdf檔、不強迫老師每個上傳欄位都需要上傳檔案】
4.要把它變成程式碼我要發佈
5.老師上傳的檔案請存在雲端硬碟資料夾,資料夾名稱為「期末影印」
6.請完整敘述操作步驟 越詳細越好
作業環境
Google試算表
Google Apps Script
Google雲端硬碟
2.Google 試算表建立 【114-1課程資料】和【回覆資料庫】
3.需求說明 【老師選擇姓名之後,從114-1課程資料的試算表調出他的資料、選擇印製份數(修課人數, +1, +2)、限制只能上傳pdf檔、不強迫老師每個上傳欄位都需要上傳檔案】
4.要把它變成程式碼我要發佈
5.老師上傳的檔案請存在雲端硬碟資料夾,資料夾名稱為「期末影印」
6.請完整敘述操作步驟 越詳細越好
作業環境
Google試算表
Google Apps Script
Google雲端硬碟
2. 資料表規則
請照下面規則建立分頁與欄位。
第1分頁: 114-1課程資料
A 欄: 科號
B 欄: 科目
C 欄: 教師姓名
D 欄: 修課人數
第2分頁: 回覆資料庫
A 欄: 時間戳記
B 欄: 教師
C 欄: 科號
D 欄: 科目
E 欄: 修課人數
F 欄: 份數選項
G 欄: 最終份數
H 欄: 是否裝訂
I 欄: 紙張尺寸
J 欄: 頁數
K 欄: 檔案連結
📄 解析結果(自動表格)
分頁:114-1課程資料
| A 欄 | B 欄 | C 欄 | D 欄 |
|---|---|---|---|
| 科號 | 科目 | 教師姓名 | 修課人數 |
分頁:回覆資料庫
| A 欄 | B 欄 | C 欄 | D 欄 | E 欄 | F 欄 | G 欄 | H 欄 | I 欄 | J 欄 | K 欄 |
|---|---|---|---|---|---|---|---|---|---|---|
| 時間戳記 | 教師 | 科號 | 科目 | 修課人數 | 份數選項 | 最終份數 | 是否裝訂 | 紙張尺寸 | 頁數 | 檔案連結 |
3. 開啟 Apps Script
從試算表開 Apps Script,才能直接用 SpreadsheetApp 讀寫資料。
- 試算表上方選單:擴充功能 → Apps Script
- 刪掉預設的程式碼(或全部選取覆蓋)
-
保留/建立檔案:
- Code.gs
- index.html
4. 貼上後端(Code.gs)
把後端程式貼到 Apps Script 的 Code.gs。
// ===== 基本設定 =====
const DATA_SHEET_NAME = '114-1課程資料';
const RESPONSE_SHEET_NAME = '回覆資料庫';
const TARGET_FOLDER_ID = '請改成自己的雲端硬碟';
// 允許的 MIME types
const ALLOWED_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
// ===== Web App 入口 =====
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('期末考卷影印需求系統')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
// ===== 教師名單 =====
function getTeacherNames() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(DATA_SHEET_NAME);
const values = sheet.getDataRange().getValues();
return [...new Set(values.slice(1).map(r => r[2]).filter(String))].sort();
}
// ===== 教師課程 =====
function getCoursesByTeacher(teacher) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(DATA_SHEET_NAME);
return sheet.getDataRange().getValues().slice(1)
.filter(r => r[2] === teacher)
.map(r => ({
id: r[0],
name: r[1],
teacher: r[2],
students: r[3]
}));
}
// ===== 表單處理 =====
function processForm(formObject) {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(RESPONSE_SHEET_NAME);
if (!sheet) {
sheet = ss.insertSheet(RESPONSE_SHEET_NAME);
sheet.appendRow([
'時間戳記','教師','科號','科目','修課人數',
'份數選項','最終份數','是否裝訂','紙張尺寸',
'頁數','檔案連結'
]);
}
const folder = DriveApp.getFolderById(TARGET_FOLDER_ID);
const rows = [];
const time = new Date();
for (const key in formObject) {
if (!key.startsWith('course_')) continue;
const id = key.replace('course_', '');
const course = JSON.parse(formObject[key]);
let fileUrl = '未上傳';
const fileObj = formObject['file_' + id];
if (fileObj && fileObj.data) {
// ===== 後端檔案格式檢查 =====
if (!ALLOWED_TYPES.includes(fileObj.type)) {
throw new Error(`不支援的檔案格式:${fileObj.name}`);
}
const blob = Utilities.newBlob(
Utilities.base64Decode(fileObj.data),
fileObj.type,
fileObj.name
);
const fileName =
`[${course.name}]${course.teacher}_${course.finalQuantity}份_${fileObj.name}`;
fileUrl = folder.createFile(blob).setName(fileName).getUrl();
}
rows.push([
time,
course.teacher,
course.id,
course.name,
course.students,
course.chosenQuantity,
course.finalQuantity,
course.binding || '否',
course.paperSize || '',
course.pageCount || '',
fileUrl
]);
}
if (rows.length) {
sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length)
.setValues(rows);
}
return { success: true, message: '已成功提交需求' };
} catch (e) {
return { success: false, message: '錯誤:' + e.message };
}
} 貼到 Apps Script 的 Code.gs 檔案
5. 貼上前端(index.html)
把前端程式貼到 Apps Script 的 index.html。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
body { font-family:"Microsoft JhengHei"; background:#f4f7f6; padding:20px; }
.card { background:#fff; padding:25px; max-width:850px; margin:auto; border-radius:8px; }
.course-item { background:#fafafa; margin:15px 0; padding:15px; border-left:5px solid #27ae60; }
label { font-weight:bold; margin-top:10px; display:block; }
select,input { width:100%; padding:8px; margin-top:5px; }
small { color:#666; display:block; margin-top:4px; }
button { width:100%; padding:15px; background:#27ae60; color:white; border:none; margin-top:20px; }
.sub-fields { display:none; }
#status { margin-top:15px; font-weight:bold; text-align:center; }
</style>
</head>
<body>
<div class="card">
<h2>期末考卷影印需求填寫</h2>
<label>教師姓名</label>
<select id="teacherSelect" onchange="loadCourses()"></select>
<div id="courseList"></div>
<button id="submitBtn" onclick="submitForm()" style="display:none;">確認提交</button>
<div id="status"></div>
</div>
<script>
const teacherSelect = document.getElementById('teacherSelect');
const courseList = document.getElementById('courseList');
const submitBtn = document.getElementById('submitBtn');
const status = document.getElementById('status');
// 載入教師
google.script.run.withSuccessHandler(list=>{
teacherSelect.innerHTML =
'<option value="">請選擇</option>' +
list.map(t=>`<option>${t}</option>`).join('');
}).getTeacherNames();
// 載入課程
function loadCourses(){
const teacher = teacherSelect.value;
if(!teacher) return;
google.script.run.withSuccessHandler(courses=>{
let html='';
courses.forEach(c=>{
html+=`
<div class="course-item">
<b>${c.id} ${c.name}(${c.students}人)</b>
<label>影印份數</label>
<select class="opt" data-id="${c.id}" data-base="${c.students}"
onchange="toggleOptions('${c.id}')">
<option value="none">不需要影印</option>
<option value="base">修課人數 (${c.students})</option>
<option value="base+1">修課人數 + 1 (${c.students+1})</option>
<option value="base+2">修課人數 + 2 (${c.students+2})</option>
</select>
<div class="sub-fields" id="sub_${c.id}">
<label>是否裝訂</label>
<select id="bind_${c.id}">
<option value="否">否</option>
<option value="是">是</option>
</select>
<label>紙張尺寸</label>
<select id="paper_${c.id}">
<option value="A4">A4</option>
<option value="A3">A3</option>
</select>
<label>頁數</label>
<input type="number" id="page_${c.id}" min="1">
<label>檔案上傳</label>
<input type="file" id="file_${c.id}"
accept=".pdf,.doc,.docx"
onchange="validateFile('${c.id}')">
<small>建議上傳 PDF 檔,以避免格式跑掉</small>
</div>
</div>`;
});
courseList.innerHTML = html;
submitBtn.style.display = 'block';
}).getCoursesByTeacher(teacher);
}
// 顯示 / 隱藏
function toggleOptions(id){
const val = document.querySelector(`.opt[data-id="${id}"]`).value;
document.getElementById('sub_'+id).style.display =
(val === 'none') ? 'none' : 'block';
}
// ===== 前端檔案格式檢查 =====
function validateFile(id){
const fileInput = document.getElementById('file_' + id);
const file = fileInput.files[0];
if(!file) return;
const allowed = ['pdf','doc','docx'];
const ext = file.name.split('.').pop().toLowerCase();
if (!allowed.includes(ext)) {
alert('僅允許上傳 PDF 或 Word 檔(.doc / .docx)');
fileInput.value = '';
}
}
// 送出
async function submitForm(){
try {
submitBtn.disabled=true;
status.innerText='提交中...';
const teacher = teacherSelect.value;
const formObj = {};
const opts = document.querySelectorAll('.opt');
for (const opt of opts){
const id = opt.dataset.id;
const base = Number(opt.dataset.base);
const val = opt.value;
let qty = 0;
if (val === 'base') qty = base;
if (val === 'base+1') qty = base+1;
if (val === 'base+2') qty = base+2;
const page = document.getElementById('page_'+id)?.value || '';
const file = document.getElementById('file_'+id)?.files[0];
if (val !== 'none' && !file) {
alert('請上傳檔案');
submitBtn.disabled=false;
return;
}
formObj['course_'+id] = JSON.stringify({
id: id,
name: document.querySelector(`[data-id="${id}"]`).closest('.course-item')
.querySelector('b').innerText,
teacher: teacher,
students: base,
chosenQuantity:
val==='none'?'不需要影印':
val==='base'?`修課人數 (${base})`:
val==='base+1'?`修課人數 + 1 (${base+1})`:
`修課人數 + 2 (${base+2})`,
finalQuantity: qty,
binding: document.getElementById('bind_'+id)?.value || '',
paperSize: document.getElementById('paper_'+id)?.value || '',
pageCount: page
});
if (file){
const b64 = await toBase64(file);
formObj['file_'+id]={ name:file.name, type:file.type, data:b64 };
}
}
google.script.run.withSuccessHandler(res=>{
status.innerText=res.message;
submitBtn.disabled=false;
}).processForm(formObj);
} catch(e){
alert(e.message);
submitBtn.disabled=false;
status.innerText='';
}
}
// File → Base64
function toBase64(file){
return new Promise(resolve=>{
const r=new FileReader();
r.onload=e=>resolve(e.target.result.split(',')[1]);
r.readAsDataURL(file);
});
}
</script>
</body>
</html>
貼到 Apps Script 的 index.html 檔案
6. 部署
部署成 Web App 後,就會拿到可分享網址。
- Apps Script 右上:部署 → 新部署
- 類型選:Web app
- 執行身分:你
- 存取權限:任何人
- 完成後複製網址
💬 評論區
老師們的交流與提問
登入 後即可發表評論