增加新布局
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { createComponentLoader } from '@/utils/pathResolver';
|
||||
|
||||
// 工具函数:将扁平菜单转换为嵌套路由
|
||||
export function convertMenusToRoutes(menus) {
|
||||
// 1. 构建父子关系映射
|
||||
const menuMap = {};
|
||||
const routes = [];
|
||||
|
||||
// 先将所有菜单存入映射表
|
||||
menus.forEach(menu => {
|
||||
// 处理路径:去掉前导斜杠,因为是子路由
|
||||
let routePath = menu.path.startsWith('/') ? menu.path.substring(1) : menu.path;
|
||||
|
||||
menuMap[menu.id] = {
|
||||
path: routePath,
|
||||
name: `menu_${menu.id}`, // 使用唯一名称
|
||||
meta: {
|
||||
icon: menu.icon,
|
||||
title: menu.name,
|
||||
id: menu.id,
|
||||
parentId: menu.parentId,
|
||||
menuPath: menu.path // 保存原始路径(完整路径,如 /dashboard)
|
||||
},
|
||||
// 有组件路径才添加 component(目录菜单可能没有)
|
||||
// componentPath 格式: /dashboard/index.vue (来自数据库)
|
||||
// 使用通用工具自动转换为别名路径并加载
|
||||
...(menu.componentPath
|
||||
? {
|
||||
component: createComponentLoader(menu.componentPath)
|
||||
}
|
||||
: {})
|
||||
};
|
||||
});
|
||||
|
||||
// 2. 构建嵌套关系,并修正子路由路径
|
||||
menus.forEach(menu => {
|
||||
const currentRoute = menuMap[menu.id];
|
||||
if (menu.parentId === 0) {
|
||||
// 顶级菜单直接加入路由
|
||||
routes.push(currentRoute);
|
||||
} else {
|
||||
// 子菜单添加到父菜单的 children 中
|
||||
const parentRoute = menuMap[menu.parentId];
|
||||
if (parentRoute) {
|
||||
// 修正子路由路径:相对于父路由的路径
|
||||
const parentPath = parentRoute.path; // 例如 "system"
|
||||
const childFullPath = currentRoute.path; // 例如 "system/users"
|
||||
|
||||
// 如果子路径以父路径开头,则只保留剩余部分
|
||||
if (childFullPath.startsWith(parentPath + '/')) {
|
||||
currentRoute.path = childFullPath.substring(parentPath.length + 1); // "users"
|
||||
} else if (childFullPath.startsWith('/')) {
|
||||
// 如果仍然有前导斜杠,去掉它
|
||||
currentRoute.path = childFullPath.substring(1);
|
||||
}
|
||||
|
||||
if (!parentRoute.children) {
|
||||
parentRoute.children = [];
|
||||
}
|
||||
parentRoute.children.push(currentRoute);
|
||||
} else {
|
||||
// 如果找不到父路由,作为顶级路由处理
|
||||
routes.push(currentRoute);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 按 order 排序
|
||||
const sortRoutes = (routesList) => {
|
||||
routesList.sort((a, b) => {
|
||||
const orderA = menus.find(m => m.id === a.meta.id)?.order || 0;
|
||||
const orderB = menus.find(m => m.id === b.meta.id)?.order || 0;
|
||||
return orderA - orderB;
|
||||
});
|
||||
routesList.forEach(route => {
|
||||
if (route.children && route.children.length > 0) {
|
||||
sortRoutes(route.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
sortRoutes(routes);
|
||||
|
||||
return routes;
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import { convertMenusToRoutes } from "./dynamicRoutes";
|
||||
|
||||
// 静态路由:登录页独立,404 页面独立
|
||||
const staticRoutes = [
|
||||
{
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: () => import("@/views/login/index.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: "Main",
|
||||
component: () => import("@/views/Main.vue"),
|
||||
redirect: "/dashboard", // 默认重定向,动态路由加载后会更新
|
||||
children: [], // 所有页面路由都会作为 children 添加到这里
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
name: "NotFound",
|
||||
component: () => import("@/views/404/404.vue"),
|
||||
meta: { requiresAuth: false }
|
||||
}
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: staticRoutes
|
||||
});
|
||||
|
||||
// 动态路由加载状态
|
||||
let dynamicRoutesAdded = false;
|
||||
|
||||
// 从 API 加载并添加动态路由
|
||||
export async function loadAndAddDynamicRoutes() {
|
||||
if (dynamicRoutesAdded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
try {
|
||||
const { getAllMenus } = await import("@/api/menu");
|
||||
const res = await getAllMenus();
|
||||
|
||||
if (res && res.success && res.data) {
|
||||
// 保存菜单到 localStorage
|
||||
localStorage.setItem('menus', JSON.stringify(res.data));
|
||||
// 添加动态路由
|
||||
addDynamicRoutes(res.data);
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
// 如果 API 失败,尝试从 localStorage 加载
|
||||
const cachedMenus = JSON.parse(localStorage.getItem('menus') || '[]');
|
||||
if (cachedMenus.length) {
|
||||
addDynamicRoutes(cachedMenus);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载动态路由失败:', error);
|
||||
// 如果 API 失败,尝试从 localStorage 加载
|
||||
const cachedMenus = JSON.parse(localStorage.getItem('menus') || '[]');
|
||||
if (cachedMenus.length) {
|
||||
addDynamicRoutes(cachedMenus);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// 添加动态路由到 Main 的 children 中
|
||||
function addDynamicRoutes(menus) {
|
||||
if (dynamicRoutesAdded || !menus?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dynamicRoutes = convertMenusToRoutes(menus);
|
||||
|
||||
// 打印路由树结构(只打印路径信息,不序列化组件)
|
||||
console.log('生成的路由树:', dynamicRoutes.map(r => ({
|
||||
path: r.path,
|
||||
name: r.name,
|
||||
hasComponent: !!r.component,
|
||||
childrenCount: r.children?.length || 0
|
||||
})));
|
||||
|
||||
// 获取主路由
|
||||
const mainRoute = router.getRoutes().find(r => r.name === 'Main');
|
||||
if (!mainRoute) {
|
||||
console.error('找不到 Main 路由');
|
||||
return;
|
||||
}
|
||||
|
||||
// 移除旧的主路由
|
||||
router.removeRoute('Main');
|
||||
|
||||
// 查找第一个有效路由作为默认重定向
|
||||
const firstRoute = findFirstValidRoute(dynamicRoutes);
|
||||
let redirectPath = "/dashboard"; // 默认值
|
||||
if (firstRoute && firstRoute.meta?.menuPath) {
|
||||
redirectPath = firstRoute.meta.menuPath;
|
||||
}
|
||||
|
||||
// 重新添加主路由,包含所有动态子路由
|
||||
router.addRoute({
|
||||
...mainRoute,
|
||||
redirect: redirectPath,
|
||||
children: dynamicRoutes // 所有动态路由作为 Main 的 children
|
||||
});
|
||||
|
||||
dynamicRoutesAdded = true;
|
||||
|
||||
// 打印路由信息用于调试
|
||||
const finalMainRoute = router.getRoutes().find(r => r.name === 'Main');
|
||||
console.log('动态路由已添加:', {
|
||||
redirect: redirectPath,
|
||||
childrenCount: dynamicRoutes.length,
|
||||
childrenPaths: finalMainRoute?.children?.map(c => ({
|
||||
path: c.path,
|
||||
name: c.name,
|
||||
metaPath: c.meta?.menuPath
|
||||
}))
|
||||
});
|
||||
|
||||
// 测试路由解析(使用完整路径)
|
||||
const testResolve = router.resolve('/dashboard');
|
||||
console.log('测试路由解析 /dashboard:', {
|
||||
matched: testResolve.matched.map(m => ({ name: m.name, path: m.path })),
|
||||
fullPath: testResolve.fullPath,
|
||||
name: testResolve.name,
|
||||
hasMatched: testResolve.matched.length > 0
|
||||
});
|
||||
}
|
||||
|
||||
// 查找第一个有效的路由(有组件的路由)
|
||||
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");
|
||||
|
||||
// 1. 登录页面处理
|
||||
if (to.path === "/login") {
|
||||
if (token) {
|
||||
// 已登录,加载动态路由后跳转到首页
|
||||
if (!dynamicRoutesAdded) {
|
||||
await loadAndAddDynamicRoutes();
|
||||
}
|
||||
next({ path: "/" });
|
||||
} else {
|
||||
// 未登录,允许访问登录页
|
||||
next();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 其他所有页面都需要认证和 Main 框架
|
||||
if (!token) {
|
||||
// 未登录,跳转到登录页
|
||||
next({ path: "/login", query: { redirect: to.path } });
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 已登录,确保动态路由已加载(必须在检查 404 之前)
|
||||
if (!dynamicRoutesAdded) {
|
||||
await loadAndAddDynamicRoutes();
|
||||
// 路由加载后,使用原始路径重新导航(确保路由表已更新)
|
||||
// 如果 to 已经是 404,使用原始路径而不是 404 路由对象
|
||||
const targetPath = to.matched.length === 0 || to.name === "NotFound"
|
||||
? to.path // 如果匹配失败或者是 404,使用原始路径
|
||||
: to.fullPath;
|
||||
next({ path: targetPath, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 已登录且路由已加载,检查是否是 404
|
||||
// 注意:这里要在路由加载完成后才能正确判断是否是 404
|
||||
if (to.name === "NotFound") {
|
||||
// 可能是真正的 404,或者是根路径重定向还没完成
|
||||
if (to.path === "/" || to.path === "") {
|
||||
// 根路径,应该已经被 Main 路由的 redirect 处理,但如果还是 404,可能是因为 redirect 还没执行
|
||||
// 等待一下让 redirect 执行
|
||||
next();
|
||||
return;
|
||||
}
|
||||
// 其他路径的 404,允许显示 404 页面
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 已登录且路由已加载,正常路由,直接放行
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user