fix: router error display 403 if the first children menu unavalaible
This commit is contained in:
@@ -145,10 +145,17 @@ const router = createRouter({
|
||||
});
|
||||
|
||||
/**全局路由-后置守卫 */
|
||||
// 在路由后置守卫中清理计数器
|
||||
router.afterEach((to, from, failure) => {
|
||||
NProgress.done();
|
||||
|
||||
// 清理成功导航的重定向计数
|
||||
if (!failure) {
|
||||
const redirectKey = `${from.path}->${to.path}`;
|
||||
redirectCount.delete(redirectKey);
|
||||
}
|
||||
|
||||
const title = to.meta?.title;
|
||||
// 设置标题
|
||||
if (!failure && title) {
|
||||
useAppStore().setTitle(to.meta.title);
|
||||
}
|
||||
@@ -164,10 +171,27 @@ const WHITE_LIST: string[] = [
|
||||
'/trace-task-hlr',
|
||||
];
|
||||
|
||||
const redirectCount = new Map<string, number>();
|
||||
const MAX_REDIRECT_COUNT = 3; // 最大重定向次数
|
||||
|
||||
/**全局路由-前置守卫 */
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start();
|
||||
|
||||
// 检查是否是 F5 刷新(from.name 为 null 且 to.path 不是根路径)
|
||||
const isRefresh = !from.name && from.path === '/';
|
||||
|
||||
// 重定向计数检查
|
||||
const redirectKey = `${from.path}->${to.path}`;
|
||||
const currentCount = redirectCount.get(redirectKey) || 0;
|
||||
|
||||
if (currentCount > MAX_REDIRECT_COUNT) {
|
||||
console.warn(`检测到重定向循环: ${redirectKey},强制跳转到首页`);
|
||||
redirectCount.delete(redirectKey);
|
||||
next({ name: 'Index', replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取系统配置信息
|
||||
const appStore = useAppStore();
|
||||
if (!appStore.loginBackground) {
|
||||
@@ -203,48 +227,321 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
// 有Token
|
||||
if (token) {
|
||||
// 防止重复访问登录页面
|
||||
if (to.path === '/login') {
|
||||
next({ name: 'Index' });
|
||||
} else {
|
||||
// 判断当前用户是否有角色信息
|
||||
const user = useUserStore();
|
||||
if (user.roles && user.roles.length === 0) {
|
||||
try {
|
||||
// 获取网元信息
|
||||
await useNeInfoStore().fnNelist();
|
||||
// 获取用户信息
|
||||
await useNeInfoStore().fnNelist();
|
||||
await user.fnGetInfo();
|
||||
// 获取路由信息
|
||||
const accessRoutes = await useRouterStore().generateRoutes();
|
||||
// 根据后台配置生成可访问的路由表
|
||||
|
||||
if (accessRoutes && accessRoutes.length !== 0) {
|
||||
for (const route of accessRoutes) {
|
||||
// 动态添加可访问路由表,http开头会异常
|
||||
if (!validHttp(route.path)) {
|
||||
router.addRoute(route);
|
||||
}
|
||||
}
|
||||
|
||||
// F5 刷新时,如果目标路由有效,直接跳转,不进行重定向
|
||||
if (isRefresh && await isRouteAccessible(to, accessRoutes)) {
|
||||
console.log(`F5 刷新,目标路由有效,直接跳转: ${to.path}`);
|
||||
next({ ...to, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (await isRouteAccessible(to, accessRoutes)) {
|
||||
next({ ...to, replace: true });
|
||||
} else {
|
||||
// F5 刷新时,如果目标路由无效,优先跳转到首页
|
||||
if (isRefresh) {
|
||||
console.log(`F5 刷新,目标路由无效,跳转到首页: ${to.path}`);
|
||||
next({ name: 'Index', replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const validRedirect = findValidRedirect(to, accessRoutes);
|
||||
if (validRedirect && validRedirect !== to.path) {
|
||||
redirectCount.set(redirectKey, currentCount + 1);
|
||||
console.log(`重定向到有效路由: ${to.path} -> ${validRedirect}`);
|
||||
next({ path: validRedirect, replace: true });
|
||||
} else {
|
||||
next({ name: 'Index', replace: true });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next({ name: 'Index', replace: true });
|
||||
}
|
||||
// 刷新替换原先路由,确保addRoutes已完成
|
||||
next({ ...to, replace: true });
|
||||
} catch (error: any) {
|
||||
console.error(`[${to.path}]: ${error.message}`);
|
||||
await user.fnLogOut();
|
||||
next({ name: 'Login' });
|
||||
} catch (error) {
|
||||
console.error('Route guard error:', error);
|
||||
next(`/login?redirect=${to.fullPath}`);
|
||||
}
|
||||
} else if (
|
||||
to.meta.neType &&
|
||||
//Array.isArray(to.meta.neType) &&
|
||||
to.meta.neType.length > 0 &&
|
||||
!useNeInfoStore().fnHasNe(to.meta.neType)
|
||||
) {
|
||||
next({ name: 'NotPermission' });
|
||||
// 找到有效的替代路由
|
||||
const validRedirect = findValidAlternative(to);
|
||||
if (validRedirect && validRedirect !== to.path) {
|
||||
redirectCount.set(redirectKey, currentCount + 1);
|
||||
console.log(`403 重定向: ${to.path} -> ${validRedirect}`);
|
||||
next({ path: validRedirect, replace: true });
|
||||
} else {
|
||||
console.log(`无有效替代路由,跳转到权限错误页面: ${to.path}`);
|
||||
next({ name: 'NotPermission' });
|
||||
}
|
||||
} else {
|
||||
// 清除重定向计数
|
||||
redirectCount.clear();
|
||||
next();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 检查路由是否可访问
|
||||
*/
|
||||
async function isRouteAccessible(to: any, accessRoutes: any[]): Promise<boolean> {
|
||||
// 检查路由是否存在于 accessRoutes 中
|
||||
const routeExists = findRouteInAccessRoutes(to.path, accessRoutes);
|
||||
|
||||
console.log(`检查路由可访问性: ${to.path}`, routeExists ? '找到' : '未找到');
|
||||
|
||||
if (!routeExists) return false;
|
||||
|
||||
// 检查网元类型
|
||||
if (to.meta?.neType && to.meta.neType.length > 0) {
|
||||
const hasNe = useNeInfoStore().fnHasNe(to.meta.neType);
|
||||
console.log(`网元类型检查: ${to.meta.neType}`, hasNe ? '有效' : '无效');
|
||||
return hasNe;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在访问路由中查找指定路径
|
||||
*/
|
||||
function findRouteInAccessRoutes(targetPath: string, routes: any[], parentPath: string = ''): any {
|
||||
for (const route of routes) {
|
||||
// 构建完整路径
|
||||
let fullPath = route.path;
|
||||
|
||||
// 如果不是绝对路径,需要拼接父路径
|
||||
if (!fullPath.startsWith('/')) {
|
||||
const cleanParentPath = parentPath.replace(/\/$/, ''); // 移除末尾斜杠
|
||||
const cleanRoutePath = fullPath.replace(/^\//, ''); // 移除开头斜杠
|
||||
fullPath = cleanParentPath + '/' + cleanRoutePath;
|
||||
}
|
||||
|
||||
// 标准化路径,移除多余的斜杠
|
||||
fullPath = fullPath.replace(/\/+/g, '/');
|
||||
|
||||
console.log(`匹配路径: ${targetPath} vs ${fullPath}`);
|
||||
|
||||
if (fullPath === targetPath) {
|
||||
return route;
|
||||
}
|
||||
|
||||
// 递归查找子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
const found = findRouteInAccessRoutes(targetPath, route.children, fullPath);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找有效的重定向目标
|
||||
*/
|
||||
function findValidRedirect(to: any, accessRoutes: any[]): string | null {
|
||||
const neStore = useNeInfoStore();
|
||||
|
||||
console.log(`查找重定向目标: ${to.path}`);
|
||||
|
||||
// 1. 查找父路由的 redirect
|
||||
const parentRoute = findParentRouteWithRedirect(to.path, accessRoutes);
|
||||
|
||||
if (parentRoute?.redirect) {
|
||||
console.log(`找到父路由重定向: ${parentRoute.path} -> ${parentRoute.redirect}`);
|
||||
|
||||
// 验证 redirect 目标是否有效
|
||||
const redirectTarget = findRouteInAccessRoutes(parentRoute.redirect, accessRoutes);
|
||||
if (redirectTarget &&
|
||||
(!redirectTarget.meta?.neType || neStore.fnHasNe(redirectTarget.meta.neType))) {
|
||||
return parentRoute.redirect;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查找同级的第一个有效路由
|
||||
const siblingRoute = findFirstValidSibling(to.path, accessRoutes);
|
||||
if (siblingRoute) {
|
||||
console.log(`找到同级有效路由: ${siblingRoute}`);
|
||||
return siblingRoute;
|
||||
}
|
||||
|
||||
// 3. 查找根级别的第一个有效路由
|
||||
const rootRoute = findFirstValidRootRoute(accessRoutes);
|
||||
if (rootRoute) {
|
||||
console.log(`找到根级有效路由: ${rootRoute}`);
|
||||
return rootRoute;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找具有 redirect 的父路由
|
||||
*/
|
||||
function findParentRouteWithRedirect(targetPath: string, routes: any[], parentPath: string = ''): any {
|
||||
for (const route of routes) {
|
||||
let fullPath = route.path;
|
||||
|
||||
if (!fullPath.startsWith('/')) {
|
||||
const cleanParentPath = parentPath.replace(/\/$/, '');
|
||||
const cleanRoutePath = fullPath.replace(/^\//, '');
|
||||
fullPath = cleanParentPath + '/' + cleanRoutePath;
|
||||
}
|
||||
|
||||
fullPath = fullPath.replace(/\/+/g, '/');
|
||||
|
||||
// 检查是否是父路径且有 redirect
|
||||
if (targetPath.startsWith(fullPath) &&
|
||||
route.redirect &&
|
||||
fullPath !== targetPath) {
|
||||
return { ...route, path: fullPath };
|
||||
}
|
||||
|
||||
// 递归查找
|
||||
if (route.children && route.children.length > 0) {
|
||||
const found = findParentRouteWithRedirect(targetPath, route.children, fullPath);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找同级的第一个有效路由
|
||||
*/
|
||||
function findFirstValidSibling(targetPath: string, routes: any[], parentPath: string = ''): string | null {
|
||||
const neStore = useNeInfoStore();
|
||||
|
||||
// 获取父路径
|
||||
const parentRouteResult = findParentOfTarget(targetPath, routes, parentPath);
|
||||
if (!parentRouteResult) return null;
|
||||
|
||||
const { parentRoute, parentFullPath } = parentRouteResult;
|
||||
|
||||
if (parentRoute.children) {
|
||||
for (const sibling of parentRoute.children) {
|
||||
let siblingFullPath = sibling.path;
|
||||
|
||||
if (!siblingFullPath.startsWith('/')) {
|
||||
const cleanParentPath = parentFullPath.replace(/\/$/, '');
|
||||
const cleanSiblingPath = siblingFullPath.replace(/^\//, '');
|
||||
siblingFullPath = cleanParentPath + '/' + cleanSiblingPath;
|
||||
}
|
||||
|
||||
siblingFullPath = siblingFullPath.replace(/\/+/g, '/');
|
||||
|
||||
if (siblingFullPath !== targetPath &&
|
||||
(!sibling.meta?.neType || neStore.fnHasNe(sibling.meta.neType))) {
|
||||
return siblingFullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找目标路径的父路由
|
||||
*/
|
||||
function findParentOfTarget(targetPath: string, routes: any[], parentPath: string = ''): { parentRoute: any, parentFullPath: string } | null {
|
||||
for (const route of routes) {
|
||||
let fullPath = route.path;
|
||||
|
||||
if (!fullPath.startsWith('/')) {
|
||||
const cleanParentPath = parentPath.replace(/\/$/, '');
|
||||
const cleanRoutePath = fullPath.replace(/^\//, '');
|
||||
fullPath = cleanParentPath + '/' + cleanRoutePath;
|
||||
}
|
||||
|
||||
fullPath = fullPath.replace(/\/+/g, '/');
|
||||
|
||||
// 检查子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
for (const child of route.children) {
|
||||
let childFullPath = child.path;
|
||||
|
||||
if (!childFullPath.startsWith('/')) {
|
||||
const cleanParentPath = fullPath.replace(/\/$/, '');
|
||||
const cleanChildPath = childFullPath.replace(/^\//, '');
|
||||
childFullPath = cleanParentPath + '/' + cleanChildPath;
|
||||
}
|
||||
|
||||
childFullPath = childFullPath.replace(/\/+/g, '/');
|
||||
|
||||
if (childFullPath === targetPath) {
|
||||
return { parentRoute: route, parentFullPath: fullPath };
|
||||
}
|
||||
}
|
||||
|
||||
// 递归查找
|
||||
const found = findParentOfTarget(targetPath, route.children, fullPath);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找根级别的第一个有效路由
|
||||
*/
|
||||
function findFirstValidRootRoute(routes: any[]): string | null {
|
||||
const neStore = useNeInfoStore();
|
||||
|
||||
for (const route of routes) {
|
||||
if (!route.meta?.neType || neStore.fnHasNe(route.meta.neType)) {
|
||||
if (route.children && route.children.length > 0) {
|
||||
// 如果有子路由,返回第一个有效子路由的完整路径
|
||||
for (const child of route.children) {
|
||||
let childFullPath = child.path;
|
||||
|
||||
if (!childFullPath.startsWith('/')) {
|
||||
const cleanParentPath = route.path.replace(/\/$/, '');
|
||||
const cleanChildPath = childFullPath.replace(/^\//, '');
|
||||
childFullPath = cleanParentPath + '/' + cleanChildPath;
|
||||
}
|
||||
|
||||
childFullPath = childFullPath.replace(/\/+/g, '/');
|
||||
|
||||
if (!child.meta?.neType || neStore.fnHasNe(child.meta.neType)) {
|
||||
return childFullPath;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return route.path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找有效的替代路由(用于 403 情况)
|
||||
*/
|
||||
function findValidAlternative(to: any): string | null {
|
||||
const routerStore = useRouterStore();
|
||||
const buildRoutes = routerStore.buildRouterData;
|
||||
|
||||
return findValidRedirect(to, buildRoutes);
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
RouteRecordRaw,
|
||||
} from 'vue-router';
|
||||
import { getRouters } from '@/api/router';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import BasicLayout from '@/layouts/BasicLayout.vue';
|
||||
import BlankLayout from '@/layouts/BlankLayout.vue';
|
||||
import LinkLayout from '@/layouts/LinkLayout.vue';
|
||||
@@ -49,7 +50,12 @@ const useRouterStore = defineStore('router', {
|
||||
async generateRoutes() {
|
||||
const res = await getRouters();
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const buildRoutes = buildRouters(res.data.concat());
|
||||
// 获取当前网元类型
|
||||
const neTypes = useNeInfoStore().getNeSelectOtions.map(v => v.value);
|
||||
// 先过滤
|
||||
const filteredRoutes = this.clearMenuItemByNeList(res.data.concat(), neTypes);
|
||||
// 再 build
|
||||
const buildRoutes = buildRouters(filteredRoutes);
|
||||
this.buildRouterData = buildRoutes;
|
||||
return buildRoutes;
|
||||
}
|
||||
@@ -103,6 +109,21 @@ const useRouterStore = defineStore('router', {
|
||||
return null;
|
||||
}
|
||||
finalItem.children = children;
|
||||
|
||||
// 只重定向到第一个可用的子菜单
|
||||
// finalItem.redirect = finalItem.children[0].path;
|
||||
// console.log(`finalItem.redirect`, finalItem.redirect);
|
||||
// 如果有子菜单,且没有重定向,则设置重定向到第一个子菜单
|
||||
if (children.length > 0) {
|
||||
let childPath = children[0].path;
|
||||
if (!childPath.startsWith('/')) {
|
||||
// 确保父路径以 / 结尾,子路径不以 / 开头
|
||||
const parentPath = finalItem.path.replace(/\/$/, '');
|
||||
childPath = parentPath + '/' + childPath.replace(/^\//, '');
|
||||
}
|
||||
finalItem.redirect = childPath;
|
||||
}
|
||||
|
||||
return finalItem;
|
||||
}
|
||||
|
||||
@@ -117,7 +138,7 @@ const useRouterStore = defineStore('router', {
|
||||
/**异步路由类型 */
|
||||
type RecordRaws = {
|
||||
path: string;
|
||||
name: string;
|
||||
name?: string;
|
||||
meta: RouteMeta;
|
||||
redirect: RouteLocationRaw;
|
||||
component: string;
|
||||
@@ -132,17 +153,18 @@ type RecordRaws = {
|
||||
* @param recordRaws 异步路由列表
|
||||
* @returns 可添加的路由列表
|
||||
*/
|
||||
function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] {
|
||||
function buildRouters(recordRaws: RouteRecordRaw[]): RouteRecordRaw[] {
|
||||
const routers: RouteRecordRaw[] = [];
|
||||
for (const item of recordRaws) {
|
||||
// 过滤旧前端菜单 是layui的菜单跳过
|
||||
if (['', '/page"'].includes(item.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 路由页面组件
|
||||
let component: RouteComponent = {};
|
||||
if (item.component) {
|
||||
const comp = item.component;
|
||||
const comp = item.component as unknown as string; // 添加类型断言
|
||||
if (comp === MENU_COMPONENT_LAYOUT_BASIC) {
|
||||
component = BasicLayout;
|
||||
} else if (comp === MENU_COMPONENT_LAYOUT_BLANK) {
|
||||
@@ -159,6 +181,17 @@ function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] {
|
||||
let children: RouteRecordRaw[] = [];
|
||||
if (item.children && item.children.length > 0) {
|
||||
children = buildRouters(item.children);
|
||||
|
||||
// 如果没有 redirect 但有子菜单,设置 redirect 到第一个子菜单
|
||||
if (!item.redirect && children.length > 0) {
|
||||
let childPath = children[0].path;
|
||||
if (!childPath?.startsWith('/')) {
|
||||
childPath = item.path.replace(/\/$/, '') + '/' + (childPath || '').replace(/^\//, '');
|
||||
}
|
||||
|
||||
// 修改 item 的 redirect(需要类型断言)
|
||||
(item as any).redirect = childPath;
|
||||
}
|
||||
}
|
||||
|
||||
// 对元数据特殊参数进行处理
|
||||
@@ -166,7 +199,9 @@ function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] {
|
||||
if (!metaIcon.startsWith('icon-')) {
|
||||
metaIcon = '';
|
||||
}
|
||||
item.meta = Object.assign(item.meta, {
|
||||
|
||||
// 更新 meta(需要类型断言)
|
||||
(item as any).meta = Object.assign(item.meta || {}, {
|
||||
icon: metaIcon,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user