diff --git a/assets/css/frontend/vip-modal.css b/assets/css/frontend/vip-modal.css new file mode 100644 index 00000000..07ea7f1a --- /dev/null +++ b/assets/css/frontend/vip-modal.css @@ -0,0 +1,257 @@ +/** + * VIP pay modal + hover cards (zibll payvip-modal / vip-card). + */ + +.aksrt-vip-modal-open { + overflow: hidden; +} + +.aksrt-vip-modal-overlay { + position: fixed; + inset: 0; + z-index: 1200; + display: none; + align-items: center; + justify-content: center; + padding: 16px; + background: rgba(0, 0, 0, 0.5); +} + +.aksrt-vip-modal-overlay.is-open { + display: flex; +} + +.aksrt-vip-modal-dialog { + width: min(560px, 100%); + max-height: min(90vh, 760px); + overflow: auto; + background: var(--main-bg-color, #fff); + border-radius: 12px; + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.22); +} + +.payvip-modal { + position: relative; + padding: 18px 16px 16px; +} + +.payvip-modal .close, +.aksrt-vip-modal-close { + position: absolute; + top: 8px; + right: 12px; + border: 0; + background: transparent; + font-size: 22px; + line-height: 1; + cursor: pointer; + color: inherit; + opacity: 0.65; +} + +.vip-cardminis { + list-style: none; + margin: 0; + padding: 0; + gap: 8px; + display: flex; +} + +.vip-cardminis > li { + flex: 1; +} + +.vip-cardminis a.aksrt-vip-tab { + display: block; + text-align: center; + padding: 10px 8px; + border-radius: 8px; + background: var(--surface-2, rgba(0, 0, 0, 0.05)); + color: inherit; + text-decoration: none; + font-weight: 600; +} + +.vip-cardminis > li.active a.aksrt-vip-tab { + background: linear-gradient(90deg, #ff6b6b, #f04494); + color: #fff; +} + +.pay-vip-form-row { + display: grid; + gap: 12px; +} + +@media (min-width: 640px) { + .pay-vip-form-row { + grid-template-columns: 1fr 1.2fr; + } +} + +.pay-vip-info { + padding: 12px; + border-radius: 10px; + background: var(--surface-2, rgba(0, 0, 0, 0.04)); +} + +.pay-vip-info.level-1 { + background: linear-gradient(135deg, rgba(255, 193, 7, 0.12), rgba(255, 152, 0, 0.08)); +} + +.pay-vip-info.level-2 { + background: linear-gradient(135deg, rgba(156, 39, 176, 0.12), rgba(63, 81, 181, 0.08)); +} + +.v-equity { + margin: 0; + padding-left: 1.1em; + font-size: 12px; + line-height: 1.6; +} + +.aksrt-vip-products { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.aksrt-vip-product-label { + display: block; + cursor: pointer; + margin: 0; +} + +.aksrt-vip-product-label input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.vip-product { + position: relative; + padding: 12px 8px; + border-radius: 10px; + border: 1px solid rgba(0, 0, 0, 0.08); + background: var(--main-bg-color, #fff); + transition: border-color 0.15s, box-shadow 0.15s; +} + +.aksrt-vip-product-label input:checked + .vip-product { + border-color: #f04494; + box-shadow: 0 0 0 1px #f04494; +} + +.product-price { + font-size: 18px; + font-weight: 700; +} + +.vip-tag { + position: absolute; + top: 0; + right: 0; + font-size: 11px; + padding: 2px 6px; + border-radius: 0 10px 0 8px; + background: #ffc107; + color: #5c3b00; +} + +.aksrt-vip-submit { + width: 100%; + margin-top: 8px; + border: 0; + border-radius: 999px; + padding: 12px 16px; + background: linear-gradient(90deg, #ff6b6b, #f04494); + color: #fff; + font-weight: 600; + cursor: pointer; +} + +.aksrt-vip-submit.is-loading, +.aksrt-vip-submit:disabled { + opacity: 0.7; + cursor: wait; +} + +.aksrt-vip-modal-tip.is-error, +.uc-vip-shop__tip.is-error { + color: #e74c3c; +} + +/* Header hover VIP cards */ +.navbar-vip.hover-show { + position: relative; +} + +.sub-vip-card { + display: none; + position: absolute; + top: calc(100% + 8px); + right: 0; + z-index: 60; + width: min(420px, 90vw); + padding: 10px; + border-radius: 12px; + background: var(--main-bg-color, #fff); + box-shadow: 0 12px 36px rgba(0, 0, 0, 0.16); + gap: 10px; +} + +.navbar-vip.hover-show:hover .sub-vip-card, +.navbar-vip.hover-show:focus-within .sub-vip-card { + display: flex; + flex-wrap: wrap; +} + +.vip-card { + position: relative; + flex: 1 1 160px; + min-width: 150px; + padding: 14px 12px; + border-radius: 10px; + overflow: hidden; + background: linear-gradient(135deg, #2b2f3a, #1b1e27); + color: #fff; +} + +.vip-card.vip-theme1 { + background: linear-gradient(135deg, #f6c453, #f39c12); + color: #3b2a00; +} + +.vip-card.vip-theme2 { + background: linear-gradient(135deg, #8e44ad, #3f51b5); + color: #fff; +} + +.vip-card .payvip-button { + display: inline-block; + margin-top: 6px; + padding: 6px 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.92); + color: #333; + text-decoration: none; + font-size: 12px; +} + +.vipbg-v2 { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + padding: 8px 10px; + border-radius: 8px; + color: #fff; + background: linear-gradient(90deg, #ffb347, #ff6b6b); +} + +.vipbg-v2.level-2 { + background: linear-gradient(90deg, #9b59b6, #3f51b5); +} + +.vip-expdate-tag { + font-size: 12px; +} diff --git a/assets/js/pay-vip.js b/assets/js/pay-vip.js new file mode 100644 index 00000000..a33e6ecb --- /dev/null +++ b/assets/js/pay-vip.js @@ -0,0 +1,266 @@ +/** + * AKSRT VIP pay modal — zibll .pay-vip parity. + * Loads modal HTML from GET /aksrt/v1/pay/vip/modal then purchases via POST /pay/vip. + */ +(function () { + "use strict"; + + function restBase() { + var aksrt = window.aksrtData || window.aksrt || {}; + var siteUrl = aksrt.siteUrl || aksrt.homeUrl || (window.location.origin + "/"); + if (window.location.protocol === "https:" && siteUrl.substring(0, 5) === "http:") { + siteUrl = "https:" + siteUrl.substring(5); + } + if (aksrt.restUrl) { + return String(aksrt.restUrl).replace(/\/$/, ""); + } + return siteUrl.replace(/\/$/, "") + "/wp-json/aksrt/v1"; + } + + function nonceHeaders() { + var aksrt = window.aksrtData || window.aksrt || {}; + var n = aksrt.restNonce || aksrt.nonce || ""; + return n ? { "X-WP-Nonce": n } : {}; + } + + function api(path, options) { + var merged = Object.assign( + { + credentials: "same-origin", + headers: Object.assign({ "Content-Type": "application/json" }, nonceHeaders()), + }, + options || {} + ); + return fetch(restBase() + path, merged).then(function (r) { + return r + .json() + .catch(function () { + return { message: "Invalid JSON (HTTP " + r.status + ")" }; + }) + .then(function (j) { + return { ok: r.ok, status: r.status, json: j }; + }); + }); + } + + function ensureModalShell() { + var el = document.getElementById("modal_pay_uservip"); + if (el) return el; + el = document.createElement("div"); + el.id = "modal_pay_uservip"; + el.className = "aksrt-vip-modal-overlay"; + el.setAttribute("aria-hidden", "true"); + el.innerHTML = + ''; + document.body.appendChild(el); + el.addEventListener("click", function (e) { + if (e.target === el || e.target.closest(".aksrt-vip-modal-close")) { + closeModal(); + } + }); + document.addEventListener("keydown", function (e) { + if (e.key === "Escape") closeModal(); + }); + return el; + } + + function openModal() { + var el = ensureModalShell(); + el.classList.add("is-open"); + el.setAttribute("aria-hidden", "false"); + document.body.classList.add("aksrt-vip-modal-open"); + } + + function closeModal() { + var el = document.getElementById("modal_pay_uservip"); + if (!el) return; + el.classList.remove("is-open"); + el.setAttribute("aria-hidden", "true"); + document.body.classList.remove("aksrt-vip-modal-open"); + } + + function bindModalUi(root) { + if (!root || root.dataset.aksrtVipBound === "1") return; + root.dataset.aksrtVipBound = "1"; + + root.addEventListener("click", function (e) { + var tab = e.target.closest("[data-vip-tab]"); + if (tab) { + e.preventDefault(); + var id = tab.getAttribute("data-vip-tab"); + root.querySelectorAll(".aksrt-vip-tab").forEach(function (a) { + a.parentElement && a.parentElement.classList.remove("active"); + }); + tab.parentElement && tab.parentElement.classList.add("active"); + root.querySelectorAll(".aksrt-vip-pane").forEach(function (p) { + var on = p.id === id; + p.hidden = !on; + p.classList.toggle("active", on); + }); + return; + } + + var submit = e.target.closest("[data-vip-submit]"); + if (submit) { + e.preventDefault(); + purchaseFromPane(submit.closest(".aksrt-vip-pane") || root, submit); + } + }); + } + + function selectedMethod(root) { + var fixed = root.querySelector("[data-aksrt-vip-method-fixed]"); + if (fixed && fixed.value) return fixed.value; + var checked = root.querySelector('input[name="aksrt_vip_pay_method"]:checked'); + if (checked) return checked.value; + var first = root.querySelector('input[name="aksrt_vip_pay_method"]'); + return first ? first.value : "balance"; + } + + function selectedPackage(root) { + var checked = root.querySelector('input[name="vip_product_id"]:checked'); + return checked ? parseInt(checked.value, 10) : 0; + } + + function setTip(root, text, isError) { + var tip = root.querySelector("[data-aksrt-vip-tip]"); + if (!tip) return; + tip.textContent = text || ""; + tip.classList.toggle("is-error", !!isError); + } + + function pollOrder(orderNumber) { + if (!orderNumber) return; + var tries = 0; + var timer = setInterval(function () { + tries += 1; + api("/pay/content/order/" + encodeURIComponent(orderNumber)) + .then(function (r) { + if (r.ok && r.json && r.json.paid) { + clearInterval(timer); + window.location.reload(); + } + }) + .catch(function () {}); + if (tries >= 40) clearInterval(timer); + }, 3000); + } + + function purchaseFromPane(pane, btn) { + var packageId = selectedPackage(pane); + if (!packageId) { + setTip(pane, "请选择套餐", true); + return; + } + if (btn.disabled) return; + btn.disabled = true; + btn.classList.add("is-loading"); + setTip(pane, "正在创建订单…"); + + api("/pay/vip", { + method: "POST", + body: JSON.stringify({ + package_id: packageId, + payment_method: selectedMethod(pane), + }), + }) + .then(function (r) { + btn.disabled = false; + btn.classList.remove("is-loading"); + if (!r.ok || (r.json && r.json.code && !r.json.success && r.json.success !== true)) { + var msg = (r.json && r.json.message) || "开通失败"; + if (r.status === 401) { + msg = "请先登录"; + document.querySelector(".signin-loader") && document.querySelector(".signin-loader").click(); + } + setTip(pane, msg, true); + return; + } + var data = r.json && r.json.data ? r.json.data : r.json; + if (data && data.redirect && (data.payment_url || data.qr_content)) { + if (data.payment_url) window.open(data.payment_url, "_blank"); + setTip(pane, data.message || "请完成支付,支付成功后将自动刷新。"); + if (data.order_number) pollOrder(data.order_number); + return; + } + setTip(pane, (data && data.message) || "开通成功,正在刷新…"); + setTimeout(function () { + window.location.reload(); + }, 400); + }) + .catch(function () { + btn.disabled = false; + btn.classList.remove("is-loading"); + setTip(pane, "网络错误,请重试", true); + }); + } + + var loading = false; + + function vipPay(trigger) { + var level = 1; + if (trigger) { + level = parseInt(trigger.getAttribute("vip-level") || trigger.getAttribute("data-vip-level") || "1", 10) || 1; + } + if (loading) return; + loading = true; + var shell = ensureModalShell(); + var body = shell.querySelector("[data-aksrt-vip-modal-body]"); + if (body) body.innerHTML = '
加载中…
'; + openModal(); + + api("/pay/vip/modal?vip_level=" + encodeURIComponent(String(level))) + .then(function (r) { + loading = false; + if (r.status === 401) { + closeModal(); + var login = document.querySelector(".signin-loader"); + if (login) login.click(); + return; + } + if (!r.ok || !r.json || !r.json.html) { + if (body) { + body.innerHTML = + '
' + + ((r.json && r.json.message) || "加载失败") + + "
"; + } + return; + } + if (body) { + body.innerHTML = r.json.html; + bindModalUi(body); + } + }) + .catch(function () { + loading = false; + if (body) { + body.innerHTML = '
网络错误
'; + } + }); + } + + function onDocClick(e) { + var t = e.target.closest(".pay-vip"); + if (!t) return; + // Allow real links without intercept if they point elsewhere and lack pay-vip intent. + e.preventDefault(); + e.stopPropagation(); + vipPay(t); + } + + function init() { + document.addEventListener("click", onDocClick, true); + } + + if (window.AksrtFrontend && typeof window.AksrtFrontend.register === "function") { + window.AksrtFrontend.register("aksrt-pay-vip", init); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } + + window.aksrtVipPay = vipPay; +})(); diff --git a/inc/helpers/aksrt-header-menu-buttons.php b/inc/helpers/aksrt-header-menu-buttons.php index 4b4a907c..67d50542 100644 --- a/inc/helpers/aksrt-header-menu-buttons.php +++ b/inc/helpers/aksrt-header-menu-buttons.php @@ -175,12 +175,21 @@ if (!function_exists('aksrt_render_header_menu_buttons')) : } if (!$is_vip) { $cls = $user_id > 0 ? 'pay-vip' : 'signin-loader'; + $hover = ''; + if (class_exists('\AKSRT\Content\VipService')) { + $svc = \AKSRT\Content\VipService::get_instance(); + if (method_exists($svc, 'render_vip_card')) { + $cards = $svc->render_vip_card(1, $user_id) . $svc->render_vip_card(2, $user_id); + if ($cards !== '') { + $hover = ''; + } + } + } $html .= ''; - } - } + $html .= '' . $hover . ''; + } } return $html; } diff --git a/inc/helpers/aksrt-vip.php b/inc/helpers/aksrt-vip.php index 61870490..ffa105d2 100644 --- a/inc/helpers/aksrt-vip.php +++ b/inc/helpers/aksrt-vip.php @@ -161,12 +161,16 @@ function aksrt_is_vip2(int $user_id = 0): bool { * * @return string */ -function aksrt_get_vip_badge_text(): string { +function aksrt_get_vip_badge_text(int $user_id = 0): string { if (!class_exists('\AKSRT\Settings\Settings')) { return 'VIP'; } $vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings(); + $level = $user_id > 0 ? aksrt_get_user_vip_level($user_id) : 0; + if ($level >= 1 && !empty($vip_settings['levels'][$level]['name'])) { + return sanitize_text_field((string) $vip_settings['levels'][$level]['name']); + } return sanitize_text_field($vip_settings['badge_text'] ?? 'VIP'); } @@ -344,10 +348,12 @@ function aksrt_render_vip_badge(int $user_id = 0, string $size = 'sm'): string { return ''; } - $badge_text = aksrt_get_vip_badge_text(); + $level = aksrt_get_user_vip_level($user_id); + $badge_text = aksrt_get_vip_badge_text($user_id); $size_class = 'aksrt-vip-badge--' . $size; + $level_class = $level >= 2 ? 'aksrt-vip-badge--vip2' : 'aksrt-vip-badge--vip1'; - return ' ' . esc_html($badge_text) . ''; + return ' ' . esc_html($badge_text) . ''; } /** @@ -359,7 +365,7 @@ function aksrt_render_vip_badge(int $user_id = 0, string $size = 'sm'): string { function aksrt_get_vip_info(int $user_id = 0): array { $level = aksrt_get_user_vip_level($user_id); $is_vip = $level >= 1; - $badge_text = aksrt_get_vip_badge_text(); + $badge_text = aksrt_get_vip_badge_text($user_id); $expires = $user_id > 0 ? (int) get_user_meta($user_id, 'aksrt_vip_expires', true) : 0; $expires_label = __('未开通', 'aksrt'); if ($level > 0) { diff --git a/src/AKSRT/Content/VipService.php b/src/AKSRT/Content/VipService.php index 031f0a7f..028fe826 100644 --- a/src/AKSRT/Content/VipService.php +++ b/src/AKSRT/Content/VipService.php @@ -11,6 +11,7 @@ class VipService { use VipServiceRest; use VipServicePurchase; use VipServicePackages; + use VipServiceModal; private static $instance = null; diff --git a/src/AKSRT/Content/VipServiceModal.php b/src/AKSRT/Content/VipServiceModal.php new file mode 100644 index 00000000..ae3775f1 --- /dev/null +++ b/src/AKSRT/Content/VipServiceModal.php @@ -0,0 +1,312 @@ +' + . esc_html__('请先登录后再开通会员。', 'aksrt') + . ''; + } + + $vip = class_exists('\AKSRT\Settings\Settings') + ? \AKSRT\Settings\Settings::get_payment_vip_settings() + : ['enabled' => false]; + if (empty($vip['enabled'])) { + return '
' + . esc_html__('会员功能未启用。', 'aksrt') + . '
'; + } + + $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 = '
'; + $html .= '
'; + $html .= '
'; + $html .= '
' . esc_html($name) . '
'; + if ($desc !== '') { + $html .= '
' . esc_html($desc) . '
'; + } + if ($equity !== '') { + $html .= ''; + } + $html .= '' + . esc_html(sprintf(__('开通%s', 'aksrt'), $name)) + . ''; + $html .= '
'; + return $html; + } + + /** + * @param array $catalog + * @param array $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 .= '
  • ' + . '' + . esc_html($lv_name) + . '
  • '; + + $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 .= '
    '; + $panes .= '
    '; + $panes .= '
    '; + $panes .= '
    ' . esc_html($lv_name) . '
    '; + if ($lv_desc !== '') { + $panes .= '
    ' . esc_html($lv_desc) . '
    '; + } + if ($equity !== '') { + $panes .= '
      ' . $equity . '
    '; + } + $panes .= '
    '; + $panes .= $products; + $panes .= $this->render_pay_methods($methods); + $panes .= ''; + $panes .= '

    '; + $panes .= '
    '; + } + + $points = ''; + if (!empty($catalog['points_exchange']['enabled']) && !empty($catalog['points_exchange']['packages'])) { + $points = ''; + } + + $html = '
    '; + $html .= ''; + $html .= '
    • ' . $avatar . '
    • '; + $html .= '
    • ' . esc_html($name) . ''; + if ($desc !== '') { + $html .= '
      ' . esc_html($desc) . '
      '; + } + $html .= '
    '; + $html .= ''; + $html .= '
    ' . $panes . '
    '; + if ($more !== '') { + $html .= '
    ' . $more . '
    '; + } + $html .= $points; + $html .= '
    '; + return $html; + } + + /** + * @param array $catalog + * @param array $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 .= '
  • ' + . '' + . esc_html($badge . $lv_name) + . '
  • '; + $products = $this->render_product_radios($items); + $equity = (string) ($levels[$lv]['equity'] ?? ''); + $panes .= '
    '; + $panes .= '
    '; + $panes .= '
    '; + $panes .= '
    ' . esc_html($lv_name) . '
    '; + if ($equity !== '') { + $panes .= '
      ' . $equity . '
    '; + } + $panes .= '
    '; + $panes .= $products; + $panes .= $this->render_pay_methods($methods); + $cta = $action === 'upgrade' ? __('立即升级', 'aksrt') : __('立即续费', 'aksrt'); + $panes .= ''; + $panes .= '

    '; + $panes .= '
    '; + } + + if ($tabs === '') { + return '
    ' + . esc_html__('当前没有可续费或升级的套餐。', 'aksrt') + . '
    '; + } + + $html = '
    '; + $html .= ''; + $html .= '
    • ' . $avatar . '
    • '; + $html .= '
    • ' . esc_html($name) . ''; + $html .= '
      ' + . esc_html($exp_label) . '
    '; + $html .= ''; + $html .= '
    ' . $panes . '
    '; + if ($more !== '') { + $html .= '
    ' . $more . '
    '; + } + $html .= '
    '; + return $html; + } + + /** + * @param array> $items + */ + private function render_product_radios(array $items): string { + if ($items === []) { + return '
    ' + . esc_html__('暂无可用套餐', 'aksrt') . '
    '; + } + $html = '
    '; + $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 .= ''; + } + $html .= '
    '; + return $html; + } + + /** + * @param array $methods + */ + private function render_pay_methods(array $methods): string { + if ($methods === []) { + return ''; + } + if (count($methods) === 1) { + $id = (string) ($methods[0]['id'] ?? 'balance'); + return ''; + } + $html = '
    '; + $html .= '' . esc_html__('支付方式', 'aksrt') . ''; + foreach ($methods as $i => $m) { + $mid = (string) ($m['id'] ?? ''); + $mname = (string) ($m['name'] ?? $mid); + $html .= ''; + } + $html .= '
    '; + return $html; + } +} diff --git a/src/AKSRT/Content/VipServiceRest.php b/src/AKSRT/Content/VipServiceRest.php index 091315d3..3997580d 100644 --- a/src/AKSRT/Content/VipServiceRest.php +++ b/src/AKSRT/Content/VipServiceRest.php @@ -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); } diff --git a/src/AKSRT/Core/ScriptsPart1b.php b/src/AKSRT/Core/ScriptsPart1b.php index 8deaf0cf..953db435 100644 --- a/src/AKSRT/Core/ScriptsPart1b.php +++ b/src/AKSRT/Core/ScriptsPart1b.php @@ -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()) {