/** * 登录检查工具 — 统一拦截未登录用户的功能操作 * * 使用方式: * import { requireLogin } from '@/common/checkLogin.js' * if (!requireLogin()) return * * 页面级调用即可,全局 navigateTo 拦截器(App.vue onLaunch 中注册)作为兜底。 * * 防重复机制: * _loginPending — requireLogin 立即设置,阻止在 setTimeout 等待期内重复调度 * _loginNavigating — navigateToLogin 设置,阻止实际跳转重复 */ /** 无需登录即可访问的页面路径白名单(全局拦截器使用) */ export const PUBLIC_PATHS = [ '/pageSubPack/loginSub/login', '/pageSubPack/loginSub/register-new-user', '/pageSubPack/loginSub/forget-pwd', '/pageSubPack/loginSub/change-pwd', '/pageSubPack/loginSub/privacy', '/pageSubPack/loginSub/user-agreement', '/pages/navigation/navigation' ] // ==================== 登录页跳转防重复锁 ==================== // 使用 getApp().globalData 存储,避免模块被分包/异步 chunk 多次实例化导致锁失效 function getLock() { const app = getApp() if (!app.globalData) app.globalData = {} return app.globalData } /** * 判断当前是否已在登录页 */ function isOnLoginPage() { const pages = getCurrentPages() if (!pages || pages.length === 0) return false const route = pages[pages.length - 1].route || '' return route.includes('loginSub/login') } /** * 跳转到登录页(统一入口,带防重复锁) * 所有需要跳转登录页的地方都应调用此函数,不要直接调用 uni.navigateTo / uni.reLaunch * * @param {object} [options] * @param {string} [options.method='navigateTo'] - 跳转方式:'navigateTo' | 'reLaunch' | 'redirectTo' * @param {string} [options.url='/pageSubPack/loginSub/login?mode=pwd'] - 目标 url */ export function navigateToLogin(options = {}) { const { method = 'navigateTo', url = '/pageSubPack/loginSub/login?mode=pwd' } = options // 已经在登录页,跳过 if (isOnLoginPage()) return const lock = getLock() // reLaunch 优先级最高,强制放行(用于 token 过期、退出登录等场景) if (method !== 'reLaunch' && lock._loginNavigating) return lock._loginNavigating = true const navFn = uni[method] if (!navFn) { lock._loginNavigating = false return } navFn({ url, fail() { // 跳转失败时重置锁,允许后续重试 lock._loginNavigating = false } }) } /** * 重置登录跳转锁(登录页 onLoad 时调用) */ export function resetLoginLock() { const lock = getLock() lock._loginPending = false lock._loginNavigating = false } /** * 判断指定路径是否为公开页面(无需登录) * @param {string} url - navigateTo 的目标 url * @returns {boolean} */ export function isPublicPath(url) { if (!url) return true // 去掉查询参数 const path = url.split('?')[0] return PUBLIC_PATHS.some(p => path === p || path.startsWith(p + '?')) } /** * 检查是否已登录;未登录则弹出提示并跳转登录页 * @param {object} [options] * @param {string} [options.message='请先登录'] - 提示文案 * @param {boolean} [options.redirect=true] - 是否自动跳转登录页 * @returns {boolean} 已登录返回 true,否则返回 false */ export function requireLogin(options = {}) { const { message = '请先登录', redirect = true } = options const token = uni.getStorageSync('token') if (token) return true uni.showToast({ title: message, icon: 'none' }) if (redirect) { // 立即加锁,防止在 setTimeout 等待期内重复调度 const lock = getLock() if (lock._loginPending) return false lock._loginPending = true setTimeout(() => { lock._loginPending = false navigateToLogin({ method: 'navigateTo' }) }, 1000) } return false }