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

+257
View File
@@ -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;
}
+266
View File
@@ -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 =
'<div class="aksrt-vip-modal-dialog" role="dialog" aria-modal="true">' +
'<div class="aksrt-vip-modal-content" data-aksrt-vip-modal-body></div></div>';
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 = '<div class="text-center padding-20 muted-2-color">加载中…</div>';
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 =
'<div class="text-center padding-20 c-red">' +
((r.json && r.json.message) || "加载失败") +
"</div>";
}
return;
}
if (body) {
body.innerHTML = r.json.html;
bindModalUi(body);
}
})
.catch(function () {
loading = false;
if (body) {
body.innerHTML = '<div class="text-center padding-20 c-red">网络错误</div>';
}
});
}
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;
})();