一、明确登录状态与页面访问权限
UniApp项目中,“未登录跳转登录页”通常不是单纯判断一个变量这么简单。实际开发时需要同时考虑页面类型、登录状态、Token有效期、跳转方式以及登录后的返回路径。
比较常见的页面可以分成两类:
-
公开页面:首页、商品列表、文章详情等,未登录也可以访问。
-
受保护页面:个人中心、订单列表、收藏、支付、账户设置等,必须登录后才能进入。
因此,更合理的做法是建立统一的登录状态判断机制,而不是在每个页面中重复编写 if (!token)。
例如,可以将登录凭证保存到本地:
JavaScriptconst token = uni.getStorageSync('token') if (!token) { uni.navigateTo({ url: '/pages/login/login' }) }
这种方式虽然简单,但随着项目规模扩大,很容易出现大量重复代码,也不方便统一维护。
二、使用Token判断用户是否登录
前端项目通常会通过 Token 判断用户当前是否处于登录状态。
登录成功后保存 Token:
JavaScriptuni.setStorageSync('token', res.data.token)
需要访问用户信息时读取:
JavaScriptconst token = uni.getStorageSync('token') if (token) { console.log('用户已登录') } else { console.log('用户未登录') }
退出登录时清除:
JavaScriptuni.removeStorageSync('token')
如果项目同时保存了用户信息,也可以一起清理:
JavaScriptuni.removeStorageSync('token') uni.removeStorageSync('userInfo')
需要注意的是,本地存在 Token 并不意味着 Token 一定有效。例如 Token 可能已经过期,因此生产项目还应该结合接口返回状态码处理登录失效问题。
三、页面级实现未登录跳转
对于某些特定页面,可以直接在页面生命周期中进行判断。
例如个人中心页面:
JavaScriptexport default { onShow() { const token = uni.getStorageSync('token') if (!token) { uni.navigateTo({ url: '/pages/login/login' }) return } this.loadUserInfo() }, methods: { loadUserInfo() { console.log('加载用户信息') } } }
这里使用 onShow 而不是单纯使用 onLoad 有一个实际好处:用户从登录页返回后,页面再次显示时可以重新检查登录状态。
不过,如果项目中存在很多需要登录的页面,每个页面都这样处理,会产生明显的重复代码。
四、通过页面白名单统一控制登录
更推荐的方式是维护一个“不需要登录”的页面白名单。
例如:
JavaScriptconst whiteList = [ '/pages/index/index', '/pages/login/login', '/pages/register/register', '/pages/article/detail' ]
然后创建统一判断方法:
JavaScriptexport function checkLogin(url) { const token = uni.getStorageSync('token') if (whiteList.includes(url)) { return true } if (!token) { uni.navigateTo({ url: `/pages/login/login?redirect=${encodeURIComponent(url)}` }) return false } return true }
这种设计将“哪些页面需要登录”和“如何跳转登录页”分离开来,后期新增业务页面时,只需要决定该页面是否加入白名单即可。
五、通过uni.addInterceptor拦截页面跳转
UniApp提供了页面跳转拦截能力,可以针对 navigateTo、redirectTo、reLaunch 等行为进行统一控制。
例如:
JavaScriptconst whiteList = [ '/pages/index/index', '/pages/login/login' ] uni.addInterceptor('navigateTo', { invoke(args) { const token = uni.getStorageSync('token') if (whiteList.includes(args.url.split('?')[0])) { return true } if (!token) { uni.navigateTo({ url: `/pages/login/login?redirect=${encodeURIComponent(args.url)}` }) return false } return true } })
这样,当用户执行:
JavaScriptuni.navigateTo({ url: '/pages/order/order' })
如果没有 Token,就会被拦截并跳转到登录页面。
不过这种方案主要针对程序主动发起的页面跳转。对于底部 TabBar 页面、启动页面以及部分特殊导航场景,还需要结合页面生命周期进行处理,不能简单认为一个拦截器就能覆盖所有登录控制需求。
六、登录后自动返回原页面
用户体验比较好的方案并不是“未登录就跳登录页”,而是:
用户访问受保护页面 → 跳转登录 → 登录成功 → 自动返回原页面。
例如用户原本准备进入:
/pages/order/order
发现没有登录后:
JavaScriptuni.navigateTo({ url: `/pages/login/login?redirect=${encodeURIComponent('/pages/order/order')}` })
登录页面获取参数:
JavaScriptexport default { onLoad(options) { this.redirect = options.redirect || '' } }
登录成功后:
JavaScriptmethods: { loginSuccess(token) { uni.setStorageSync('token', token) if (this.redirect) { uni.redirectTo({ url: decodeURIComponent(this.redirect) }) } else { uni.switchTab({ url: '/pages/index/index' }) } } }
这种方式可以明显减少用户重复操作。
例如用户点击“我的订单”,系统发现未登录,进入登录页。登录成功后直接进入订单页面,而不是重新让用户寻找刚才的功能。
七、处理TabBar页面的特殊跳转
UniApp中的 TabBar 页面不能简单使用 navigateTo 跳转。
例如:
JavaScriptuni.switchTab({ url: '/pages/user/user' })
如果登录页需要返回的是 TabBar 页面,就应该根据目标页面类型选择对应的 API。
常见跳转方式可以这样区分:
JavaScriptuni.navigateTo({ url: '/pages/order/order' })
适用于普通页面。
JavaScriptuni.redirectTo({ url: '/pages/order/order' })
适用于关闭当前页面后进入目标页面。
JavaScriptuni.reLaunch({ url: '/pages/index/index' })
适用于重新启动页面栈。
JavaScriptuni.switchTab({ url: '/pages/user/user' })
适用于 TabBar 页面。
因此,登录成功后的返回逻辑最好不要简单写死成一个 API,而应该根据目标页面类型进行处理。
八、封装统一的登录检查方法
实际项目中,可以创建一个 auth.js 文件。
JavaScriptconst whiteList = [ '/pages/index/index', '/pages/login/login', '/pages/register/register' ] export function isLogin() { return !!uni.getStorageSync('token') } export function requireLogin(redirect = '') { if (isLogin()) { return true } const target = redirect || '/pages/index/index' uni.navigateTo({ url: `/pages/login/login?redirect=${encodeURIComponent(target)}` }) return false } export function logout() { uni.removeStorageSync('token') uni.removeStorageSync('userInfo') }
业务页面只需要调用:
JavaScriptimport { requireLogin } from '@/utils/auth.js' export default { onShow() { if (!requireLogin('/pages/order/order')) { return } this.getOrderList() } }
这样可以将登录逻辑集中管理。
九、登录状态失效时自动跳转
只检查本地 Token 还不够。
例如用户登录后保存了 Token:
token = abc123
几天后服务器已经将该 Token 判定为过期,但浏览器本地仍然保存着 abc123。
此时:
JavaScriptuni.getStorageSync('token')
仍然能够获取到值,因此单纯判断 Token 是否存在会产生误判。
更可靠的方式是在网络请求封装层统一处理,例如:
JavaScriptuni.request({ url: baseUrl + '/api/order/list', method: 'GET', header: { Authorization: `Bearer ${uni.getStorageSync('token')}` }, success(res) { if (res.statusCode === 401) { uni.removeStorageSync('token') uni.navigateTo({ url: '/pages/login/login' }) return } // 正常处理业务数据 } })
实际项目一般会进一步封装 request 方法,让所有接口统一处理 401、Token过期和登录失效。
十、避免重复跳转登录页
一个常见问题是:多个接口同时返回未登录状态,可能连续执行多次:
JavaScriptuni.navigateTo({ url: '/pages/login/login' })
最终可能出现多个登录页面堆叠。
可以增加一个跳转锁:
JavaScriptlet isRedirecting = false export function goLogin() { if (isRedirecting) { return } isRedirecting = true uni.navigateTo({ url: '/pages/login/login', complete() { setTimeout(() => { isRedirecting = false }, 500) } }) }
之后统一使用:
JavaScriptgoLogin()
而不是在各个业务模块中直接调用 navigateTo。
十一、推荐的完整实现结构
一个相对清晰的 UniApp 登录控制结构可以设计为:
项目 ├── pages │ ├── index │ ├── login │ ├── order │ └── user ├── utils │ ├── auth.js │ └── request.js ├── store │ └── user.js └── App.vue
其中:
auth.js
负责登录状态、退出登录和登录跳转。
request.js
负责 Token 注入、接口请求以及 401 登录失效处理。
user.js
负责集中维护用户信息和登录状态。
页面生命周期
负责页面进入时的权限检查。
这种职责划分比把所有代码塞进 App.vue 更容易维护。
十二、Vue 3 Composition API中的实现方式
如果使用 UniApp + Vue 3,可以进一步采用组合式函数封装。
例如:
JavaScript// composables/useAuth.js import { computed } from 'vue' export function useAuth() { const token = computed(() => uni.getStorageSync('token')) const isLogin = computed(() => !!token.value) const login = (tokenValue) => { uni.setStorageSync('token', tokenValue) } const logout = () => { uni.removeStorageSync('token') uni.removeStorageSync('userInfo') } const checkLogin = (redirect) => { if (isLogin.value) { return true } uni.navigateTo({ url: `/pages/login/login?redirect=${encodeURIComponent(redirect)}` }) return false } return { token, isLogin, login, logout, checkLogin } }
页面中可以这样使用:
JavaScriptimport { useAuth } from '@/composables/useAuth.js' const { isLogin, checkLogin } = useAuth() const openOrder = () => { if (!checkLogin('/pages/order/order')) { return } uni.navigateTo({ url: '/pages/order/order' }) }
对于采用 Vue 3 开发的中大型 UniApp 项目,这种方式可以减少业务代码和认证逻辑之间的耦合。
十三、登录跳转方案的选择建议
不同项目可以采用不同方案:
| 方案 | 适用场景 | 推荐程度 |
|---|---|---|
| 页面内直接判断Token | 小型项目、少量受保护页面 | ★★★ |
| 白名单统一判断 | 中小型项目 | ★★★★ |
| 页面跳转拦截 | 有统一路由控制需求的项目 | ★★★★ |
| 请求层处理401 | 所有需要Token认证的项目 | ★★★★★ |
| Auth封装 + 请求拦截 | 中大型项目 | ★★★★★ |
比较稳妥的组合通常是:
页面权限控制 + Auth统一封装 + 请求层401处理 + 登录后返回原页面。
这样既能解决“用户未登录无法进入页面”的问题,也能处理“登录状态后来失效”的情况。
十四、开发时容易踩到的坑
1. 只判断Token是否存在
JavaScriptif (uni.getStorageSync('token')) { // 已登录 }
只能说明本地存在 Token,并不能证明服务器端认证仍然有效。
2. 登录页本身被拦截
如果没有把登录页加入白名单,就可能出现:
未登录 ↓ 访问登录页 ↓ 权限检查 ↓ 发现未登录 ↓ 再次跳登录页
最终形成重复跳转。
3. 忽略TabBar页面
switchTab 与 navigateTo 的使用规则不同,登录后的目标页面如果属于 TabBar,就应该使用正确的导航方式。
4. 没有保存redirect
没有保存原始访问地址时,登录成功后只能返回固定首页,用户体验较差。
5. 退出登录后没有清理用户数据
如果只删除 Token,却保留旧的 userInfo,页面可能短时间显示已经退出账号的用户信息。
6. 多处重复实现登录逻辑
项目中同时存在:
JavaScriptif (!token) {}
JavaScriptif (!isLogin()) {}
以及多个不同版本的登录跳转代码,后期非常容易产生行为不一致的问题。
十五、一个更完整的业务流程
实际应用中,可以将整个认证流程设计成:
用户访问页面 ↓ 判断页面是否需要登录 ↓ ┌──是──→ 检查Token │ ↓ │ Token有效? │ ↙ ↘ │ 是 否 │ ↓ ↓ │ 正常进入 保存当前页面 │ ↓ │ 登录页 │ ↓ │ 登录成功 │ ↓ │ 保存新Token │ ↓ │ 返回原页面 │ └──否──→ 直接访问
接口请求则独立处理:
发起接口请求 ↓ 携带Token ↓ 服务器验证 ↓ 返回401? ↙ ↘ 是 否 ↓ ↓ 清理Token 正常处理 ↓ 跳转登录页
前端页面权限和后端接口权限形成两层保护,可以避免仅依赖前端判断带来的安全问题。
十六、前端登录判断不能代替后端鉴权
需要特别强调,UniApp中的登录拦截主要解决的是用户体验和页面访问控制,不能作为真正的安全边界。
例如:
JavaScriptif (token) { uni.navigateTo({ url: '/pages/admin/admin' }) }
只能控制普通用户界面上的页面跳转。
真正的接口权限仍然必须由服务器验证 Token、用户身份以及操作权限。
因此,即使用户通过修改客户端代码绕过页面判断,也不应该能够通过接口直接获取无权限的数据。
一个可靠的系统应该遵循:
前端:控制页面访问体验 + 后端:验证身份与权限
两者缺一不可。
十七、推荐的最终方案
如果是普通的 UniApp 项目,可以采用以下思路:
-
登录成功后保存 Token。
-
使用 Auth 模块统一判断登录状态。
-
设置公开页面白名单。
-
受保护页面进入时检查登录状态。
-
未登录时保存当前页面路径。
-
跳转到登录页面。
-
登录成功后恢复原页面。
-
请求层统一携带 Token。
-
收到
401时清理失效 Token。 -
避免重复跳转登录页面。
-
对 TabBar 页面使用
switchTab。 -
后端始终进行真正的身份和权限验证。
对于小型项目,可以直接使用页面生命周期判断;对于中大型项目,则建议将 Auth、路由控制、请求拦截、Token管理和登录回跳 分开设计。这样不仅能够实现 UniApp 未登录跳转登录页,还能形成一套完整、稳定且易于扩展的用户认证机制。