处理vue页面登录失败问题

This commit is contained in:
andy 2026-08-04 14:50:51 +08:00
parent 296e6d74df
commit 174f8454c3
2 changed files with 54 additions and 42 deletions

View File

@ -238,11 +238,9 @@ const router = createRouter({
// 已添加的动态路由
const addedRouteNames = new Set<string>()
/**
*
*/
*/
export function addDynamicRoutes(menus: any) { //MenuInfo[]
//console.log('[动态路由] 开始处理菜单:', menus)
const addRoutes = (menuList: any) => { //MenuInfo[]
@ -250,22 +248,22 @@ export function addDynamicRoutes(menus: any) { //MenuInfo[]
// 只处理菜单类型(type=2),且有 path
if (menu.type === 2 && menu.path) {
const routeName = 'Dynamic-' + menu.id
// 检查是否已经有同路径的静态路由
const existingRoutes = router.getRoutes()
const menuPath = menu.path.startsWith('/') ? menu.path.slice(1) : menu.path
const pathExists = existingRoutes.some(r => r.path === '/' + menuPath || r.path === menuPath)
if (pathExists) {
// console.log(`[动态路由] 跳过(已存在): ${menuPath}`)
continue
}
if (addedRouteNames.has(routeName)) {
//console.log(`[动态路由] 跳过(已添加): ${menuPath}`)
continue
}
// 判断是否是外链菜单
if (menu.isFrame === 1 && menu.component) {
// 外链菜单,使用 iframe 组件
@ -287,9 +285,9 @@ export function addDynamicRoutes(menus: any) { //MenuInfo[]
const componentName = menu.component.startsWith('/') ? menu.component.slice(1) : menu.component
const componentPath = `/src/views/${componentName}.vue`
const component = modules[componentPath]
//console.log(`[动态路由] 处理: path=${menuPath}, component=${componentPath}, 组件存在=${!!component}`)
if (component) {
router.addRoute('Layout', {
path: menuPath,
@ -308,14 +306,14 @@ export function addDynamicRoutes(menus: any) { //MenuInfo[]
}
}
}
// 递归处理子菜单
if (menu.children && menu.children.length > 0) {
addRoutes(menu.children)
}
}
}
addRoutes(menus)
//console.log('[动态路由] 当前所有路由:', router.getRoutes().map(r => r.path))
}
@ -337,7 +335,7 @@ export function resetRouter() {
router.beforeEach(async (to, _from, next) => {
console.log('路由测试');
const userStore = useUserStore()
//document.title = `${to.meta.title || ''} - CSY Admin`
if (to.meta.requiresAuth === false) {
next()
@ -372,7 +370,7 @@ router.beforeEach(async (to, _from, next) => {
}
// if (!userStore.user) {
// try {
// try {
// await userStore.getInfo()
// // 添加动态路由(只添加新的,不影响已有的)
// addDynamicRoutes(userStore.menus)
@ -389,7 +387,7 @@ router.beforeEach(async (to, _from, next) => {
// next()
// return
// }
})
export default router
export default router

View File

@ -1,4 +1,7 @@
import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios'
import { useUserStore } from '@/stores/user'
// const BASE_API = import.meta.env.VITE_APP_BASE_API
// API响应结构
interface ApiResponse<T = any> {
@ -11,24 +14,12 @@ interface ApiResponse<T = any> {
interface CryptoConfig {
enabled: boolean
publicKey: string
aesKey: string
aesKey: string // AES密钥的Base64编码
}
// 加密配置缓存
let cryptoConfigCache: CryptoConfig | null = null
// 从 localStorage 安全读取 token避免循环依赖
function getToken(): string | null {
try {
const raw = localStorage.getItem('mes-user')
if (raw) {
const data = JSON.parse(raw)
return data?.token || null
}
} catch {}
return null
}
// 获取加密配置
export async function fetchCryptoConfig(): Promise<CryptoConfig> {
if (cryptoConfigCache) {
@ -51,7 +42,7 @@ export function clearCryptoConfigCache() {
cryptoConfigCache = null
}
// 判断是否是AES加密的响应数据
// 判断是否是AES加密的响应数据格式iv.encryptedData
function isAesEncryptedData(data: any): boolean {
if (typeof data !== 'string') {
return false
@ -60,9 +51,11 @@ function isAesEncryptedData(data: any): boolean {
if (parts.length !== 2) {
return false
}
// 检查两部分是否都是有效的Base64且IV长度正确
try {
atob(parts[0])
atob(parts[1])
// IV是12字节Base64后是16字符
return parts[0].length === 16 && parts[1].length > 10
} catch {
return false
@ -80,6 +73,7 @@ async function aesDecrypt(encryptedData: string, aesKeyBase64: string): Promise<
const data = Uint8Array.from(atob(parts[1]), c => c.charCodeAt(0))
const keyBytes = Uint8Array.from(atob(aesKeyBase64), c => c.charCodeAt(0))
// 导入AES密钥
const aesKey = await crypto.subtle.importKey(
'raw',
keyBytes,
@ -88,6 +82,7 @@ async function aesDecrypt(encryptedData: string, aesKeyBase64: string): Promise<
['decrypt']
)
// 解密
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv },
aesKey,
@ -110,24 +105,36 @@ async function decryptResponseData(data: string): Promise<any> {
return JSON.parse(decryptedStr)
} catch (error) {
console.error('响应解密失败', error)
// 解密失败可能是密钥过期,清除缓存
cryptoConfigCache = null
return data
}
}
//axios.defaults.headers["Content-Type"] = 'application/json;charset=utf-8' //"application/x-www-form-urlencoded;charset=utf-8"; //"application/json;charset=utf-8";
// 创建axios实例
// 后端 API 统一使用 /api 前缀
const service: AxiosInstance = axios.create({
baseURL: '/api',
baseURL:'/api',
timeout: 30000
})
// 请求拦截器 - 直接从 localStorage 读取 token
// 请求拦截器
service.interceptors.request.use(
(config) => {
const token = getToken()
if (token) {
config.headers['Authorization'] = token
const userStore = useUserStore()
if (userStore.token) {
config.headers['Authorization'] = userStore.token
}
if (config.data instanceof FormData) {
// 文件上传:不设置 JSON 请求头、不序列化,交给浏览器自动处理 multipart
return config
}
config.headers["Content-Type"] = 'application/json;charset=utf-8'
config.data = config.data instanceof Object ? JSON.stringify(config.data) : config.data
return config
},
(error) => {
@ -141,25 +148,32 @@ let isLoggingOut = false
// 响应拦截器
service.interceptors.response.use(
async (response: AxiosResponse<ApiResponse>) => {
// blob 类型响应直接返回
// 如果是 blob 类型响应(文件下载),直接返回
if (response.config.responseType === 'blob') {
return response.data
}
if (response.config.responseType === 'arraybuffer') {
// 二进制流不走code校验直接完整返回ArrayBuffer
return response.data
}
const res = response.data
if (res.code !== 200) {
// logout 接口返回 401 时不显示错误消息(避免干扰)
const isLogoutRequest = response.config.url?.includes('/auth/logout')
// 401 未授权
// 401 未授权,弹出明确提示并跳转登录(防止重复调用)
if (res.code === 401 && !isLoggingOut && !isLogoutRequest) {
window.$message?.warning('当前用户登录已过期,请重新登录')
isLoggingOut = true
window.$message?.error('当前用户登录已过期,请重新登录')
// 清除本地 token
localStorage.removeItem('mes-user')
const userStore = useUserStore()
await userStore.logout()
isLoggingOut = false
// 跳转到登录页
window.location.href = '/login'
return Promise.reject(new Error('登录已过期'))
}
@ -170,7 +184,7 @@ service.interceptors.response.use(
return Promise.reject(new Error(res.message || '请求失败'))
}
// AES 解密
// 检查响应数据是否是AES加密的自动解密
if (isAesEncryptedData(res.data)) {
try {
return await decryptResponseData(res.data)