diff --git a/.env b/.env index 3ac03ac..7abe551 100644 --- a/.env +++ b/.env @@ -9,7 +9,8 @@ VITE_PORT = 3006 VITE_BASE_URL = / # API 地址前缀 -VITE_API_URL = https://mzl.dev.hnxinyouhe.com +# VITE_API_URL = https://mzl.dev.hnxinyouhe.com +VITE_API_URL = https://x-server.hnxinyouhe.com # 权限模式( frontend | backend ) VITE_ACCESS_MODE = frontend diff --git a/.env.development b/.env.development index e82d846..45ed3e5 100644 --- a/.env.development +++ b/.env.development @@ -7,7 +7,8 @@ VITE_BASE_URL = / # VITE_API_URL = https://m1.apifoxmock.com/m1/6400575-6097373-default # VITE_API_URL = http://219.157.255.213:25702 # VITE_API_URL = https://mzl.dev.hnxinyouhe.com -VITE_API_URL = https://fdesk.hnxinyouhe.com +# VITE_API_URL = https://fdesk.hnxinyouhe.com +VITE_API_URL = https://x-server.hnxinyouhe.com # Delete console diff --git a/.env.production b/.env.production index 729be18..9b6c051 100644 --- a/.env.production +++ b/.env.production @@ -4,7 +4,8 @@ VITE_BASE_URL = / # API 地址前缀 -VITE_API_URL = https://fdesk.hnxinyouhe.com +# VITE_API_URL = https://fdesk.hnxinyouhe.com +VITE_API_URL = https://x-server.hnxinyouhe.com # Delete console VITE_DROP_CONSOLE = true \ No newline at end of file diff --git a/MENU_PERMISSION_FIX.md b/MENU_PERMISSION_FIX.md new file mode 100644 index 0000000..d4f6833 --- /dev/null +++ b/MENU_PERMISSION_FIX.md @@ -0,0 +1,77 @@ +# 菜单权限控制问题修复方案 + +## 问题描述 + +在后端控制模式下,当普通用户登录后再退出登录管理员时,管理员特有的菜单会出现404错误。 + +## 问题原因 + +1. **路由注册状态未正确重置**:`isRouteRegistered` 标志位在用户切换时没有正确重置 +2. **动态路由清理不完整**:`resetRouterState` 函数只清理了带有 `meta.dynamic` 标记的路由 +3. **工作台标签页验证时机问题**:`validateWorktabs` 在路由重新注册后执行,但可能没有正确清理无效的标签页 +4. **用户信息变化检测缺失**:没有检测用户切换事件,导致路由状态没有及时更新 + +## 修复方案 + +### 1. 改进路由状态重置 (`src/router/guards/beforeEach.ts`) + +- 修改 `resetRouterState` 函数,清理所有动态注册的路由(不仅仅是带有dynamic标记的) +- 添加工作台标签页的清理 +- 使用菜单store的 `clearMenuList` 方法 + +### 2. 添加用户切换检测 (`src/router/guards/beforeEach.ts`) + +- 在 `handleRouteGuard` 函数中添加用户信息变化检测 +- 使用 `sessionStorage` 缓存用户信息,检测用户切换 +- 当检测到用户信息变化时,强制重置路由状态 + +### 3. 改进菜单API (`src/api/menuApi.ts`) + +- 添加用户登录状态验证 +- 改进菜单数据验证逻辑 +- 添加更详细的错误处理和日志 + +### 4. 完善用户登出逻辑 (`src/store/modules/user.ts`) + +- 在 `logOut` 方法中添加更完整的状态清理 +- 清理用户信息缓存 +- 确保工作台标签页被正确清理 + +### 5. 增强菜单Store (`src/store/modules/menu.ts`) + +- 添加 `clearMenuList` 方法用于清理菜单数据 +- 提供更完整的状态管理 + +### 6. 改进错误处理 (`src/router/guards/beforeEach.ts`) + +- 修改 `handleMenuError` 函数,确保在菜单处理失败时正确清理所有状态 +- 添加更详细的状态清理逻辑 + +## 测试工具 + +创建了 `src/utils/menuPermissionTest.ts` 测试工具,包含: + +- `testMenuPermissionSwitch()`: 测试菜单权限切换的正确性 +- `simulateUserSwitch()`: 模拟用户切换过程 + +## 使用方法 + +1. 在浏览器控制台中调用 `testMenuPermissionSwitch()` 检查当前状态 +2. 在用户切换后调用 `simulateUserSwitch()` 模拟切换过程 +3. 观察控制台输出,确认路由和菜单状态正确 + +## 预期效果 + +修复后,当用户切换时: + +1. 所有动态路由会被正确清理 +2. 菜单数据会被重新加载 +3. 工作台标签页会被验证和清理 +4. 不会出现404错误 +5. 用户权限会正确应用 + +## 注意事项 + +- 确保后端返回的菜单数据与用户权限匹配 +- 在开发环境中可以使用测试工具验证修复效果 +- 建议在生产环境中添加更多的日志记录用于问题排查 \ No newline at end of file diff --git a/src/api/menuApi.ts b/src/api/menuApi.ts index 68506c7..dc26481 100644 --- a/src/api/menuApi.ts +++ b/src/api/menuApi.ts @@ -11,23 +11,37 @@ export const menuService = { async getMenuList(delay = 300): Promise { try { const userStore = useUserStore() + + // 验证用户登录状态 + if (!userStore.isLogin) { + throw new Error('用户未登录') + } + // 模拟接口返回的菜单数据 const menuData = userStore.info.menu - if(menuData.length==0){ + + // 验证菜单数据 + if (!menuData || !Array.isArray(menuData) || menuData.length === 0) { ElMessage.error('暂无系统菜单权限') + return { menuList: [] } } - const menuData1 = asyncRoutes + // 处理菜单数据 - // const menuList1 = menuData1.map((route) => menuDataToRouter(route)) const menuList = menuData.map((route: any) => menuDataToRouter(route)) - // console.log(menuList1) - // console.log(menuList) + + // 验证处理后的菜单列表 + if (!menuList || menuList.length === 0) { + ElMessage.error('菜单数据处理失败') + return { menuList: [] } + } + // 模拟接口延迟 await new Promise((resolve) => setTimeout(resolve, delay)) return { menuList } } catch (error) { + console.error('获取菜单失败:', error) throw error instanceof Error ? error : new Error('获取菜单失败') } } diff --git a/src/router/guards/beforeEach.ts b/src/router/guards/beforeEach.ts index 1d093f2..6621eb1 100644 --- a/src/router/guards/beforeEach.ts +++ b/src/router/guards/beforeEach.ts @@ -197,7 +197,22 @@ async function registerAndStoreMenu( */ function handleMenuError(error: unknown): void { console.error('菜单处理失败:', error) - useUserStore().logOut() + + // 清理所有状态 + const userStore = useUserStore() + const menuStore = useMenuStore() + const worktabStore = useWorktabStore() + + // 清空菜单和工作台 + menuStore.clearMenuList() + worktabStore.clearAll() + + // 重置路由注册状态 + isRouteRegistered.value = false + + // 登出用户 + userStore.logOut() + throw error instanceof Error ? error : new Error('获取菜单列表失败,请重新登录') } @@ -233,13 +248,34 @@ function isValidMenuList(menuList: AppRouteRecord[]): boolean { */ export function resetRouterState(router: Router): void { isRouteRegistered.value = false - // 清理动态注册的路由 + + // 清理所有动态注册的路由(不仅仅是带有dynamic标记的) + const routesToRemove: string[] = [] router.getRoutes().forEach((route) => { - if (route.meta?.dynamic) { - router.removeRoute(route.name as string) + // 排除静态路由和登录页面 + if (route.name && + route.name !== 'Login' && + route.name !== 'Exception404' && + route.name !== 'Exception500' && + !route.path.startsWith('/exception/')) { + routesToRemove.push(route.name as string) } }) + + // 移除动态路由 + routesToRemove.forEach((routeName) => { + try { + router.removeRoute(routeName) + } catch (error) { + console.warn(`移除路由失败: ${routeName}`, error) + } + }) + // 清空菜单数据 const menuStore = useMenuStore() - menuStore.setMenuList([]) + menuStore.clearMenuList() + + // 清空工作台标签页 + const worktabStore = useWorktabStore() + worktabStore.clearAll() } diff --git a/src/store/modules/menu.ts b/src/store/modules/menu.ts index 6f3df52..3f7e716 100644 --- a/src/store/modules/menu.ts +++ b/src/store/modules/menu.ts @@ -10,11 +10,17 @@ export const useMenuStore = defineStore('menuStore', () => { const setMenuList = (list: AppRouteRecord[]) => (menuList.value = list) const setMenuWidth = (width: string) => (menuWidth.value = width) + + const clearMenuList = () => { + menuList.value = [] + menuWidth.value = '' + } return { menuList, menuWidth, setMenuList, - setMenuWidth + setMenuWidth, + clearMenuList } }) diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 5a1e6a8..1a7f160 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -110,9 +110,18 @@ export const useUserStore = defineStore( accessToken.value = '' refreshToken.value = '' if (refreshTimeout) clearTimeout(refreshTimeout) // 清理定时器 - useWorktabStore().opened = [] + + // 清理工作台标签页 + useWorktabStore().clearAll() + + // 清理sessionStorage中的用户信息缓存 + sessionStorage.removeItem('lastUserInfo') sessionStorage.removeItem('iframeRoutes') + + // 重置路由状态 resetRouterState(router) + + // 跳转到登录页 router.push(RoutesAlias.Login) } diff --git a/src/utils/http/index.ts b/src/utils/http/index.ts index 7aeebf8..0de84a8 100644 --- a/src/utils/http/index.ts +++ b/src/utils/http/index.ts @@ -81,8 +81,15 @@ axiosInstance.interceptors.response.use( } }, (error) => { - - return Promise.reject(handleError(error)) + const { code, msg } = error.response.data + switch (code) { + case ApiStatus.unauthorized: + logOut() + throw new HttpError(msg || $t('httpMsg.unauthorized'), ApiStatus.unauthorized) + default: + throw new HttpError(msg || $t('httpMsg.requestFailed'), code) + } + // return Promise.reject(handleError(error)) } ) diff --git a/src/utils/sys/console.ts b/src/utils/sys/console.ts index c3659eb..f45212b 100644 --- a/src/utils/sys/console.ts +++ b/src/utils/sys/console.ts @@ -1,21 +1,2 @@ // ANSI 转义码生成网站 https://patorjk.com/software/taag/#p=display&f=Big&t=ABB%0A -const asciiArt = ` -\x1b[32m✨ 你好!欢迎使用 Art Design Pro! -\x1b[0m -\x1b[36m💡 如果您觉得项目对您有帮助,请点击下面的链接为我点个 ★Star 支持一下!祝您使用愉快! -\x1b[0m -\x1b[33m🌟 GitHub: https://github.com/Daymychen/art-design-pro -\x1b[0m -\x1b[31m✨ 技术支持(QQ群): 821834289,如果你有任何问题,请加入QQ群,我们会在第一时间为你解答! -\x1b[0m - -\x1b[36m _ _______ _________ ______ ________ ______ _____ ______ ____ _____ _______ _______ ___ -\x1b[36m / \\ |_ __ \\ | _ _ | |_ _ \`.|_ __ |.' ____ \\ |_ _|.' ___ ||_ \\|_ _| |_ __ \\|_ __ \\ .' \`. -\x1b[36m / _ \\ | |__) ||_/ | | \\_| | | \`. \\ | |_ \\_|| (___ \\_| | | / .' \\_| | \\ | | | |__) | | |__) | / .-. \\ -\x1b[36m / ___ \\ | __ / | | | | | | | _| _ _.____\`. | | | | ____ | |\\ \\| | | ___/ | __ / | | | | -\x1b[36m _/ / \\ \\_ _| | \\ \\_ _| |_ _| |_.' /_| |__/ || \\____) | _| |_\\ \`.___] |_| |_\\ |_ _| |_ _| | \\ \\_\\ \`-' / -\x1b[36m|____| |____||____| |___||_____| |______.'|________| \\______.'|_____|\\\`._____.'|_____|\\____| |_____| |____| |___|\\\`.___.' -\x1b[0m -` - -console.log(asciiArt) +console.log('')