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*\. '