From 95289829ee55be65571c4facf1e86fa9741d2e78 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 17 Sep 2026 22:03:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E4=BF=AE=E5=A4=8D=E5=85=85?= =?UTF-8?q?=E5=80=BC=E5=BC=B9=E7=AA=97=E5=A3=B3=E5=85=A8=E5=B1=8F=E9=9A=90?= =?UTF-8?q?=E5=BD=A2=E9=81=AE=E7=BD=A9=E5=AF=BC=E8=87=B4=E6=95=B4=E9=A1=B5?= =?UTF-8?q?=E4=B8=8D=E5=8F=AF=E7=82=B9=E5=87=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:弹窗壳为服务端静态输出(Zibll 由 refresh_modal JS 按需创建,且其 CSS 无 bootstrap .modal 基础规则,故无此问题)。AKSRT 中 bootstrap.min.css 的 .modal{position:fixed;z-index:1050;display:none} 先加载,main.css 的 .flex{display:flex}(弹窗壳对齐 Zibll 所需的工具类)后加载且同特异性 后者胜 → 弹窗壳永久 display:flex 铺满全屏,叠加 .fade 无 .in 时的 opacity:0,形成隐形遮罩拦截整页点击。 修复:credits.css 以 (0,2,0) 特异性压制—— .modal[data-aksrt-charge-modal]{display:none}(静止态隐藏)+ .modal[data-aksrt-charge-modal].in{display:block}(展示态)。 credits.js 开关维持第 20 轮的自包含实现(内联 display:block 驱动), 关闭撤除内联样式后即回到隐藏规则,无 JS 改动。 新增契约 tests/widget-system/balance-charge-modal.test.cjs(3 例): 壳结构与 data 钩子、静止态隐藏规则(防 .flex 覆盖回潮)、开关自包含 (禁止回退 jQuery modal 运行时)。相关契约 29/29 通过。 --- .gitignore | 1 + assets/css/routes/user-center/credits.css | 13 +++++ .../balance-charge-modal.test.cjs | 54 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 tests/widget-system/balance-charge-modal.test.cjs diff --git a/.gitignore b/.gitignore index 7ac8ecf0..13569637 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ !/tests/widget-system/single-cover-height-contract.test.cjs !/tests/widget-system/frontend-lifecycle-contract.test.cjs !/tests/widget-system/card-pass-universal-redeem.test.cjs +!/tests/widget-system/balance-charge-modal.test.cjs !/tests/admin/ /tests/admin/* !/tests/admin/charge-cards-page-contract.test.cjs diff --git a/assets/css/routes/user-center/credits.css b/assets/css/routes/user-center/credits.css index 353bfa93..959d14ef 100644 --- a/assets/css/routes/user-center/credits.css +++ b/assets/css/routes/user-center/credits.css @@ -160,6 +160,19 @@ /* ===== 余额充值弹窗 — Zibll balance-charge modal parity ===== .zib-widget 基础卡样式与 .vip-product/.vip-tag 的 Zibll 原值仅在充值表单内生效 (vip-modal.css 在全局以同类名覆盖了它们,这里按 Zibll main.css 5962/5928/5990 回位)。 */ + +/* 弹窗壳静止态必须隐藏。Zibll 无此问题:它的 bootstrap 无 .modal 基础规则, + 且弹窗壳由 refresh_modal JS 按需创建;本主题是服务端静态输出,若不在此 + 显式隐藏,main.css 的 .flex{display:flex}(后加载、与 bootstrap 的 + .modal{display:none} 同特异性)会让弹窗永久 display:flex 铺满全屏, + 叠加 .fade 的 opacity:0 形成隐形遮罩,拦截整页点击。 */ +.modal[data-aksrt-charge-modal] { + display: none; +} +.modal[data-aksrt-charge-modal].in { + display: block; +} + .balance-charge-form .zib-widget { clear: both; background-color: var(--main-bg-color); diff --git a/tests/widget-system/balance-charge-modal.test.cjs b/tests/widget-system/balance-charge-modal.test.cjs new file mode 100644 index 00000000..e276898b --- /dev/null +++ b/tests/widget-system/balance-charge-modal.test.cjs @@ -0,0 +1,54 @@ +/** + * 余额充值弹窗壳契约(第 19 轮 Zibll 对齐 + 第 21 轮全页不可点击回归护栏)。 + * + * 关键约束:本主题的弹窗壳是服务端静态输出,与 Zibll(refresh_modal JS + * 按需创建、且其 CSS 无 bootstrap .modal 基础规则)架构不同。若弹窗壳在 + * 静止态没有被显式隐藏,main.css 的 .flex{display:flex}(后加载、与 + * bootstrap.min.css 的 .modal{display:none} 同特异性、后者胜)会让 + * `class="modal flex jc fade"` 壳永久 display:flex 铺满全屏,叠加 .fade + * 的 opacity:0 形成隐形遮罩 → 整页所有按钮不可点击(2026-09-17 实测故障)。 + * + * 因此 credits.css 必须以 (0,2,0) 特异性压制: + * .modal[data-aksrt-charge-modal] { display: none; } + * .modal[data-aksrt-charge-modal].in { display: block; } + * 展示态由 credits.js 自包含开关的内联 display:block 驱动(不依赖 + * jQuery/Bootstrap 运行时),关闭后内联样式撤除即回到隐藏规则。 + */ +const test = require('node:test'); +const assert = require('node:assert'); +const fs = require('node:fs'); +const path = require('node:path'); + +const root = path.join(__dirname, '..', '..'); +const read = (...seg) => fs.readFileSync(path.join(root, ...seg), 'utf8'); + +const wallet = read('inc', 'helpers', 'user-center-renderers', 'credits-wallet.php'); +const creditsJs = read('assets', 'js', 'credits.js'); +const creditsCss = read('assets', 'css', 'routes', 'user-center', 'credits.css'); + +test('弹窗壳为 Zibll refresh_modal 同构静态壳并带 data 钩子', () => { + assert.match(wallet, /class="modal flex jc fade"[^>]*data-aksrt-charge-modal/); + assert.match(wallet, /data-aksrt-charge-form/); + assert.match(wallet, /data-dismiss="modal"/); + assert.match(wallet, /initiate-pay/); +}); + +test('弹窗壳静止态必须被 credits.css 显式隐藏(.flex 覆盖回归护栏)', () => { + // (0,2,0) 特异性压制 main.css 的 .flex{display:flex}。 + assert.match(creditsCss, /\.modal\[data-aksrt-charge-modal\]\s*\{\s*display:\s*none;/); + assert.match(creditsCss, /\.modal\[data-aksrt-charge-modal\]\.in\s*\{\s*display:\s*block;/); +}); + +test('开关自包含:不依赖 jQuery/Bootstrap modal 运行时', () => { + assert.match(creditsJs, /function openChargeModal/); + assert.match(creditsJs, /function closeChargeModal/); + assert.match(creditsJs, /data-aksrt-charge-open/); + // 动态创建遮罩层(bootstrap.min.css 已有 .modal-backdrop 规则)。 + assert.match(creditsJs, /modal-backdrop fade in/); + // 充值弹窗开关不得回退到 jQuery modal 运行时(时序异常时弹窗打不开)。 + assert.doesNotMatch(creditsJs, /\.modal\(\s*['"]show['"]/); + assert.doesNotMatch(creditsJs, /hidden\.bs\.modal/); + // 打开前移除旧缓存 hidden 标记;关闭走 300ms fade-out 收尾。 + assert.match(creditsJs, /removeAttribute\('hidden'\)/); + assert.match(creditsJs, /void modal\.offsetWidth/); +});