From a120f61e131bb9faf555cb7873f0c979459ba65b Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 19 Oct 2026 14:04:30 +0800 Subject: [PATCH] fix(vip): render points exchange description Co-Authored-By: Claude Opus 5 (1M context) --- assets/css/frontend/vip-modal.css | 21 +++++++ src/Content/VipServiceModal.php | 4 +- .../vip-modal-copy-fields.test.cjs | 5 +- ...ip-points-exchange-description-harness.php | 61 +++++++++++++++++++ .../vip-points-exchange-modal.test.cjs | 23 +++++++ .../vip-settings-structured-contract.test.cjs | 1 + .../vip-settings-structured-harness.php | 1 + 7 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 tests/widget-system/vip-points-exchange-description-harness.php diff --git a/assets/css/frontend/vip-modal.css b/assets/css/frontend/vip-modal.css index a90c5480..851a6f6e 100644 --- a/assets/css/frontend/vip-modal.css +++ b/assets/css/frontend/vip-modal.css @@ -64,6 +64,27 @@ z-index: 100055; } +/* public.css redeclares .muted-box after legacy utilities, overriding its + background, radius, and padding utilities. Restore the reference box contract. */ +.modal[data-aksrt-vip-points-modal] .muted-box.padding-h10 { + background-color: var(--this-bg, var(--muted-border-color)); + border-radius: var(--main-radius); + padding: 10px 15px; +} + +.modal[data-aksrt-vip-points-modal] .muted-box.padding-10, +.modal[data-aksrt-vip-points-modal] .aksrt-vip-points-desc { + background-color: var(--this-bg, var(--muted-border-color)); + border-radius: var(--main-radius); + padding: 10px; +} + +.modal[data-aksrt-vip-points-modal] .aksrt-vip-points-desc { + display: block; + margin-top: 20px; + color: var(--muted-2-color, #999); +} + .pay-vip-form-row { display: grid; gap: 12px; diff --git a/src/Content/VipServiceModal.php b/src/Content/VipServiceModal.php index a41646ba..8928a9fc 100644 --- a/src/Content/VipServiceModal.php +++ b/src/Content/VipServiceModal.php @@ -239,11 +239,11 @@ trait VipServiceModal { return ''; } - $desc_text = trim((string) ($points_ex['desc'] ?? '')); + $desc_text = trim((string) ($vip['points_exchange']['desc'] ?? '')); $desc = $desc_text !== '' // 管理员编辑的富文本说明:保存端已 wp_kses_post,这里同级别输出 // (参考主题 zibpay_get_vip_points_exchange_modal 原样拼接)。 - ? '
' . wp_kses_post($desc_text) . '
' + ? '
' . wp_kses_post($desc_text) . '
' : ''; // 最低价套餐仍超出用户积分 → 参考主题的 badg c-red btn-block 提示。 diff --git a/tests/widget-system/vip-modal-copy-fields.test.cjs b/tests/widget-system/vip-modal-copy-fields.test.cjs index d724d5d7..55b23935 100644 --- a/tests/widget-system/vip-modal-copy-fields.test.cjs +++ b/tests/widget-system/vip-modal-copy-fields.test.cjs @@ -224,7 +224,8 @@ test('积分兑换说明按 post 级 HTML 放行并原样渲染', () => { assert.match(schemaPhp, /'key' => 'pay_vip_points_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'sanitize_type' => 'html'/); const vipSettings = slice(paymentRuntime, 'public static function get_payment_vip_settings', 'public static function get_payment_revenue_settings'); assert.match(vipSettings, /'desc'\s+=> wp_kses_post\(\(string\) \(\$settings\['pay_vip_points_exchange_desc'\]/); - // 弹窗渲染端不再 esc_html 剥标签。 - assert.match(modalSource, /
' \. wp_kses_post\(\$desc_text\)/); + // 弹窗从同一份 VIP 设置上下文读取说明,不经 catalog 投影遗漏;说明容器保留参考主题的 HTML 与定位类。 + assert.match(modalSource, /\$desc_text = trim\(\(string\) \(\$vip\['points_exchange'\]\['desc'\] \?\? ''\)\);/); + assert.match(modalSource, /
' \. wp_kses_post\(\$desc_text\)/); assert.doesNotMatch(modalSource, /esc_html\(\$desc_text\)/); }); diff --git a/tests/widget-system/vip-points-exchange-description-harness.php b/tests/widget-system/vip-points-exchange-description-harness.php new file mode 100644 index 00000000..bea527a7 --- /dev/null +++ b/tests/widget-system/vip-points-exchange-description-harness.php @@ -0,0 +1,61 @@ + */ + private function get_vip_settings_context(): array { + return [ + 'enabled' => true, + 'levels' => [1 => ['enabled' => true, 'name' => 'VIP1']], + 'points_exchange' => [ + 'enabled' => true, + 'desc' => '

后台配置的兑换说明

', + ], + ]; + } + + /** @return array */ + public function get_catalog(int $user_id = 0): array { + return [ + 'points_exchange' => [ + 'packages' => [[ + 'package_id' => 6001, + 'level' => 1, + 'points' => 500, + 'time' => 30, + 'unit' => 'day', + ]], + ], + 'user_points' => 1000, + 'currency_name' => '积分', + ]; + } + } + + echo (new VipPointsDescriptionProbe())->get_points_exchange_modal_html(1); +} diff --git a/tests/widget-system/vip-points-exchange-modal.test.cjs b/tests/widget-system/vip-points-exchange-modal.test.cjs index 294a2125..ac67305a 100644 --- a/tests/widget-system/vip-points-exchange-modal.test.cjs +++ b/tests/widget-system/vip-points-exchange-modal.test.cjs @@ -22,6 +22,7 @@ const test = require('node:test'); const assert = require('node:assert'); const fs = require('node:fs'); const path = require('node:path'); +const { execFileSync } = require('node:child_process'); const root = path.join(__dirname, '..', '..'); const read = (...seg) => fs.readFileSync(path.join(root, ...seg), 'utf8'); @@ -80,6 +81,28 @@ test('弹窗壳随 wp_footer 全局输出且不进入 catalog 载荷', () => { assert.doesNotMatch(modal, /'vip_url'/); }); +test('我的积分盒恢复参考主题 muted-box 的背景、圆角与内边距', () => { + assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\s+\.muted-box\.padding-h10\s*\{\s*background-color:\s*var\(--this-bg,\s*var\(--muted-border-color\)\);\s*border-radius:\s*var\(--main-radius\);\s*padding:\s*10px 15px;\s*\}/); + assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\s+\.muted-box\.padding-10,\s*\.modal\[data-aksrt-vip-points-modal\]\s+\.aksrt-vip-points-desc\s*\{\s*background-color:\s*var\(--this-bg,\s*var\(--muted-border-color\)\);\s*border-radius:\s*var\(--main-radius\);\s*padding:\s*10px;\s*\}/); +}); + +test('后台兑换说明直接从 VIP 设置读取并作为富文本置于套餐与提交动作之间', () => { + assert.match(modal, /\$desc_text = trim\(\(string\) \(\$vip\['points_exchange'\]\['desc'\] \?\? ''\)\);/); + assert.match(modal, /aksrt-vip-points-desc/); + assert.match(modal, /wp_kses_post\(\$desc_text\)/); + assert.match(modal, /\. \$header\s*\. '
' \. \$user_box \. \$lists \. \$desc \. \$form/); + assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\s+\.aksrt-vip-points-desc\s*\{[^}]*display:\s*block;[^}]*margin-top:\s*20px;[^}]*color:\s*var\(--muted-2-color,\s*#999\);/s); + assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\s+\.muted-box\.padding-10,\s*\.modal\[data-aksrt-vip-points-modal\]\s+\.aksrt-vip-points-desc\s*\{[^}]*padding:\s*10px;/s); +}); + +test('兑换说明从后台设置实际输出,保留 HTML 并显示在卡片与 CTA 之间', () => { + const rendered = execFileSync(process.env.PHP_BINARY || 'php', [ + path.join(__dirname, 'vip-points-exchange-description-harness.php'), + ], { encoding: 'utf8' }); + assert.match(rendered, /class="muted-box muted-2-color padding-10 em09 mt20 aksrt-vip-points-desc">

后台配置的兑换说明<\/p><\/div>/); + assert.ok(rendered.indexOf('aksrt-vip-points-desc') < rendered.indexOf('data-aksrt-vip-points-submit')); +}); + test('弹窗壳静止态必须被 vip-modal.css 显式隐藏(.flex 覆盖回归护栏)', () => { assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\s*\{\s*display:\s*none;/); assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\.in\s*\{\s*display:\s*flex;/); diff --git a/tests/widget-system/vip-settings-structured-contract.test.cjs b/tests/widget-system/vip-settings-structured-contract.test.cjs index 5698cd44..82a5d399 100644 --- a/tests/widget-system/vip-settings-structured-contract.test.cjs +++ b/tests/widget-system/vip-settings-structured-contract.test.cjs @@ -371,6 +371,7 @@ test('运行时归一化:默认值、价格净化、零价与非法行剔除', assert.strictEqual(custom.upgrade.permanent_tag_class, 'jb-vip2'); // 积分兑换:非法行(积分为 0 / 等级越界 / 时长为空)被剔除。 assert.strictEqual(custom.points_exchange.packages.length, 3); + assert.strictEqual(custom.points_exchange.desc, '

积分兑换说明

'); assert.strictEqual(custom.points_exchange.packages[0].days, 30); assert.strictEqual(custom.points_exchange.packages[1].is_permanent, true); assert.strictEqual(custom.points_exchange.packages[2].days, 90); diff --git a/tests/widget-system/vip-settings-structured-harness.php b/tests/widget-system/vip-settings-structured-harness.php index ada3658e..79442d64 100644 --- a/tests/widget-system/vip-settings-structured-harness.php +++ b/tests/widget-system/vip-settings-structured-harness.php @@ -155,6 +155,7 @@ namespace { ], 'vip_2_renew_product' => [], 'pay_vip_points_exchange_s' => true, + 'pay_vip_points_exchange_desc' => '

积分兑换说明

', 'pay_vip_points_exchange_product' => [ ['points' => 800, 'level' => 1, 'time' => 30, 'unit' => 'day'], ['points' => 9000, 'level' => 2, 'time' => 'Permanent'],