fix(vip): 积分兑换弹窗改服务端渲染并与 Zibll 1:1 对齐

/user/vip/ 的积分兑换会员弹窗原先是前端自行拼装的,结构与层级都与参考主题
Zibll 不一致:Zibll 走 zibpay_get_vip_points_exchange_modal() 服务端渲染静态
壳,前端只按需开合。改为同一模型,前端不再拼 HTML。

- VipServiceModal 新增 get_points_exchange_modal_html():门控(未登录/未开启
  返回空串)、vip-card 套餐选择、积分不足提示与提交表单;wp_footer 挂载输出
- pay-vip.js 移除 renderPointsModal / pointsPackage / exchangeFromModal /
  vipPointsPay,改为自包含开合(.in + 内联 display:flex + 独立遮罩 +
  body.modal-open + ESC),保留 aksrt-vip-points-toast 的 load/success/danger 契约
- 移除 /user/vip/ 页面底部独立积分兑换入口及其样式(uc-vip-points-link)
- credits.js 清理依赖全站不存在选择器的死代码 initVipPointsExchange()
- vip-modal.css 删除 71 行自实现样式,改为 .modal[data-aksrt-vip-points-modal]
  显隐契约;层级 100050 < 遮罩 100055 < 弹窗 100060 < notyf 10000000
- 新增 vip-points-exchange-modal 契约测试(7 项),toast-contract 同步

静止态必须显式 display:none,否则 .flex{display:flex} 会覆盖 .modal{display:none}
形成隐形遮罩拦截整页点击。

未写数据迁移。

验证:node --test tests/widget-system/*.test.cjs 308 项全部通过;php -l 全部
改动文件通过;隔离渲染桩确认积分充足 / 积分不足 / 功能关闭 / 未登录四种情形
的 HTML 产物正确。
This commit is contained in:
2026-09-18 20:05:05 +08:00
parent d2447f8aa6
commit 95ff0761b7
11 changed files with 401 additions and 318 deletions
+24
View File
@@ -503,3 +503,27 @@ if (!function_exists('aksrt_format_vip_equity_html')) {
return $html;
}
}
/**
* 全局积分兑换会员弹窗(参考主题 refresh_modal 同构静态壳)。
*
* 入口位于会员购买弹窗(.pay-vip → assets/js/pay-vip.js)内,而 .pay-vip
* 触发点全站可见(页头、浮动按钮、用户中心、付费卡),因此弹窗壳在满足
* 条件时随 wp_footer 全局输出一次;显隐由 pay-vip.js 自包含开关驱动,
* 静止态由 vip-modal.css 的 .modal[data-aksrt-vip-points-modal] 显式隐藏。
*/
if (!function_exists('aksrt_render_vip_points_exchange_modal')) :
function aksrt_render_vip_points_exchange_modal(): void {
if (!is_user_logged_in() || !class_exists('\AKSRT\Content\VipService')) {
return;
}
$html = \AKSRT\Content\VipService::get_instance()->get_points_exchange_modal_html(get_current_user_id());
if ($html === '') {
return;
}
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- 各字段已在生成时转义。
}
endif;
add_action('wp_footer', 'aksrt_render_vip_points_exchange_modal');
@@ -21,15 +21,6 @@ if (empty($vip_settings['enabled'])) {
$user_id = (int) ($user_id ?? get_current_user_id());
$vip_state = function_exists('aksrt_get_user_vip_state') ? aksrt_get_user_vip_state($user_id) : [];
$level = (int) ($vip_state['level'] ?? 0);
$points_exchange = [];
$user_points = 0;
$currency_name = __('积分', 'aksrt');
if (class_exists('\\AKSRT\\Content\\VipService')) {
$vip_catalog = \AKSRT\Content\VipService::get_instance()->get_catalog($user_id);
$points_exchange = is_array($vip_catalog['points_exchange'] ?? null) ? $vip_catalog['points_exchange'] : [];
$user_points = (int) ($vip_catalog['user_points'] ?? 0);
$currency_name = (string) ($vip_catalog['currency_name'] ?? $currency_name);
}
$expires = max(0, (int) ($vip_state['current_expires'] ?? $vip_state['expires'] ?? 0));
$base_level = min(2, max(0, (int) ($vip_state['fallback_level'] ?? 0)));
$base_expires = max(0, (int) ($vip_state['fallback_expires'] ?? 0));
@@ -162,20 +153,6 @@ foreach ([1, 2] as $lv) {
$cards .= '</div></div>';
}
// Points exchange entry (available for guests, ordinary users, and VIP renewals/upgrades).
$points_html = '';
$points_pkgs = is_array($points_exchange['packages'] ?? null) ? $points_exchange['packages'] : [];
if (!empty($points_exchange['enabled']) && $points_pkgs !== []) {
$points_html = '<div class="uc-vip-points-link" data-aksrt-vip-points-modal>'
. '<div class="uc-vip-points-link__icon" aria-hidden="true"><i class="fa fas fa-money-bill"></i></div>'
. '<div class="uc-vip-points-link__body">'
. '<strong>' . esc_html__('积分兑换会员', 'aksrt') . '</strong>'
. '<span>' . sprintf(esc_html__('当前有 %1$s %2$s,可兑换专属会员权益', 'aksrt'), esc_html((string) $user_points), esc_html($currency_name)) . '</span>'
. '</div>'
. '<button type="button" class="uc-vip-points-link__action" data-aksrt-vip-points-open>'
. esc_html__('立即兑换', 'aksrt')
. ' <i class="fa fas fa-chevron-right" aria-hidden="true"></i></button></div>';
}
?>
<div class="uc-vip-tab" data-aksrt-vip-tab>
<div class="uc-vip-summary" data-aksrt-vip-summary data-vip-state="<?php echo esc_attr($vip_state); ?>" data-vip-level="<?php echo (int) $vip_level_number; ?>" data-vip-permanent="<?php echo $vip_is_permanent ? 'true' : 'false'; ?>">
@@ -187,8 +164,6 @@ if (!empty($points_exchange['enabled']) && $points_pkgs !== []) {
<?php echo $cards; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<?php echo $points_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php if (!empty($vip_settings['more'])) : ?>
<div class="muted-2-color em09 muted-box padding-10 mini-radius mt15">
<?php echo $vip_settings['more']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>