Files
hzdyjs/mobile/src/utils/route-guard.js
T
zlwu afe3e234e9 feat(mobile): 实现全局路由守卫功能,自动处理用户未登录跳转
✨ 新增功能:
- 全局路由守卫模块 (route-guard.js)
- 多层次权限验证 (登录状态+Token存在+Token过期检查)
- 自动拦截机制 (页面级+跳转级+HTTP级+应用级)
- 401响应自动处理和跳转
- 路由守卫测试页面

🔧 技术实现:
- checkPageAccess() 全局页面权限检查
- guardedNavigate/guardedSwitchTab 带权限检查的跳转
- handleUnauthorized() HTTP拦截器401处理
- 公开页面配置管理
- 优雅的错误提示和跳转逻辑

📱 页面集成:
- App.vue 初始化路由守卫
- profile/workbench 页面添加权限检查
- index 页面优化跳转逻辑
- 新增测试页面验证功能

📚 文档完善:
- 移动端路由守卫使用指南
- 详细的变更记录和技术说明
- 最佳实践和注意事项

🎯 核心效果:
用户登录失效或未登录时,访问任何需要权限的页面都自动跳转到登录页,提升安全性和用户体验。
2025-07-04 22:28:49 +08:00

260 lines
6.6 KiB
JavaScript

/**
* 全局路由守卫
* 处理页面访问权限控制和登录状态检查
*/
import { userStorage } from './request';
import { userState } from './user';
// 无需登录验证的页面列表
const PUBLIC_PAGES = [
'/pages/login/index',
'/pages/index/index', // 首页作为公开页面,可以访问但功能受限
];
// 登录页路径
const LOGIN_PAGE = '/pages/login/index';
/**
* 检查页面是否为公开页面
* @param {string} path 页面路径
*/
function isPublicPage(path) {
return PUBLIC_PAGES.some((publicPath) => {
// 精确匹配或前缀匹配
return path === publicPath || path.startsWith(publicPath + '?');
});
}
/**
* 获取当前页面路径
*/
function getCurrentPagePath() {
try {
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
return '/' + currentPage.route;
}
} catch (e) {
console.error('获取当前页面路径失败:', e);
}
return '';
}
/**
* 执行登录检查和跳转
* @param {string} redirectReason 跳转原因
*/
function redirectToLogin(redirectReason = '请先登录') {
console.log('🔄 重定向到登录页:', redirectReason);
// 清除可能存在的无效登录信息
userStorage.clearLoginInfo();
// 提示用户
uni.showToast({
title: redirectReason,
icon: 'none',
duration: 2000,
});
// 延迟跳转,确保Toast显示
setTimeout(() => {
uni.reLaunch({
url: LOGIN_PAGE,
fail: (error) => {
console.error('跳转登录页失败:', error);
// 如果reLaunch失败,尝试其他方式
uni.redirectTo({
url: LOGIN_PAGE,
});
},
});
}, 500);
}
/**
* 全局页面守卫检查
* 在每个页面的onLoad或onShow中调用
* @param {string} pagePath 页面路径(可选,自动获取当前页面)
*/
export function checkPageAccess(pagePath = null) {
try {
// 获取页面路径
const currentPath = pagePath || getCurrentPagePath();
console.log('🔍 检查页面访问权限:', currentPath);
// 如果是公开页面,允许访问
if (isPublicPage(currentPath)) {
console.log('✅ 公开页面,允许访问');
return true;
}
// 检查登录状态
if (!userStorage.isLoggedIn()) {
console.log('❌ 用户未登录');
redirectToLogin('请先登录');
return false;
}
// 检查Token是否存在
const token = userStorage.getToken();
if (!token) {
console.log('❌ Token不存在');
redirectToLogin('登录信息无效,请重新登录');
return false;
}
// 检查Token是否过期
if (userState.checkTokenExpiration()) {
console.log('❌ Token已过期');
redirectToLogin('登录已过期,请重新登录');
return false;
}
console.log('✅ 页面访问权限验证通过');
return true;
} catch (error) {
console.error('页面访问权限检查失败:', error);
redirectToLogin('系统错误,请重新登录');
return false;
}
}
/**
* 页面导航守卫
* 在页面跳转前进行权限检查
* @param {string} url 目标页面URL
* @param {object} options 跳转选项
*/
export function guardedNavigate(url, options = {}) {
try {
console.log('🚀 准备跳转到:', url);
// 解析URL获取页面路径
const urlPath = url.split('?')[0];
// 如果目标是公开页面,直接跳转
if (isPublicPage(urlPath)) {
return uni.navigateTo({ url, ...options });
}
// 检查登录状态
if (!userStorage.isLoggedIn()) {
redirectToLogin('请先登录后访问');
return false;
}
// 检查Token有效性
const token = userStorage.getToken();
if (!token || userState.checkTokenExpiration()) {
redirectToLogin('登录已失效,请重新登录');
return false;
}
// 执行跳转
return uni.navigateTo({ url, ...options });
} catch (error) {
console.error('页面跳转守卫失败:', error);
uni.showToast({
title: '页面跳转失败',
icon: 'none',
});
return false;
}
}
/**
* Tab页面切换守卫
* @param {string} url Tab页面URL
*/
export function guardedSwitchTab(url) {
try {
console.log('🔄 准备切换Tab到:', url);
const urlPath = url.split('?')[0];
// 如果是公开Tab页面,允许切换
if (isPublicPage(urlPath)) {
return uni.switchTab({ url });
}
// 检查登录状态
if (!userStorage.isLoggedIn() || !userStorage.getToken()) {
redirectToLogin('请先登录');
return false;
}
// 检查Token有效性
if (userState.checkTokenExpiration()) {
redirectToLogin('登录已过期,请重新登录');
return false;
}
return uni.switchTab({ url });
} catch (error) {
console.error('Tab切换守卫失败:', error);
return false;
}
}
/**
* 全局混入方法,用于在页面中自动检查权限
*/
export const routeGuardMixin = {
onLoad() {
// 页面加载时检查权限
this.checkPageAccess();
},
onShow() {
// 页面显示时检查权限(防止后台切换回来时状态失效)
this.checkPageAccess();
},
methods: {
// 在页面中可直接调用的权限检查方法
checkPageAccess() {
return checkPageAccess();
},
// 带权限检查的页面跳转
guardedNavigate(url, options) {
return guardedNavigate(url, options);
},
// 带权限检查的Tab切换
guardedSwitchTab(url) {
return guardedSwitchTab(url);
},
},
};
/**
* 初始化路由守卫
* 在App.vue中调用
*/
export function initRouteGuard() {
console.log('🛡️ 初始化全局路由守卫');
// 页面加载完成后检查当前页面权限
uni.$on('pageLoad', () => {
checkPageAccess();
});
// 监听应用进入前台
uni.$on('onShow', () => {
console.log('📱 应用进入前台,检查登录状态');
checkPageAccess();
});
}
export default {
checkPageAccess,
guardedNavigate,
guardedSwitchTab,
routeGuardMixin,
initRouteGuard,
};