fix(payment): 修复添加卡密页「确认提交」按钮静默失效

数字输入(auto_num / auto_price / auto_rand_number_limit /
auto_rand_password_limit)此前被平铺成原生 HTML5 约束属性
(min/max/step),叠加两个问题后表现为按钮点了毫无反应:

1) 数值彼此不自洽:参考主题 CSF spinner 的 default=20 / min=1 /
   step=10 合法格点是 1,11,21…,默认值 20 当场就非法;
   auto_rand_number_limit 同理(默认 20、格点 1,6,11…)。
2) 这些行是条件显隐的(field_row 输出 <tr data-dep>,不满足依赖时
   display:none)。提交时浏览器仍会校验隐藏控件,而隐藏控件无法聚焦
   去弹提示气泡,于是 Chrome 只往控制台丢一句
   "An invalid form control ... is not focusable" 便静默拒绝提交——
   不跳转、不报错、无 notice,用户看到的就是按钮坏了。

处置:数字输入改用 data-min/data-max/data-step + step="any"
(与参考主题 CSF_Field_spinner::render() 语义一致),不再写任何原生
约束属性;auto_price 一并去掉 min="0",面额下限改由服务端夹取兜底
(max(0.0, ...)),避免「去掉客户端下限」变成放行负面额。

验证:
- 无头 Chrome 探针:btn.click() 触发的 submit 事件 0 → 1,
  参与校验控件 23 个、无效 3 个 → 0 个;
- 服务端扫描页面 25 个 input:带原生约束的 0 个;
- 服务端面额夹取:-5 拒绝、0.001 拒绝、12.5 正常生成 1 张;
- 新增 tests/admin/charge-cards-form-constraints.test.cjs(4 例),
  并做负向对照(重引入 min/step 后 2 pass / 2 fail);
- tests/admin 四个套件合计 22 pass / 0 fail。
This commit is contained in:
2026-09-17 13:19:05 +08:00
parent 52d790c654
commit 481f40378a
3 changed files with 126 additions and 2 deletions
+22 -2
View File
@@ -346,7 +346,7 @@ class PayChargeCards {
);
$this->field_row(
__('面额', 'aksrt'),
'<input type="number" step="0.01" min="0" name="auto_price" value="0" />',
'<input type="number" step="any" name="auto_price" value="0" />',
[
['key' => 'add_type', 'op' => '==', 'val' => 'auto'],
['key' => 'type', 'op' => 'any', 'val' => 'balance,credits'],
@@ -703,6 +703,9 @@ class PayChargeCards {
case 'balance':
case 'credits':
$auto_price = isset($_REQUEST['auto_price']) ? floatval(round((float) $_REQUEST['auto_price'], 2)) : 0;
// 面额不接受负数。数字输入不再写原生 min(见 spinner() 注释:原生约束会让条件隐藏行
// 阻止表单提交),因此下限由服务端兜底。
$auto_price = max(0.0, $auto_price);
if (!$auto_price) {
$this->page_notice(__('错误!', 'aksrt'), __('请输入卡密的面额', 'aksrt'), 'error');
return;
@@ -999,8 +1002,25 @@ class PayChargeCards {
return $html . '</div>';
}
/**
* 数字输入(对齐参考主题 CSF spinner 字段的语义)。
*
* **min / max / step 只放在 data-* 上,绝不写成原生约束属性。**
* 参考主题 `CSF_Field_spinner::render()` 渲染的就是
* `data-min/data-max/data-step` + `step="any"`,原生 min/max/step 一个都不写。
* 移植时把它们平铺成原生属性,会引入两类问题:
* 1) 这些值彼此不自洽 —— 例如 default=20 / min=1 / step=10 的合法格点是 1,11,21…,
* 写成原生 step 会让**默认值当场非法**(20 不在格点上);
* 2) 本表单的行是条件显隐的。HTML5 约束校验会检查**隐藏**控件,而隐藏控件无法聚焦、
* 浏览器弹不出提示气泡,于是直接**静默拒绝提交**(只有控制台一句
* "An invalid form control ... is not focusable"),页面毫无反应 ——
* 用户看到的就是「确认提交按钮不起作用」。
* 取值范围由服务端兜底:`ChargeCard::generate_cards()` 内已对数量与位数做 max/min 夹取。
*/
private function spinner(string $name, int $value, int $min, int $max, int $step, string $unit): string {
return '<input type="number" class="small-text" name="' . esc_attr($name) . '" value="' . esc_attr((string) $value) . '" min="' . esc_attr((string) $min) . '" max="' . esc_attr((string) $max) . '" step="' . esc_attr((string) $step) . '" /> <span class="description">' . esc_html($unit) . '</span>';
return '<input type="number" class="small-text" step="any" name="' . esc_attr($name) . '" value="' . esc_attr((string) $value) . '"'
. ' data-min="' . esc_attr((string) $min) . '" data-max="' . esc_attr((string) $max) . '" data-step="' . esc_attr((string) $step) . '" />'
. ' <span class="description">' . esc_html($unit) . '</span>';
}
private function page_notice(string $title, string $msg, string $type = 'success'): void {