type_labels(); ?>

field_row( __('卡密类型', 'aksrt'), $this->button_set('type', $type_labels, 'balance'), [] ); $this->field_row( __('添加方式', 'aksrt'), $this->button_set('add_type', [ 'auto' => __('系统自动生成', 'aksrt'), 'import' => __('导入卡密', 'aksrt'), ], 'auto'), [] ); // 自动生成 $this->field_row( __('生成数量', 'aksrt'), $this->spinner('auto_num', 20, __('张', 'aksrt')), [['key' => 'add_type', 'op' => '==', 'val' => 'auto']], __('需要生成多少张卡密(1 - 1000 的整数,单次生成数量太多可能会对服务器性能造成影响)', 'aksrt') ); $this->field_row( __('标识', 'aksrt'), '', [['key' => 'add_type', 'op' => '==', 'val' => 'auto']], __('对生成的卡密做标记标识,方便后期查找管理', 'aksrt') . '
' . sprintf(esc_html__('注意:标识中不能有%1$s字符,系统已占用', 'aksrt'), 'shipped') ); $this->field_row( __('面额', 'aksrt'), '', [ ['key' => 'add_type', 'op' => '==', 'val' => 'auto'], ['key' => 'type', 'op' => 'any', 'val' => 'balance,credits'], ], __('单张卡密的面额', 'aksrt') ); $this->field_row( __('兑换会员', 'aksrt'), __('会员等级', 'aksrt') . ' ' . $this->button_set('vip_exchange[level]', [ '1' => $this->vip_level_name(1), '2' => $this->vip_level_name(2), ], '1'), [ ['key' => 'add_type', 'op' => '==', 'val' => 'auto'], ['key' => 'type', 'op' => '==', 'val' => 'vip'], ] ); $this->field_row( ' ', __('兑换会员时长', 'aksrt') . '
', [ ['key' => 'add_type', 'op' => '==', 'val' => 'auto'], ['key' => 'type', 'op' => '==', 'val' => 'vip'], ], __('填', 'aksrt') . 'Permanent' . __('为永久', 'aksrt') ); $this->field_row( ' ', __('时长单位', 'aksrt') . ' ' . $this->button_set('vip_exchange[unit]', [ 'day' => __('天', 'aksrt'), 'month' => __('个月', 'aksrt'), ], 'month'), [ ['key' => 'add_type', 'op' => '==', 'val' => 'auto'], ['key' => 'type', 'op' => '==', 'val' => 'vip'], ['key' => 'vip_exchange[time]', 'op' => '!=', 'val' => 'Permanent'], ] ); $this->field_row( __('自定义显示名称', 'aksrt'), __('卡号显示名称', 'aksrt') . '

' . __('卡密显示名称', 'aksrt') . '
', [ ['key' => 'add_type', 'op' => '==', 'val' => 'auto'], ['key' => 'type', 'op' => '==', 'val' => 'custom'], ] ); $this->field_row( __('高级选项', 'aksrt'), '', [['key' => 'add_type', 'op' => '==', 'val' => 'auto']] ); $this->field_row( __('自定义卡号位数', 'aksrt'), $this->spinner('auto_rand_number_limit', 20, __('位数', 'aksrt')), [ ['key' => 'add_type', 'op' => '==', 'val' => 'auto'], ['key' => 'auto_top_s', 'op' => 'checked'], ] ); $this->field_row( __('自定义密码位数', 'aksrt'), $this->spinner('auto_rand_password_limit', 35, __('位数', 'aksrt')), [ ['key' => 'add_type', 'op' => '==', 'val' => 'auto'], ['key' => 'auto_top_s', 'op' => 'checked'], ], __('自定义自动生成的长度(不能太短,太短可能会出现重复)如果启用了单密码模式,可以将卡号位数设置为0', 'aksrt') ); // 导入提示统一放在「卡密数据」正上方:切换卡密类型/添加方式时, // 命中的那一条紧邻示例数据与输入框,便于对照填写。 $this->field_row( __('单密码模式', 'aksrt'), '', [ ['key' => 'add_type', 'op' => '==', 'val' => 'import'], ['key' => 'type', 'op' => '==', 'val' => 'custom'], ] ); $this->submessage_row( [ ['key' => 'add_type', 'op' => '!=', 'val' => 'auto'], ['key' => 'type', 'op' => 'any', 'val' => 'balance,credits'], ], '

' . esc_html__('导入卡密', 'aksrt') . '

' . esc_html__('数据示例', 'aksrt') . '

4849977843617 3w20V4Qe7dRhAIoPYum6KSA 50 ' . sprintf(esc_html__('%1$s余额', 'aksrt'), esc_html('50' . $this->currency_unit())) . '
6484027668705 SkdJZ5vWcpqPxrbs3pqPcpq 100 ' . sprintf(esc_html__('%1$s余额', 'aksrt'), esc_html('100' . $this->currency_unit())) . '
' ); $this->submessage_row( [ ['key' => 'add_type', 'op' => '!=', 'val' => 'auto'], ['key' => 'type', 'op' => '==', 'val' => 'vip'], ], '

' . esc_html__('导入会员兑换卡密', 'aksrt') . '

' . esc_html__('数据示例', 'aksrt') . '

4849977843617 3w20V4Qe7dRhAIoPYum6KSA level=1&time=5&unit=day ' . esc_html__('5天一级会员', 'aksrt') . '
6484027668705 SkdJZ5vWcpqPxrbs3pqPcpq level=2&time=Permanent ' . esc_html__('永久2级会员', 'aksrt') . '
' ); $this->submessage_row( [ ['key' => 'add_type', 'op' => '==', 'val' => 'import'], ['key' => 'type', 'op' => '==', 'val' => 'custom'], ['key' => 'only_pass_s', 'op' => 'unchecked'], ], '

' . esc_html__('自定义卡密导入', 'aksrt') . '

' . esc_html__('数据示例', 'aksrt') . '

4849977843617 3w20V4Qe7dRhAIoPYum6KSA ' . esc_html__('平台卡密01', 'aksrt') . ' card_name=' . esc_html__('授权号', 'aksrt') . '&password_name=' . esc_html__('授权码', 'aksrt') . '
6484027668705 SkdJZ5vWcpqPxrbs3pqPcpq ' . esc_html__('平台卡密01', 'aksrt') . ' card_name=' . esc_html__('授权号', 'aksrt') . '&password_name=' . esc_html__('授权码', 'aksrt') . '
111111@qq.com 11assssddsds ' . esc_html__('某某账号', 'aksrt') . ' card_name=' . esc_html__('账号', 'aksrt') . '&password_name=' . esc_html__('密码', 'aksrt') . '
' ); $this->submessage_row( [ ['key' => 'add_type', 'op' => '==', 'val' => 'import'], ['key' => 'type', 'op' => '==', 'val' => 'custom'], ['key' => 'only_pass_s', 'op' => 'checked'], ], '

' . esc_html__('自定义卡密导入(单密码模式)', 'aksrt') . '

' . esc_html__('数据示例', 'aksrt') . '

3w20V4Qe7dRhAIoPYum6KSA ' . esc_html__('平台卡密01', 'aksrt') . ' password_name=' . esc_html__('授权码', 'aksrt') . '
SkdJZ5vWcpqPxrbs3pqPcpq ' . esc_html__('平台卡密01', 'aksrt') . ' password_name=' . esc_html__('授权码', 'aksrt') . '
' ); $this->field_row( __('卡密数据', 'aksrt'), '', [['key' => 'add_type', 'op' => '==', 'val' => 'import']] ); $this->field_row( __('自定义分隔符号', 'aksrt'), '', [['key' => 'add_type', 'op' => '==', 'val' => 'import']], __('卡号和密码之间分割符号(默认为空格分割)', 'aksrt') ); ?>
field_row( __('卡密类型', 'aksrt'), $this->button_set('type', $this->type_labels(), 'balance'), [] ); $this->field_row( __('选择状态', 'aksrt'), $this->button_set('status', [ 'all' => __('全部', 'aksrt'), '0' => __('未使用', 'aksrt'), 'used' => __('已使用', 'aksrt'), ], 'all'), [['key' => 'type', 'op' => '!=', 'val' => 'custom']] ); $this->field_row( __('导出格式', 'aksrt'), $this->button_set('export_format', [ 'text' => __('文本文档', 'aksrt'), 'xls' => __('Excel表格', 'aksrt'), ], 'xls'), [] ); $this->field_row( ' ', __('分隔符号', 'aksrt') . '
', [['key' => 'export_format', 'op' => '==', 'val' => 'text']], __('卡号和密码之间分割符号(默认为空格分割)', 'aksrt') ); ?>
$deps */ private function field_row(string $title, string $html, array $deps = [], string $desc = ''): void { $dep_attr = $deps ? ' data-dep="' . esc_attr((string) wp_json_encode($deps)) . '"' : ''; $hidden = ($deps && !$this->deps_visible($deps)) ? ' style="display:none;"' : ''; echo '' . esc_html($title) . '' . $html; // phpcs:ignore WordPress.Security.EscapeOutput -- 内部构造,已转义。 if ($desc !== '') { echo '

' . wp_kses_post($desc) . '

'; } echo ''; } /** * @param array $deps */ private function submessage_row(array $deps, string $content): void { $dep_attr = ' data-dep="' . esc_attr((string) wp_json_encode($deps)) . '"'; $hidden = $this->deps_visible($deps) ? '' : ' style="display:none;"'; // 提示块**不要**挂 WP 的 .notice / .notice-warning:wp-admin/js/common.js 在 DOM // 就绪时会执行 // $('div.updated, div.error, div.notice').not('.inline, .below-h2').insertAfter($headerEnd) // 把页面上所有「非 inline」的 notice 整块搬到 .wp-header-end(不存在时取 .wrap 内首个 // h1/h2)之后。提示块一旦带上 notice,就会被搬到页面顶部、脱离「添加方式—卡密数据」 // 之间的字段区,而且搬迁发生在求值之后,行内的显隐逻辑无从纠正。 // 外观改由 .aksrt-submsg 自带(见 print_assets),与参考主题 CSF content 字段一致。 // data-dep 与初始隐藏状态同时挂在 和内层提示块上:提示块若脱离所在行,仍能自行显隐。 echo '' . $content . ''; // phpcs:ignore WordPress.Security.EscapeOutput -- 文案已转义。 } /** * 服务端按表单默认值预判依赖可见性(求值规则与行内 JS 完全一致)。 * 仅在表单渲染默认值(添加方式=系统自动生成、类型=余额)下,把不满足依赖的行标记为初始隐藏, * 避免四条导入提示首帧铺开。 * * 注意:已知键必须覆盖 field_row/submessage_row 用到的所有依赖键,否则该键的判定会与 JS 不一致, * 出现「服务端判可见、JS 判隐藏」之类的错配(例如自定义卡密的两条导入提示同时铺开)。 * * @param array $deps */ private function deps_visible(array $deps): bool { foreach ($deps as $dep) { $key = (string) ($dep['key'] ?? ''); $val = (string) ($dep['val'] ?? ''); $op = (string) ($dep['op'] ?? ''); if (!$this->is_known_dep_key($key)) { // 未登记的键:不做服务端判定,交 JS 处理。 continue; } $v = (string) $this->default_form_value($key); switch ($op) { case '==': if ($v !== $val) { return false; } break; case '!=': if ($v === $val) { return false; } break; case 'any': if (!in_array($v, explode(',', $val), true)) { return false; } break; case 'checked': if ($v === '') { return false; } break; case 'unchecked': if ($v !== '') { return false; } break; } } return true; } /** * 可在服务端求值的字段键(须完整覆盖表单依赖中使用的键,与 default_form_value 保持一致)。 */ private function is_known_dep_key(string $key): bool { static $known = [ 'add_type', 'type', 'auto_top_s', 'only_pass_s', 'vip_exchange[time]', 'export_format', 'status', ]; return in_array($key, $known, true); } /** * 表单字段的初始默认值(须与 field_row/button_set/spinner 渲染的默认值保持同步)。 */ private function default_form_value(string $key): string { switch ($key) { case 'add_type': return 'auto'; case 'type': return 'balance'; case 'export_format': return 'xls'; case 'status': return 'all'; // 未勾选的 checkbox/switcher 与空文本,在依赖求值中均等价于空字符串。 case 'auto_top_s': case 'only_pass_s': case 'vip_exchange[time]': return ''; default: return ''; } } /** * button_set:radio 按钮组(参考主题 CSF button_set 的原生替代)。 * * @param array $options */ private function button_set(string $name, array $options, string $current): string { $html = '
'; foreach ($options as $value => $label) { $html .= ''; } return $html . '
'; } /** * 数字输入(参考主题 CSF spinner / number 字段的原生替代)。 * * **一律不写原生 min / max / step 约束属性,只写 `step="any"`:** * - 本表单的行是条件显隐的(`field_row()` 会给不满足依赖的 `` 打上 `display:none`)。 * HTML5 约束校验会检查**隐藏**控件,而隐藏控件无法聚焦、浏览器弹不出提示气泡, * 于是直接**静默拒绝提交**(控制台只有一句 "An invalid form control ... is not focusable"), * 页面毫无反应 —— 用户看到的就是「确认提交按钮不起作用」。 * - `step="any"` 不能省:`type="number"` 缺省 `step=1`,会退回整数网格,把非整数输入判为非法, * 同样触发上面的静默拒绝。 * * 取值范围不在标记里表达,由服务端单点约束:生成数量见 `handle_add()` 的 (0, 1000] 判定, * 卡号 / 密码位数见 `ChargeCard::generate_cards()`。 */ private function spinner(string $name, int $value, string $unit): string { return '' . ' ' . esc_html($unit) . ''; } private function page_notice(string $title, string $msg, string $type = 'success'): void { $html = ''; $html .= $title ? '

' . esc_html($title) . '

' : ''; $html .= $msg ? '

' . esc_html($msg) . '

' : ''; if ($html) { echo '
' . $html . '
'; // phpcs:ignore WordPress.Security.EscapeOutput } } }