/** * 沉默期强制延期拦截:当前资产有"沉默期已过期待延期"的套餐时, * 除首页(强制延期弹窗)、登录/授权、绑定手机号、延期与支付相关页面外,其他页面一律不能进入, * 统一引导到延期页。 */ 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 }); }); } }); };