commits
This commit is contained in:
+176
-176
@@ -1,176 +1,176 @@
|
||||
import { createComponentLoader } from '@/utils/pathResolver';
|
||||
|
||||
function computeFullPath(menuPath, parentPath) {
|
||||
if (!menuPath) return parentPath || '';
|
||||
if (menuPath.startsWith('/')) {
|
||||
return menuPath.replace(/\/+/g, '/');
|
||||
}
|
||||
const base = (parentPath || '').replace(/\/$/, '');
|
||||
return `${base}/${menuPath}`.replace(/\/+/g, '/');
|
||||
}
|
||||
|
||||
/** 将子路由的绝对路径转为相对父布局的路径,供 Vue Router 嵌套使用 */
|
||||
function toRelativeChildPath(parentAbs, childAbs) {
|
||||
const base = (parentAbs || '').replace(/\/$/, '');
|
||||
const target = (childAbs || '').replace(/\/$/, '');
|
||||
if (!target) return '';
|
||||
if (target === base) return '';
|
||||
const prefix = `${base}/`;
|
||||
if (target.startsWith(prefix)) {
|
||||
return target.slice(prefix.length);
|
||||
}
|
||||
// 兜底:取最后一段(菜单 path 配置异常时)
|
||||
const parts = target.split('/').filter(Boolean);
|
||||
return parts.length ? parts[parts.length - 1] : '';
|
||||
}
|
||||
|
||||
function hasPageComponent(menu) {
|
||||
return menu.type === 4 || (menu.component_path && String(menu.component_path).trim() !== '');
|
||||
}
|
||||
|
||||
function resolvePageComponent(menu) {
|
||||
if (menu.type === 4) {
|
||||
return () => import('@/views/onepage/index.vue');
|
||||
}
|
||||
if (menu.component_path && String(menu.component_path).trim() !== '') {
|
||||
return createComponentLoader(menu.component_path);
|
||||
}
|
||||
return () => import('@/views/404/404.vue');
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单子节点 -> 嵌套路由(path 相对 layoutAbsPath)
|
||||
*/
|
||||
function convertNestedMenuChildren(children, layoutAbsPath) {
|
||||
if (!children || children.length === 0) return [];
|
||||
return children.map((child) => nestedMenuToRoute(child, layoutAbsPath));
|
||||
}
|
||||
|
||||
function nestedMenuToRoute(menu, layoutAbsPath) {
|
||||
const childAbs = computeFullPath(menu.path, layoutAbsPath);
|
||||
const relPath = toRelativeChildPath(layoutAbsPath, childAbs);
|
||||
const hasChildren = menu.children && menu.children.length > 0;
|
||||
const ownPage = hasPageComponent(menu);
|
||||
|
||||
const meta = {
|
||||
title: menu.title,
|
||||
icon: menu.icon,
|
||||
id: menu.id,
|
||||
componentPath: menu.component_path,
|
||||
};
|
||||
|
||||
// 既有自己的页面又有子菜单:套一层 EmptyLayout,避免父页面组件里没有 <router-view> 导致子路由无法渲染
|
||||
if (hasChildren && ownPage) {
|
||||
return {
|
||||
path: relPath,
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: () => import('@/views/layouts/EmptyLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: `menu_${menu.id}_index`,
|
||||
meta: { ...meta },
|
||||
component: resolvePageComponent(menu),
|
||||
},
|
||||
...convertNestedMenuChildren(menu.children, childAbs),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 纯目录 + 子节点
|
||||
if (hasChildren && !ownPage) {
|
||||
const route = {
|
||||
path: relPath,
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: () => import('@/views/layouts/EmptyLayout.vue'),
|
||||
children: convertNestedMenuChildren(menu.children, childAbs),
|
||||
};
|
||||
const firstChild = menu.children[0];
|
||||
if (firstChild && firstChild.path) {
|
||||
const firstAbs = computeFullPath(firstChild.path, childAbs);
|
||||
const firstRel = toRelativeChildPath(childAbs, firstAbs);
|
||||
if (firstRel) {
|
||||
route.redirect = firstRel;
|
||||
}
|
||||
}
|
||||
return route;
|
||||
}
|
||||
|
||||
// 叶子页面
|
||||
return {
|
||||
path: relPath,
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: resolvePageComponent(menu),
|
||||
};
|
||||
}
|
||||
|
||||
// 递归转换嵌套菜单为嵌套路由
|
||||
export function convertMenusToRoutes(menus, parentPath = '') {
|
||||
if (!menus || menus.length === 0) return [];
|
||||
|
||||
return menus.map((menu) => {
|
||||
const fullPath = menu.path
|
||||
? menu.path.startsWith('/')
|
||||
? menu.path.replace(/\/+/g, '/')
|
||||
: `${(parentPath || '').replace(/\/$/, '')}/${menu.path}`.replace(/\/+/g, '/')
|
||||
: '';
|
||||
|
||||
const hasChildren = menu.children && menu.children.length > 0;
|
||||
const ownPage = hasPageComponent(menu);
|
||||
|
||||
const meta = {
|
||||
title: menu.title,
|
||||
icon: menu.icon,
|
||||
id: menu.id,
|
||||
componentPath: menu.component_path,
|
||||
};
|
||||
|
||||
// 顶层:有页面 + 有子菜单 -> EmptyLayout + 默认子路由 + 相对 path 子路由
|
||||
if (hasChildren && ownPage) {
|
||||
return {
|
||||
path: fullPath || menu.path || '',
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: () => import('@/views/layouts/EmptyLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: `menu_${menu.id}_index`,
|
||||
meta: { ...meta },
|
||||
component: resolvePageComponent(menu),
|
||||
},
|
||||
...convertNestedMenuChildren(menu.children, fullPath),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const route = {
|
||||
path: fullPath || menu.path || '',
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
};
|
||||
|
||||
if (menu.type === 4) {
|
||||
route.component = () => import('@/views/onepage/index.vue');
|
||||
} else if (menu.component_path && menu.component_path.trim() !== '') {
|
||||
route.component = createComponentLoader(menu.component_path);
|
||||
} else if (hasChildren) {
|
||||
route.component = () => import('@/views/layouts/EmptyLayout.vue');
|
||||
route.children = convertMenusToRoutes(menu.children, fullPath);
|
||||
const firstChild = menu.children[0];
|
||||
if (firstChild && firstChild.path) {
|
||||
const childFullPath = firstChild.path.startsWith('/')
|
||||
? firstChild.path
|
||||
: `${fullPath}/${firstChild.path}`;
|
||||
route.redirect = childFullPath;
|
||||
}
|
||||
} else {
|
||||
route.component = () => import('@/views/404/404.vue');
|
||||
}
|
||||
|
||||
return route;
|
||||
});
|
||||
}
|
||||
import { createComponentLoader } from '@/utils/pathResolver';
|
||||
|
||||
function computeFullPath(menuPath, parentPath) {
|
||||
if (!menuPath) return parentPath || '';
|
||||
if (menuPath.startsWith('/')) {
|
||||
return menuPath.replace(/\/+/g, '/');
|
||||
}
|
||||
const base = (parentPath || '').replace(/\/$/, '');
|
||||
return `${base}/${menuPath}`.replace(/\/+/g, '/');
|
||||
}
|
||||
|
||||
/** 将子路由的绝对路径转为相对父布局的路径,供 Vue Router 嵌套使用 */
|
||||
function toRelativeChildPath(parentAbs, childAbs) {
|
||||
const base = (parentAbs || '').replace(/\/$/, '');
|
||||
const target = (childAbs || '').replace(/\/$/, '');
|
||||
if (!target) return '';
|
||||
if (target === base) return '';
|
||||
const prefix = `${base}/`;
|
||||
if (target.startsWith(prefix)) {
|
||||
return target.slice(prefix.length);
|
||||
}
|
||||
// 兜底:取最后一段(菜单 path 配置异常时)
|
||||
const parts = target.split('/').filter(Boolean);
|
||||
return parts.length ? parts[parts.length - 1] : '';
|
||||
}
|
||||
|
||||
function hasPageComponent(menu) {
|
||||
return menu.type === 4 || (menu.component_path && String(menu.component_path).trim() !== '');
|
||||
}
|
||||
|
||||
function resolvePageComponent(menu) {
|
||||
if (menu.type === 4) {
|
||||
return () => import('@/views/onepage/index.vue');
|
||||
}
|
||||
if (menu.component_path && String(menu.component_path).trim() !== '') {
|
||||
return createComponentLoader(menu.component_path);
|
||||
}
|
||||
return () => import('@/views/404/404.vue');
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单子节点 -> 嵌套路由(path 相对 layoutAbsPath)
|
||||
*/
|
||||
function convertNestedMenuChildren(children, layoutAbsPath) {
|
||||
if (!children || children.length === 0) return [];
|
||||
return children.map((child) => nestedMenuToRoute(child, layoutAbsPath));
|
||||
}
|
||||
|
||||
function nestedMenuToRoute(menu, layoutAbsPath) {
|
||||
const childAbs = computeFullPath(menu.path, layoutAbsPath);
|
||||
const relPath = toRelativeChildPath(layoutAbsPath, childAbs);
|
||||
const hasChildren = menu.children && menu.children.length > 0;
|
||||
const ownPage = hasPageComponent(menu);
|
||||
|
||||
const meta = {
|
||||
title: menu.title,
|
||||
icon: menu.icon,
|
||||
id: menu.id,
|
||||
componentPath: menu.component_path,
|
||||
};
|
||||
|
||||
// 既有自己的页面又有子菜单:套一层 EmptyLayout,避免父页面组件里没有 <router-view> 导致子路由无法渲染
|
||||
if (hasChildren && ownPage) {
|
||||
return {
|
||||
path: relPath,
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: () => import('@/views/layouts/EmptyLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: `menu_${menu.id}_index`,
|
||||
meta: { ...meta },
|
||||
component: resolvePageComponent(menu),
|
||||
},
|
||||
...convertNestedMenuChildren(menu.children, childAbs),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 纯目录 + 子节点
|
||||
if (hasChildren && !ownPage) {
|
||||
const route = {
|
||||
path: relPath,
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: () => import('@/views/layouts/EmptyLayout.vue'),
|
||||
children: convertNestedMenuChildren(menu.children, childAbs),
|
||||
};
|
||||
const firstChild = menu.children[0];
|
||||
if (firstChild && firstChild.path) {
|
||||
const firstAbs = computeFullPath(firstChild.path, childAbs);
|
||||
const firstRel = toRelativeChildPath(childAbs, firstAbs);
|
||||
if (firstRel) {
|
||||
route.redirect = firstRel;
|
||||
}
|
||||
}
|
||||
return route;
|
||||
}
|
||||
|
||||
// 叶子页面
|
||||
return {
|
||||
path: relPath,
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: resolvePageComponent(menu),
|
||||
};
|
||||
}
|
||||
|
||||
// 递归转换嵌套菜单为嵌套路由
|
||||
export function convertMenusToRoutes(menus, parentPath = '') {
|
||||
if (!menus || menus.length === 0) return [];
|
||||
|
||||
return menus.map((menu) => {
|
||||
const fullPath = menu.path
|
||||
? menu.path.startsWith('/')
|
||||
? menu.path.replace(/\/+/g, '/')
|
||||
: `${(parentPath || '').replace(/\/$/, '')}/${menu.path}`.replace(/\/+/g, '/')
|
||||
: '';
|
||||
|
||||
const hasChildren = menu.children && menu.children.length > 0;
|
||||
const ownPage = hasPageComponent(menu);
|
||||
|
||||
const meta = {
|
||||
title: menu.title,
|
||||
icon: menu.icon,
|
||||
id: menu.id,
|
||||
componentPath: menu.component_path,
|
||||
};
|
||||
|
||||
// 顶层:有页面 + 有子菜单 -> EmptyLayout + 默认子路由 + 相对 path 子路由
|
||||
if (hasChildren && ownPage) {
|
||||
return {
|
||||
path: fullPath || menu.path || '',
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
component: () => import('@/views/layouts/EmptyLayout.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: `menu_${menu.id}_index`,
|
||||
meta: { ...meta },
|
||||
component: resolvePageComponent(menu),
|
||||
},
|
||||
...convertNestedMenuChildren(menu.children, fullPath),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const route = {
|
||||
path: fullPath || menu.path || '',
|
||||
name: `menu_${menu.id}`,
|
||||
meta,
|
||||
};
|
||||
|
||||
if (menu.type === 4) {
|
||||
route.component = () => import('@/views/onepage/index.vue');
|
||||
} else if (menu.component_path && menu.component_path.trim() !== '') {
|
||||
route.component = createComponentLoader(menu.component_path);
|
||||
} else if (hasChildren) {
|
||||
route.component = () => import('@/views/layouts/EmptyLayout.vue');
|
||||
route.children = convertMenusToRoutes(menu.children, fullPath);
|
||||
const firstChild = menu.children[0];
|
||||
if (firstChild && firstChild.path) {
|
||||
const childFullPath = firstChild.path.startsWith('/')
|
||||
? firstChild.path
|
||||
: `${fullPath}/${firstChild.path}`;
|
||||
route.redirect = childFullPath;
|
||||
}
|
||||
} else {
|
||||
route.component = () => import('@/views/404/404.vue');
|
||||
}
|
||||
|
||||
return route;
|
||||
});
|
||||
}
|
||||
|
||||
+195
-195
@@ -1,195 +1,195 @@
|
||||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import { convertMenusToRoutes } from "./dynamicRoutes";
|
||||
|
||||
// 静态子路由:需要在 Main 框架内显示的页面
|
||||
const staticMainChildren = [
|
||||
{
|
||||
path: "/user/userProfile",
|
||||
name: "userProfile",
|
||||
component: () => import("@/views/user/userProfile.vue"),
|
||||
meta: { requiresAuth: true, title: "用户中心" }
|
||||
},
|
||||
// 兼容拼写错误的路径重定向
|
||||
{
|
||||
path: "/apps/erp/dashborad",
|
||||
redirect: "/apps/erp/dashboard"
|
||||
}
|
||||
];
|
||||
|
||||
// 静态路由:登录页独立、home 导航门户独立、404 页面独立
|
||||
const staticRoutes = [
|
||||
{
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: () => import("@/views/login/index.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: "/register",
|
||||
name: "Register",
|
||||
component: () => import("@/views/login/register.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: "/forget",
|
||||
name: "ForgetPassword",
|
||||
component: () => import("@/views/login/forget.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: "/home",
|
||||
name: "Home",
|
||||
component: () => import("@/views/home/index.vue"),
|
||||
meta: { requiresAuth: true, title: "系统导航", isStandalone: true }
|
||||
},
|
||||
// 兼容路径拼写错误:dashborad -> dashboard
|
||||
{
|
||||
path: "/apps/erp/dashborad",
|
||||
redirect: "/apps/erp/dashboard"
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
name: "NotFound",
|
||||
component: () => import("@/views/404/404.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
}
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: staticRoutes
|
||||
});
|
||||
|
||||
let dynamicRoutesAdded = false;
|
||||
let dynamicRoutesData = [];
|
||||
let routesLoadingPromise = null;
|
||||
|
||||
export function resetDynamicRoutes() {
|
||||
dynamicRoutesAdded = false;
|
||||
routesLoadingPromise = null;
|
||||
}
|
||||
|
||||
export async function loadAndAddDynamicRoutes() {
|
||||
if (routesLoadingPromise) {
|
||||
return routesLoadingPromise;
|
||||
}
|
||||
|
||||
if (dynamicRoutesAdded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
routesLoadingPromise = (async () => {
|
||||
try {
|
||||
const { useMenuStore } = await import("@/stores/menu");
|
||||
const menuStore = useMenuStore();
|
||||
const menuData = await menuStore.fetchMenus();
|
||||
|
||||
if (menuData && menuData.length > 0) {
|
||||
addDynamicRoutes(menuData);
|
||||
dynamicRoutesAdded = true;
|
||||
routesLoadingPromise = null;
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
dynamicRoutesAdded = true;
|
||||
routesLoadingPromise = null;
|
||||
return Promise.resolve();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载动态路由失败:', error);
|
||||
dynamicRoutesAdded = true;
|
||||
routesLoadingPromise = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
|
||||
return routesLoadingPromise;
|
||||
}
|
||||
|
||||
// 核心修改:移除扁平化,直接使用嵌套菜单生成路由
|
||||
function addDynamicRoutes(menus) {
|
||||
if (!menus?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接转换嵌套菜单为嵌套路由(不再扁平化)
|
||||
const dynamicRoutes = convertMenusToRoutes(menus);
|
||||
|
||||
if (router.hasRoute('Main')) {
|
||||
router.removeRoute('Main');
|
||||
}
|
||||
|
||||
// 重新添加主路由,合并静态子路由和动态路由
|
||||
router.addRoute({
|
||||
path: "/",
|
||||
name: "Main",
|
||||
component: () => import("@/views/Main.vue"),
|
||||
redirect: "/dashboard",
|
||||
meta: { requiresAuth: true },
|
||||
children: [...staticMainChildren, ...dynamicRoutes] // 合并静态和动态子路由
|
||||
});
|
||||
|
||||
dynamicRoutesAdded = true;
|
||||
}
|
||||
|
||||
function findRouteByName(routes, routeName) {
|
||||
for (const route of routes) {
|
||||
if (route.name === routeName) {
|
||||
return route;
|
||||
}
|
||||
if (route.children) {
|
||||
const found = findRouteByName(route.children, routeName);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findFirstValidRoute(routes) {
|
||||
for (const route of routes) {
|
||||
if (route.component) {
|
||||
return route;
|
||||
}
|
||||
if (route.children && route.children.length > 0) {
|
||||
const childRoute = findFirstValidRoute(route.children);
|
||||
if (childRoute) {
|
||||
return childRoute;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const token = localStorage.getItem("token");
|
||||
const publicPaths = ["/login", "/register", "/forget"];
|
||||
|
||||
if (publicPaths.includes(to.path)) {
|
||||
if (token) {
|
||||
if (!dynamicRoutesAdded) {
|
||||
await loadAndAddDynamicRoutes();
|
||||
}
|
||||
next({ path: "/home" });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
next({ path: "/login", query: { redirect: to.path } });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dynamicRoutesAdded) {
|
||||
await loadAndAddDynamicRoutes();
|
||||
// 路由加载后重新导航,确保路由匹配正确
|
||||
next({ path: to.path, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import { convertMenusToRoutes } from "./dynamicRoutes";
|
||||
|
||||
// 静态子路由:需要在 Main 框架内显示的页面
|
||||
const staticMainChildren = [
|
||||
{
|
||||
path: "/user/userProfile",
|
||||
name: "userProfile",
|
||||
component: () => import("@/views/user/userProfile.vue"),
|
||||
meta: { requiresAuth: true, title: "用户中心" }
|
||||
},
|
||||
// 兼容拼写错误的路径重定向
|
||||
{
|
||||
path: "/apps/erp/dashborad",
|
||||
redirect: "/apps/erp/dashboard"
|
||||
}
|
||||
];
|
||||
|
||||
// 静态路由:登录页独立、home 导航门户独立、404 页面独立
|
||||
const staticRoutes = [
|
||||
{
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: () => import("@/views/login/index.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: "/register",
|
||||
name: "Register",
|
||||
component: () => import("@/views/login/register.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: "/forget",
|
||||
name: "ForgetPassword",
|
||||
component: () => import("@/views/login/forget.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: "/home",
|
||||
name: "Home",
|
||||
component: () => import("@/views/home/index.vue"),
|
||||
meta: { requiresAuth: true, title: "系统导航", isStandalone: true }
|
||||
},
|
||||
// 兼容路径拼写错误:dashborad -> dashboard
|
||||
{
|
||||
path: "/apps/erp/dashborad",
|
||||
redirect: "/apps/erp/dashboard"
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
name: "NotFound",
|
||||
component: () => import("@/views/404/404.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
}
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: staticRoutes
|
||||
});
|
||||
|
||||
let dynamicRoutesAdded = false;
|
||||
let dynamicRoutesData = [];
|
||||
let routesLoadingPromise = null;
|
||||
|
||||
export function resetDynamicRoutes() {
|
||||
dynamicRoutesAdded = false;
|
||||
routesLoadingPromise = null;
|
||||
}
|
||||
|
||||
export async function loadAndAddDynamicRoutes() {
|
||||
if (routesLoadingPromise) {
|
||||
return routesLoadingPromise;
|
||||
}
|
||||
|
||||
if (dynamicRoutesAdded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
routesLoadingPromise = (async () => {
|
||||
try {
|
||||
const { useMenuStore } = await import("@/stores/menu");
|
||||
const menuStore = useMenuStore();
|
||||
const menuData = await menuStore.fetchMenus();
|
||||
|
||||
if (menuData && menuData.length > 0) {
|
||||
addDynamicRoutes(menuData);
|
||||
dynamicRoutesAdded = true;
|
||||
routesLoadingPromise = null;
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
dynamicRoutesAdded = true;
|
||||
routesLoadingPromise = null;
|
||||
return Promise.resolve();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载动态路由失败:', error);
|
||||
dynamicRoutesAdded = true;
|
||||
routesLoadingPromise = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
|
||||
return routesLoadingPromise;
|
||||
}
|
||||
|
||||
// 核心修改:移除扁平化,直接使用嵌套菜单生成路由
|
||||
function addDynamicRoutes(menus) {
|
||||
if (!menus?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接转换嵌套菜单为嵌套路由(不再扁平化)
|
||||
const dynamicRoutes = convertMenusToRoutes(menus);
|
||||
|
||||
if (router.hasRoute('Main')) {
|
||||
router.removeRoute('Main');
|
||||
}
|
||||
|
||||
// 重新添加主路由,合并静态子路由和动态路由
|
||||
router.addRoute({
|
||||
path: "/",
|
||||
name: "Main",
|
||||
component: () => import("@/views/Main.vue"),
|
||||
redirect: "/dashboard",
|
||||
meta: { requiresAuth: true },
|
||||
children: [...staticMainChildren, ...dynamicRoutes] // 合并静态和动态子路由
|
||||
});
|
||||
|
||||
dynamicRoutesAdded = true;
|
||||
}
|
||||
|
||||
function findRouteByName(routes, routeName) {
|
||||
for (const route of routes) {
|
||||
if (route.name === routeName) {
|
||||
return route;
|
||||
}
|
||||
if (route.children) {
|
||||
const found = findRouteByName(route.children, routeName);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findFirstValidRoute(routes) {
|
||||
for (const route of routes) {
|
||||
if (route.component) {
|
||||
return route;
|
||||
}
|
||||
if (route.children && route.children.length > 0) {
|
||||
const childRoute = findFirstValidRoute(route.children);
|
||||
if (childRoute) {
|
||||
return childRoute;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const token = localStorage.getItem("token");
|
||||
const publicPaths = ["/login", "/register", "/forget"];
|
||||
|
||||
if (publicPaths.includes(to.path)) {
|
||||
if (token) {
|
||||
if (!dynamicRoutesAdded) {
|
||||
await loadAndAddDynamicRoutes();
|
||||
}
|
||||
next({ path: "/home" });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
next({ path: "/login", query: { redirect: to.path } });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dynamicRoutesAdded) {
|
||||
await loadAndAddDynamicRoutes();
|
||||
// 路由加载后重新导航,确保路由匹配正确
|
||||
next({ path: to.path, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user