做好計時器邏輯之後,我們要來實作計時結束後的互動——當使用者按下停止計時時,自動彈出 Modal 彈窗,讓使用者編輯當次備註、勾選任務完成狀態,並提供 「儲存紀錄」 與 「捨棄當次計時」 兩種彈性選擇。
在 index.html 中,需要建立一個專用的 Modal結構。為了避免使用者不小心點到背景或按錯鍵導致剛計時完的資料消失,這裡使用了兩個關鍵的 Bootstrap 屬性:
data-bs-backdrop="static":鎖定背景,防止使用者點擊 Modal 外部時誤關視窗。
data-bs-keyboard="false":停用鍵盤 Esc 鍵關閉彈窗的功能。
<!-- 計時結束紀錄 Modal (核心結構) -->
<div class="modal fade" id="saveTimerModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title font-weight-bold"><i class="bi bi-check-circle-fill text-success me-2"></i>完成計時!</h5>
</div>
<div class="modal-body">
<!-- 顯示累積時間 -->
<h3 id="modal-focus-time" class="text-primary font-monospace text-center">00:00:00</h3>
<!-- 輸入表單:備註與完成狀態 -->
<textarea id="modal-note" class="form-control mb-3" placeholder="記錄一下完成項目吧..."></textarea>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="modal-is-completed" />
<label class="form-check-label" for="modal-is-completed">將此任務標記為已完成</label>
</div>
</div>
<div class="modal-footer d-flex justify-content-between">
<button type="button" class="btn btn-outline-danger" id="btn-discard-session">捨棄此次計時</button>
<button type="button" class="btn btn-primary" id="btn-save-session">儲存紀錄</button>
</div>
</div>
</div>
</div>
在 all.js 中,我們改用 Bootstrap JS 原生 API (bootstrap.Modal) 來實作手動控制。整體運作流程如下:
[按下停止] ➔ 計算總秒數 ➔ 寫入並打開 Modal ➔ [使用者選擇: 儲存 或 捨棄] ➔ 重置主介面
先取得頁面上的 DOM 元素,再使用 Bootstrap 的 Modal 類別建立一個可以操作的 Modal 物件,同時建立全域變數 currentSessionSeconds,用來暫存當次計時結束時的精確秒數。
// 初始化 Bootstrap Modal 物件
const saveTimerModalElement = document.querySelector("#saveTimerModal");
const saveTimerModal = new bootstrap.Modal(saveTimerModalElement);
// 暫存當次結束時累積的總秒數
let currentSessionSeconds = 0;
stopTimer)原本按下「停止」是直接重置 UI,現在改為先計算時間並判斷。若 計時小於 1 秒 (例如誤觸),就直接重置而不打擾使用者;反之,則將時間寫入 Modal 並呼叫 .show() 彈出視窗。
function stopTimer() {
currentSessionSeconds = getCalculatedSeconds();
// 防誤觸:未滿 1 秒不處理,直接重置
if (currentSessionSeconds < 1) {
resetTimerUI();
return;
}
// 停止計時器並更新 Modal 數據
clearInterval(timerInterval);
isRunning = false;
document.querySelector("#modal-focus-time").textContent = formatTime(currentSessionSeconds);
document.querySelector("#modal-note").value = ""; // 清空上次輸入
saveTimerModal.show(); // 跳出 Modal
}
這兩個動作最終都會關閉彈窗並呼叫 resetTimerUI() 讓主介面歸零,差別在於 儲存時會將資料打包成 JSON 物件:
捨棄紀錄 (discardSession):完全不做資料寫入,直接關閉 Modal 並重置。
儲存紀錄 (saveSession):收集秒數、備註與勾選狀態,打包成獨立物件以利後續存入 IndexedDB。
// 捨棄紀錄:關閉彈窗並重置
function discardSession() {
saveTimerModal.hide();
resetTimerUI();
}
// 儲存紀錄:打包資料後關閉並重置
function saveSession() {
const sessionData = {
id: Date.now(),
duration: currentSessionSeconds,
note: document.querySelector("#modal-note").value.trim(),
isCompleted: document.querySelector("#modal-is-completed").checked,
createdAt: new Date().toISOString()
};
console.log("暫存紀錄成功:", sessionData);
saveTimerModal.hide();
resetTimerUI();
}
// 綁定事件
document.querySelector("#btn-discard-session").addEventListener("click", discardSession);
document.querySelector("#btn-save-session").addEventListener("click", saveSession);
document.querySelector("#btnStop").addEventListener("click", stopTimer);

使用者體驗防護:透過 data-bs-backdrop="static" 避免點到背景關閉視窗,確保使用者不會因誤觸丟失辛苦計時的資料。
乾淨的資料收集:使用 .trim() 處理字串,就算使用者完全不寫備註,也能回傳空字串 "" 避免資料出錯。
流程模組化:不管是點擊「儲存」還是「捨棄」,最後都統一呼叫 resetTimerUI(),保持程式碼簡潔與單一職責原則。
個人筆記:
JavaScript | 時間格式轉換:toISOString() 怎麼用?