修改菜单问题

This commit is contained in:
浅念
2025-07-14 10:09:10 +08:00
parent 2ad479b006
commit 812e3c4b0c
10 changed files with 170 additions and 37 deletions
+2 -1
View File
@@ -9,7 +9,8 @@ VITE_PORT = 3006
VITE_BASE_URL = / VITE_BASE_URL = /
# API 地址前缀 # 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 ) # 权限模式( frontend | backend )
VITE_ACCESS_MODE = frontend VITE_ACCESS_MODE = frontend
+2 -1
View File
@@ -7,7 +7,8 @@ VITE_BASE_URL = /
# VITE_API_URL = https://m1.apifoxmock.com/m1/6400575-6097373-default # VITE_API_URL = https://m1.apifoxmock.com/m1/6400575-6097373-default
# VITE_API_URL = http://219.157.255.213:25702 # VITE_API_URL = http://219.157.255.213:25702
# VITE_API_URL = https://mzl.dev.hnxinyouhe.com # 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 # Delete console
+2 -1
View File
@@ -4,7 +4,8 @@
VITE_BASE_URL = / VITE_BASE_URL = /
# API 地址前缀 # 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 # Delete console
VITE_DROP_CONSOLE = true VITE_DROP_CONSOLE = true
+77
View File
@@ -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. 用户权限会正确应用
## 注意事项
- 确保后端返回的菜单数据与用户权限匹配
- 在开发环境中可以使用测试工具验证修复效果
- 建议在生产环境中添加更多的日志记录用于问题排查
+19 -5
View File
@@ -11,23 +11,37 @@ export const menuService = {
async getMenuList(delay = 300): Promise<MenuResponse> { async getMenuList(delay = 300): Promise<MenuResponse> {
try { try {
const userStore = useUserStore() const userStore = useUserStore()
// 验证用户登录状态
if (!userStore.isLogin) {
throw new Error('用户未登录')
}
// 模拟接口返回的菜单数据 // 模拟接口返回的菜单数据
const menuData = userStore.info.menu const menuData = userStore.info.menu
if(menuData.length==0){
// 验证菜单数据
if (!menuData || !Array.isArray(menuData) || menuData.length === 0) {
ElMessage.error('暂无系统菜单权限') ElMessage.error('暂无系统菜单权限')
return { menuList: [] }
} }
const menuData1 = asyncRoutes
// 处理菜单数据 // 处理菜单数据
// const menuList1 = menuData1.map((route) => menuDataToRouter(route))
const menuList = menuData.map((route: any) => 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)) await new Promise((resolve) => setTimeout(resolve, delay))
return { menuList } return { menuList }
} catch (error) { } catch (error) {
console.error('获取菜单失败:', error)
throw error instanceof Error ? error : new Error('获取菜单失败') throw error instanceof Error ? error : new Error('获取菜单失败')
} }
} }
+41 -5
View File
@@ -197,7 +197,22 @@ async function registerAndStoreMenu(
*/ */
function handleMenuError(error: unknown): void { function handleMenuError(error: unknown): void {
console.error('菜单处理失败:', error) 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('获取菜单列表失败,请重新登录') throw error instanceof Error ? error : new Error('获取菜单列表失败,请重新登录')
} }
@@ -233,13 +248,34 @@ function isValidMenuList(menuList: AppRouteRecord[]): boolean {
*/ */
export function resetRouterState(router: Router): void { export function resetRouterState(router: Router): void {
isRouteRegistered.value = false isRouteRegistered.value = false
// 清理动态注册的路由
// 清理所有动态注册的路由(不仅仅是带有dynamic标记的)
const routesToRemove: string[] = []
router.getRoutes().forEach((route) => { 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() const menuStore = useMenuStore()
menuStore.setMenuList([]) menuStore.clearMenuList()
// 清空工作台标签页
const worktabStore = useWorktabStore()
worktabStore.clearAll()
} }
+7 -1
View File
@@ -11,10 +11,16 @@ export const useMenuStore = defineStore('menuStore', () => {
const setMenuWidth = (width: string) => (menuWidth.value = width) const setMenuWidth = (width: string) => (menuWidth.value = width)
const clearMenuList = () => {
menuList.value = []
menuWidth.value = ''
}
return { return {
menuList, menuList,
menuWidth, menuWidth,
setMenuList, setMenuList,
setMenuWidth setMenuWidth,
clearMenuList
} }
}) })
+10 -1
View File
@@ -110,9 +110,18 @@ export const useUserStore = defineStore(
accessToken.value = '' accessToken.value = ''
refreshToken.value = '' refreshToken.value = ''
if (refreshTimeout) clearTimeout(refreshTimeout) // 清理定时器 if (refreshTimeout) clearTimeout(refreshTimeout) // 清理定时器
useWorktabStore().opened = []
// 清理工作台标签页
useWorktabStore().clearAll()
// 清理sessionStorage中的用户信息缓存
sessionStorage.removeItem('lastUserInfo')
sessionStorage.removeItem('iframeRoutes') sessionStorage.removeItem('iframeRoutes')
// 重置路由状态
resetRouterState(router) resetRouterState(router)
// 跳转到登录页
router.push(RoutesAlias.Login) router.push(RoutesAlias.Login)
} }
+9 -2
View File
@@ -81,8 +81,15 @@ axiosInstance.interceptors.response.use(
} }
}, },
(error) => { (error) => {
const { code, msg } = error.response.data
return Promise.reject(handleError(error)) 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))
} }
) )
+1 -20
View File
@@ -1,21 +1,2 @@
// ANSI 转义码生成网站 https://patorjk.com/software/taag/#p=display&f=Big&t=ABB%0A // ANSI 转义码生成网站 https://patorjk.com/software/taag/#p=display&f=Big&t=ABB%0A
const asciiArt = ` console.log('')
\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)