Merge pull request 'Worktree prd modal performance' (#387) from worktree-prd-modal-performance into main

Reviewed-on: CYL/wordpress-theme-aksrt#387
This commit is contained in:
kate522 committed 2026-08-05 02:41:15 +08:00
commit 8627cce60f
8 files changed
+153 -48

No files matched your search

@@ -4,10 +4,10 @@ export const frontendModalRolloutSchema = {
icon: 'fas fa-bolt', icon: 'fas fa-bolt',
description: '按领域控制前台弹窗新流程的分阶段发布。', description: '按领域控制前台弹窗新流程的分阶段发布。',
defaults: { defaults: {
frontend_modal_rollout_identity: false, frontend_modal_rollout_identity: true,
frontend_modal_rollout_badge: false, frontend_modal_rollout_badge: true,
frontend_modal_rollout_order: false, frontend_modal_rollout_order: true,
frontend_modal_rollout_vip: false, frontend_modal_rollout_vip: true,
}, },
groups: [ groups: [
{ {
+5 -1
View File
@@ -10,11 +10,15 @@
var DEFAULT_TTL = 15000; var DEFAULT_TTL = 15000;
var entries = Object.create(null); var entries = Object.create(null);
var flights = Object.create(null); var flights = Object.create(null);
var lastScope = "";
function scope() { function scope() {
var config = window.aksrtData || window.aksrt || {}; var config = window.aksrtData || window.aksrt || {};
var userId = parseInt(config.userId || 0, 10) || 0; var userId = parseInt(config.userId || 0, 10) || 0;
return (config.isLoggedIn && userId > 0 ? "user:" + userId : "guest") + ":v1"; var current = (config.isLoggedIn && userId > 0 ? "user:" + userId : "guest") + ":v1";
if (lastScope && lastScope !== current) clear();
lastScope = current;
return current;
} }
function token(key) { function token(key) {
+9 -4
View File
@@ -342,11 +342,12 @@
}, },
}; };
session.flight = cacheable var flight = cacheable
? cache.acquire(cacheKey, request, acquireOptions) ? cache.acquire(cacheKey, request, acquireOptions)
: { promise: request(), release: function () {} }; : { promise: request(), release: function () {} };
session.flight = flight;
return session.flight.promise.then(function (result) { return flight.promise.then(function (result) {
if (!session.closed && active === session) renderResult(session, result); if (!session.closed && active === session) renderResult(session, result);
return session.shell; return session.shell;
}).catch(function (error) { }).catch(function (error) {
@@ -354,8 +355,12 @@
if (!aborted && !session.closed && active === session) renderError(session, error); if (!aborted && !session.closed && active === session) renderError(session, error);
return session.shell; return session.shell;
}).then(function (shell) { }).then(function (shell) {
if (session.flight && typeof session.flight.release === "function") session.flight.release(); // A refresh/replacement may have installed a newer flight on this session.
session.flight = null; // Only the owner of this promise may release and clear the slot.
if (session.flight === flight) {
if (flight && typeof flight.release === "function") flight.release();
session.flight = null;
}
return shell; return shell;
}); });
} }
+37 -2
View File
@@ -13,6 +13,9 @@
var detailOrderNumber = ""; var detailOrderNumber = "";
var detailAutoRefreshStarted = false; var detailAutoRefreshStarted = false;
var detailSession = null;
var detailReplacement = false;
var detailReplacementSession = null;
function toast(message, type) { function toast(message, type) {
window.AksrtToast(message, type || "info"); window.AksrtToast(message, type || "info");
@@ -48,7 +51,12 @@
} }
function activeDetail(number) { function activeDetail(number) {
return detailOrderNumber === number && !!window.AksrtModalRuntime.getActive(); return detailOrderNumber === number
&& !!detailSession
&& !detailSession.closed
&& detailSession.options
&& detailSession.options.kind === "orders"
&& window.AksrtModalRuntime.getActive() === detailSession.shell;
} }
function orderTag(orderNumber) { function orderTag(orderNumber) {
@@ -115,8 +123,12 @@
cacheKey: "orders:detail:" + orderNumber, cacheKey: "orders:detail:" + orderNumber,
cacheTags: ["orders", orderTag(orderNumber)], cacheTags: ["orders", orderTag(orderNumber)],
skeleton: detailSkeleton(), skeleton: detailSkeleton(),
onState: function (_, session) {
if (orderNumber === detailOrderNumber) detailSession = session;
},
renderData: function (data, session) { renderData: function (data, session) {
if (orderNumber !== detailOrderNumber) return false; if (orderNumber !== detailOrderNumber) return false;
detailSession = session;
if (!data || typeof data !== "object") { if (!data || typeof data !== "object") {
session.content.innerHTML = '<p class="c-red">订单不存在</p>'; session.content.innerHTML = '<p class="c-red">订单不存在</p>';
return; return;
@@ -137,6 +149,9 @@
function openDetail(orderNumber) { function openDetail(orderNumber) {
if (!orderNumber) return; if (!orderNumber) return;
detailSession = null;
detailReplacement = false;
detailReplacementSession = null;
detailOrderNumber = orderNumber; detailOrderNumber = orderNumber;
detailAutoRefreshStarted = false; detailAutoRefreshStarted = false;
window.AksrtModalRuntime.openRemote(detailOptions(orderNumber, false)); window.AksrtModalRuntime.openRemote(detailOptions(orderNumber, false));
@@ -144,7 +159,16 @@
function refreshDetail(orderNumber) { function refreshDetail(orderNumber) {
if (!orderNumber || !activeDetail(orderNumber)) return; if (!orderNumber || !activeDetail(orderNumber)) return;
window.AksrtModalRuntime.openRemote(detailOptions(orderNumber, true)); detailReplacement = true;
detailReplacementSession = detailSession;
var refreshPromise = window.AksrtModalRuntime.openRemote(detailOptions(orderNumber, true));
refreshPromise.then(function () {
detailReplacement = false;
detailReplacementSession = null;
}, function () {
detailReplacement = false;
detailReplacementSession = null;
});
} }
function currentViewPath() { function currentViewPath() {
@@ -301,6 +325,17 @@
if (cancel) { event.preventDefault(); closeOrder(cancel); } if (cancel) { event.preventDefault(); closeOrder(cancel); }
}); });
document.addEventListener("aksrt:modal:close", function (event) {
var detail = event && event.detail ? event.detail : {};
if (!detailSession || detail.session !== detailSession) return;
if (detail.reason === "replace" && detailReplacement && detail.session === detailReplacementSession) return;
detailSession = null;
detailOrderNumber = "";
detailAutoRefreshStarted = false;
detailReplacement = false;
detailReplacementSession = null;
});
document.addEventListener("click", function (event) { document.addEventListener("click", function (event) {
var refresh = event.target.closest("[data-aksrt-order-refresh]"); var refresh = event.target.closest("[data-aksrt-order-refresh]");
if (refresh) { if (refresh) {
+84 -25
View File
@@ -16,6 +16,8 @@
var MAX_DELAY = 15000; var MAX_DELAY = 15000;
var createFlights = Object.create(null); var createFlights = Object.create(null);
var polls = Object.create(null); var polls = Object.create(null);
var lastScope = "";
var scopeGeneration = 0;
var SUCCESS_STATUSES = { var SUCCESS_STATUSES = {
paid: true, paid: true,
delivered: true, delivered: true,
@@ -32,14 +34,21 @@
function userScope() { function userScope() {
var config = window.aksrtData || window.aksrt || {}; var config = window.aksrtData || window.aksrt || {};
var userId = parseInt(config.userId || config.user_id || 0, 10) || 0; var userId = parseInt(config.userId || config.user_id || 0, 10) || 0;
if (userId > 0) return "user:" + userId; var current = userId > 0 ? "user:" + userId : (config.isLoggedIn ? "auth" : "guest");
return config.isLoggedIn ? "auth" : "guest"; if (lastScope && lastScope !== current) clearFlights();
lastScope = current;
return current;
} }
function clearFlights() { function clearFlights() {
scopeGeneration += 1;
Object.keys(createFlights).forEach(function (key) {
var flight = createFlights[key];
if (flight && typeof flight.cancel === "function") flight.cancel();
});
createFlights = Object.create(null); createFlights = Object.create(null);
Object.keys(polls).forEach(function (orderNumber) { Object.keys(polls).forEach(function (pollKey) {
var active = polls[orderNumber]; var active = polls[pollKey];
if (active && active.handle && typeof active.handle.abort === "function") active.handle.abort(); if (active && active.handle && typeof active.handle.abort === "function") active.handle.abort();
}); });
polls = Object.create(null); polls = Object.create(null);
@@ -84,6 +93,9 @@
function callback(options, name) { function callback(options, name) {
var args = Array.prototype.slice.call(arguments, 2); var args = Array.prototype.slice.call(arguments, 2);
if (!options || typeof options[name] !== "function") return; if (!options || typeof options[name] !== "function") return;
if (options.scope && userScope() !== options.scope) return;
if (options.generation !== undefined && scopeGeneration !== options.generation) return;
if (typeof options.isActive === "function" && !options.isActive()) return;
try { try {
options[name].apply(null, args); options[name].apply(null, args);
} catch (error) { } catch (error) {
@@ -102,25 +114,41 @@
} }
var key = userScope() + ":" + String(packageId) + ":" + method; var key = userScope() + ":" + String(packageId) + ":" + method;
if (createFlights[key]) return createFlights[key]; if (createFlights[key]) return createFlights[key].promise;
var promise = request("/pay/vip", { var requestController = !options.signal && window.AbortController
method: "POST", ? new window.AbortController()
body: JSON.stringify({ : null;
package_id: packageId, var flight = {
payment_method: method, promise: request("/pay/vip", {
method: "POST",
body: JSON.stringify({
package_id: packageId,
payment_method: method,
}),
signal: options.signal || (requestController ? requestController.signal : undefined),
}), }),
signal: options.signal, cancel: function () {
if (typeof options.cancel === "function") {
try { options.cancel(); } catch (error) {}
}
if (requestController) {
try { requestController.abort(); } catch (error) {}
}
},
};
createFlights[key] = flight;
flight.promise.then(function () {
if (createFlights[key] === flight) delete createFlights[key];
}, function () {
if (createFlights[key] === flight) delete createFlights[key];
}); });
createFlights[key] = promise; return flight.promise;
promise.then(function () {
if (createFlights[key] === promise) delete createFlights[key];
});
return promise;
} }
function exchange(options) { function exchange(options) {
options = options || {}; options = options || {};
userScope();
var packageId = parseInt(options.packageId || options.package_id || 0, 10); var packageId = parseInt(options.packageId || options.package_id || 0, 10);
if (!packageId) { if (!packageId) {
return Promise.resolve({ ok: false, status: 400, json: { message: "兑换套餐无效" } }); return Promise.resolve({ ok: false, status: 400, json: { message: "兑换套餐无效" } });
@@ -145,7 +173,7 @@
} }
function poll(orderNumber, options) { function poll(orderNumber, options) {
options = options || {}; options = Object.assign({}, options || {});
orderNumber = String(orderNumber || ""); orderNumber = String(orderNumber || "");
if (!orderNumber) { if (!orderNumber) {
return { return {
@@ -153,7 +181,12 @@
abort: function () {}, abort: function () {},
}; };
} }
if (polls[orderNumber]) return polls[orderNumber].handle; var pollScope = userScope();
var pollGeneration = scopeGeneration;
if (!options.scope) options.scope = pollScope;
if (options.generation === undefined) options.generation = pollGeneration;
var pollKey = pollScope + ":" + orderNumber;
if (polls[pollKey]) return polls[pollKey].handle;
var maxAttempts = Math.max(1, Number(options.maxAttempts || DEFAULT_MAX_ATTEMPTS)); var maxAttempts = Math.max(1, Number(options.maxAttempts || DEFAULT_MAX_ATTEMPTS));
var maxElapsed = Math.max(1000, Number(options.maxElapsed || DEFAULT_MAX_ELAPSED)); var maxElapsed = Math.max(1000, Number(options.maxElapsed || DEFAULT_MAX_ELAPSED));
@@ -190,11 +223,16 @@
state.controller = null; state.controller = null;
} }
if (state.onVisibility) document.removeEventListener("visibilitychange", state.onVisibility); if (state.onVisibility) document.removeEventListener("visibilitychange", state.onVisibility);
if (polls[orderNumber] && polls[orderNumber].handle === state.handle) delete polls[orderNumber]; if (polls[pollKey] && polls[pollKey].handle === state.handle) delete polls[pollKey];
state.resolve(result); state.resolve(result);
} }
function terminal(data, response, status) { function terminal(data, response, status) {
if ((options.scope && userScope() !== options.scope)
|| (options.generation !== undefined && scopeGeneration !== options.generation)) {
finish({ terminal: false, aborted: true, status: "scope_changed" });
return;
}
var success = !!(data && data.paid) || !!SUCCESS_STATUSES[status]; var success = !!(data && data.paid) || !!SUCCESS_STATUSES[status];
invalidate(options.tags || ["vip", "orders"]); invalidate(options.tags || ["vip", "orders"]);
callback(options, "onTerminal", { callback(options, "onTerminal", {
@@ -226,6 +264,8 @@
function run() { function run() {
state.timer = null; state.timer = null;
if (state.done || state.aborted) return; if (state.done || state.aborted) return;
// Detect account transitions and explicit auth resets even when no event reached this page.
if (userScope() !== pollScope || scopeGeneration !== pollGeneration || state.done || state.aborted) return;
if (document.hidden) { if (document.hidden) {
state.waitingForVisible = true; state.waitingForVisible = true;
return; return;
@@ -289,7 +329,7 @@
finish({ terminal: false, aborted: true, status: "aborted" }); finish({ terminal: false, aborted: true, status: "aborted" });
}, },
}; };
polls[orderNumber] = state; polls[pollKey] = state;
schedule(0); schedule(0);
return state.handle; return state.handle;
} }
@@ -299,9 +339,23 @@
var activePoll = null; var activePoll = null;
var stopped = false; var stopped = false;
var controller = window.AbortController ? new window.AbortController() : null; var controller = window.AbortController ? new window.AbortController() : null;
var startScope = userScope();
var startGeneration = scopeGeneration;
if (controller) options.signal = controller.signal; if (controller) options.signal = controller.signal;
options.scope = startScope;
options.generation = startGeneration;
options.isActive = function () {
return !stopped && scopeGeneration === startGeneration && userScope() === startScope;
};
options.cancel = function () {
if (controller) {
try { controller.abort(); } catch (error) {}
}
};
var promise = purchase(options).then(function (response) { var promise = purchase(options).then(function (response) {
if (stopped) return { ok: false, aborted: true, status: "aborted" }; if (!options.isActive()) {
return { ok: false, aborted: true, status: stopped ? "aborted" : "scope_changed" };
}
var data = response.data || response.json || {}; var data = response.data || response.json || {};
if (!response.ok) { if (!response.ok) {
callback(options, "onError", response); callback(options, "onError", response);
@@ -314,6 +368,9 @@
callback(options, "onError", missing); callback(options, "onError", missing);
return missing; return missing;
} }
if (!options.isActive()) {
return { ok: false, aborted: true, status: "scope_changed" };
}
activePoll = poll(data.order_number, options); activePoll = poll(data.order_number, options);
activePoll.promise.then(function (result) { activePoll.promise.then(function (result) {
if (result.success) callback(options, "onSuccess", result); if (result.success) callback(options, "onSuccess", result);
@@ -326,6 +383,9 @@
poll: activePoll, poll: activePoll,
}; };
} }
if (!options.isActive()) {
return { ok: false, aborted: true, status: "scope_changed" };
}
invalidate(options.tags || ["vip", "orders"]); invalidate(options.tags || ["vip", "orders"]);
callback(options, "onSuccess", { terminal: true, success: true, data: data, response: response }); callback(options, "onSuccess", { terminal: true, success: true, data: data, response: response });
return { ok: true, pending: false, data: data, response: response }; return { ok: true, pending: false, data: data, response: response };
@@ -335,9 +395,7 @@
promise: promise, promise: promise,
abort: function () { abort: function () {
stopped = true; stopped = true;
if (controller) { options.cancel();
try { controller.abort(); } catch (error) {}
}
if (activePoll && typeof activePoll.abort === "function") activePoll.abort(); if (activePoll && typeof activePoll.abort === "function") activePoll.abort();
}, },
}; };
@@ -349,7 +407,8 @@
poll: poll, poll: poll,
start: start, start: start,
abort: function (orderNumber) { abort: function (orderNumber) {
var active = polls[String(orderNumber || "")]; var key = userScope() + ":" + String(orderNumber || "");
var active = polls[key];
if (active && active.handle) active.handle.abort(); if (active && active.handle) active.handle.abort();
}, },
}; };
@@ -24,28 +24,28 @@ class SchemaToolsFrontendModalRollout {
'key' => 'frontend_modal_rollout_identity', 'key' => 'frontend_modal_rollout_identity',
'label' => '官方认证新流程', 'label' => '官方认证新流程',
'type' => 'switch', 'type' => 'switch',
'default' => false, 'default' => true,
'help' => '启用统一弹窗运行时与认证快照流程。', 'help' => '启用统一弹窗运行时与认证快照流程。',
], ],
[ [
'key' => 'frontend_modal_rollout_badge', 'key' => 'frontend_modal_rollout_badge',
'label' => '徽章新流程', 'label' => '徽章新流程',
'type' => 'switch', 'type' => 'switch',
'default' => false, 'default' => true,
'help' => '启用统一 JSON 徽章流程和短缓存。', 'help' => '启用统一 JSON 徽章流程和短缓存。',
], ],
[ [
'key' => 'frontend_modal_rollout_order', 'key' => 'frontend_modal_rollout_order',
'label' => '订单新流程', 'label' => '订单新流程',
'type' => 'switch', 'type' => 'switch',
'default' => false, 'default' => true,
'help' => '启用订单详情快速开壳与状态同步流程。', 'help' => '启用订单详情快速开壳与状态同步流程。',
], ],
[ [
'key' => 'frontend_modal_rollout_vip', 'key' => 'frontend_modal_rollout_vip',
'label' => '会员支付新流程', 'label' => '会员支付新流程',
'type' => 'switch', 'type' => 'switch',
'default' => false, 'default' => true,
'help' => '启用统一会员购买、支付和结果同步流程。', 'help' => '启用统一会员购买、支付和结果同步流程。',
], ],
], ],
+4 -4
View File
@@ -575,10 +575,10 @@ trait ScriptsPart1c {
// Alias used by modal-auth / follow / report (same value as restUrl). // Alias used by modal-auth / follow / report (same value as restUrl).
'apiUrl' => esc_url_raw(rest_url('aksrt/v1')), 'apiUrl' => esc_url_raw(rest_url('aksrt/v1')),
'frontendModalRollout' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_frontend_modal_rollout() : [ 'frontendModalRollout' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_frontend_modal_rollout() : [
'vip' => false, 'vip' => true,
'order' => false, 'order' => true,
'identity' => false, 'identity' => true,
'badge' => false, 'badge' => true,
], ],
'stickyEnabled' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_enabled'] : true, 'stickyEnabled' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_enabled'] : true,
'stickyPages' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_pages'] : ['home', 'cat'], 'stickyPages' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_pages'] : ['home', 'cat'],
+6 -4
View File
@@ -20,10 +20,12 @@ class FrontendModalRuntime {
$settings = Settings::get_all(); $settings = Settings::get_all();
return [ return [
'vip' => !empty($settings['frontend_modal_rollout_vip']), // Canonical-only flows must be live when the option has never been configured.
'order' => !empty($settings['frontend_modal_rollout_order']), // An explicit false remains an intentional per-domain kill switch.
'identity' => !empty($settings['frontend_modal_rollout_identity']), 'vip' => !array_key_exists('frontend_modal_rollout_vip', $settings) || !empty($settings['frontend_modal_rollout_vip']),
'badge' => !empty($settings['frontend_modal_rollout_badge']), 'order' => !array_key_exists('frontend_modal_rollout_order', $settings) || !empty($settings['frontend_modal_rollout_order']),
'identity' => !array_key_exists('frontend_modal_rollout_identity', $settings) || !empty($settings['frontend_modal_rollout_identity']),
'badge' => !array_key_exists('frontend_modal_rollout_badge', $settings) || !empty($settings['frontend_modal_rollout_badge']),
]; ];
} }
} }