路由拦截器的核心思想是 在页面跳转之前插入一道"关卡",统一判断是否放行
plantext
用户点击按钮
│
▼
┌─────────────┐
│ invoke 拦截 │ ← 在这里做登录校验、权限判断、参数清洗
└──────┬───────┘
│
┌────┴────┐
▼ ▼
return true return false
│ │
▼ ▼
正常跳转 阻断跳转(可提示/重定向)
代码实现:uni.addInterceptor 文档:https://uniapp.dcloud.net.cn/api/interceptor.html
js
uni.addInterceptor(method, {
// 调用前拦截 ------ 返回 false 可阻断跳转
invoke(args) {
// args.url = '/pages/index/index?id=1'
// args.events = { ... } // Vue3 的事件通信
return true // true 放行,false 阻断
},
// 成功回调拦截
success(result) {
// 可在此修改回调参数
return result
},
// 失败回调拦截
fail(err) {
console.error(err)
return err
},
// 完成回调拦截(无论成功失败)
complete(result) {
return result
}
})
可拦截的路由方法(共 5 个)
| 方法 | 行为 | 被拦截 |
|---|---|---|
| uni.navigateTo | 保留当前页,跳转新页面(可返回) | 是 |
| uni.redirectTo | 关闭当前页,跳转新页面(不可返回) | 是 |
| uni.reLaunch | 关闭所有页面,打开新页面 | 是 |
| uni.switchTab | 跳转 TabBar 页面 | 是 |
| uni.navigateBack | 返回上一页 | 否(无法拦截) |
完整路由拦截代码实现
js
/**
* uni-app 路由拦截器
*
* 在 main.js 中 new Vue() 之前引入:
* import '@/common/routerInterceptor.js'
*
* 功能:
* 1. 模块加载时对 H5 首屏做登录校验
* 2. 拦截 navigateTo / redirectTo / reLaunch / switchTab,未登录一律跳登录页
* 3. 跨 Tab 页 token 被清除时自动同步(仅 H5)
*/
import store from '@/store/index.js'
// 常量
//登录页面path
const LOGIN_PAGE = '/pages/login/login'
// 日志头
const LOG_TAG = '[RouterInterceptor]'
/** 免登录白名单 */
const whiteSet = new Set([
'/pages/login/login',
'/pages/test/test2',
'/pages/test/test',
// 按需追加 ...
])
/** 当前运行平台是否为 H5 */
// VUE_APP_PLATFORM 避免使用uniapp cli 构建时无此变量
// const isH5 = process.env.VUE_APP_PLATFORM === 'h5'
// #ifdef H5
const isH5 = true
// #endif
// #ifndef H5
const isH5 = false
// #endif
/** 从 store 中实时读取 token */
const getToken = () => {
//localStorage.getItem('token')
return store.getters.token
}
/**
* 从完整 url 中提取页面路径(去掉 ? 及后面的参数)
* @param {string} url - e.g. '/pages/index/index?id=1'
* @returns {string} e.g. '/pages/index/index'
*/
function extractPath(url) {
return url.split('?')[0]
}
/**
* 判断页面路径是否需要鉴权
* @param {string} path
* @returns {boolean}
*/
function requiresAuth(path) {
return !whiteSet.has(path)
}
/**
* 获取当前路由路径(处理 hash / history 模式)
* - hash 模式:返回 hash 中的路径部分(不含 # 和查询参数)
* - history 模式:返回 pathname
* @returns {string} 例如 "/user/detail"
*/
function getCurrentRoute() {
const hash = location.hash;
// hash 以 #/ 开头视为 hash 路由(#/path)
if (hash && hash.charAt(0) === '#' && hash.charAt(1) === '/') {
// #/user/detail?id=1 => /user/detail
const hash = location.hash.slice(1); // 去掉 #
const qIdx = hash.indexOf('?');
const pIdx = hash.indexOf('#'); // hash 内嵌锚点
let end = hash.length;
if (qIdx !== -1) end = Math.min(end, qIdx);
if (pIdx !== -1) end = Math.min(end, pIdx);
return hash.slice(0, end) || '/';
}
return location.pathname || '/';
}
/**
* 跳转到登录页
* - 防止已在登录页时重复跳转
* - toast 提示后延时跳转,让用户能看到提示
*/
function jumpToLogin() {
const pages = getCurrentPages()
const currentPage = pages.length ? `/${pages[pages.length - 1].route}` : ''
console.log(LOG_TAG, `jumpToLogin, current: ${currentPage}`)
if (currentPage === LOGIN_PAGE) return
uni.showToast({ title: '请先登录', icon: 'none', duration: 1500 })
setTimeout(() => {
uni.redirectTo({ url: LOGIN_PAGE })
}, 500)
}
// 模块加载时立即执行一次,处理用户直接通过地址栏访问的场景
;
(function initialGuard() {
const token = getToken()
const currentPath = getCurrentRoute()
console.log(LOG_TAG, `initial path = ${currentPath}, hasToken = ${!!token}`)
if (!token && requiresAuth(currentPath)) {
jumpToLogin()
}
})()
// 路由方法拦截
const METHODS = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab']
METHODS.forEach((method) => {
uni.addInterceptor(method, {
invoke(args) {
const path = extractPath(args.url)
const token = getToken()
console.log(LOG_TAG, `${method} → ${path}, hasToken = ${!!token}`)
if (!token && requiresAuth(path)) {
jumpToLogin()
return false // 阻断本次跳转
}
return true
},
fail(err) {
console.error(LOG_TAG, `${method} failed:`, err)
},
})
})
// 跨 Tab 页 token 同步(仅 H5)
// 可以在另一个 Tab 页退出登录,通过 localStorage 事件通知本页清除 token
if (isH5 && typeof window !== 'undefined') {
window.addEventListener('storage', (e) => {
if (e.key === 'token' && !e.newValue) {
console.log(LOG_TAG, 'token removed in another tab, syncing')
store.commit('SET_TOKEN', '')
}
})
}
跳转流程如下:
plantext
uni.navigateTo({ url: '/pages/profile/profile?id=1' })
│
▼
invoke(args) 触发
│
▼
extractPath(args.url) → '/pages/profile/profile'
│
▼
getToken() 有值吗?
│
┌────┴────┐
Yes No
│ │
▼ ▼
在白名单? 在白名单?
│ │
┌┴┐ ┌┴┐
No Yes No Yes
│ │ │ │
▼ ▼ ▼ ▼
✗ ✓ ✗ ✓
│ │ │ │
│ │ │ └→ 放行 return true
│ └→ 放行 return true
│
▼
return true (有 token 就直接放行,不看白名单)
getToken() = null, 不在白名单:
│
▼
redirectToLogin('/pages/profile/profile')
│
▼
showToast('请先登录')
│
▼ (1500ms)
uni.redirectTo({
url: '/pages/login/login?redirect=%2Fpages%2Fprofile%2Fprofile'
})
这里token 过期交给服务处理,可以使用 请求拦截器(axios / uni.request)的响应拦截统一处理:
js
// 响应拦截器(401 处理)
uni.addInterceptor('request', {
success(res) {
if (res.statusCode === 401) {
store.commit('SET_TOKEN', '')
redirectToLogin()
}
return res
}
})