✨ 新增功能: - 全局路由守卫模块 (route-guard.js) - 多层次权限验证 (登录状态+Token存在+Token过期检查) - 自动拦截机制 (页面级+跳转级+HTTP级+应用级) - 401响应自动处理和跳转 - 路由守卫测试页面 🔧 技术实现: - checkPageAccess() 全局页面权限检查 - guardedNavigate/guardedSwitchTab 带权限检查的跳转 - handleUnauthorized() HTTP拦截器401处理 - 公开页面配置管理 - 优雅的错误提示和跳转逻辑 📱 页面集成: - App.vue 初始化路由守卫 - profile/workbench 页面添加权限检查 - index 页面优化跳转逻辑 - 新增测试页面验证功能 📚 文档完善: - 移动端路由守卫使用指南 - 详细的变更记录和技术说明 - 最佳实践和注意事项 🎯 核心效果: 用户登录失效或未登录时,访问任何需要权限的页面都自动跳转到登录页,提升安全性和用户体验。
260 lines
6.6 KiB
JavaScript
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,
|
|
};
|