perf(modal): unify cancellable frontend modal flows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 02:04:55 +08:00
co-authored by Claude Fable 5
parent 25ce1d8f43
commit 5375ad6429
36 changed files with 2623 additions and 1806 deletions
+81 -105
View File
@@ -14,49 +14,23 @@
if (!aksrt) {
return;
}
// Prefer restUrl/apiUrl from wp_localize (handles plain permalinks:
// /?rest_route=/aksrt/v1/...). Never hardcode /wp-json/ under siteUrl.
var REST = '';
if (aksrt.restUrl) {
REST = String(aksrt.restUrl).replace(/\/?$/, '');
} else if (aksrt.apiUrl) {
REST = String(aksrt.apiUrl).replace(/\/?$/, '');
} else if (aksrt.siteUrl) {
var siteUrl = String(aksrt.siteUrl);
if (window.location.protocol === 'https:' && siteUrl.substring(0, 5) === 'http:') {
siteUrl = 'https:' + siteUrl.substring(5);
}
REST = siteUrl.replace(/\/$/, '') + '/wp-json/aksrt/v1';
} else {
return;
}
// Force HTTPS if page is HTTPS (mixed content).
if (window.location.protocol === 'https:' && REST.substring(0, 5) === 'http:') {
REST = 'https:' + REST.substring(5);
}
var NONCE_HEADER = aksrt.restNonce ? { 'X-WP-Nonce': aksrt.restNonce } : (aksrt.nonce ? { 'X-WP-Nonce': aksrt.nonce } : {});
var activePoll = null;
function api(path, options) {
var merged = Object.assign({
credentials: 'same-origin',
headers: Object.assign({ 'Content-Type': 'application/json' }, NONCE_HEADER),
}, options || {});
return fetch(REST + path, merged).then(function (r) {
// 401 时去掉 nonce 重试(页面缓存可能导致 nonce 过期,GET 请求可用 cookie 认证)
if (r.status === 401 && (!options || !options.method || options.method === 'GET')) {
var retry = Object.assign({}, merged, {
headers: { 'Content-Type': 'application/json' }
});
return fetch(REST + path, retry).then(function (r2) {
return r2.json().catch(function () {
return { message: '服务器返回了非 JSON 响应 (HTTP ' + r2.status + ')' };
}).then(function (j) { return { ok: r2.ok, status: r2.status, json: j }; });
});
}
return r.json().catch(function () {
return { message: '服务器返回了非 JSON 响应 (HTTP ' + r.status + ')' };
}).then(function (j) { return { ok: r.ok, status: r.status, json: j }; });
return window.AksrtRequest.request(path, options || {}).then(function (response) {
return {
ok: response.ok,
status: response.status,
json: response.json || {},
data: response.data,
};
}).catch(function (error) {
var response = error && error.response ? error.response : {};
return {
ok: false,
status: error && error.status ? error.status : (response.status || 0),
json: error && error.json ? error.json : (response.json || { message: error && error.message }),
};
});
}
@@ -411,12 +385,13 @@
// ===== VIP 积分兑换 =====
// ===== VIP 积分兑换 =====
function initVipPointsExchange() {
var root = document.querySelector('[data-aksrt-vip-points]');
if (!root) return;
if (!root || !window.AksrtModalRuntime.isRolloutEnabled('vip')) return;
var tip = root.querySelector('[data-aksrt-vip-points-tip]');
var busy = false;
var coordinator = window.AksrtVipPurchaseCoordinator;
function setTip(text, isError) {
if (!tip) return;
tip.textContent = text || '';
@@ -431,9 +406,9 @@
busy = true;
btn.classList.add('is-loading');
setTip('正在兑换…');
api('/pay/vip/points-exchange', {
method: 'POST',
body: JSON.stringify({ package_id: packageId })
coordinator.exchange({
packageId: packageId,
tags: ['vip', 'orders']
}).then(function (r) {
busy = false;
btn.classList.remove('is-loading');
@@ -455,9 +430,10 @@
// ===== VIP 开通 =====
function initVipShop() {
var root = document.querySelector('[data-aksrt-vip-shop]');
if (!root) return;
if (!root || !window.AksrtModalRuntime.isRolloutEnabled('vip')) return;
var tip = root.querySelector('[data-aksrt-vip-tip]');
var busy = false;
var coordinator = window.AksrtVipPurchaseCoordinator;
function setTip(text, isError) {
if (!tip) return;
@@ -474,23 +450,6 @@
return first ? first.value : 'balance';
}
function pollOrder(orderNumber) {
if (!orderNumber) return;
var tries = 0;
var maxTries = 40;
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);
setTip('支付成功,正在刷新…');
window.location.reload();
}
}).catch(function () { /* ignore */ });
if (tries >= maxTries) clearInterval(timer);
}, 3000);
}
root.addEventListener('click', function (e) {
var btn = e.target.closest('[data-aksrt-vip-buy]');
if (!btn || busy) return;
@@ -498,38 +457,46 @@
var packageId = parseInt(btn.getAttribute('data-package-id') || '0', 10);
if (!packageId) return;
busy = true;
btn.disabled = true;
btn.classList.add('is-loading');
setTip('正在创建订单…');
api('/pay/vip', {
method: 'POST',
body: JSON.stringify({
package_id: packageId,
payment_method: selectedMethod()
})
}).then(function (r) {
busy = false;
btn.classList.remove('is-loading');
if (!r.ok || (r.json && r.json.code && !r.json.success)) {
var msg = (r.json && r.json.message) || '开通失败';
setTip(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');
}
coordinator.start({
packageId: packageId,
paymentMethod: selectedMethod(),
tags: ['vip', 'orders'],
onPending: function (data) {
busy = false;
btn.disabled = false;
btn.classList.remove('is-loading');
if (data.payment_url) window.open(data.payment_url, '_blank');
setTip(data.message || '请完成支付,支付成功后将自动刷新。');
if (data.order_number) pollOrder(data.order_number);
return;
},
onSuccess: function (result) {
busy = false;
btn.disabled = false;
btn.classList.remove('is-loading');
setTip((result.data && result.data.message) || '开通成功,正在刷新…');
window.location.reload();
},
onFailure: function () {
busy = false;
btn.disabled = false;
btn.classList.remove('is-loading');
setTip('支付未完成,请重试。', true);
},
onTimeout: function () {
busy = false;
btn.disabled = false;
btn.classList.remove('is-loading');
setTip('支付状态暂未确认,请稍后在订单中心查看。', true);
},
onError: function (response) {
busy = false;
btn.disabled = false;
btn.classList.remove('is-loading');
setTip((response.json && response.json.message) || '开通失败', true);
}
setTip((data && data.message) || '开通成功,正在刷新…');
window.location.reload();
}).catch(function () {
busy = false;
btn.classList.remove('is-loading');
setTip('网络错误,请重试', true);
});
});
}
@@ -588,6 +555,9 @@
return;
}
var data = (r.json && r.json.data) ? r.json.data : r.json;
if (type === 'vip' && window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === 'function') {
window.AksrtModalRuntime.invalidate(['vip', 'orders']);
}
setTip((data && data.message) || '兑换成功,正在刷新…');
window.location.reload();
}).catch(function () {
@@ -608,26 +578,28 @@
}
function closeModal(el) {
if (!el) return;
if (activePoll && typeof activePoll.abort === 'function') activePoll.abort();
activePoll = null;
el.hidden = true;
el.classList.remove('is-open');
if (!document.querySelector('.uc-modal.is-open')) {
document.body.classList.remove('uc-modal-open');
}
}
function pollPaidOrder(orderNumber) {
function pollPaidOrder(orderNumber, setTip, tags) {
var coordinator = window.AksrtVipPurchaseCoordinator;
if (!orderNumber) return;
var tries = 0;
var maxTries = 40;
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);
var handle = coordinator.poll(orderNumber, {
tags: tags || ['credits', 'orders'],
onTerminal: function (result) {
activePoll = null;
if (result.success) {
if (typeof setTip === 'function') setTip('支付成功,正在刷新…');
window.location.reload();
}
}).catch(function () {});
if (tries >= maxTries) clearInterval(timer);
}, 3000);
}
});
return handle;
}
function initBalanceCharge() {
@@ -645,7 +617,11 @@
btn.addEventListener('click', function () { openModal(modal); });
});
modal.querySelectorAll('[data-aksrt-charge-close]').forEach(function (btn) {
btn.addEventListener('click', function () { closeModal(modal); });
btn.addEventListener('click', function () {
if (activePoll && typeof activePoll.abort === 'function') activePoll.abort();
activePoll = null;
closeModal(modal);
});
});
modal.querySelectorAll('.uc-charge-package input').forEach(function (inp) {
inp.addEventListener('change', function () {
@@ -694,7 +670,7 @@
if (data && data.redirect && (data.payment_url || data.qr_content)) {
if (data.payment_url) window.open(data.payment_url, '_blank');
setTip(data.message || '请完成支付,支付成功后将自动刷新。');
if (data.order_number) pollPaidOrder(data.order_number);
if (data.order_number) activePoll = pollPaidOrder(data.order_number, setTip, ['credits', 'orders']);
return;
}
setTip((data && data.message) || '充值成功,正在刷新…');
@@ -917,7 +893,7 @@
if (data && data.redirect && (data.payment_url || data.qr_content)) {
if (data.payment_url) window.open(data.payment_url, '_blank');
setTip(data.message || '请完成支付,支付成功后将自动刷新。');
if (data.order_number) pollPaidOrder(data.order_number);
if (data.order_number) activePoll = pollPaidOrder(data.order_number, setTip, ['credits', 'orders']);
return;
}
setTip((data && data.message) || '购买成功,正在刷新…');