diff --git a/static/public/css/theme.css b/static/public/css/theme.css index f84384f..786dcf3 100644 --- a/static/public/css/theme.css +++ b/static/public/css/theme.css @@ -155,6 +155,13 @@ button[type="button"], input[type="button"] { background: var(--accent-soft); co flex-wrap: wrap; margin-top: 1.25rem; } +/* ===== 防重复点击:请求进行中按钮禁用态反馈 ===== */ +button:disabled, +input[type="submit"]:disabled, +input[type="button"]:disabled { + opacity: 0.5; + cursor: not-allowed; +} .form-check { display: flex; align-items: center; diff --git a/static/public/js/dnfs.js b/static/public/js/dnfs.js index 0af3b5f..3bd0609 100644 --- a/static/public/js/dnfs.js +++ b/static/public/js/dnfs.js @@ -191,14 +191,15 @@ var minimap = document.getElementById("minimap"); var is_expand = true; input_note_submit.addEventListener('click', function(event) { + if (input_note_submit.disabled) return; // 防重复点击 var title = input_note_title.value var content = input_note_content.value var pid = (stack.length == 0) ? 0 : stack[stack.length - 1]; //alert(stack.length) //alert(pid) - addNode(pid, title, content).then(result => { - //alert(result.message); - }) + input_note_submit.disabled = true; + addNode(pid, title, content) + .finally(() => { input_note_submit.disabled = false; }); }) input_note_clear.addEventListener('click', function(event) { @@ -207,13 +208,17 @@ input_note_clear.addEventListener('click', function(event) { }) delete_note.addEventListener('click', (event) => { + if (delete_note.disabled) return; // 防重复点击 var id = (stack.length == 0) ? 0 : stack[stack.length - 1]; if (id == 0) { alert('无法删除根节点'); console.log("节点未删除"); } else { - deleteNode(id); + delete_note.disabled = true; + // confirm 取消时 deleteNode 直接返回 undefined,统一用 Promise.resolve 兜底恢复按钮 + Promise.resolve(deleteNode(id)) + .finally(() => { delete_note.disabled = false; }); } }) diff --git a/static/public/js/login.js b/static/public/js/login.js index 8690405..fc3b7dc 100644 --- a/static/public/js/login.js +++ b/static/public/js/login.js @@ -14,8 +14,11 @@ const loginButton = document.getElementById('loginBtn'); /* ── 图形验证码(gateway 代理 captcha 服务:GET /captcha/image) ── */ let captchaToken = ''; +let captchaLoading = false; // 防重复点击:加载中忽略再次触发 async function refreshCaptcha() { + if (captchaLoading) return; + captchaLoading = true; captchaToken = ''; try { const res = await fetch('/captcha/image', { cache: 'no-store' }); @@ -28,6 +31,8 @@ async function refreshCaptcha() { } catch (e) { captchaImg.removeAttribute('src'); captchaImg.alt = '加载失败,点击重试'; + } finally { + captchaLoading = false; } } captchaImg.addEventListener('click', refreshCaptcha); diff --git a/static/public/js/view.js b/static/public/js/view.js index 41240a1..f42723e 100644 --- a/static/public/js/view.js +++ b/static/public/js/view.js @@ -127,7 +127,10 @@ function render() { } // ===== 数据拉取(接口与参数与原版本一致) ===== +let isLoading = false; // 防重复点击:请求进行中忽略再次触发 async function loadData() { + if (isLoading) return; + isLoading = true; loading.style.display = "block"; fileList.innerHTML = ""; empty.style.display = "none"; @@ -145,6 +148,8 @@ async function loadData() { } catch (e) { fileList.innerHTML = `