週末的點閱率都很差,那就讓我來回顧一下黑歷史吧!今天沒有任何有價值的技術分享,就只是一個輕鬆的客服日常小故事:五彩斑斕的表格、東拼西湊的codes,和一點點我當時真的以為自己沒那麼愚蠢的錯覺。
可能週五半夜(Day12)的題目真的太無聊了,幾乎沒有人點閱。都已經來到第二週了,我也差不多該寫一些對得起《我只是不想加班:一名客服人員的GAS自救之路》這個標題的內容😅
今天就讓我隨便挑一個簡單的實作故事來分享吧!
※ 以下案例經過大幅改編與簡化
主管要求客服人員於值班後,將共用的待辦清單做整理,依截止日期由近至遠排序。
該待辦清單表格請參考此示例:《to-do-legacy》。[^1]
其中,括號內的日期即為截止日期。
主管身體力行,親自示範了用手動拖曳的方式,一列一列地將各別事項排列整齊。
我們都知道,不論是Excel或是Google Sheets,都有內建排序功能,只要我們將欄位拆分清楚,就能依日期做排序、或依類型做篩選。我向主管提案可以將既有表格拆分並重構為:《重要公告》與《to-do-refactor》。
可是提案被否決了。
經多次溝通與其他相關表格優化的feedback後,能夠揣摩並同理主管有意保留部分information asymmetry,以便在upward stakeholder management中保有更高的strategic adaptability與tactical bandwidth。進一步考察,主管與主管的主管雙方皆有相同之需求,並且在任務執行產生偏差或額外成本時,能夠保留clear lines of accountability,以利責任釐清與組織內部追蹤。
已知《to-do-legacy》乍看五彩斑斕,其實有許多看似bugs,實則不可撼動之features。
身為第一線的基層客服人員,在維持既有features的前提下,我決定使用GAS腳本來模仿Google Sheets內建的排序功能。
首先,建立一個新的按鈕:
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("自定義")
.addItem("依時限排序", "sortRowsByDeadline")
.addToUi();
}
接著,建立一個空白的欄位並隱藏。將日期extract出來,放進隱藏的欄位:[^2]
function processDatesInRange() {
const activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const startRow = 3;
const endRow = activeSheet.getLastRow();
const columnC = activeSheet.getRange(startRow, 3, endRow - startRow + 1, 1);
columnC.getValues().forEach((row, index) => {
const cellValue = row[0];
const cell = columnC.offset(index, 0);
const contentInBrackets = extractContentInBrackets(cellValue);
const cleanedContent = cleanNonNumericCharacters(contentInBrackets);
const formattedDate = formatDate(cleanedContent);
if (formattedDate) {
cell.offset(0, -1).setValue(formattedDate);
} else {
cell.offset(0, -1).setValue("");
}
});
}
最後,寫一個排序的邏輯:[^3]
/**
* Sorts rows by Column B in ascending order.
*
* @param {number} startRow - The row index to start sorting from (1-based index).
* @param {number} numRows - The number of rows to include in the sorting range.
*/
function sortRows(startRow, numRows) {
const activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const range = activeSheet.getRange(
startRow,
1,
numRows,
activeSheet.getLastColumn()
);
range.sort({ column: 2, ascending: true });
}
function sortRowsByDeadline() {
const activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
let startRow;
let numRows;
processDatesInRange();
const data = activeSheet.getRange("C:C").getValues();
for (let i = 2; i < data.length; i++) {
if (data[i][0] === "待辦清單") {
startRow = i + 2;
break;
}
}
const regex = /^[\u4e00-\u9fa5]{2}部門$/;
for (let i = startRow - 1; i < data.length; i++) {
if (regex.test(data[i][0])) {
numRows = i - (startRow - 1);
break;
}
}
sortRows(startRow, numRows);
}
如此一來,就可以把每日處理這個大表的瑣碎步驟自動化:
function generateDailyTodoSheet() {
copySheet();
deleteGrayRows();
sortRowsByDeadline();
}
當然,在這個場景,GAS腳本只能讓瑣碎步驟更簡便一點、稍微減少重複作業的機械疲倦感。它還沒辦法如宏大的AI Agents敘事那般,自主判斷並代為與stakeholders溝通,也無法處理cross-functional accountability reallocation或陪我們一起走predefined failure trajectory。
希望今天分享的小故事,可以給讀者一點靈感:很多時候,在實務上存在各種限制導致我們無法使用modern solution,但相信聰明的你一定能找到其他pragmatic workaround。
[^1]: 所基原版經大幅簡化後,僅保留一小部分多彩設計之神韻。
[^2]: 現在重看,根本黑歷史,寫得很醜,例如過度拆分出在其他function不會用到的extractContentInBrackets
&cleanNonNumericCharacters
、沒有考慮到使用者typo的情況、offset
無法應對表格欄位變動等等。
[^3]: 是的,程式碼沒辦法完全對應我給的示例表格,請原諒我不想重構這些爛扣。