处理vue页面登录失败问题
This commit is contained in:
parent
296e6d74df
commit
174f8454c3
@ -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
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user