Commit 0a384086 by User

路由丢失问题解决

parent 072843b7
# 动态路由刷新修复方案 v2.0
## 问题分析
### 根本原因
1. **初始化时序问题**: `window.uiGlobalConfig` 在页面刷新时可能没有及时初始化
2. **状态丢失**: 动态路由存储在内存中,页面刷新后丢失
3. **依赖缺失**: 没有可靠的状态恢复机制
### 修复策略
- ✅ 添加localStorage缓存机制
- ✅ 实现智能等待和重试逻辑
- ✅ 提供多级降级方案
- ✅ 增强错误处理和日志
## 修复内容
### 1. 缓存机制 (`src/router/routes/index.ts`)
```typescript
// 新增功能:
- cacheDynamicRoutes(): 缓存路由到localStorage
- restoreDynamicRoutesFromCache(): 从缓存恢复路由
- waitForGlobalConfig(): 等待uiGlobalConfig初始化
- clearDynamicRoutesCache(): 清除缓存(登录时调用)
```
### 2. 改进的路由生成 (`generateDynamicRoutes`)
- 支持强制刷新参数
- 优先从缓存恢复
- 智能等待uiGlobalConfig
- 自动缓存新生成的路由
- 完善的错误处理
### 3. 增强的路由初始化 (`src/store/modules/route/index.ts`)
- initDynamicAuthRoute: 确保动态路由数据可用
- forceReloadAuthRoute: 强制重新生成(忽略缓存)
- 多级降级方案
### 4. 登录流程优化 (`src/store/modules/auth/index.ts`)
- 登录成功后清除旧缓存
- 确保获取最新路由数据
### 5. 调试工具 (`src/utils/route-debug.ts`)
- debugRouteState(): 全局调试函数
- 详细的日志记录
## 使用说明
### 开发环境测试
1. 启动项目: `pnpm dev`
2. 登录系统
3. 导航到任意动态路由页面
4. 刷新页面(F5 或 Ctrl+F5)
5. 检查控制台日志,确认路由恢复成功
### 调试命令
在浏览器控制台中使用:
```javascript
// 查看路由状态
debugRouteState()
// 手动清除缓存
localStorage.removeItem('dynamic_routes_cache')
// 查看缓存内容
JSON.parse(localStorage.getItem('dynamic_routes_cache') || '{}')
```
### 预期日志输出
正常情况下应该看到:
```
📍 从缓存恢复了 X 个动态路由
📍 开始初始化动态认证路由...
✅ 动态认证路由初始化成功
```
异常情况下会看到:
```
⚠️ 动态路由缓存已过期,清除缓存
📍 开始生成动态路由...
✅ 成功生成并缓存了 X 个动态路由
```
## 缓存策略
### 缓存触发条件
- 首次成功生成动态路由
- uiGlobalConfig.InternalCode 存在
### 缓存失效条件
- 超过24小时
- InternalCode 不匹配
- 缓存数据格式错误
### 缓存清除时机
- 用户登录成功
- 手动调用 clearDynamicRoutesCache()
## 故障排查
### 如果路由仍然丢失
1. 检查控制台是否有错误信息
2. 运行 `debugRouteState()` 查看状态
3. 检查 localStorage 中是否有缓存数据
4. 确认 uiGlobalConfig.InternalCode 是否正确设置
### 常见问题
1. **uiGlobalConfig 未初始化**: 等待最多10秒,超时后报错
2. **API调用失败**: 自动重试,最终使用缓存数据
3. **缓存过期**: 自动清除并重新生成
## 性能优化
### 缓存优势
- 页面刷新时立即可用,无需等待API
- 减少不必要的API调用
- 提升用户体验
### 内存优化
- 缓存大小限制在合理范围内
- 自动清理过期数据
- 避免内存泄漏
## 向后兼容
- 完全兼容现有代码
- 不影响原有功能
- 渐进式增强
<!doctype html> <!doctype html>
<html lang="zh-cmn-Hans"> <html lang="zh-cmn-Hans">
<head> <head>
<meta name="buildTime" content="2025-06-19 14:14:13"> <meta name="buildTime" content="2025-06-19 17:47:20">
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" /> <link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" /> <meta name="color-scheme" content="light dark" />
<title>VueDashboard</title> <title>VueDashboard</title>
<script type="module" crossorigin src="/Content/VueDashboardUi/VueDashboard1/assets/index-DIrm9V-K.js"></script> <script type="module" crossorigin src="/Content/VueDashboardUi/VueDashboard1/assets/index-DGGMzivr.js"></script>
<link rel="stylesheet" crossorigin href="/Content/VueDashboardUi/VueDashboard1/assets/index-B2SFJ6Fn.css"> <link rel="stylesheet" crossorigin href="/Content/VueDashboardUi/VueDashboard1/assets/index-B2SFJ6Fn.css">
</head> </head>
<body> <body>
......
...@@ -190,39 +190,155 @@ const customRoutes: ElegantRoute[] = [ ...@@ -190,39 +190,155 @@ const customRoutes: ElegantRoute[] = [
// 将动态菜单路由存储在全局变量中 // 将动态菜单路由存储在全局变量中
let dynamicMenuRoutes: ElegantRoute[] = []; let dynamicMenuRoutes: ElegantRoute[] = [];
// 动态路由缓存键名
const DYNAMIC_ROUTES_CACHE_KEY = 'dynamic_routes_cache';
const GLOBAL_CONFIG_CACHE_KEY = 'ui_global_config_cache';
// 等待 uiGlobalConfig 初始化的函数
function waitForGlobalConfig(timeout = 10000): Promise<any> {
return new Promise((resolve, reject) => {
if (window.uiGlobalConfig?.InternalCode) {
resolve(window.uiGlobalConfig);
return;
}
let attempts = 0;
const maxAttempts = timeout / 100;
const checkInterval = setInterval(() => {
attempts++;
if (window.uiGlobalConfig?.InternalCode) {
clearInterval(checkInterval);
resolve(window.uiGlobalConfig);
} else if (attempts >= maxAttempts) {
clearInterval(checkInterval);
console.warn('等待 uiGlobalConfig 初始化超时');
reject(new Error('uiGlobalConfig initialization timeout'));
}
}, 100);
});
}
// 缓存动态路由到 localStorage
function cacheDynamicRoutes(routes: ElegantRoute[], globalConfig: any) {
try {
const cacheData = {
routes,
timestamp: Date.now(),
internalCode: globalConfig.InternalCode,
version: '1.0' // 版本号,用于缓存失效
};
localStorage.setItem(DYNAMIC_ROUTES_CACHE_KEY, JSON.stringify(cacheData));
} catch (error) {
console.warn('缓存动态路由失败:', error);
}
}
// 从 localStorage 恢复动态路由
function restoreDynamicRoutesFromCache(): boolean {
try {
const cached = localStorage.getItem(DYNAMIC_ROUTES_CACHE_KEY);
if (!cached) return false;
const cacheData = JSON.parse(cached);
// 检查缓存是否过期(24小时)
const now = Date.now();
const cacheAge = now - cacheData.timestamp;
const maxAge = 24 * 60 * 60 * 1000; // 24小时
if (cacheAge > maxAge) {
console.log('动态路由缓存已过期,清除缓存');
localStorage.removeItem(DYNAMIC_ROUTES_CACHE_KEY);
return false;
}
// 检查 InternalCode 是否匹配
if (window.uiGlobalConfig?.InternalCode &&
cacheData.internalCode !== window.uiGlobalConfig.InternalCode) {
console.log('InternalCode 不匹配,清除缓存');
localStorage.removeItem(DYNAMIC_ROUTES_CACHE_KEY);
return false;
}
// 恢复路由数据
if (cacheData.routes && Array.isArray(cacheData.routes)) {
dynamicMenuRoutes = [...cacheData.routes];
console.log(`从缓存恢复了 ${dynamicMenuRoutes.length} 个动态路由`);
return true;
}
return false;
} catch (error) {
console.warn('恢复动态路由缓存失败:', error);
localStorage.removeItem(DYNAMIC_ROUTES_CACHE_KEY);
return false;
}
}
// 动态获取菜单数据并生成路由 // 动态获取菜单数据并生成路由
export async function generateDynamicRoutes() { export async function generateDynamicRoutes(forceRefresh = false) {
try { try {
// 如果不是强制刷新,先尝试从缓存中恢复
if (!forceRefresh) {
const cached = restoreDynamicRoutesFromCache();
if (cached) {
console.log('从缓存中恢复动态路由成功');
return;
}
}
// 清空之前的动态路由 // 清空之前的动态路由
dynamicMenuRoutes = []; dynamicMenuRoutes = [];
if (!window.uiGlobalConfig?.InternalCode) { // 等待 uiGlobalConfig 初始化
console.warn('InternalCode not found, skipping dynamic route generation'); const globalConfig = await waitForGlobalConfig();
if (!globalConfig?.InternalCode) {
console.warn('InternalCode not found even after waiting, skipping dynamic route generation');
return; return;
} }
console.log('开始生成动态路由...');
const { data: menus } = await getRootMenu( const { data: menus } = await getRootMenu(
`/Restful/Kivii.Basic.Entities.Menu/Show.json?RootInternalCode=${window.uiGlobalConfig.InternalCode}` `/Restful/Kivii.Basic.Entities.Menu/Show.json?RootInternalCode=${globalConfig.InternalCode}`
); );
const MenuThree = getMenuThree(menus?.MenusMain?.Results); if (!menus || !menus.MenusMain?.Results) {
const MenuRoot = menus?.MenuRoot; console.warn('菜单数据为空,无法生成动态路由');
return;
}
const MenuThree = getMenuThree(menus.MenusMain.Results);
const MenuRoot = menus.MenuRoot;
// 存储 MenuRoot 到 store // 存储 MenuRoot 到 store
if (MenuRoot) { if (MenuRoot) {
setTimeout(() => { setTimeout(() => {
const routeStore = useRouteStore(); try {
routeStore.setMenuRoot(MenuRoot); const routeStore = useRouteStore();
routeStore.setMenuRoot(MenuRoot);
} catch (error) {
console.warn('设置 MenuRoot 到 store 失败:', error);
}
}, 100); }, 100);
} }
const MenuThree2 = generateRoutes(MenuThree); const MenuThree2 = generateRoutes(MenuThree);
if (MenuThree2.length > 0) { if (MenuThree2.length > 0) {
dynamicMenuRoutes = [...MenuThree2]; dynamicMenuRoutes = [...MenuThree2];
// 缓存到 localStorage
cacheDynamicRoutes(dynamicMenuRoutes, globalConfig);
console.log(`成功生成并缓存了 ${MenuThree2.length} 个动态路由`);
} else {
console.warn('未生成任何动态路由');
} }
} catch (error) { } catch (error) {
console.error('Failed to generate dynamic routes:', error); console.error('生成动态路由失败:', error);
// 失败时尝试从缓存恢复
restoreDynamicRoutesFromCache();
} }
} }
...@@ -313,13 +429,30 @@ export function getAuthVueRoutes(routes: ElegantConstRoute[]) { ...@@ -313,13 +429,30 @@ export function getAuthVueRoutes(routes: ElegantConstRoute[]) {
return transformElegantRoutesToVueRoutes(routes, layouts, views); return transformElegantRoutesToVueRoutes(routes, layouts, views);
} }
// 初始化时生成一次动态路由(兼容性) // 添加清除缓存的导出函数
// 确保在浏览器环境且必要的全局配置已准备好时才执行 export function clearDynamicRoutesCache() {
if (typeof window !== 'undefined' && window.uiGlobalConfig?.InternalCode) { try {
// 使用 setTimeout 确保应用完全初始化后再执行 localStorage.removeItem(DYNAMIC_ROUTES_CACHE_KEY);
setTimeout(() => { console.log('动态路由缓存已清除');
generateDynamicRoutes().catch(error => { } catch (error) {
console.warn('Initial dynamic route generation failed:', error); console.warn('清除动态路由缓存失败:', error);
}); }
}, 100); }
// 应用启动时尝试恢复动态路由
// 这会在模块加载时立即执行,确保路由尽早可用
if (typeof window !== 'undefined') {
// 先尝试从缓存恢复
const restored = restoreDynamicRoutesFromCache();
if (!restored) {
// 如果缓存恢复失败,尝试等待 uiGlobalConfig 并生成新路由
setTimeout(async () => {
try {
await generateDynamicRoutes(false); // 不强制刷新,允许从缓存恢复
} catch (error) {
console.warn('初始动态路由生成失败:', error);
}
}, 500); // 增加延迟时间,确保 uiGlobalConfig 有更多时间初始化
}
} }
...@@ -100,6 +100,14 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => { ...@@ -100,6 +100,14 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
/** Initialize routes after login */ /** Initialize routes after login */
async function initializeAuthRoutes(redirect: boolean) { async function initializeAuthRoutes(redirect: boolean) {
// 登录后清除动态路由缓存,确保获取最新数据
try {
const { clearDynamicRoutesCache } = await import('@/router/routes');
clearDynamicRoutesCache();
} catch (error) {
console.warn('清除动态路由缓存失败:', error);
}
await routeStore.forceReloadAuthRoute(); await routeStore.forceReloadAuthRoute();
if (!routeStore.isInitAuthRoute) { if (!routeStore.isInitAuthRoute) {
......
...@@ -229,6 +229,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => { ...@@ -229,6 +229,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
/** Force reload auth route - 强制重新加载路由 */ /** Force reload auth route - 强制重新加载路由 */
async function forceReloadAuthRoute() { async function forceReloadAuthRoute() {
console.log('强制重新加载认证路由...');
// 重置路由状态 // 重置路由状态
setIsInitAuthRoute(false); setIsInitAuthRoute(false);
...@@ -238,11 +240,13 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => { ...@@ -238,11 +240,13 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
// 清除现有的auth routes // 清除现有的auth routes
authRoutes.value = []; authRoutes.value = [];
// 重新生成动态路由数据 // 强制重新生成动态路由数据(忽略缓存)
await generateDynamicRoutes(); await generateDynamicRoutes(true);
// 重新初始化路由 // 重新初始化路由
await initAuthRoute(); await initAuthRoute();
console.log('认证路由重新加载完成');
} }
/** Init static auth route */ /** Init static auth route */
...@@ -264,38 +268,61 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => { ...@@ -264,38 +268,61 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
/** Init dynamic auth route */ /** Init dynamic auth route */
async function initDynamicAuthRoute() { async function initDynamicAuthRoute() {
// 首先尝试使用后端接口获取路由 try {
const { data, error } = await fetchGetUserRoutes(); console.log('开始初始化动态认证路由...');
if (!error && data?.routes) { // 确保动态路由数据可用(从缓存或API)
const { routes, home } = data; await generateDynamicRoutes(false);
addAuthRoutes(routes); // 首先尝试使用后端接口获取路由
const { data, error } = await fetchGetUserRoutes();
handleConstantAndAuthRoutes(); if (!error && data?.routes) {
const { routes, home } = data;
setRouteHome(home); addAuthRoutes(routes);
handleUpdateRootRouteRedirect(home); handleConstantAndAuthRoutes();
setIsInitAuthRoute(true); setRouteHome(home);
} else {
// 如果后端接口获取失败,使用静态路由模式
console.warn('Dynamic route fetch failed, fallback to static routes');
// 重新创建静态路由(这会重新调用接口获取最新菜单数据) handleUpdateRootRouteRedirect(home);
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
if (authStore.isStaticSuper) { setIsInitAuthRoute(true);
addAuthRoutes(staticAuthRoutes); console.log('动态认证路由初始化成功(使用后端接口)');
} else { } else {
const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles); // 如果后端接口获取失败,使用静态路由模式
addAuthRoutes(filteredAuthRoutes); console.warn('后端接口获取动态路由失败,回退到静态路由模式');
}
handleConstantAndAuthRoutes(); // 创建静态路由(这会使用已生成/恢复的动态菜单路由)
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
setIsInitAuthRoute(true); if (authStore.isStaticSuper) {
addAuthRoutes(staticAuthRoutes);
} else {
const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles);
addAuthRoutes(filteredAuthRoutes);
}
handleConstantAndAuthRoutes();
setIsInitAuthRoute(true);
console.log('动态认证路由初始化成功(使用静态路由模式)');
}
} catch (error) {
console.error('动态认证路由初始化失败:', error);
// 发生错误时,尝试使用基本的静态路由确保应用可用
try {
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
addAuthRoutes(staticAuthRoutes);
handleConstantAndAuthRoutes();
setIsInitAuthRoute(true);
console.log('使用基本静态路由作为降级方案');
} catch (fallbackError) {
console.error('降级方案也失败了:', fallbackError);
throw fallbackError;
}
} }
} }
......
/**
* 路由调试工具
* 用于诊断动态路由初始化问题
*/
export function debugRouteState() {
const info = {
timestamp: new Date().toISOString(),
uiGlobalConfig: {
exists: !!window.uiGlobalConfig,
internalCode: window.uiGlobalConfig?.InternalCode || null,
isAuthenticated: window.uiGlobalConfig?.IsAuthenticated || null,
displayName: window.uiGlobalConfig?.DisplayName || null
},
localStorage: {
token: !!localStorage.getItem('token'),
dynamicRoutesCache: !!localStorage.getItem('dynamic_routes_cache'),
userInfo: !!localStorage.getItem('userInfo')
},
router: {
routes: (window as any).__VUE_ROUTER__?.getRoutes?.()?.length || 0,
currentRoute: (window as any).__VUE_ROUTER__?.currentRoute?.value?.name || null
}
};
console.group('🔍 路由状态调试信息');
console.table(info);
console.groupEnd();
return info;
}
// 在全局暴露调试函数
if (typeof window !== 'undefined') {
(window as any).debugRouteState = debugRouteState;
}
export function logRouteInitStep(step: string, details?: any) {
console.log(`📍 路由初始化: ${step}`, details || '');
}
export function logRouteError(error: string, details?: any) {
console.error(`❌ 路由错误: ${error}`, details || '');
}
export function logRouteSuccess(message: string, details?: any) {
console.log(`✅ 路由成功: ${message}`, details || '');
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment