Files
wordpress-theme-aksrt/assets/js/pay-vip.js
T
kate522 5fd7c212ed fix(runtime): 按参考主题硬切换为 loader/main/auto_fun 单一 DOM 生命周期
- 删除 AksrtFrontend registry/rehydrate 双路架构及 build/public.js 拼包入口,
  前台唯一初始路径为 PHP 本地栈(jQuery 1.9.1 → Bootstrap 3.4.1)
  → loader_js(_win/aksrtData 唯一注入)→ tbquire(['main']) → runtime/main.min.js
- window.auto_fun = debounce(z_auto_fun, 100) 为唯一重扫入口;所有模块改为
  执行一次 + off/on('auto_fun.aksrt-*') 命名空间重扫,data/class 标记幂等
- 局部更新协议统一:HTML 注入 → post_ajax.ed → auto_fun();
  远程 modal → loaded.bs.modal → auto_fun();JSON 成功 → zib_ajax.success
- 灯箱恢复 Miniimgbox/imgbox 唯一 owner(imgbox-container、a[data-imgbox]、
  imgbox_data),删除 AksrtLightbox/data-aksrt-lightbox 第二实现及 CSS/overlay,
  _win.imgbox* 重新接上 post-display 设置
- 死代码清理:runtime 下 11 组被自有实现替代的旧模块副本、无消费 vendored 库、
  顶层 grade.min.js、revenue.js、无调用方的 aksrt_get_win_runtime_config(),
  以及 loader 中 16 条死 mapping
- PHP 入队改为本地栈 + loader_js,routed/widget/submit 依赖统一换绑;
  用户卡片发布按钮恢复 nav_user_newposts_btn 设置门控
- 测试对齐:新增 frontend-lifecycle-contract,重写 navigation/lazyload/toast/
  auth-source/comment/page-display/post-visual 等断言为目标架构契约

验证:node --test widget-system+navigation 223 通过 0 失败;
构建脚本重建 main/imgbox min 与 CSS bundles;改动 PHP php -l、JS node --check
全部通过;git diff --check 干净;credits 重入 harness 通过。
2026-09-03 12:44:31 +08:00

572 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* AKSRT VIP pay modal — JSON view model + canonical purchase coordinator.
*
* @package AKSRT
*/
(function () {
"use strict";
function text(value) {
return String(value == null ? "" : value);
}
function node(tag, className, value) {
var el = document.createElement(tag);
if (className) el.className = className;
if (value !== undefined && value !== null) el.textContent = text(value);
return el;
}
function lazyImage(key, src, className, size) {
var template = document.createElement("template");
template.innerHTML = "<img" + window.aksrtGetLazyImageAttr(key, src, className, size) + ' alt="" decoding="async">';
return template.content.firstElementChild;
}
function append(parent, child) {
if (child) parent.appendChild(child);
return child;
}
function levelMap(levels) {
var map = Object.create(null);
(Array.isArray(levels) ? levels : []).forEach(function (level) {
if (level && level.level) map[String(level.level)] = level;
});
return map;
}
function actionLabel(action) {
if (action === "renew") return "续费";
if (action === "upgrade") return "升级";
return "开通";
}
function actionKey(action) {
return action === "pay" ? "purchase" : action;
}
function actionReason(reason) {
if (reason === "no_payment_method") return "暂无可用支付方式";
if (reason === "no_package") return "暂无可用套餐";
if (reason === "not_available") return "当前不可用";
return "";
}
function renderEquity(parent, value) {
if (!value) return;
var equity = node("div");
// The server sanitizes and wraps this admin-authored fragment before JSON.
equity.innerHTML = text(value);
while (equity.firstChild) parent.appendChild(equity.firstChild);
}
function renderProducts(parent, items) {
if (!Array.isArray(items) || !items.length) {
append(parent, node("div", "muted-2-color text-center padding-10", "暂无可用套餐"));
return;
}
var wrap = node("div", "mb10 label-box charge-box aksrt-vip-products");
items.forEach(function (item, index) {
if (!item || !item.package_id) return;
var label = node("label", "aksrt-vip-product-label");
var input = node("input");
input.className = "hide vip-product-input";
input.type = "radio";
input.name = "vip_product_id_" + String(item.level || "group");
input.setAttribute("data-aksrt-vip-package", "");
input.value = String(item.package_id);
input.checked = index === 0;
label.appendChild(input);
var product = node("div", "aksrt-panel vip-product relative text-center product-box");
if (item.discount_tag) append(product, node("div", "abs-right vip-tag badg jb-yellow", item.discount_tag));
if (item.title) append(product, node("div", "em09 mb3", item.title));
var priceRow = node("div", "flex abl gap6 jc");
append(priceRow, node("div", "product-price c-red", item.price_label || ""));
if (item.show_price_label) {
var original = node("div", "original-price relative muted-2-color");
var strike = node("s", "", item.show_price_label);
append(original, strike);
append(priceRow, original);
}
append(product, priceRow);
if (item.days_label) append(product, node("div", "muted-2-color c-blue em09 p2-10 badg radius", item.days_label));
append(label, product);
append(wrap, label);
});
append(parent, wrap);
}
function renderMethods(parent, methods) {
methods = Array.isArray(methods) ? methods : [];
if (!methods.length) {
append(parent, node("div", "muted-2-color text-center padding-10", "暂无可用支付方式"));
return;
}
if (methods.length === 1) {
var one = node("input");
one.type = "hidden";
one.name = "aksrt_vip_pay_method";
one.value = text(methods[0].id || "balance");
one.setAttribute("data-aksrt-vip-method-fixed", "");
append(parent, one);
return;
}
var wrap = node("div", "uc-vip-shop__methods mb10");
wrap.setAttribute("data-aksrt-vip-methods", "");
append(wrap, node("span", "uc-vip-shop__methods-label", "支付方式"));
methods.forEach(function (method, index) {
var label = node("label", "uc-vip-shop__method");
var input = node("input");
input.type = "radio";
input.name = "aksrt_vip_pay_method";
input.value = text(method.id || "balance");
input.checked = index === 0;
append(label, input);
append(label, node("span", "", method.name || method.id || ""));
append(wrap, label);
});
append(parent, wrap);
}
function renderPointsModal(body, view) {
view = view || {};
if (body.replaceChildren) body.replaceChildren();
else body.innerHTML = "";
var catalog = view.catalog || {};
var exchange = catalog.points_exchange || {};
var member = view.member || {};
var packages = Array.isArray(exchange.packages) ? exchange.packages : [];
var currency = text(catalog.currency_name || "积分");
var modal = node("div", "box-body payvip-modal aksrt-vip-points-modal");
modal.setAttribute("data-aksrt-vip-points-modal-body", "");
var close = node("button", "aksrt-modal-close", "×");
close.type = "button";
close.setAttribute("data-aksrt-modal-close", "");
close.setAttribute("aria-label", "关闭");
append(modal, close);
append(modal, node("h2", "aksrt-vip-points-modal__title", "积分兑换会员"));
append(modal, node("p", "aksrt-vip-points-modal__desc", exchange.desc || "使用积分兑换会员权益。"));
var balance = node("div", "aksrt-vip-points-modal__balance");
append(balance, node("span", "aksrt-vip-points-modal__balance-label", "我的积分"));
append(balance, node("strong", "aksrt-vip-points-modal__balance-value", text(member.points || 0) + " " + currency));
append(modal, balance);
var list = node("div", "aksrt-vip-points-modal__packages");
if (!view.enabled || view.allowed_actions && view.allowed_actions.points_exchange === false || !exchange.enabled || !packages.length) {
append(list, node("div", "aksrt-vip-points-modal__empty", "暂无可兑换的会员"));
} else {
packages.forEach(function (item, index) {
if (!item || !item.package_id) return;
var cost = Number(item.points || 0);
var enough = Number(member.points || 0) >= cost;
var label = node("label", "aksrt-vip-points-option" + (enough ? "" : " is-disabled"));
var input = node("input");
input.type = "radio";
input.name = "aksrt_vip_points_package";
input.value = String(item.package_id);
input.checked = index === 0 && enough;
input.disabled = !enough;
input.setAttribute("data-aksrt-vip-points-package", "");
append(label, input);
var card = node("span", "aksrt-vip-points-option__card");
append(card, node("strong", "aksrt-vip-points-option__title", item.title || ("VIP" + text(item.level || ""))));
append(card, node("span", "aksrt-vip-points-option__days", item.days_label || "会员权益"));
var costLabel = enough ? text(item.price_label || (cost + " " + currency)) : "积分不足";
append(card, node("span", "aksrt-vip-points-option__cost", costLabel));
append(label, card);
append(list, label);
});
}
append(modal, list);
var submit = node("button", "aksrt-vip-points-submit", "立即兑换");
submit.type = "button";
submit.setAttribute("data-aksrt-vip-points-submit", "");
submit.disabled = !view.enabled || !exchange.enabled || (view.allowed_actions && view.allowed_actions.points_exchange === false) || !packages.some(function (item) { return item && Number(member.points || 0) >= Number(item.points || 0); });
append(modal, submit);
if (exchange.desc) append(modal, node("div", "aksrt-vip-points-modal__note", exchange.desc));
append(body, modal);
}
function pointsPackage(root) {
var checked = root.querySelector('[data-aksrt-vip-points-package]:checked');
return checked ? parseInt(checked.value, 10) : 0;
}
function exchangeFromModal(root, button) {
var packageId = pointsPackage(root);
if (!button || button.disabled) return;
if (!packageId) {
notyf("请选择兑换套餐", "danger");
return;
}
var coordinator = window.AksrtVipPurchaseCoordinator;
if (!coordinator || typeof coordinator.exchange !== "function") {
notyf("会员兑换暂不可用", "danger");
return;
}
button.disabled = true;
button.classList.add("is-loading");
notyf("正在兑换会员…", "load", "", "aksrt-vip-points-toast");
coordinator.exchange({ packageId: packageId, tags: ["vip", "orders"] }).then(function (response) {
button.classList.remove("is-loading");
if (response && response.ok) {
var data = response.data || response.json || {};
notyf(data.message || "兑换成功,正在刷新…", "success", 5, "aksrt-vip-points-toast");
window.setTimeout(function () { window.location.reload(); }, 800);
return;
}
button.disabled = false;
notyf(response && response.json && response.json.message ? response.json.message : "兑换失败,请重试。", "danger", 5, "aksrt-vip-points-toast");
}).catch(function () {
button.disabled = false;
button.classList.remove("is-loading");
notyf("网络异常,请稍后重试。", "danger", 5, "aksrt-vip-points-toast");
});
}
function renderModalView(body, view) {
view = view || {};
if (body.replaceChildren) body.replaceChildren();
else body.innerHTML = "";
if (!view.enabled) {
append(body, node("div", "box-body payvip-modal text-center padding-20", "会员功能未启用。"));
return;
}
var catalog = view.catalog || {};
var member = view.member || {};
var levels = levelMap(view.levels);
var groups = Array.isArray(catalog.level_packages) ? catalog.level_packages : [];
var methods = Array.isArray(view.payment_methods) ? view.payment_methods : [];
var allowed = view.allowed_actions || {};
var reasons = view.action_reasons || {};
var modal = node("div", "box-body payvip-modal");
modal.setAttribute("data-aksrt-vip-modal", "");
if (view.version && view.version.catalog) modal.setAttribute("data-aksrt-vip-version", view.version.catalog);
var close = node("button", "aksrt-modal-close", "×");
close.type = "button";
close.setAttribute("data-aksrt-modal-close", "");
close.setAttribute("aria-label", "关闭");
append(modal, close);
var userBox = node("ul", "list-inline user-box flex ac mb10");
var avatarItem = node("li", "avatar-img");
if (member.avatar_url) {
var avatar = node("img");
avatar.src = text(member.avatar_url);
avatar.alt = "";
avatar.width = 42;
avatar.height = 42;
append(avatarItem, avatar);
}
append(userBox, avatarItem);
var userInfo = node("li");
append(userInfo, node("b", "em12", member.name || ""));
if (member.level > 0) {
var expiry = node("div", "muted-2-color em09");
var badge = node("span", "badg c-yellow vip-expdate-tag", member.expires_label || "");
append(expiry, badge);
append(userInfo, expiry);
} else if (member.state === "expired") {
append(userInfo, node("div", "muted-2-color em09", "已过期"));
} else if (catalog.desc) {
append(userInfo, node("div", "muted-2-color em09", catalog.desc));
}
append(userBox, userInfo);
append(modal, userBox);
var tabs = node("ul", "flex jsb mt10 mb20 vip-cardminis gutters-5 nav");
var content = node("div", "tab-content mt10");
var activeIndex = -1;
groups.forEach(function (group, index) {
if (activeIndex >= 0 || !group) return;
if (Number(group.level) === Number(view.prefer_level)) activeIndex = index;
});
if (activeIndex < 0 && groups.length) activeIndex = 0;
groups.forEach(function (group, index) {
if (!group || !Array.isArray(group.items) || !group.items.length) return;
var level = Number(group.level || 0);
var action = text(group.action || "pay");
var tabId = "tab-payvip-" + level + "-" + action;
var active = index === activeIndex;
var tabItem = node("li", "relative" + (active ? " active" : ""));
var tab = node("a", "aksrt-vip-tab", actionLabel(action) + (group.name || "VIP" + level));
tab.href = "javascript:;";
tab.setAttribute("data-vip-tab", tabId);
append(tabItem, tab);
append(tabs, tabItem);
var pane = node("div", "tab-pane aksrt-vip-pane" + (active ? " active" : ""));
pane.id = tabId;
pane.hidden = !active;
var row = node("div", "pay-vip-form-row");
var info = node("div", "pay-vip-info level-" + level);
var levelInfo = levels[String(level)] || {};
var levelHeader = node("div", "aksrt-vip-level-header");
if (levelInfo.img_icon) {
var levelIcon = lazyImage("lazy_other", text(levelInfo.img_icon), "aksrt-vip-level-icon");
append(levelHeader, levelIcon);
}
append(levelHeader, node("div", "v-name font-bold", group.name || levelInfo.name || "VIP" + level));
append(info, levelHeader);
if (levelInfo.desc) append(info, node("div", "muted-2-color em09 mb6", levelInfo.desc));
renderEquity(info, levelInfo.equity);
append(row, info);
var products = node("div", "pay-vip-products");
renderProducts(products, group.items);
renderMethods(products, methods);
var actionName = actionKey(action);
var actionAllowed = allowed[actionName] === true;
var reason = actionReason(reasons[actionName]);
var canPurchase = actionAllowed && methods.length > 0;
var submit = node("button", "but jb-red btn-block padding-lg radius aksrt-vip-submit", canPurchase ? (group.items[0].cta_label || actionLabel(action)) : (reason || "暂无可用支付方式"));
submit.type = "button";
submit.disabled = !canPurchase;
submit.setAttribute("data-vip-submit", "");
append(products, submit);
append(row, products);
append(pane, row);
append(content, pane);
});
append(modal, tabs);
append(modal, content);
if (catalog.help_text) append(modal, node("div", "muted-2-color em09 mb6", catalog.help_text));
if (catalog.more) append(modal, node("div", "muted-2-color mb10 em09 muted-box padding-10 mini-radius", catalog.more));
var points = catalog.points_exchange || {};
if (allowed.points_exchange !== false && points.enabled && Array.isArray(points.packages) && points.packages.length && catalog.vip_url) {
var pointsWrap = node("div", "mt10");
var pointsLink = node("a", "but c-green btn-block", "积分兑换会员");
pointsLink.href = text(catalog.vip_url);
append(pointsWrap, pointsLink);
append(modal, pointsWrap);
}
append(body, modal);
}
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('[data-aksrt-vip-package]:checked');
return checked ? parseInt(checked.value, 10) : 0;
}
var activePurchase = null;
var activePurchaseToastId = "";
function restoreButton(button) {
if (!button) return;
button.disabled = false;
button.classList.remove("is-loading");
}
function purchaseFromPane(pane, button) {
var packageId = selectedPackage(pane);
if (!packageId) {
notyf("请选择套餐", "danger");
return;
}
if (!button || button.disabled) return;
var coordinator = window.AksrtVipPurchaseCoordinator;
if (!coordinator || typeof coordinator.start !== "function") {
notyf("会员购买暂不可用", "danger");
return;
}
var waitingPayment = false;
button.disabled = true;
button.classList.add("is-loading");
activePurchaseToastId = "aksrt-vip-purchase-toast";
notyf("正在创建会员订单…", "load", "", activePurchaseToastId);
activePurchase = coordinator.start({
packageId: packageId,
paymentMethod: selectedMethod(pane),
tags: ["vip", "orders"],
onPending: function (data) {
waitingPayment = true;
if (data.payment_url) window.open(data.payment_url, "_blank");
notyf(data.message || "请完成支付,支付成功后将自动刷新。", "info", 5, activePurchaseToastId);
activePurchaseToastId = "aksrt-vip-payment-toast";
notyf("等待支付结果…", "load", "", activePurchaseToastId);
},
onSuccess: function (result) {
activePurchase = null;
restoreButton(button);
var toastId = waitingPayment ? "aksrt-vip-payment-toast" : "aksrt-vip-purchase-toast";
notyf((result.data && result.data.message) || "开通成功,正在刷新…", "success", 5, toastId);
activePurchaseToastId = "";
window.setTimeout(function () { window.location.reload(); }, 800);
},
onFailure: function (result) {
activePurchase = null;
restoreButton(button);
notyf(result.data && result.data.message ? result.data.message : "支付未完成,请重试。", "danger", 5, "aksrt-vip-payment-toast");
activePurchaseToastId = "";
},
onTimeout: function () {
activePurchase = null;
restoreButton(button);
notyf("支付状态暂未确认,请稍后在订单中心查看。", "danger", 5, "aksrt-vip-payment-toast");
activePurchaseToastId = "";
},
onError: function (response) {
activePurchase = null;
restoreButton(button);
var message = response && response.json && response.json.message ? response.json.message : "开通失败";
if (response && response.status === 401) {
message = "请先登录";
var login = document.querySelector(".signin-loader");
if (login) login.click();
}
notyf(message, "danger", 5, activePurchaseToastId || "aksrt-vip-purchase-toast");
activePurchaseToastId = "";
},
});
}
function bindModalUi(root) {
if (!root || root.dataset.aksrtVipBound === "1") return;
root.dataset.aksrtVipBound = "1";
root.addEventListener("click", function (event) {
var tab = event.target.closest("[data-vip-tab]");
if (tab) {
event.preventDefault();
var id = tab.getAttribute("data-vip-tab");
root.querySelectorAll(".aksrt-vip-tab").forEach(function (item) {
if (item.parentElement) item.parentElement.classList.toggle("active", item === tab);
});
root.querySelectorAll(".aksrt-vip-pane").forEach(function (pane) {
var active = pane.id === id;
pane.hidden = !active;
pane.classList.toggle("active", active);
});
return;
}
var pointsSubmit = event.target.closest("[data-aksrt-vip-points-submit]");
if (pointsSubmit) {
event.preventDefault();
exchangeFromModal(pointsSubmit.closest("[data-aksrt-vip-points-modal-body]") || root, pointsSubmit);
return;
}
var submit = event.target.closest("[data-vip-submit]");
if (submit) {
event.preventDefault();
purchaseFromPane(submit.closest(".aksrt-vip-pane") || root, submit);
}
});
}
function vipPointsPay() {
window.AksrtModalRuntime.openRemote({
url: "/pay/vip/modal",
query: { vip_level: 1, mode: "points" },
className: "aksrt-vip-modal-shell aksrt-vip-points-modal-shell",
mobileBottom: false,
skeleton: '<div class="aksrt-vip-modal-panel"><div class="aksrt-vip-modal-content" data-aksrt-vip-modal-body><div class="text-center padding-20 muted-2-color">加载中…</div></div></div>',
telemetry: "vip-points",
kind: "vip",
responseType: "json",
cacheTtl: 15000,
cacheTags: ["vip"],
renderData: function (data, session) {
renderPointsModal(session.content, data);
bindModalUi(session.content);
},
onError: function (error) {
var unauthorized = error && error.code === "unauthorized";
notyf(unauthorized ? "请先登录" : ((error && error.message) || "会员兑换信息加载失败,请重试"), "danger");
if (unauthorized) {
window.AksrtModalRuntime.close("unauthorized");
var login = document.querySelector(".signin-loader");
if (login) login.click();
}
},
});
}
function vipPay(trigger) {
var level = trigger
? parseInt(trigger.getAttribute("vip-level") || trigger.getAttribute("data-vip-level") || "1", 10) || 1
: 1;
window.AksrtModalRuntime.openRemote({
url: "/pay/vip/modal",
query: { vip_level: level },
className: "aksrt-vip-modal-shell",
skeleton: '<div class="aksrt-vip-modal-panel"><div class="aksrt-vip-modal-content" data-aksrt-vip-modal-body><div class="text-center padding-20 muted-2-color">加载中…</div></div></div>',
telemetry: "vip",
kind: "vip",
responseType: "json",
cacheTtl: 15000,
cacheTags: ["vip"],
renderData: function (data, session) {
renderModalView(session.content, data);
bindModalUi(session.content);
},
onError: function (error) {
var unauthorized = error && error.code === "unauthorized";
notyf(unauthorized ? "请先登录" : ((error && error.message) || "会员套餐加载失败,请重试"), "danger");
if (unauthorized) {
window.AksrtModalRuntime.close("unauthorized");
var login = document.querySelector(".signin-loader");
if (login) login.click();
}
},
});
}
function onDocClick(event) {
var pointsTrigger = event.target.closest("[data-aksrt-vip-points-modal], [data-aksrt-vip-points-open]");
if (pointsTrigger) {
event.preventDefault();
event.stopPropagation();
vipPointsPay();
return;
}
var trigger = event.target.closest(".pay-vip, a.pay-vip, .float-btn.pay-vip, [data-aksrt-pay-vip]");
if (!trigger) return;
var href = (trigger.getAttribute("href") || "").trim();
if (href && href !== "#" && href.indexOf("javascript:") !== 0 && !trigger.classList.contains("pay-vip") && !trigger.hasAttribute("data-aksrt-pay-vip") && !trigger.hasAttribute("vip-level") && !trigger.hasAttribute("data-vip-level")) {
return;
}
event.preventDefault();
event.stopPropagation();
vipPay(trigger);
}
document.addEventListener("aksrt:modal:close", function (event) {
var detail = event && event.detail ? event.detail : {};
var options = detail.session && detail.session.options ? detail.session.options : {};
if (options.kind !== "vip" || !activePurchase) return;
if (typeof activePurchase.abort === "function") activePurchase.abort();
activePurchase = null;
if (activePurchaseToastId) {
notyf("已取消会员购买", "warning", 3, activePurchaseToastId);
activePurchaseToastId = "";
}
});
function bindOnce() {
if (window.__aksrtPayVipBound) return;
window.__aksrtPayVipBound = true;
document.addEventListener("click", onDocClick, true);
}
jQuery(document).off("auto_fun.aksrt-pay-vip").on("auto_fun.aksrt-pay-vip", bindOnce);
})();