修复:原来的统一去了404, 现在加了403, 和部分bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m54s

This commit is contained in:
sexygoat
2026-03-03 16:26:04 +08:00
parent 7e9acda1ab
commit 237eeed87a
8 changed files with 274 additions and 91 deletions

View File

@@ -110,14 +110,22 @@ async function handleRouteGuard(
return
}
// 尝试刷新路由重新注册
// 路由未匹配时,判断是 404 还是 403
if (userStore.isLogin) {
isRouteRegistered.value = false
await handleDynamicRoutes(to, router, next)
return
// 检查路由是否存在于 asyncRoutes 中
const routeExistsInAsync = checkRouteExistsInAsyncRoutes(to.path, asyncRoutes)
console.log(`[路由检查] 目标路径: ${to.path}, 是否存在于asyncRoutes: ${routeExistsInAsync}`)
if (routeExistsInAsync) {
// 路由存在于 asyncRoutes 但未注册,说明用户没有权限
console.warn('路由存在但用户无权限访问:', to.path)
next(RoutesAlias.Exception403 || '/exception/403')
return
}
}
// 如果以上都不匹配跳转到404
// 路由不存在,跳转到 404
console.log(`[路由检查] 路径 ${to.path} 不存在于asyncRoutes跳转404`)
next(RoutesAlias.Exception404)
}
@@ -374,7 +382,7 @@ function convertBackendMenuToRoute(
}
}
// 递归处理所有层级的 children
// 递归处理后端返回的 children
if (menu.children && menu.children.length > 0) {
const children = menu.children
.map((child: any) => convertBackendMenuToRoute(child, routeMap, menuUrl))
@@ -453,6 +461,88 @@ function isValidMenuList(menuList: AppRouteRecord[]): boolean {
return Array.isArray(menuList) && menuList.length > 0
}
/**
* 检查路由是否存在于 asyncRoutes 中
* 支持动态参数匹配,如 /account-management/enterprise-customer/customer-accounts/3000 匹配 enterprise-customer/customer-accounts/:id
* @param targetPath 目标路由路径,如 /account-management/enterprise-customer/customer-accounts/3000
* @param routes 路由配置数组
* @param parentPath 父路由路径
*/
function checkRouteExistsInAsyncRoutes(
targetPath: string,
routes: AppRouteRecord[],
parentPath = ''
): boolean {
for (const route of routes) {
// 构建完整路径
const fullPath = route.path.startsWith('/')
? route.path
: parentPath
? `${parentPath}/${route.path}`.replace(/\/+/g, '/')
: `/${route.path}`
// 检查当前路由是否匹配(支持动态参数)
const isMatch = matchRoutePath(targetPath, fullPath)
if (isMatch) {
console.log(`[路由匹配成功] 目标: ${targetPath}, 匹配到: ${fullPath}`)
return true
}
// 递归检查子路由
if (route.children && route.children.length > 0) {
if (checkRouteExistsInAsyncRoutes(targetPath, route.children, fullPath)) {
return true
}
}
}
return false
}
/**
* 匹配路由路径,支持动态参数
* @param targetPath 实际路径,如 /account-management/enterprise-customer/customer-accounts/3000
* @param routePath 路由定义路径,如 /account-management/enterprise-customer/customer-accounts/:id
*/
function matchRoutePath(targetPath: string, routePath: string): boolean {
// 移除查询参数
const cleanTargetPath = targetPath.split('?')[0]
const cleanRoutePath = routePath.split('?')[0]
// 如果完全匹配,直接返回
if (cleanTargetPath === cleanRoutePath) {
return true
}
// 将路径分割成段
const targetSegments = cleanTargetPath.split('/').filter(Boolean)
const routeSegments = cleanRoutePath.split('/').filter(Boolean)
// 段数必须相同
if (targetSegments.length !== routeSegments.length) {
return false
}
// 逐段比较
for (let i = 0; i < routeSegments.length; i++) {
const routeSegment = routeSegments[i]
const targetSegment = targetSegments[i]
// 如果是动态参数(以 : 开头),跳过比较
if (routeSegment.startsWith(':')) {
continue
}
// 如果不是动态参数,必须完全匹配
if (routeSegment !== targetSegment) {
return false
}
}
return true
}
/**
* 重置路由相关状态
*/

View File

@@ -107,10 +107,12 @@ export const hasRoutePermission = (
*/
function checkMenuAccess(path: string, menus: any[]): boolean {
for (const menu of menus) {
// 检查当前菜单的 URL 是否匹配
if (menu.url && (path === menu.url || path.startsWith(menu.url + '/'))) {
// 检查当前菜单的 URL 是否匹配(支持动态参数)
if (menu.url && matchMenuPath(path, menu.url)) {
console.log(`[菜单权限检查] 路径 ${path} 匹配菜单 ${menu.url}`)
return true
}
// 递归检查子菜单
if (menu.children && menu.children.length > 0) {
if (checkMenuAccess(path, menu.children)) {
@@ -118,9 +120,53 @@ function checkMenuAccess(path: string, menus: any[]): boolean {
}
}
}
console.log(`[菜单权限检查] 路径 ${path} 未找到匹配的菜单`)
return false
}
/**
* 匹配菜单路径,支持动态参数
* @param actualPath 实际访问路径,如 /account-management/enterprise-customer/customer-accounts/3000
* @param menuPath 菜单定义路径,如 /account-management/enterprise-customer/customer-accounts/:id
*/
function matchMenuPath(actualPath: string, menuPath: string): boolean {
// 移除查询参数
const cleanActualPath = actualPath.split('?')[0]
const cleanMenuPath = menuPath.split('?')[0]
// 如果完全匹配,直接返回
if (cleanActualPath === cleanMenuPath) {
return true
}
// 将路径分割成段
const actualSegments = cleanActualPath.split('/').filter(Boolean)
const menuSegments = cleanMenuPath.split('/').filter(Boolean)
// 段数必须相同
if (actualSegments.length !== menuSegments.length) {
return false
}
// 逐段比较
for (let i = 0; i < menuSegments.length; i++) {
const menuSegment = menuSegments[i]
const actualSegment = actualSegments[i]
// 如果是动态参数(以 : 开头),跳过比较
if (menuSegment.startsWith(':')) {
continue
}
// 如果不是动态参数,必须完全匹配
if (menuSegment !== actualSegment) {
return false
}
}
return true
}
/**
* 检查 Token 是否有效
* 简单检查,真实项目中应该验证 JWT 或者调用后端接口