fix(modal): close rollout and async lifecycle gaps
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5375ad6429
commit
aa865e24e1
8 files changed
+86
-31
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: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
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 SUCCESS_STATUSES = {
|
var SUCCESS_STATUSES = {
|
||||||
paid: true,
|
paid: true,
|
||||||
delivered: true,
|
delivered: true,
|
||||||
@@ -32,14 +33,16 @@
|
|||||||
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() {
|
||||||
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);
|
||||||
@@ -121,6 +124,7 @@
|
|||||||
|
|
||||||
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: "兑换套餐无效" } });
|
||||||
@@ -153,7 +157,9 @@
|
|||||||
abort: function () {},
|
abort: function () {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (polls[orderNumber]) return polls[orderNumber].handle;
|
var pollScope = userScope();
|
||||||
|
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,7 +196,7 @@
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +232,8 @@
|
|||||||
function run() {
|
function run() {
|
||||||
state.timer = null;
|
state.timer = null;
|
||||||
if (state.done || state.aborted) return;
|
if (state.done || state.aborted) return;
|
||||||
|
// Detect an account transition even when no auth event reached this page.
|
||||||
|
if (userScope() !== pollScope || state.done || state.aborted) return;
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
state.waitingForVisible = true;
|
state.waitingForVisible = true;
|
||||||
return;
|
return;
|
||||||
@@ -289,7 +297,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;
|
||||||
}
|
}
|
||||||
@@ -349,7 +357,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' => '启用统一会员购买、支付和结果同步流程。',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -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']),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user