diff --git a/admin/main_test.go b/admin/main_test.go index 855b841..8e6061a 100644 --- a/admin/main_test.go +++ b/admin/main_test.go @@ -112,3 +112,24 @@ func TestMainPagesReturnOK(t *testing.T) { } } } + +// 公共弹窗不能只看 click.target 关闭:从密码框拖选到遮罩松开时, +// 浏览器会把 click 目标合成为遮罩。这个静态守卫确保 #56 的手势起点判断不被删掉; +// 真实指针行为另用 Chrome 回归验证。 +func TestModalBackdropRequiresPointerStart(t *testing.T) { + js, err := staticFS.ReadFile("static/js/app.js") + if err != nil { + t.Fatalf("读取公共 JavaScript 失败: %v", err) + } + source := string(js) + for _, required := range []string{ + `addEventListener("pointerdown"`, + `pointerStartedOnBackdrop = e.target === modal`, + `e.target === modal && pointerStartedOnBackdrop`, + `addEventListener("pointercancel"`, + } { + if !strings.Contains(source, required) { + t.Errorf("公共弹窗缺少拖选防误关闭守卫 %q", required) + } + } +} diff --git a/admin/static/js/app.js b/admin/static/js/app.js index 0dc6627..679c7b5 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -164,9 +164,22 @@ }); document.querySelectorAll(".modal-backdrop").forEach(function (modal) { - /* 关闭:叉、取消按钮,以及点弹窗外面的灰底 */ + /* 关闭:叉、取消按钮,以及真正点弹窗外面的灰底。 + + 不能只看 click 的 target:从输入框按下拖选到灰底松开时,浏览器会把 + 合成的 click 目标设成灰底。只有手势也从灰底开始,才算有意关闭。 */ + var pointerStartedOnBackdrop = false; + modal.addEventListener("pointerdown", function (e) { + pointerStartedOnBackdrop = e.target === modal; + }); + modal.addEventListener("pointercancel", function () { + pointerStartedOnBackdrop = false; + }); modal.addEventListener("click", function (e) { - if (e.target === modal || e.target.closest("[data-modal-close]")) { + var clickedCloseButton = e.target.closest("[data-modal-close]"); + var clickedBackdrop = e.target === modal && pointerStartedOnBackdrop; + pointerStartedOnBackdrop = false; + if (clickedBackdrop || clickedCloseButton) { closeModal(modal); } }); diff --git a/docs/admin/05-ui-specification.md b/docs/admin/05-ui-specification.md index 76f6913..88bff9b 100644 --- a/docs/admin/05-ui-specification.md +++ b/docs/admin/05-ui-specification.md @@ -37,6 +37,10 @@ `[必须]` 页面在 1366×768 上可用。列多了让表格**横向滚动**,不要压缩列宽把字挤成两行。 +`[必须]` 公共弹窗可以通过关闭按钮、取消按钮、Esc 或直接点击灰色遮罩关闭; +但从输入框或弹窗内容开始拖选、在遮罩上松开时不得关闭。遮罩关闭逻辑必须同时确认 +指针按下和最终点击都发生在遮罩本身,避免误丢用户尚未提交的输入。 + ## 3. 表格通用规则 - `[必须]` 第一列是勾选框,表头有全选。