🎯 學期開始的重頭戲就是註冊收費三聯單啦!
經歷過古早年代需要幫忙學生蓋三聯單名字,
最後收費還要老師代收的年代!
不過現在學校都是委託銀行處理收費問題,繳費方式越來越多元。
但收費三聯單還是需要級任老師的幫忙,
之前的社團費用都是由社團老師代收,
但現在很多費用都需先入學校公庫才能使用,
對級任在製作三聯單上又是一個大工程!
接觸了才知道,
原來選修其他本土語的課本費都不相同,而且還需補其中差價。
選 原、越、泰 語的學生 則 不用收 本土語 課本費用,
程度不一的學生,授課老師還會降低難度,使用不同年級的課本。
光是這個部分,我想負責的老師就又要傷腦筋了。
接下來就要正式上線了,
發現原來的設計級任老師會看到其他學生的基本資料,
心裡感謝似乎有些不妥,建議改成不需勾選。
資料全部由承辦老師後台編輯,最後由老師校對簽名確認!
只要老師用學校帳號登入 Chrome 瀏覽器 連結網址,系統就會顯示自己該班的資料。
目前應該已經測試得差不多了,老師可以試用看看,
如果合用也可以下載回去修改成學校的版本。
1.費用工作表 - 由各處室承辦人負責輸入正確金額
2.設定收費項目 - 由出納組設定(學年度 - 上下學期)
3.學生資料
- 校務系統 - 教務處 - 註冊組 - 學生資料管理 - 報表列印 - 名冊輸出 下載名冊以利後續使用

4.身分別
- 校務系統 - 教務處 - 註冊組 - 學生資料理 - 身份管理 - 學生身分別清冊
- 校務系統 - 教務處 - 註冊組 - 學生資料理 - 身份管理 - 學生身分別清冊
- ✅ 本人身心障礙(身心障礙生)
- ✅ 家長身心障礙
- ✅ 家庭突發因素(幸福餐劵補助)
- ✅ 原住民
- ✅ 低收入戶
- ✅ 中低收入戶
- 最後由承辦人確認是否為重度殘障手冊 - 可減免平安保險費

4-1 身份別 - 整理資料(下載後記得依序排一下欄位順序)
- 班級
- 座號
- 姓名
- 身分別
5. 本土語 - 由 教學組長 共編提供
- 客1 - 一年級閩南語、客語課本差價
- 客2 - 二年級閩南語、客語課本差價
- 客3 - 三年級閩南語、客語課本差價
- 客4 - 四年級閩南語、客語課本差價
- 客5 - 五年級閩南語、客語課本差價
- 客6 - 六年級閩南語、客語課本差價
- 原 - 扣除閩南語課本價錢
- 越 - 扣除閩南語課本價錢
- 泰 - 扣除閩南語課本價錢
6. 特殊生 - 由 特教組長 共編提供
7. 社團名冊 - 由 訓育組長 共編提供
8. 校外教學名冊 - 由 生教組長 共編提供
9. 校對各學年學生減免金額資料

10. 相關欄位公式
篩選 特殊生:
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT($O$2:$O$121, "0")) & "-" & TRIM(TEXT($P$2:$P$121, "0")) & "-" & TRIM(TEXT($Q$2:$Q$121, "0")), {TRIM(TEXT('特殊生'!A1:A, "0")) & "-" & TRIM(TEXT('特殊生'!B1:B, "0")) & "-" & TRIM(TEXT('特殊生'!D1:D, "0")), '特殊生'!G1:G}, 2, FALSE), ""))篩選 低收入戶:
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT($O$2:$O$121, "0")) & "-" & TRIM(TEXT($P$2:$P$121, "0")) & "-" & TRIM(TEXT($Q$2:$Q$121, "0")), {TRIM(TEXT('身份別'!A1:A, "0")) & "-" & TRIM(TEXT('身份別'!B1:B, "0")) & "-" & TRIM(TEXT('身份別'!D1:D, "0")), '身份別'!K1:K}, 2, FALSE), ""))篩選 家長身心障礙:
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT($O$2:$O$121, "0")) & "-" & TRIM(TEXT($P$2:$P$121, "0")) & "-" & TRIM(TEXT($Q$2:$Q$121, "0")), {TRIM(TEXT('身份別'!A1:A, "0")) & "-" & TRIM(TEXT('身份別'!B1:B, "0")) & "-" & TRIM(TEXT('身份別'!D1:D, "0")), '身份別'!I1:I}, 2, FALSE), ""))篩選 中低收入戶:
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT($O$2:$O$121, "0")) & "-" & TRIM(TEXT($P$2:$P$121, "0")) & "-" & TRIM(TEXT($Q$2:$Q$121, "0")), {TRIM(TEXT('身份別'!A1:A, "0")) & "-" & TRIM(TEXT('身份別'!B1:B, "0")) & "-" & TRIM(TEXT('身份別'!D1:D, "0")), '身份別'!J1:J}, 2, FALSE), ""))
篩選 家庭突發因素(幸福餐劵補助):
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT($O$2:$O$121, "0")) & "-" & TRIM(TEXT($P$2:$P$121, "0")) & "-" & TRIM(TEXT($Q$2:$Q$121, "0")), {TRIM(TEXT('身份別'!A1:A, "0")) & "-" & TRIM(TEXT('身份別'!B1:B, "0")) & "-" & TRIM(TEXT('身份別'!D1:D, "0")), '身份別'!L1:L}, 2, FALSE), ""))
篩選 本人身心障礙(身心障礙生):
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT($O$2:$O$121, "0")) & "-" & TRIM(TEXT($P$2:$P$121, "0")) & "-" & TRIM(TEXT($Q$2:$Q$121, "0")), {TRIM(TEXT('身份別'!A1:A, "0")) & "-" & TRIM(TEXT('身份別'!B1:B, "0")) & "-" & TRIM(TEXT('身份別'!D1:D, "0")), '身份別'!H1:H}, 2, FALSE), ""))
篩選 原住民身份:
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT($O$2:$O$121, "0")) & "-" & TRIM(TEXT($P$2:$P$121, "0")) & "-" & TRIM(TEXT($Q$2:$Q$121, "0")), {TRIM(TEXT('身份別'!A1:A, "0")) & "-" & TRIM(TEXT('身份別'!B1:B, "0")) & "-" & TRIM(TEXT('身份別'!D1:D, "0")), '身份別'!G1:G}, 2, FALSE), ""))篩選 家長會費:
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT($O$2:$O$121, "0")) & "-" & TRIM(TEXT($P$2:$P$121, "0")) & "-" & TRIM(TEXT($Q$2:$Q$121, "0")), {TRIM(TEXT('學生資料'!A1:A, "0")) & "-" & TRIM(TEXT('學生資料'!B1:B, "0")) & "-" & TRIM(TEXT('學生資料'!D1:D, "0")), '學生資料'!M1:M}, 2, FALSE), ""))篩選 本土語:
=ARRAYFORMULA(IFERROR(VLOOKUP(
O2:O121 & "-" & P2:P121 & "-" & Q2:Q121,
{
'本土語'!A1:A & "-" & '本土語'!B1:B & "-" & '本土語'!D1:D,
'本土語'!F1:F
},
2,
FALSE
), ""))

O2:O121 & "-" & P2:P121 & "-" & Q2:Q121,
{
'本土語'!A1:A & "-" & '本土語'!B1:B & "-" & '本土語'!D1:D,
'本土語'!F1:F
},
2,
FALSE
), ""))
篩選 課後照顧:
=ARRAYFORMULA(IFERROR(VLOOKUP(
TRIM(TEXT($O$2:$O$121, "0")) & "-" & TRIM(TEXT($P$2:$P$121, "0")) & "-" & TRIM(TEXT($Q$2:$Q$121, "0")),
{
TRIM(TEXT('課後照顧'!A1:A, "0")) & "-" &
TRIM(TEXT('課後照顧'!B1:B, "0")) & "-" &
TRIM(TEXT('課後照顧'!D1:D, "0")),
'課後照顧'!G1:G
},
2, FALSE
), ""))
篩選 是否繳交 平安保險 費用:
=IF(B2="低收入戶",0,IF(D2="中低收入戶",0,IF(E2="家庭突發因素(幸福餐劵補助)",0,IF(G2="原住民",0,'費用'!$O$1))))
篩選 是否繳交 教科書 費用:
=IFS(
B2="低收入戶", 0,
C2="家長身心障礙", 0,
D2="中低收入戶", INDEX('費用'!$B$1:$B$6, O2) - '費用'!$O$3,
E2="家庭突發因素(幸福餐劵補助)", INDEX('費用'!$B$1:$B$6, O2) - '費用'!$O$3,
LEFT(I2, 1)="客", INDEX('費用'!$B$1:$B$6, O2) + INDEX('費用'!$F$1:$F$6, VALUE(RIGHT(I2, 1))),
OR(I2="原", I2="越", I2="泰"), INDEX('費用'!$B$1:$B$6, O2) - INDEX('費用'!$H$1:$H$6, O2),
TRUE, INDEX('費用'!$B$1:$B$6, O2)
)
篩選 是否繳交 家長會費:
=IF(OR(B2="低收入戶", D2="中低收入戶", E2="家庭突發因素(幸福餐劵補助)"), 0, IF(H2="家長會費", '費用'!$O$2, 0))
篩選 社團費用:
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT(O2:O121, "0")) & "-" & TRIM(TEXT(P2:P121, "0")) & "-" & TRIM(TEXT(Q2:Q121, "0")), {TRIM(TEXT('社團'!A1:A, "0")) & "-" & TRIM(TEXT('社團'!B1:B, "0")) & "-" & TRIM(TEXT('社團'!D1:D, "0")), '社團'!G1:G}, 2, FALSE), ""))
篩選 課後照顧班費用:
=ARRAYFORMULA(IF(J2:J121=TRUE, IF(LEN(O2:O121), VLOOKUP(O2:O121, {ROW('費用'!L1:L6), '費用'!L1:L6}, 2, FALSE), ""), ""))
篩選 校外教學 費用:
=ARRAYFORMULA(IFERROR(VLOOKUP(TRIM(TEXT(O2:O121, "0")) & "-" & TRIM(TEXT(P2:P121, "0")) & "-" & TRIM(TEXT(Q2:Q121, "0")), {TRIM(TEXT('社團'!A1:A, "0")) & "-" & TRIM(TEXT('校外教學'!B1:B, "0")) & "-" & TRIM(TEXT('校外教學'!D1:D, "0")), '校外教學'!G1:G}, 2, FALSE), ""))
11. 建議列印紙本,請導師簽名確認。
Login.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>資料查詢系統</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f0f2f5;
margin: 0;
}
.container {
background-color: #fff;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
box-sizing: border-box;
margin: 2rem auto;
}
#login-container {
max-width: 400px;
}
#data-container {
display: none;
width: 100%;
max-width: none;
margin: 0;
border-radius: 0;
box-shadow: none;
padding: 1rem 1.5rem;
}
h2 { text-align: center; color: #333; margin-top:0; }
label { font-weight: bold; margin-bottom: 0.5rem; display: block; }
input[type="text"], input[type="password"] {
width: 100%;
padding: 0.75rem;
margin-bottom: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
input[type="button"] {
width: 100%;
padding: 0.75rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
input[type="button"]:hover { background-color: #0056b3; }
#message { color: red; text-align: center; margin-top: 1rem; min-height: 1.2em; }
#data-table-wrapper {
overflow-x: auto;
width: 100%;
}
table {
border-collapse: collapse;
width: 100%;
margin-top: 0.4rem;
}
th, td {
border: 1px solid #ddd;
padding: 0.4rem 0.75rem;
text-align: center;
white-space: nowrap;
}
th {
background-color: #f2f2f2;
position: sticky;
top: 0;
z-index: 1;
}
/* --- 新增處:設定表格交錯顏色 --- */
tbody tr:nth-child(even) {
background-color: #f9f9f9; /* 為偶數行設定一個淡灰色背景 */
}
</style>
</head>
<body>
<div class="container" id="loading-container">
<h2>資料查詢系統</h2>
<p id="message">正在驗證您的身份並載入資料...</p>
</div>
<div class="container" id="data-container">
<h2 id="welcome-message"></h2>
<div id="data-table-wrapper"></div>
</div>
<script>
// 當頁面載入完成後,立即執行
window.addEventListener('load', function() {
google.script.run
.withSuccessHandler(onDataReceived)
.withFailureHandler(onFailure)
.getUserData(); // 直接呼叫新的後端函式
});
function onDataReceived(response) {
document.getElementById('loading-container').style.display = 'none';
if (response.success) {
document.getElementById('data-container').style.display = 'block';
document.getElementById('welcome-message').innerText = `歡迎,${response.user}。提醒:資料僅供老師參考,最後請以出組組長紙本為主!`;
const wrapper = document.getElementById('data-table-wrapper');
let html = '<table><thead><tr>';
response.headers.forEach(header => html += `<th>${header}</th>`);
html += '</tr></thead><tbody>';
response.data.forEach(row => {
html += '<tr>';
row.forEach(cell => html += `<td>${cell}</td>`);
html += '</tr>';
});
html += '</tbody></table>';
wrapper.innerHTML = html;
} else {
// 如果驗證失敗或出錯,顯示錯誤訊息
const messageDiv = document.getElementById('message');
messageDiv.innerText = response.message;
messageDiv.style.color = 'red';
document.getElementById('loading-container').style.display = 'block';
}
}
function onFailure(error) {
const messageDiv = document.getElementById('message');
messageDiv.innerText = "發生未預期的錯誤:" + error.message;
messageDiv.style.color = 'red';
document.getElementById('loading-container').style.display = 'block';
}
</script>
</body>
</html>
程式碼.gs
const SPREADSHEET_ID = '試算表ID'; //填自己的試算表ID
function doGet() {
return HtmlService.createHtmlOutputFromFile('Login')
.setTitle('資料查詢系統');
}
function getUserData() {
try {
const userEmail = Session.getActiveUser().getEmail();
if (!userEmail) {
return { success: false, message: '無法識別您的 Google 帳號。請確認您已登入並授權。' };
}
const permissions = getUserPermissions(userEmail);
if (!permissions) {
return { success: false, message: `您的帳號 (${userEmail}) 未被授權存取本系統。` };
}
return fetchData(userEmail, permissions);
} catch (e) {
Logger.log('getUserData 錯誤: ' + e.toString());
return { success: false, message: '發生未預期的錯誤。' };
}
}
// --- 偵錯函式 (保留供未來使用) ---
function debugUserPermissions() {
const testEmail = '您可以換成任何想測試的 Email'; // 您可以換成任何想測試的 Email
Logger.log("--- 開始權限偵錯 ---");
Logger.log("正在測試的 Email: " + testEmail);
const permissions = getUserPermissions(testEmail);
if (permissions === null) {
Logger.log("偵錯結果: 找不到此 Email。");
Logger.log("可能原因: 您在上面 testEmail 變數中填寫的 Email,與 Users 工作表 A 欄中的任何一筆 Email 都不相符。請仔細核對,前後不能有空格。");
} else {
Logger.log("偵錯結果: 成功找到 Email!");
Logger.log("程式讀取到的 SheetName (B欄): '" + permissions.sheetName + "'");
Logger.log("程式讀取到的 DataRange (C欄): '" + permissions.dataRange + "'");
if (!permissions.sheetName || !permissions.dataRange) {
Logger.log(">>> 問題判斷: SheetName 或 DataRange 其中一項為空值 (空白),這就是導致錯誤的原因! <<<");
} else {
Logger.log(">>> 資料看起來都存在,請仔細檢查引號中的內容是否有誤 (例如多餘的空格、名稱與工作表分頁不符)。");
}
}
Logger.log("--- 偵錯結束 ---");
}
/**
* 根據 Email 在 Users 工作表中查找權限設定。
* (此函式無需修改)
*/
function getUserPermissions(email) {
const USER_SHEET_NAME = 'Users';
const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(USER_SHEET_NAME);
const data = sheet.getDataRange().getValues();
for (let i = 1; i < data.length; i++) {
const storedEmail = data[i][0];
if (storedEmail.toLowerCase() === email.toLowerCase()) {
return {
sheetName: data[i][1], // B欄: SheetName
dataRange: data[i][2] // C欄: DataRange
};
}
}
return null;
}
/**
* [⭐️ 已升級] 根據權限設定,抓取並組合單一或多個不連續範圍的資料。
* @param {string} userEmail - 當前使用者 Email (用於顯示歡迎訊息)。
* @param {object} permissions - 包含 sheetName 和 dataRange 的物件。
* @returns {object} 返回包含資料或錯誤訊息的物件。
*/
function fetchData(userEmail, permissions) {
const { sheetName, dataRange } = permissions;
try {
if (!sheetName || !dataRange) {
return { success: false, message: '您的帳號權限設定不正確(缺少工作表或範圍),請聯絡管理員。' };
}
const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(sheetName);
if (!sheet) {
return { success: false, message: `設定的資料表 "${sheetName}" 不存在。` };
}
// --- 核心升級:解析並處理單一或多個範圍 ---
// 1. 將 dataRange 字串用分號分割成一個範圍陣列
const rangeStrings = dataRange.split(';').map(r => r.trim()).filter(r => r);
if (rangeStrings.length === 0) {
return { success: false, message: 'DataRange 格式錯誤或為空。' };
}
// 2. 處理標題:
// 如果是單一範圍 (如 K2:AB31),就智慧抓取第1列的對應標題。
// 如果是多範圍 (如 A1:AC1;A20:AC51),就使用第一個範圍當作標題。
let headers;
const firstRange = sheet.getRange(rangeStrings[0]);
if (rangeStrings.length === 1) {
// 單一範圍模式
const startCol = firstRange.getColumn();
const numCols = firstRange.getNumColumns();
headers = sheet.getRange(1, startCol, 1, numCols).getValues()[0];
} else {
// 多範圍模式,第一個範圍就是標題
headers = firstRange.getValues()[0];
}
// 3. 獲取並合併所有資料範圍
let allUserData = [];
if (rangeStrings.length === 1) {
// 單一範圍模式,第一個(也是唯一的)範圍就是資料
allUserData = firstRange.getValues();
} else {
// 多範圍模式,從第二個範圍開始遍歷合併
for (let i = 1; i < rangeStrings.length; i++) {
const currentData = sheet.getRange(rangeStrings[i]).getValues();
allUserData = allUserData.concat(currentData);
}
}
return { success: true, user: userEmail.split('@')[0], headers: headers, data: allUserData };
} catch(e) {
Logger.log('fetchData 錯誤: ' + e.toString());
if (e.message.includes("Range not found")) {
return { success: false, message: `權限設定中的範圍 "${dataRange}" 包含無效的儲存格名稱,請聯絡管理員。`};
}
return { success: false, message: '讀取資料時發生錯誤。' };
}
}
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('🎯資料處理')
.addItem('0️⃣🧺清空身份別資料', 'clearRange')
.addItem('1️⃣⚙️分割身份別資料', 'splitOrCopyData')
.addSeparator()
.addItem('2️⃣✅校對減免金額', 'calculateDiscounts')
.addItem('3️⃣🧹清空AC欄位資料', 'clearABColumn')
.addSeparator()
.addItem('4️⃣🚀將資料複製到新工作表手動修改', 'copyFilteredRangeWithHeaderAndWidthManualName')
.addToUi();
}
function splitOrCopyData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var data = sheet.getDataRange().getValues();
var targetKeywords = ["原住民", "本人身心障礙(身心障礙生)", "家長身心障礙", "中低收入戶", "低收入戶", "家庭突發因素(幸福餐劵補助)"];
for (var i = 1; i < data.length; i++) {
var fColumnValue = data[i][5];
if (typeof fColumnValue === 'string') {
var splitValues = fColumnValue.includes(';') ? fColumnValue.split(';') : [fColumnValue]; // 如果沒有分號,將整個值放入陣列
for (var j = 0; j < targetKeywords.length; j++) {
var keyword = targetKeywords[j];
if (splitValues.includes(keyword)) {
sheet.getRange(i + 1, 7 + j).setValue(keyword);
}
}
}
}
}
function clearRange() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange('G2:L').clearContent();
}
/**
* @OnlyCurrentDoc
*
* 這個腳本用於根據學生資料和費用表來計算折扣金額。
* 它會將結果寫入學生資料表中的指定欄位。
*/
function calculateDiscounts() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var feeSheet = ss.getSheetByName("費用");
if (!feeSheet) {
SpreadsheetApp.getUi().alert("找不到『費用』工作表!");
return;
}
// --- 讀取 年級→教科書費用(費用!A:B) ---
// 假設一年級~六年級都在前幾列,取到第20列以防萬一
var gradeRows = feeSheet.getRange("A1:B20").getValues();
var gradeFeeMap = {};
gradeRows.forEach(function (r) {
var g = (r[0] || "").toString().trim();
var fee = Number(r[1]);
if (g && !isNaN(fee)) gradeFeeMap[g] = fee;
});
// --- 新增:讀取 年級、客家費用、原住民/越南費用(費用!A:F:H) ---
var gradeFeeRows = feeSheet.getRange("A1:H20").getValues();
var gradeFeeFandHMap = {};
gradeFeeRows.forEach(function (r) {
var g = (r[0] || "").toString().trim();
var fFee = Number(r[5]); // F欄的金額
var hFee = Number(r[7]); // H欄的金額
if (g) {
gradeFeeFandHMap[g] = {
'fFee': isNaN(fFee) ? 0 : fFee,
'hFee': isNaN(hFee) ? 0 : hFee
};
}
});
// --- 讀取其他費用(費用!N:O) ---
var feeRows = feeSheet.getRange("N1:O50").getValues(); // 包含標題也沒關係
var fees = {};
feeRows.forEach(function (r) {
var name = (r[0] || "").toString().trim();
var val = Number(r[1]);
if (name && !isNaN(val)) fees[name] = val;
});
// 正規化年級:把 O 欄的「1 / 一年級 / 1年級」都轉成「一年級」來對照費用表
function normalizeGrade(val) {
var zh = ["零", "一", "二", "三", "四", "五", "六"];
if (typeof val === "number") {
var n = Math.floor(val);
return (n >= 1 && n <= 6) ? zh[n] + "年級" : "";
}
var s = (val || "").toString().trim();
// 含數字
var m = s.match(/([1-6])/);
if (m) return zh[Number(m[1])] + "年級";
// 含中文數字
for (var i = 1; i <= 6; i++) {
if (s.indexOf(zh[i]) !== -1) return zh[i] + "年級";
}
// 已經是「一年級」這種
if (/^[一二三四五六]年級$/.test(s)) return s;
return "";
}
var lastRow = sheet.getLastRow();
if (lastRow < 2) return;
// --- A:G 身份別(7 欄,與每一列一一對應) ---
var identities = sheet.getRange("A2:G" + lastRow).getValues();
// --- O 欄 年級 ---
var gradeCol = sheet.getRange("O2:O" + lastRow).getValues();
// --- 新增:I 欄 類別 ---
var categoryCol = sheet.getRange("I2:I" + lastRow).getValues();
var results = [];
for (var i = 0; i < gradeCol.length; i++) {
var gradeKey = normalizeGrade(gradeCol[i][0]);
if (!gradeKey) { results.push([""]); continue; }
var textbookFee = Number(gradeFeeMap[gradeKey]) || 0;
// A:G -> [特殊生, 低收入戶, 家庭突發因素, 本人身心障礙, 中低收入戶, 家長身心障礙, 原住民]
var row = identities[i] || [];
var isSpecialStudent = !!(row[0]);
var isLowIncome = !!(row[1]);
var isParentDisabled = !!(row[2]);
var isMidLowIncome = !!(row[3]);
var isSuddenFactor = !!(row[4]);
var isDisabled = !!(row[5]);
var isIndigenous = !!(row[6]);
// 新增:讀取 I 欄值
var category = (categoryCol[i][0] || "").toString().trim();
var discounts = [];
// 所有金額來自「費用」表,沒有就當 0
var safe = function (k, d) { return (typeof fees[k] === "number" ? fees[k] : (d || 0)); };
// 新增:根據 I 欄類別計算費用
var newFeeInfo = gradeFeeFandHMap[gradeKey];
if (newFeeInfo) {
if (category === "客") {
discounts.push({ type: "客語課本差額", amount: newFeeInfo.fFee, priority: 0 });
} else if (category === "原" || category === "越") {
discounts.push({ type: "原民族語課本/越語課本", amount: newFeeInfo.hFee, priority: 0 });
}
}
if (isSpecialStudent) discounts.push({ type: "特殊生", amount: safe("特殊生補助", 0), priority: 1 });
if (isLowIncome) discounts.push({ type: "低收入戶", amount: textbookFee + safe("平安保險", 0), priority: 2 });
if (isSuddenFactor) discounts.push({ type: "家庭突發因素", amount: safe("註冊組代收代辦費", 0) + safe("平安保險", 0), priority: 3 });
if (isDisabled) discounts.push({ type: "本人身心障礙", amount: safe("平安保險", 0) + safe("家長會費", 0), priority: 4 });
if (isMidLowIncome) discounts.push({ type: "中低收入戶", amount: Math.min(textbookFee, 500) + safe("平安保險", 0) + safe("家長會費", 0), priority: 5 });
if (isParentDisabled) discounts.push({ type: "家長身心障礙", amount: textbookFee, priority: 6 });
if (isIndigenous) discounts.push({ type: "原住民", amount: safe("平安保險", 0), priority: 7 });
if (discounts.length > 0) {
var pick = discounts.reduce(function (a, b) { return (a.priority < b.priority) ? a : b; });
results.push([pick.type + ": " + pick.amount + "元"]);
} else {
results.push([""]);
}
}
// --- 寫入 AC 欄(用 A1 位置,避免欄位插入造成位移) ---
sheet.getRange("AC2:AC" + (results.length + 1)).setValues(results);
}
function clearABColumn() {
// 取得當前活動中的工作表
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
// 定義要清除的範圍 AC2:AC
const range = sheet.getRange("AC2:AC");
// 清除範圍內的內容
range.clearContent();
}
/**
* 複製活頁簿中指定範圍和標題列到一個新工作表,
* 只複製 N 欄有資料的列。同時保留原始欄位的寬度。
* 新的工作表名稱由使用者手動輸入。
*/
function copyFilteredRangeWithHeaderAndWidthManualName() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = spreadsheet.getActiveSheet();
var ui = SpreadsheetApp.getUi();
// 取得目前工作表的最後一列
var lastRow = activeSheet.getLastRow();
if (lastRow < 1) {
ui.alert('目前工作表沒有資料可以複製!');
return;
}
// 1. 彈出對話框讓使用者輸入新工作表的名稱
var response = ui.prompt('請為新的工作表命名:', ui.ButtonSet.OK_CANCEL);
// 檢查使用者是否取消
if (response.getSelectedButton() == ui.Button.CANCEL) {
return;
}
var newSheetName = response.getResponseText();
if (!newSheetName) {
ui.alert('工作表名稱不能為空,腳本已結束。');
return;
}
// 2. 取得原始欄位的寬度 (K到AC欄)
// K欄是第11欄,AC欄是第29欄
var sourceWidths = [];
for (var i = 11; i <= 29; i++) {
sourceWidths.push(activeSheet.getColumnWidth(i));
}
// 3. 取得標題列的範圍 (第一列,K到AC欄)
// 19 是 K 到 AC 的總欄數 (29 - 11 + 1)
var headerRange = activeSheet.getRange(1, 11, 1, 19);
var headerValues = headerRange.getValues();
// 4. 取得所有內容範圍 (從第二列到最後一列,K到AC欄)
var bodyRange = activeSheet.getRange(2, 11, lastRow - 1, 19);
var bodyValues = bodyRange.getValues();
// 5. 篩選出 N 欄有資料的列
// N 欄在 K 到 AC 這個範圍中的相對位置是第 4 欄 (26 - 22)
var filteredBodyValues = bodyValues.filter(function(row) {
// N 欄是第 4 個元素 (K=0, L=1, M=2, N=3)
return row[3] && row[3].toString().trim() !== '';
});
if (filteredBodyValues.length === 0) {
ui.alert('在 N 欄中沒有找到任何資料,腳本已結束。');
return;
}
// 6. 將標題和篩選後的內容合併
var allValues = headerValues.concat(filteredBodyValues);
// 7. 創建一個新的工作表
// 為了避免名稱重複,可以檢查是否已存在
if (spreadsheet.getSheetByName(newSheetName)) {
ui.alert('名稱為「' + newSheetName + '」的工作表已存在,請重新執行並輸入新的名稱。');
return;
}
var newSheet = spreadsheet.insertSheet(newSheetName);
// 8. 複製所有值到新工作表中
newSheet.getRange(1, 1, allValues.length, allValues[0].length).setValues(allValues);
// 9. 將取得的欄寬應用到新工作表上
for (var i = 0; i < sourceWidths.length; i++) {
newSheet.setColumnWidth(i + 1, sourceWidths[i]);
}
// 10. 提示使用者完成
ui.alert('已成功複製 N 欄有資料的列到新的工作表:' + newSheetName + '。');
}
快來試試吧~

沒有留言:
張貼留言
歡迎大家一起留言討論!