perf(modal): unify cancellable frontend modal flows
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+81
-105
@@ -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) || '购买成功,正在刷新…');
|
||||
|
||||
Reference in New Issue
Block a user