核心收穫速覽
- 事件綁定去抖:動態綁定必須
.off() 或 .one(),否則重複觸發。
- 模態框組件化:參數化標題、內容、按鈕文字、回調函數、傳遞數據,實現高復用。
- Checkbox 全選陷阱:jQuery
.attr('checked') vs .prop('checked')、DOM 原生 .checked 差異。
- 批量刪除前後端協作:前端收集 ID 陣列 → AJAX POST → 後端事務處理 → 逐條返回結果。
一、模態框確認組件 showMsg()
HTML 模板(放入頁面底部)
<div id="msg" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 id="myModalLabel" class="modal-title"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button id="msgConfirm" type="button" class="btn btn-danger"></button>
</div>
</div>
</div>
</div>
JS 封裝(ES6 改寫,支援 Bootstrap 5)
/**
* 通用確認模態框
* @param {string} title - 標題
* @param {string} content - 內容(支援 HTML)
* @param {string} confirmText - 確認按鈕文字
* @param {Function} onConfirm - 確認回調,接收 payload
* @param {*} payload - 透傳給回調的數據
*/
function showMsg(title, content, confirmText, onConfirm, payload = null) {
const modalEl = document.getElementById('msg');
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
modalEl.querySelector('#myModalLabel').innerHTML = title;
modalEl.querySelector('.modal-body').innerHTML = content;
const btn = modalEl.querySelector('#msgConfirm');
btn.textContent = confirmText;
// 關鍵:先移除舊監聽器,防止重複綁定
btn.replaceWith(btn.cloneNode(true)); // 核暴力解綁
const newBtn = modalEl.querySelector('#msgConfirm');
newBtn.addEventListener('click', () => {
modal.hide();
if (typeof onConfirm === 'function') onConfirm(payload);
}, { once: true }); // {once:true} 自動解綁
modal.show();
}
// 調用範例
showMsg('確認刪除', '此操作不可恢復', '刪除', (id) => {
deleteContent(id);
}, 123);
二、自動消失的提示組件 showToast()
function showToast(message, type = 'success') {
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger';
const html = `
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>`;
$('#toastContainer').prepend(html); // 需頁面預留 #toastContainer
// 3 秒自動關閉
setTimeout(() => {
$('.alert').first().alert('close');
}, 3000);
}
三、Checkbox 全選/反選:避開 jQuery 陷阱
錯誤寫法(會失效)
// ❌ attr 僅操作 HTML 屬性,不影響 DOM property
$('#selectAll').click(() => $('input[name="ids[]"]').attr('checked', true));
正確寫法三種
// 1. prop(jQuery 1.6+ 推薦)
$('#selectAll').on('change', function() {
$('input[name="ids[]"]').prop('checked', this.checked);
});
// 2. 原生 DOM 迭代(效能最佳、零依賴)
document.getElementById('selectAll').addEventListener('change', function() {
document.querySelectorAll('input[name="ids[]"]').forEach(cb => cb.checked = this.checked);
});
// 3. 事件委派:行點擊切換該行 checkbox
$('#dataTable tbody').on('click', 'tr', function(e) {
if (e.target.type === 'checkbox') return; // 避免雙重切換
const cb = this.querySelector('input[type="checkbox"]');
if (cb) cb.checked = !cb.checked;
});
四、批量刪除完整流程
前端:收集勾選 ID → POST 陣列
function batchDelete() {
const ids = [...document.querySelectorAll('input[name="ids[]"]:checked')]
.map(cb => cb.value);
if (!ids.length) return showToast('請選擇項目', 'error');
showMsg('批量刪除', `確定刪除 ${ids.length} 筆資料?`, '確定', () => {
fetch('/admin/api/batch-delete', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRF-TOKEN': token},
body: JSON.stringify({ ids })
}).then(r => r.json()).then(res => {
if (res.success) {
showToast('刪除成功');
setTimeout(() => location.reload(), 800);
} else showToast(res.message || '失敗', 'error');
});
});
}
後端(Laravel 風格範例)
public function batchDelete(Request $request) {
$ids = $request->json('ids', []);
if (empty($ids)) return response()->json(['success' => false, 'message' => '空 ID']);
$results = [];
DB::transaction(function() use ($ids, &$results) {
foreach ($ids as $id) {
try {
$deleted = Content::destroy($id);
$results[] = ['id' => $id, 'state' => $deleted ? 'ok' : 'no'];
} catch (\Exception $e) {
$results[] = ['id' => $id, 'state' => 'no', 'error' => $e->getMessage()];
}
}
});
return response()->json(['success' => true, 'data' => $results]);
}
五、分頁與表格渲染優化建議
- 使用
template 標籤或 JS 模板引擎(如 Handlebars)替代字串拼接。
- 大數據量採用虛擬滾動或無限載入,避免一次性渲染數千行
tr。
- 分頁元件抽離為獨立函數,支援
page_start/page_end/current 參數。