feat(vip): Batch 2 pay-vip modal REST, JS, and CSS

This commit is contained in:
kate522 committed 2026-07-21 16:45:23 +08:00
1 parent a4562627d8
commit 965032f2e9
8 files changed
+917 -7

No files matched your search

+1
View File
@@ -11,6 +11,7 @@ class VipService {
use VipServiceRest;
use VipServicePurchase;
use VipServicePackages;
use VipServiceModal;
private static $instance = null;
+312
View File
@@ -0,0 +1,312 @@
<?php
/**
* VIP modal + card HTML (zibll pay-vip modal / vip-card parity).
*
* @package AKSRT
*/
namespace AKSRT\Content;
defined('ABSPATH') || exit;
trait VipServiceModal {
/**
* Build pay / renew / upgrade modal HTML for a user.
*/
public function render_modal_html(int $user_id, int $prefer_level = 1): string {
if ($user_id <= 0) {
return '<div class="box-body payvip-modal text-center padding-20">'
. esc_html__('请先登录后再开通会员。', 'aksrt')
. '</div>';
}
$vip = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_payment_vip_settings()
: ['enabled' => false];
if (empty($vip['enabled'])) {
return '<div class="box-body payvip-modal text-center padding-20">'
. esc_html__('会员功能未启用。', 'aksrt')
. '</div>';
}
$catalog = $this->get_catalog($user_id);
$user_level = (int) ($catalog['user_level'] ?? 0);
$prefer_level = max(1, min(2, $prefer_level));
if ($user_level > 0) {
return $this->render_modal_viped($user_id, $catalog, $vip, $prefer_level);
}
return $this->render_modal_pay($user_id, $catalog, $vip, $prefer_level);
}
/**
* Mini hover card for header (zibll zibpay_get_vip_card).
*/
public function render_vip_card(int $level, int $user_id = 0): string {
$level = max(1, min(2, $level));
$vip = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_payment_vip_settings()
: [];
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
if (empty($levels[$level]['enabled'])) {
return '';
}
$name = (string) ($levels[$level]['name'] ?? ('VIP' . $level));
$desc = (string) ($levels[$level]['desc'] ?? '');
$equity = (string) ($levels[$level]['equity'] ?? '');
$cls = $user_id > 0 ? 'pay-vip' : 'signin-loader';
$html = '<div class="vip-card pointer level-' . $level . ' vip-theme' . $level . ' ' . esc_attr($cls) . '" vip-level="' . $level . '">';
$html .= '<div class="abs-center vip-baicon"><i class="fa fa-diamond" aria-hidden="true"></i></div>';
$html .= '<div class="relative">';
$html .= '<div class="vip-name font-bold">' . esc_html($name) . '</div>';
if ($desc !== '') {
$html .= '<div class="muted-2-color em09 mb6">' . esc_html($desc) . '</div>';
}
if ($equity !== '') {
$html .= '<ul class="mb10 relative em09 v-equity">' . $equity . '</ul>';
}
$html .= '<a class="but jb-blue radius payvip-button" href="javascript:;">'
. esc_html(sprintf(__('开通%s', 'aksrt'), $name))
. '</a>';
$html .= '</div></div>';
return $html;
}
/**
* @param array<string,mixed> $catalog
* @param array<string,mixed> $vip
*/
private function render_modal_pay(int $user_id, array $catalog, array $vip, int $prefer_level): string {
$user = get_userdata($user_id);
$name = $user ? $user->display_name : '';
$avatar = get_avatar($user_id, 42);
$desc = (string) ($vip['desc'] ?? $catalog['desc'] ?? '');
$more = (string) ($vip['more'] ?? '');
$groups = is_array($catalog['level_packages'] ?? null) ? $catalog['level_packages'] : [];
$methods = is_array($catalog['payment_methods'] ?? null) ? $catalog['payment_methods'] : [];
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
$active_lv = !empty($levels[$prefer_level]['enabled']) ? $prefer_level : 0;
if ($active_lv <= 0) {
foreach ([1, 2] as $lv) {
if (!empty($levels[$lv]['enabled'])) {
$active_lv = $lv;
break;
}
}
}
$tabs = '';
$panes = '';
foreach ([1, 2] as $lv) {
if (empty($levels[$lv]['enabled'])) {
continue;
}
$is_active = ($lv === $active_lv);
$lv_name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv));
$tabs .= '<li class="relative' . ($is_active ? ' active' : '') . '">'
. '<a class="aksrt-vip-tab" href="javascript:;" data-vip-tab="tab-payvip-' . $lv . '">'
. esc_html($lv_name)
. '</a></li>';
$group_items = [];
foreach ($groups as $g) {
if ((int) ($g['level'] ?? 0) === $lv) {
$group_items = is_array($g['items'] ?? null) ? $g['items'] : [];
break;
}
}
$products = $this->render_product_radios($group_items);
$equity = (string) ($levels[$lv]['equity'] ?? '');
$lv_desc = (string) ($levels[$lv]['desc'] ?? '');
$panes .= '<div class="tab-pane aksrt-vip-pane' . ($is_active ? ' active' : '') . '" id="tab-payvip-' . $lv . '"' . ($is_active ? '' : ' hidden') . '>';
$panes .= '<div class="pay-vip-form-row">';
$panes .= '<div class="pay-vip-info level-' . $lv . '">';
$panes .= '<div class="v-name font-bold">' . esc_html($lv_name) . '</div>';
if ($lv_desc !== '') {
$panes .= '<div class="muted-2-color em09 mb6">' . esc_html($lv_desc) . '</div>';
}
if ($equity !== '') {
$panes .= '<ul class="v-equity">' . $equity . '</ul>';
}
$panes .= '</div><div class="pay-vip-products">';
$panes .= $products;
$panes .= $this->render_pay_methods($methods);
$panes .= '<button type="button" class="but jb-red btn-block padding-lg radius aksrt-vip-submit" data-vip-submit>'
. esc_html__('立即开通', 'aksrt') . '</button>';
$panes .= '<p class="aksrt-vip-modal-tip muted-2-color em09 mt10" data-aksrt-vip-tip></p>';
$panes .= '</div></div></div>';
}
$points = '';
if (!empty($catalog['points_exchange']['enabled']) && !empty($catalog['points_exchange']['packages'])) {
$points = '<div class="mt10"><a class="but c-green btn-block" href="'
. esc_url(function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('vip') : '#')
. '">' . esc_html__('积分兑换会员', 'aksrt') . '</a></div>';
}
$html = '<div class="box-body payvip-modal" data-aksrt-vip-modal>';
$html .= '<button type="button" class="close aksrt-vip-modal-close" aria-label="Close">&times;</button>';
$html .= '<ul class="list-inline user-box flex ac mb10"><li class="avatar-img">' . $avatar . '</li>';
$html .= '<li><b class="em12">' . esc_html($name) . '</b>';
if ($desc !== '') {
$html .= '<div class="muted-2-color em09">' . esc_html($desc) . '</div>';
}
$html .= '</li></ul>';
$html .= '<ul class="flex jsb mt10 mb20 vip-cardminis gutters-5 nav">' . $tabs . '</ul>';
$html .= '<div class="tab-content mt10">' . $panes . '</div>';
if ($more !== '') {
$html .= '<div class="muted-2-color mb10 em09 muted-box padding-10 mini-radius">' . $more . '</div>';
}
$html .= $points;
$html .= '</div>';
return $html;
}
/**
* @param array<string,mixed> $catalog
* @param array<string,mixed> $vip
*/
private function render_modal_viped(int $user_id, array $catalog, array $vip, int $prefer_level): string {
$user = get_userdata($user_id);
$name = $user ? $user->display_name : '';
$avatar = get_avatar($user_id, 42);
$exp_label = (string) ($catalog['user_expires_label'] ?? '');
$groups = is_array($catalog['level_packages'] ?? null) ? $catalog['level_packages'] : [];
$methods = is_array($catalog['payment_methods'] ?? null) ? $catalog['payment_methods'] : [];
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
$more = (string) ($vip['more'] ?? '');
$tabs = '';
$panes = '';
$active_set = false;
foreach ($groups as $g) {
$lv = (int) ($g['level'] ?? 0);
$action = (string) ($g['action'] ?? 'renew');
$items = is_array($g['items'] ?? null) ? $g['items'] : [];
if ($lv <= 0 || $items === []) {
continue;
}
$is_active = !$active_set;
if ($is_active) {
$active_set = true;
}
$lv_name = (string) ($g['name'] ?? ($levels[$lv]['name'] ?? ('VIP' . $lv)));
$badge = $action === 'upgrade' ? __('升级', 'aksrt') : __('续费', 'aksrt');
$tab_id = 'tab-payvip-' . $lv . '-' . $action;
$tabs .= '<li class="relative' . ($is_active ? ' active' : '') . '">'
. '<a class="aksrt-vip-tab" href="javascript:;" data-vip-tab="' . esc_attr($tab_id) . '">'
. esc_html($badge . $lv_name)
. '</a></li>';
$products = $this->render_product_radios($items);
$equity = (string) ($levels[$lv]['equity'] ?? '');
$panes .= '<div class="tab-pane aksrt-vip-pane' . ($is_active ? ' active' : '') . '" id="' . esc_attr($tab_id) . '"' . ($is_active ? '' : ' hidden') . '>';
$panes .= '<div class="pay-vip-form-row">';
$panes .= '<div class="pay-vip-info level-' . $lv . '">';
$panes .= '<div class="v-name font-bold">' . esc_html($lv_name) . '</div>';
if ($equity !== '') {
$panes .= '<ul class="v-equity">' . $equity . '</ul>';
}
$panes .= '</div><div class="pay-vip-products">';
$panes .= $products;
$panes .= $this->render_pay_methods($methods);
$cta = $action === 'upgrade' ? __('立即升级', 'aksrt') : __('立即续费', 'aksrt');
$panes .= '<button type="button" class="but jb-red btn-block padding-lg radius aksrt-vip-submit" data-vip-submit>'
. esc_html($cta) . '</button>';
$panes .= '<p class="aksrt-vip-modal-tip muted-2-color em09 mt10" data-aksrt-vip-tip></p>';
$panes .= '</div></div></div>';
}
if ($tabs === '') {
return '<div class="box-body payvip-modal text-center padding-20">'
. esc_html__('当前没有可续费或升级的套餐。', 'aksrt')
. '</div>';
}
$html = '<div class="box-body payvip-modal" data-aksrt-vip-modal>';
$html .= '<button type="button" class="close aksrt-vip-modal-close" aria-label="Close">&times;</button>';
$html .= '<ul class="list-inline user-box flex ac mb10"><li class="avatar-img">' . $avatar . '</li>';
$html .= '<li><b class="em12">' . esc_html($name) . '</b>';
$html .= '<div class="muted-2-color em09"><span class="badg c-yellow vip-expdate-tag">'
. esc_html($exp_label) . '</span></div></li></ul>';
$html .= '<ul class="flex jsb mt10 mb20 vip-cardminis gutters-5 nav">' . $tabs . '</ul>';
$html .= '<div class="tab-content mt10">' . $panes . '</div>';
if ($more !== '') {
$html .= '<div class="muted-2-color mb10 em09 muted-box padding-10 mini-radius">' . $more . '</div>';
}
$html .= '</div>';
return $html;
}
/**
* @param array<int,array<string,mixed>> $items
*/
private function render_product_radios(array $items): string {
if ($items === []) {
return '<div class="muted-2-color text-center padding-10">'
. esc_html__('暂无可用套餐', 'aksrt') . '</div>';
}
$html = '<div class="mb10 label-box charge-box aksrt-vip-products">';
$i = 0;
foreach ($items as $item) {
$pid = (int) ($item['package_id'] ?? 0);
if ($pid <= 0) {
continue;
}
$i++;
$price = (string) ($item['price_label'] ?? '');
$show = (string) ($item['show_price_label'] ?? '');
$tag = (string) ($item['discount_tag'] ?? '');
$time = (string) ($item['days_label'] ?? '');
$title = (string) ($item['title'] ?? '');
$checked = $i === 1 ? ' checked' : '';
$html .= '<label class="aksrt-vip-product-label">';
$html .= '<input class="hide vip-product-input" type="radio" name="vip_product_id" value="'
. esc_attr((string) $pid) . '"' . $checked . '>';
$html .= '<div class="zib-widget vip-product relative text-center product-box">';
if ($tag !== '') {
$html .= '<div class="abs-right vip-tag badg jb-yellow">' . esc_html($tag) . '</div>';
}
if ($title !== '') {
$html .= '<div class="em09 mb3">' . esc_html($title) . '</div>';
}
$html .= '<div class="flex abl gap6 jc"><div class="product-price c-red">' . esc_html($price) . '</div>';
if ($show !== '') {
$html .= '<div class="original-price relative muted-2-color"><s>' . esc_html($show) . '</s></div>';
}
$html .= '</div>';
if ($time !== '') {
$html .= '<div class="muted-2-color c-blue em09 p2-10 badg radius">' . esc_html($time) . '</div>';
}
$html .= '</div></label>';
}
$html .= '</div>';
return $html;
}
/**
* @param array<int,array{id?:string,name?:string}> $methods
*/
private function render_pay_methods(array $methods): string {
if ($methods === []) {
return '<input type="hidden" name="aksrt_vip_pay_method" value="balance" data-aksrt-vip-method-fixed>';
}
if (count($methods) === 1) {
$id = (string) ($methods[0]['id'] ?? 'balance');
return '<input type="hidden" name="aksrt_vip_pay_method" value="' . esc_attr($id) . '" data-aksrt-vip-method-fixed>';
}
$html = '<div class="uc-vip-shop__methods mb10" data-aksrt-vip-methods>';
$html .= '<span class="uc-vip-shop__methods-label">' . esc_html__('支付方式', 'aksrt') . '</span>';
foreach ($methods as $i => $m) {
$mid = (string) ($m['id'] ?? '');
$mname = (string) ($m['name'] ?? $mid);
$html .= '<label class="uc-vip-shop__method"><input type="radio" name="aksrt_vip_pay_method" value="'
. esc_attr($mid) . '"' . ($i === 0 ? ' checked' : '') . '> <span>'
. esc_html($mname) . '</span></label>';
}
$html .= '</div>';
return $html;
}
}
+34
View File
@@ -30,6 +30,40 @@ trait VipServiceRest {
'callback' => [$this, 'rest_points_exchange'],
'permission_callback' => [$this, 'check_rest_nonce'],
]);
register_rest_route('aksrt/v1', '/pay/vip/modal', [
'methods' => 'GET',
'callback' => [$this, 'rest_modal'],
'permission_callback' => [$this, 'check_rest_nonce'],
'args' => [
'vip_level' => [
'type' => 'integer',
'default' => 1,
],
],
]);
}
/**
* GET /pay/vip/modal?vip_level=1
*/
public function rest_modal(\WP_REST_Request $request) {
$uid = get_current_user_id();
if ($uid <= 0) {
return new \WP_Error('aksrt_need_login', __('请先登录后再开通会员。', 'aksrt'), [
'status' => 401,
'login_url' => function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(),
]);
}
$level = (int) $request->get_param('vip_level');
if ($level < 1 || $level > 2) {
$level = 1;
}
$html = $this->render_modal_html($uid, $level);
return new \WP_REST_Response([
'html' => $html,
'user_level' => function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($uid) : 0,
], 200);
}
+25
View File
@@ -161,6 +161,31 @@ trait ScriptsPart1b {
);
}
// VIP modal (zibll .pay-vip)
$vip_on = class_exists('\AKSRT\Settings\Settings')
&& !empty(\AKSRT\Settings\Settings::get_payment_vip_settings()['enabled']);
if ($vip_on) {
$vip_css = get_template_directory() . '/assets/css/frontend/vip-modal.css';
if (file_exists($vip_css)) {
wp_enqueue_style(
'aksrt-vip-modal',
$base_uri . '/assets/css/frontend/vip-modal.css',
[$core_dep],
(string) filemtime($vip_css)
);
}
$vip_js = get_template_directory() . '/assets/js/pay-vip.js';
if (file_exists($vip_js)) {
wp_enqueue_script(
'aksrt-pay-vip',
$base_uri . '/assets/js/pay-vip.js',
['aksrt-script'],
(string) filemtime($vip_js),
true
);
}
}
// Print stylesheet — only load on singular pages
if (is_singular()) {