Files
jiapuapp/utils/navigation.js
T
2026-07-27 06:50:23 +08:00

837 lines
27 KiB
JavaScript

import {
NOTICE_TARGETS,
ROOT_ROUTE_KEYS,
getRoute,
getRouteKeyByPath,
} from "./navigation-routes.js";
const navigationResults = new Map();
const pageInstanceTokens = new WeakMap();
let navigationInFlight = null;
let pageInstanceSequence = 0;
const hasOwn = (value, key) =>
Object.prototype.hasOwnProperty.call(value, key);
const getPageInstanceToken = (page) => {
if ((typeof page !== "object" || page === null) && typeof page !== "function") {
return null;
}
let token = pageInstanceTokens.get(page);
if (!token) {
pageInstanceSequence += 1;
token = pageInstanceSequence;
pageInstanceTokens.set(page, token);
}
return token;
};
const snapshotDataRecord = (name, value) => {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
throw new TypeError(`${name} 必须是对象`);
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== Object.prototype && prototype !== null) {
throw new TypeError(`${name} 必须是普通对象`);
}
const snapshot = Object.create(null);
const descriptors = Object.getOwnPropertyDescriptors(value);
for (const key of Reflect.ownKeys(descriptors)) {
if (typeof key !== "string") {
throw new TypeError(`${name} 不接受 Symbol 字段`);
}
const descriptor = descriptors[key];
if (!descriptor.enumerable) {
throw new TypeError(`${name} 字段 ${key} 必须可枚举`);
}
if (!hasOwn(descriptor, "value")) {
throw new TypeError(`${name} 字段 ${key} 不得使用访问器`);
}
Object.defineProperty(snapshot, key, {
value: descriptor.value,
enumerable: true,
writable: false,
configurable: false,
});
}
return Object.freeze(snapshot);
};
const assertScalarString = (name, value) => {
if (typeof value !== "string" || value.length === 0) {
throw new TypeError(`导航参数 ${name} 必须是非空字符串`);
}
};
const validateRouteParams = (routeKey, params, requireAll) => {
const route = getRoute(routeKey);
if (!route) throw new Error(`未知路由键:${routeKey}`);
const normalizedParams = snapshotDataRecord(`${routeKey} 导航参数`, params);
const allowed = new Set([...route.requiredParams, ...route.optionalParams]);
for (const name of Object.keys(normalizedParams)) {
if (!allowed.has(name)) {
throw new Error(`${routeKey} 不接受导航参数 ${name}`);
}
assertScalarString(name, normalizedParams[name]);
}
if (requireAll) {
for (const name of route.requiredParams) {
if (!hasOwn(normalizedParams, name)) {
throw new Error(`${routeKey} 缺少导航参数 ${name}`);
}
}
}
return { route, params: normalizedParams };
};
const encodeQuery = (entries) =>
entries
.map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`)
.join("&");
const buildValidatedRouteUrl = (routeKey, route, params, sourceKey = "") => {
if (sourceKey !== "") {
assertScalarString("sourceKey", sourceKey);
if (!route.allowedSources.includes(sourceKey)) {
throw new Error(`${sourceKey} 不能进入 ${routeKey}`);
}
}
const entries = [...route.requiredParams, ...route.optionalParams]
.filter((name) => hasOwn(params, name))
.map((name) => [name, params[name]]);
if (sourceKey !== "") entries.push(["sourceKey", sourceKey]);
const query = encodeQuery(entries);
return query ? `${route.path}?${query}` : route.path;
};
export const buildRouteUrl = (routeKey, params = {}, sourceKey = "") => {
const validated = validateRouteParams(routeKey, params, true);
return buildValidatedRouteUrl(
routeKey,
validated.route,
validated.params,
sourceKey,
);
};
const getPagePath = (page) => {
const rawPath = page?.route || page?.$page?.route || page?.$page?.fullPath;
if (typeof rawPath !== "string" || rawPath.length === 0) return "";
return rawPath.split("?")[0];
};
const getPageParams = (page, route) => {
const params = Object.create(null);
const routeFields = [...route.requiredParams, ...route.optionalParams];
const candidates = [page?.$page?.query, page?.$page?.options, page?.options];
// UniApp 各端暴露页面参数的位置不同。只投影注册表声明字段,避免把
// sourceKey 等传输字段误当成目标业务参数;后出现的容器拥有更高优先级,
// 但空的顶层 options 不会遮蔽 $page.query/$page.options。
for (const candidate of candidates) {
if (candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) {
continue;
}
for (const name of routeFields) {
const descriptor = Object.getOwnPropertyDescriptor(candidate, name);
if (!descriptor) continue;
if (!descriptor.enumerable) {
throw new TypeError(`页面导航参数 ${name} 必须可枚举`);
}
if (!hasOwn(descriptor, "value")) {
throw new TypeError(`页面导航参数 ${name} 不得使用访问器`);
}
params[name] = descriptor.value;
}
}
return params;
};
const readPageStack = () => {
if (typeof getCurrentPages !== "function") {
throw new Error("当前运行环境不支持页面栈读取");
}
const stack = getCurrentPages();
if (!Array.isArray(stack)) throw new Error("页面栈格式无效");
return stack;
};
const getPageRouteKey = (page) => getRouteKeyByPath(getPagePath(page));
const assertActualSource = (routeKey, sourceKey) => {
const route = getRoute(routeKey);
if (!route) throw new Error(`未知路由键:${routeKey}`);
assertScalarString("sourceKey", sourceKey);
if (!route.allowedSources.includes(sourceKey)) {
throw new Error(`${sourceKey} 不能进入 ${routeKey}`);
}
const stack = readPageStack();
const actualSource = stack.length > 0
? getPageRouteKey(stack[stack.length - 1])
: null;
if (actualSource !== sourceKey) {
throw new Error(
`当前真实页面 ${actualSource || "UNKNOWN"} 与声明来源 ${sourceKey} 不一致`,
);
}
};
const isCurrentTarget = (routeKey, params) => {
const stack = readPageStack();
if (stack.length === 0) return false;
const currentPage = stack[stack.length - 1];
if (getPageRouteKey(currentPage) !== routeKey) return false;
const route = getRoute(routeKey);
const currentParams = getPageParams(currentPage, route);
for (const name of [...route.requiredParams, ...route.optionalParams]) {
const targetHasParam = hasOwn(params, name);
const currentHasParam = hasOwn(currentParams, name);
if (targetHasParam !== currentHasParam) return false;
if (targetHasParam && currentParams[name] !== params[name]) return false;
}
return true;
};
const runUniNavigation = (
key,
invoke,
onStart = null,
onAbort = null,
onSuccess = null,
) => {
if (navigationInFlight?.key === key) return navigationInFlight.promise;
if (navigationInFlight) return Promise.resolve(false);
let resolvePromise;
let rejectPromise;
let settled = false;
const promise = new Promise((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
});
const flight = { key, promise };
navigationInFlight = flight;
const releaseFlight = () => {
if (navigationInFlight === flight) navigationInFlight = null;
};
const resolveFlight = () => {
if (settled) return;
settled = true;
releaseFlight();
resolvePromise(true);
};
const rejectFlight = (error) => {
if (settled) return;
settled = true;
let rejection = error;
try {
onAbort?.();
} catch (abortError) {
rejection = abortError;
}
releaseFlight();
rejectPromise(rejection);
};
try {
onStart?.();
invoke({
success: () => {
try {
onSuccess?.();
resolveFlight();
} catch (error) {
rejectFlight(error);
}
},
fail: (error) => rejectFlight(
new Error(error?.errMsg || "页面跳转失败"),
),
complete: () => {
if (!settled) {
rejectFlight(new Error("页面跳转未返回 success 或 fail"));
}
},
});
} catch (error) {
rejectFlight(error);
}
return promise;
};
const asNavigationPromise = (execute) => {
try {
return execute();
} catch (error) {
return Promise.reject(error);
}
};
const validateNavigationResult = (routeKey, result) => {
const route = getRoute(routeKey);
if (!route) throw new Error(`未知路由键:${routeKey}`);
const normalizedResult = snapshotDataRecord(`${routeKey} 导航结果`, result);
const fields = Reflect.ownKeys(normalizedResult);
const allowedFields = new Set(["operation", "entityId", "refresh"]);
for (const field of fields) {
if (typeof field !== "string" || !allowedFields.has(field)) {
throw new Error(`${routeKey} 导航结果不接受字段 ${String(field)}`);
}
}
for (const requiredField of ["operation", "refresh"]) {
if (!hasOwn(normalizedResult, requiredField)) {
throw new Error(`${routeKey} 导航结果缺少 ${requiredField}`);
}
}
assertScalarString("operation", normalizedResult.operation);
if (!route.resultOperations.includes(normalizedResult.operation)) {
throw new Error(`${routeKey} 不接受结果 operation=${normalizedResult.operation}`);
}
if (hasOwn(normalizedResult, "entityId")) {
assertScalarString("entityId", normalizedResult.entityId);
}
if (typeof normalizedResult.refresh !== "boolean") {
throw new TypeError("导航结果 refresh 必须是布尔值");
}
return Object.freeze({
operation: normalizedResult.operation,
...(hasOwn(normalizedResult, "entityId")
? { entityId: normalizedResult.entityId }
: {}),
refresh: normalizedResult.refresh,
});
};
const runWithNavigationResult = (
routeKey,
targetPage,
sourcePage,
targetParams,
result,
key,
invoke,
) => {
const envelope = Object.seal({
targetPageToken: getPageInstanceToken(targetPage),
sourcePageToken: getPageInstanceToken(sourcePage),
targetParams,
result,
});
let started = false;
const rollback = () => {
if (navigationResults.get(routeKey) !== envelope) return;
navigationResults.delete(routeKey);
};
const bindCreatedTarget = () => {
if (navigationResults.get(routeKey) !== envelope) return;
const stack = readPageStack();
const currentPage = stack.length > 0 ? stack[stack.length - 1] : null;
if (getPageRouteKey(currentPage) !== routeKey) {
throw new Error(`${routeKey} 导航成功后未找到规范目标页`);
}
const currentPageToken = getPageInstanceToken(currentPage);
const route = getRoute(routeKey);
const currentParams = validateRouteParams(
routeKey,
getPageParams(currentPage, route),
true,
).params;
const paramsMatch = routeParamsEqual(
route,
currentParams,
envelope.targetParams,
);
if (envelope.targetPageToken !== null) {
if (currentPageToken !== envelope.targetPageToken || !paramsMatch) {
throw new Error(`${routeKey} 导航成功后的目标实例或参数不匹配`);
}
return;
}
if (currentPageToken === envelope.sourcePageToken || !paramsMatch) {
throw new Error(`${routeKey} 导航成功后的目标实例或参数不匹配`);
}
envelope.targetPageToken = currentPageToken;
};
const promise = runUniNavigation(
key,
invoke,
() => {
started = true;
// 任意新完成流程取得全局转场锁后,所有旧结果都已经错过各自目标页的
// onShow 生命周期,必须先整体淘汰再写入当前结果;当前转场失败时只删除
// 当前 envelope,不复活任何路由的陈旧结果。
navigationResults.clear();
navigationResults.set(routeKey, envelope);
},
rollback,
bindCreatedTarget,
);
if (!started) return promise;
// 清理副作用挂在原 Promise 上,但仍返回原 Promise,保证相同语义调用的
// Promise 身份一致;失败处理分支吞掉派生链结果,不改变调用方收到的拒绝。
promise.then(
(completed) => {
if (!completed) rollback();
},
() => rollback(),
);
return promise;
};
const pushPage = (url) =>
runUniNavigation(`push:${url}`, (callbacks) =>
uni.navigateTo({ url, ...callbacks }));
const activateExistingSinglePage = (routeKey, params, url) => {
const stack = readPageStack();
const targetIndexes = [];
for (let index = stack.length - 2; index >= 0; index -= 1) {
if (getPageRouteKey(stack[index]) === routeKey) {
targetIndexes.push(index);
}
}
if (targetIndexes.length > 1) {
const error = new Error("T03_STACK_CONFLICT:页面栈中存在多个成员页实例");
error.code = "T03_STACK_CONFLICT";
throw error;
}
const targetIndex = targetIndexes[0] ?? -1;
if (targetIndex < 0) return pushPage(url);
// T03 是当前唯一 single 页面。复用实例前必须锁定家谱上下文;若跨家谱强行
// 复用,成员轨迹和一次性结果都会串到错误领域,因此明确失败而不是猜测回退。
const existingParams = validateRouteParams(
routeKey,
getPageParams(stack[targetIndex], getRoute(routeKey)),
true,
).params;
if (existingParams.genealogyId !== params.genealogyId) {
const error = new Error("T03_CONTEXT_CONFLICT:既有成员页属于其他家谱");
error.code = "T03_CONTEXT_CONFLICT";
throw error;
}
const result = validateNavigationResult(routeKey, {
operation: "member-open-requested",
entityId: params.personId,
refresh: false,
});
const delta = stack.length - 1 - targetIndex;
const transitionKey = `single:${routeKey}:${delta}:${params.personId}`;
return runWithNavigationResult(
routeKey,
stack[targetIndex],
stack[stack.length - 1],
existingParams,
result,
transitionKey,
(callbacks) => uni.navigateBack({ delta, ...callbacks }),
);
};
export const openPage = (routeKey, params = {}, sourceKey = "") =>
asNavigationPromise(() => {
const validated = validateRouteParams(routeKey, params, true);
const url = buildValidatedRouteUrl(
routeKey,
validated.route,
validated.params,
sourceKey,
);
assertActualSource(routeKey, sourceKey);
if (isCurrentTarget(routeKey, validated.params)) return Promise.resolve(false);
if (validated.route.kind === "single") {
return activateExistingSinglePage(routeKey, validated.params, url);
}
return pushPage(url);
});
export const goRoot = (routeKey, params = {}) =>
asNavigationPromise(() => {
if (!ROOT_ROUTE_KEYS.includes(routeKey)) {
throw new Error(`${routeKey} 不是根语义`);
}
const validated = validateRouteParams(routeKey, params, true);
const url = buildValidatedRouteUrl(
routeKey,
validated.route,
validated.params,
);
if (isCurrentTarget(routeKey, validated.params)) return Promise.resolve(false);
return runUniNavigation(`root:${url}`, (callbacks) =>
uni.reLaunch({ url, ...callbacks }));
});
export const openNoticeTarget = (targetType, params, sourceKey = "N02") =>
asNavigationPromise(() => {
assertScalarString("通知目标类型", targetType);
assertScalarString("通知来源", sourceKey);
if (!hasOwn(NOTICE_TARGETS, targetType)) {
throw new Error(`未知通知目标:${targetType}`);
}
if (!["N01", "N02"].includes(sourceKey)) {
throw new Error(`${sourceKey} 不能进入通知目标`);
}
const target = NOTICE_TARGETS[targetType];
const normalizedParams = snapshotDataRecord("通知目标参数", params);
const expectedParams = new Set(target.params);
for (const name of Object.keys(normalizedParams)) {
if (!expectedParams.has(name)) {
throw new Error(`${targetType} 通知目标不接受参数 ${name}`);
}
assertScalarString(name, normalizedParams[name]);
}
for (const name of target.params) {
if (!hasOwn(normalizedParams, name)) {
throw new Error(`${targetType} 通知目标缺少参数 ${name}`);
}
}
return target.routeKey === "G01"
? goRoot(target.routeKey, normalizedParams)
: openPage(target.routeKey, normalizedParams, sourceKey);
});
const collectParentParams = (childRoute, childParams, parentRoute) => {
const parentParams = {};
const parentFields = [
...parentRoute.requiredParams,
...parentRoute.optionalParams,
];
for (const name of parentFields) {
if (
hasOwn(childParams, name) &&
typeof childParams[name] === "string" &&
childParams[name].length > 0
) {
parentParams[name] = childParams[name];
}
}
for (const [parentParam, childParam] of Object.entries(childRoute.parentParamMap)) {
if (
hasOwn(childParams, childParam) &&
typeof childParams[childParam] === "string" &&
childParams[childParam].length > 0
) {
parentParams[parentParam] = childParams[childParam];
}
}
return parentParams;
};
const runParentFallback = (routeKey, params) => {
let childRoute = getRoute(routeKey);
let childParams = params;
// 深链没有可信历史。逐级只继承注册表声明的同名字段和 parentParamMap;任何
// 必填字段不足的中间页都不能伪造,继续上溯到首个可合法构造的父语义。
while (childRoute?.parent) {
const parentKey = childRoute.parent;
const parentRoute = getRoute(parentKey);
const parentParams = collectParentParams(childRoute, childParams, parentRoute);
const hasRequiredParams = parentRoute.requiredParams.every((name) =>
hasOwn(parentParams, name));
if (hasRequiredParams) {
const url = buildRouteUrl(parentKey, parentParams);
if (ROOT_ROUTE_KEYS.includes(parentKey)) {
return runUniNavigation(`fallback-root:${url}`, (callbacks) =>
uni.reLaunch({ url, ...callbacks }));
}
return runUniNavigation(`fallback-replace:${url}`, (callbacks) =>
uni.redirectTo({ url, ...callbacks }));
}
childRoute = parentRoute;
childParams = parentParams;
}
return Promise.resolve(false);
};
export const goBack = () =>
asNavigationPromise(() => {
const stack = readPageStack();
if (stack.length === 0) throw new Error("当前页面栈为空");
if (stack.length > 1) {
return runUniNavigation("back:1", (callbacks) =>
uni.navigateBack({ delta: 1, ...callbacks }));
}
const currentPage = stack[0];
const currentRouteKey = getPageRouteKey(currentPage);
if (!currentRouteKey) throw new Error("当前页面不在活动路由注册表中");
if (ROOT_ROUTE_KEYS.includes(currentRouteKey)) return Promise.resolve(false);
const currentRoute = getRoute(currentRouteKey);
return runParentFallback(
currentRouteKey,
getPageParams(currentPage, currentRoute),
);
});
const findNearestPageIndex = (stack, routeKey) => {
for (let index = stack.length - 2; index >= 0; index -= 1) {
if (getPageRouteKey(stack[index]) === routeKey) return index;
}
return -1;
};
const getResultTransitionKey = (result) => {
if (!result) return "";
const entityKey = hasOwn(result, "entityId")
? `1:${encodeURIComponent(result.entityId)}`
: "0";
return `:${encodeURIComponent(result.operation)}:${entityKey}:${result.refresh ? "1" : "0"}`;
};
const routeParamsEqual = (route, left, right) => {
for (const name of [...route.requiredParams, ...route.optionalParams]) {
const leftHasParam = hasOwn(left, name);
const rightHasParam = hasOwn(right, name);
if (leftHasParam !== rightHasParam) return false;
if (leftHasParam && left[name] !== right[name]) return false;
}
return true;
};
const assertResultSourceContext = (targetRoute, targetParams, sourcePage) => {
const sourceRouteKey = getPageRouteKey(sourcePage);
const sourceRoute = getRoute(sourceRouteKey);
if (!sourceRoute) return;
const sourceParams = validateRouteParams(
sourceRouteKey,
getPageParams(sourcePage, sourceRoute),
true,
).params;
const targetFields = new Set([
...targetRoute.requiredParams,
...targetRoute.optionalParams,
]);
for (const name of [...sourceRoute.requiredParams, ...sourceRoute.optionalParams]) {
if (
targetFields.has(name) &&
hasOwn(sourceParams, name) &&
hasOwn(targetParams, name) &&
sourceParams[name] !== targetParams[name]
) {
throw new Error(`完成来源上下文 ${name} 与目标不一致`);
}
}
};
const isImplicitRootOptionalContext = (
routeKey,
route,
existingParams,
targetParams,
name,
sourcePage,
) => {
if (
!ROOT_ROUTE_KEYS.includes(routeKey) ||
!route.optionalParams.includes(name) ||
hasOwn(existingParams, name) ||
!hasOwn(targetParams, name)
) {
return false;
}
const sourceRouteKey = getPageRouteKey(sourcePage);
const sourceRoute = getRoute(sourceRouteKey);
if (!sourceRoute) return false;
const sourceParams = validateRouteParams(
sourceRouteKey,
getPageParams(sourcePage, sourceRoute),
true,
).params;
return hasOwn(sourceParams, name) && sourceParams[name] === targetParams[name];
};
const returnToValidated = (routeKey, targetParams, result) => {
const validatedTarget = validateRouteParams(
routeKey,
targetParams,
result !== null,
);
const route = validatedTarget.route;
const normalizedTargetParams = validatedTarget.params;
const stack = readPageStack();
const targetIndex = findNearestPageIndex(stack, routeKey);
const sourcePage = stack.length > 0 ? stack[stack.length - 1] : null;
let targetPage = null;
let targetIdentity;
let transitionKey;
let invoke;
if (targetIndex >= 0) {
targetPage = stack[targetIndex];
const existingParams = getPageParams(stack[targetIndex], route);
const validatedExisting = validateRouteParams(routeKey, existingParams, true);
targetIdentity = validatedExisting.params;
for (const [name, value] of Object.entries(normalizedTargetParams)) {
if (
(!hasOwn(validatedExisting.params, name) ||
validatedExisting.params[name] !== value) &&
!isImplicitRootOptionalContext(
routeKey,
route,
validatedExisting.params,
normalizedTargetParams,
name,
sourcePage,
)
) {
throw new Error(`${routeKey} 显式目标参数 ${name} 与最近实例不一致`);
}
}
const delta = stack.length - 1 - targetIndex;
transitionKey = `return:${routeKey}:${delta}${getResultTransitionKey(result)}`;
invoke = (callbacks) => uni.navigateBack({ delta, ...callbacks });
} else {
const completeTarget = validateRouteParams(
routeKey,
normalizedTargetParams,
true,
);
targetIdentity = completeTarget.params;
const url = buildValidatedRouteUrl(routeKey, route, completeTarget.params);
const resultKey = getResultTransitionKey(result);
if (ROOT_ROUTE_KEYS.includes(routeKey)) {
transitionKey = `return-root:${url}${resultKey}`;
invoke = (callbacks) => uni.reLaunch({ url, ...callbacks });
} else {
transitionKey = `return-replace:${url}${resultKey}`;
invoke = (callbacks) => uni.redirectTo({ url, ...callbacks });
}
}
if (result) {
assertResultSourceContext(route, targetIdentity, sourcePage);
}
return result
? runWithNavigationResult(
routeKey,
targetPage,
sourcePage,
targetIdentity,
result,
transitionKey,
invoke,
)
: runUniNavigation(transitionKey, invoke);
};
export const returnTo = (routeKey, targetParams = {}, ...unsupportedArgs) =>
asNavigationPromise(() => {
if (unsupportedArgs.length > 0) {
throw new Error("returnTo 不接受流程结果,请使用 finishPage");
}
return returnToValidated(routeKey, targetParams, null);
});
export const finishPage = (routeKey, targetParams = {}, result) =>
asNavigationPromise(() => {
const normalizedResult = validateNavigationResult(routeKey, result);
return returnToValidated(routeKey, targetParams, normalizedResult);
});
export const consumeNavigationResult = (routeKey) => {
const route = getRoute(routeKey);
if (!route) throw new Error(`未知路由键:${routeKey}`);
const stack = readPageStack();
if (
stack.length === 0 ||
getPageRouteKey(stack[stack.length - 1]) !== routeKey
) {
return null;
}
if (!navigationResults.has(routeKey)) return null;
const envelope = navigationResults.get(routeKey);
const currentPage = stack[stack.length - 1];
const currentPageToken = getPageInstanceToken(currentPage);
if (envelope.targetPageToken !== null) {
if (currentPageToken !== envelope.targetPageToken) return null;
} else if (currentPageToken === envelope.sourcePageToken) {
return null;
}
const currentParams = validateRouteParams(
routeKey,
getPageParams(currentPage, route),
true,
).params;
if (!routeParamsEqual(route, currentParams, envelope.targetParams)) return null;
navigationResults.delete(routeKey);
return envelope.result;
};
const resolveBackActionFromContext = ({
transientOpen = false,
internalTrail = false,
dirty = false,
submitting = false,
}) => {
if (transientOpen) return "close-transient";
if (internalTrail) return "pop-internal-trail";
if (submitting) return "block-submitting";
if (dirty) return "confirm-discard";
return "go-back";
};
const validateBackContext = (context) => {
const normalizedContext = snapshotDataRecord("返回守卫上下文", context);
for (const flag of ["transientOpen", "internalTrail", "dirty", "submitting"]) {
if (hasOwn(normalizedContext, flag) && typeof normalizedContext[flag] !== "boolean") {
throw new TypeError(`返回守卫状态 ${flag} 必须是布尔值`);
}
}
return normalizedContext;
};
// onBackPress 必须同步返回布尔值;而网关自己的 navigateBack 会再次触发该钩子。
// 这里统一放行网关回调来源,并把页面异步守卫从同步平台钩子中安全分离。
export const handleBackPress = (event, requestBack) => {
if (event?.from === "navigateBack") return false;
if (typeof requestBack !== "function") {
throw new TypeError("handleBackPress 的 requestBack 必须是函数");
}
void Promise.resolve()
.then(requestBack)
.catch((error) => console.error("页面返回守卫执行失败", error));
return true;
};
export const resolveBackAction = (context = {}) =>
resolveBackActionFromContext(validateBackContext(context));
export const runBackGuard = async (context = {}) => {
const normalizedContext = validateBackContext(context);
const action = resolveBackActionFromContext(normalizedContext);
if (action === "go-back") return goBack();
const callback = normalizedContext[action];
if (typeof callback !== "function") {
throw new TypeError(`返回守卫缺少 ${action} 回调`);
}
const outcome = await callback();
if (action !== "confirm-discard") {
return true;
}
if (outcome !== true) return false;
return goBack();
};