145 lines
4.9 KiB
JavaScript
145 lines
4.9 KiB
JavaScript
/**
|
||
* 沉默期强制延期拦截:当前资产有"沉默期已过期待延期"的套餐时,
|
||
* 除首页(强制延期弹窗)、登录/授权、绑定手机号、延期与支付相关页面外,其他页面一律不能进入,
|
||
* 统一引导到延期页。
|
||
*/
|
||
import { isSilenceReminderBlocking, pickSilenceReminder } from './silence.js';
|
||
|
||
export const SILENCE_EXTENSION_PAGE = '/pages/silence-extension/silence-extension';
|
||
|
||
// 不拦截的页面:登录授权、绑定手机号、首页(自带强制弹窗)、延期办理与支付相关页面
|
||
export const SILENCE_GUARD_ALLOWED_PAGES = Object.freeze([
|
||
'pages/index/index',
|
||
'pages/login/login',
|
||
'pages/auth/auth',
|
||
'pages/error/error',
|
||
'pages/bind/bind',
|
||
'pages/silence-extension/silence-extension',
|
||
'pages/silence-payment/silence-payment',
|
||
'pages/silence-orders/silence-orders',
|
||
'pages/silence-orders/detail',
|
||
'pages/silence-records/silence-records',
|
||
'pages/alipay-payment/alipay-payment'
|
||
]);
|
||
|
||
// 未被拦截的结果缓存时长;被拦截时每次都重新查询,延期完成后可立即放行
|
||
const PASS_CACHE_MS = 5 * 60 * 1000;
|
||
const NAVIGATION_METHODS = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'];
|
||
const CHECKED_FLAG = '__silenceGuardChecked';
|
||
|
||
export const pagePathOf = (url) => String(url || '').split('?')[0].replace(/^\/+/, '');
|
||
|
||
export const isSilenceGuardAllowed = (url) => SILENCE_GUARD_ALLOWED_PAGES.includes(pagePathOf(url));
|
||
|
||
export const silenceExtensionUrl = (reminder) =>
|
||
`${SILENCE_EXTENSION_PAGE}?package_usage_id=${reminder.packageUsageId}`;
|
||
|
||
/**
|
||
* 创建拦截器核心(依赖可注入,便于测试)。
|
||
* deps.getSession(): { token, identifier }
|
||
* deps.loadPackageHistory(identifier): Promise<{ items }>
|
||
* deps.now(): number
|
||
*/
|
||
export const createSilenceGuard = ({ getSession, loadPackageHistory, now = () => Date.now() }) => {
|
||
let passCache = null;
|
||
let pending = null;
|
||
|
||
const sessionKey = () => {
|
||
const { token, identifier } = getSession();
|
||
const id = String(identifier || '').trim();
|
||
return token && id ? { key: `${token}|${id}`, identifier: id } : null;
|
||
};
|
||
|
||
/** 命中"未拦截"缓存时同步返回 true,可直接放行 */
|
||
const isKnownPass = () => {
|
||
const session = sessionKey();
|
||
if (!session) return true;
|
||
return !!passCache && passCache.key === session.key && now() - passCache.at < PASS_CACHE_MS;
|
||
};
|
||
|
||
/** 查询当前资产是否需要强制延期;返回需要延期的提醒,或 null(放行) */
|
||
const resolveBlock = () => {
|
||
const session = sessionKey();
|
||
if (!session) return Promise.resolve(null);
|
||
if (isKnownPass()) return Promise.resolve(null);
|
||
if (pending?.key === session.key) return pending.promise;
|
||
const promise = loadPackageHistory(session.identifier)
|
||
.then((data) => {
|
||
const reminder = pickSilenceReminder(data?.items, now());
|
||
if (isSilenceReminderBlocking(reminder)) {
|
||
passCache = null;
|
||
return reminder;
|
||
}
|
||
passCache = { key: session.key, at: now() };
|
||
return null;
|
||
})
|
||
.catch((error) => {
|
||
// 查询失败不锁死用户,放行但不缓存,下次跳转再查
|
||
console.error('沉默期拦截检查失败', error);
|
||
return null;
|
||
})
|
||
.finally(() => {
|
||
if (pending?.promise === promise) pending = null;
|
||
});
|
||
pending = { key: session.key, promise };
|
||
return promise;
|
||
};
|
||
|
||
const reset = () => {
|
||
passCache = null;
|
||
pending = null;
|
||
};
|
||
|
||
return { isKnownPass, resolveBlock, reset };
|
||
};
|
||
|
||
const notifyBlocked = () => {
|
||
uni.showToast({ title: '套餐沉默期已过期,请先办理延期', icon: 'none', duration: 2000 });
|
||
};
|
||
|
||
/**
|
||
* 安装全局拦截:
|
||
* 1. 拦截 navigateTo/redirectTo/reLaunch/switchTab:目标页不在白名单时先检查,需要延期则改去延期页;
|
||
* 2. 页面显示时(直接打开链接、刷新、浏览器后退)再检查一次,需要延期则替换为延期页。
|
||
*/
|
||
export const installSilenceGuard = (app, guard) => {
|
||
NAVIGATION_METHODS.forEach((method) => {
|
||
uni.addInterceptor(method, {
|
||
invoke(args) {
|
||
if (args && args[CHECKED_FLAG]) {
|
||
delete args[CHECKED_FLAG];
|
||
return;
|
||
}
|
||
if (isSilenceGuardAllowed(args?.url) || guard.isKnownPass()) return;
|
||
guard.resolveBlock().then((reminder) => {
|
||
if (reminder) {
|
||
notifyBlocked();
|
||
uni.navigateTo({ url: silenceExtensionUrl(reminder) });
|
||
return;
|
||
}
|
||
uni[method]({ ...args, [CHECKED_FLAG]: true });
|
||
});
|
||
return false;
|
||
}
|
||
});
|
||
});
|
||
|
||
app.mixin({
|
||
onShow() {
|
||
const route = this.$page?.route || this.route;
|
||
if (!route || isSilenceGuardAllowed(route)) return;
|
||
const known = guard.isKnownPass();
|
||
if (known) return;
|
||
uni.showLoading({ title: '加载中', mask: true });
|
||
guard.resolveBlock().then((reminder) => {
|
||
uni.hideLoading();
|
||
const pages = getCurrentPages();
|
||
const current = pages[pages.length - 1];
|
||
if (!reminder || (current?.route && current.route !== route)) return;
|
||
notifyBlocked();
|
||
uni.redirectTo({ url: silenceExtensionUrl(reminder), [CHECKED_FLAG]: true });
|
||
});
|
||
}
|
||
});
|
||
};
|