fix(payment): 添加卡密页提示块自带依赖状态,脱离所在行也不再铺开

用户复报「四条导入提示仍然全部铺开」。其 DevTools 实测给出关键判据:
同一页面上 formClass=1 / submsg=4 / depRows=17 与线上一致,但四条提示的
父级 <tr> 为 null、computed display 为 block —— 也就是说提示块虽在 DOM 中,
却不在任何表格行内。此时依赖状态只挂在 <tr> 上,隐藏与切换都无处落地,
于是四条提示永久可见、切换卡密类型/添加方式也没有反应。

修复:把 data-dep 与初始隐藏状态同时挂到内层提示块上,求值器改为
「谁带 data-dep 就管谁」,行与提示块各自落自己的 display。

变更(src/Admin/Native/PayChargeCards.php):
- submessage_row():<tr> 与内层 <div class="aksrt-submsg"> 共用同一份
  data-dep;初始隐藏状态合并进提示块原有的 margin/padding 内联样式,
  不产生重复的 style 属性。原「只给 <tr> 加 style」的写法保留给行本身。
- evalDeps():选择器由 '.aksrt-cardpass-form tr[data-dep]' 放宽为
  '.aksrt-cardpass-form [data-dep]',并改用 el.style.display 落值。

测试(tests/admin/charge-cards-dependency.test.cjs):
- DOM 桩按真实结构为每条提示建「行 + 提示块」两个元素,新增用例
  「提示块与所在行同层落 display」断言两层始终同状态(默认 8 个元素全隐藏、
  命中依赖时仅该提示的两层可见、切回默认两层同时收起)。
- 契约用例新增三条静态断言:提示块必须自带 data-dep、必须自带初始隐藏状态、
  求值选择器必须同时覆盖行与提示块。
- 负向验证:把提示块的 data-dep 去掉后跑测试 → pass 5 / fail 1,护栏生效。

验证:
- node --test 两个 admin 契约测试 10/10 通过;php -l 无语法错误;
- 线上抓取:tr_total=19 / tr_with_dep=17 / div_with_dep=4 / display_none=18;
  四条提示的 div 均带 data-dep="…" 与 display:none。
- 真实浏览器模拟「提示块被搬出所在行」(foster-parent 到 <table> 之前)后:
  默认态四条全部 none;切到「会员兑换 + 导入卡密」时,只有已脱离行的
  「导入会员兑换卡密」变为可见(ownStyle 中的 display:none 被移除),
  其余三条维持 none;切回默认四条全部收 none。
This commit is contained in:
2026-09-17 11:41:05 +08:00
parent 5243bbf0d4
commit 670e271ff6
2 changed files with 56 additions and 12 deletions
+12 -5
View File
@@ -853,8 +853,13 @@ class PayChargeCards {
* @param array<int,array{key:string,op:string,val:string}> $deps
*/
private function submessage_row(array $deps, string $content): void {
$hidden = $this->deps_visible($deps) ? '' : ' style="display:none;"';
echo '<tr data-dep="' . esc_attr((string) wp_json_encode($deps)) . '"' . $hidden . '><td colspan="2"><div class="aksrt-submsg notice notice-warning" style="margin:6px 0;padding:6px 12px;">' . $content . '</div></td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput -- 文案已转义。
$dep_attr = ' data-dep="' . esc_attr((string) wp_json_encode($deps)) . '"';
$hidden = $this->deps_visible($deps) ? '' : 'display:none;';
// data-dep 与初始隐藏状态必须同时挂在 <tr> 和内层提示块上,不能只依赖行:
// 提示块一旦脱离所在行(表格上下文里出现不被允许的节点时,HTML 解析器会把它
// foster-parent 到 <table> 之前),只有 <tr> 带状态会让四条提示永久铺开、
// 且切换卡密类型/添加方式时求值结果无处落地。自带状态后两种结构都成立。
echo '<tr' . $dep_attr . ($hidden === '' ? '' : ' style="' . $hidden . '"') . '><td colspan="2"><div' . $dep_attr . ' class="aksrt-submsg notice notice-warning" style="margin:6px 0;padding:6px 12px;' . $hidden . '">' . $content . '</div></td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput -- 文案已转义。
}
/**
@@ -1075,9 +1080,11 @@ class PayChargeCards {
return els[0].value;
}
function evalDeps() {
document.querySelectorAll('.aksrt-cardpass-form tr[data-dep]').forEach(function (tr) {
// 选择器同时匹配行元素与提示块自身:提示块可能脱离所在行,
// 因此每层都各自带 data-dep 并各自落 display,谁在 DOM 里就管谁。
document.querySelectorAll('.aksrt-cardpass-form [data-dep]').forEach(function (el) {
try {
var deps = JSON.parse(tr.getAttribute('data-dep'));
var deps = JSON.parse(el.getAttribute('data-dep'));
var show = deps.every(function (d) {
var v = formVal(d.key);
switch (d.op) {
@@ -1089,7 +1096,7 @@ class PayChargeCards {
}
return true;
});
tr.style.display = show ? '' : 'none';
el.style.display = show ? '' : 'none';
} catch (e) { /* noop */ }
});
}