276 lines
8.1 KiB
JavaScript
276 lines
8.1 KiB
JavaScript
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: [
|
||
// 仪表盘路由:静态路由,永远放在第一个
|
||
{
|
||
path: "dashboard",
|
||
name: "Dashboard",
|
||
component: () => import("@/views/dashboard/index.vue"),
|
||
meta: {
|
||
requiresAuth: true,
|
||
title: "仪表盘",
|
||
icon: "fa-solid fa-gauge",
|
||
id: 1,
|
||
isStatic: true // 标记为静态路由
|
||
}
|
||
}
|
||
],
|
||
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;
|
||
// 路由加载 Promise,用于确保路由加载完成
|
||
let routesLoadingPromise = null;
|
||
|
||
// 从 API 加载并添加动态路由
|
||
export async function loadAndAddDynamicRoutes() {
|
||
// 如果已经有加载中的 Promise,直接返回它
|
||
if (routesLoadingPromise) {
|
||
return routesLoadingPromise;
|
||
}
|
||
|
||
// 如果已经加载过,直接返回
|
||
if (dynamicRoutesAdded) {
|
||
return Promise.resolve();
|
||
}
|
||
|
||
// 创建加载 Promise
|
||
routesLoadingPromise = (async () => {
|
||
try {
|
||
// 直接从 API 获取菜单数据
|
||
const { getAllMenus } = await import("@/api/menu");
|
||
const res = await getAllMenus();
|
||
|
||
if (res && res.success && res.data) {
|
||
// 添加动态路由
|
||
addDynamicRoutes(res.data);
|
||
dynamicRoutesAdded = true;
|
||
routesLoadingPromise = null;
|
||
return Promise.resolve();
|
||
} else {
|
||
dynamicRoutesAdded = true;
|
||
routesLoadingPromise = null;
|
||
return Promise.resolve();
|
||
}
|
||
} catch (error) {
|
||
// 即使出错也标记为已加载,避免无限重试
|
||
dynamicRoutesAdded = true;
|
||
routesLoadingPromise = null;
|
||
return Promise.resolve();
|
||
}
|
||
})();
|
||
|
||
return routesLoadingPromise;
|
||
}
|
||
|
||
// 添加动态路由到 Main 的 children 中
|
||
function addDynamicRoutes(menus) {
|
||
if (!menus?.length) {
|
||
return;
|
||
}
|
||
|
||
// 如果已经添加过,先移除旧路由(刷新时可能需要重新添加)
|
||
if (dynamicRoutesAdded) {
|
||
// 移除 Main 路由以便重新添加
|
||
if (router.hasRoute('Main')) {
|
||
router.removeRoute('Main');
|
||
}
|
||
dynamicRoutesAdded = false;
|
||
}
|
||
|
||
// 过滤掉 ID 为 1 的仪表盘菜单,因为它是静态路由
|
||
const filteredMenus = menus.filter(menu => menu.id !== 1);
|
||
|
||
const dynamicRoutes = convertMenusToRoutes(filteredMenus);
|
||
|
||
// 获取主路由
|
||
const mainRoute = router.getRoutes().find(r => r.name === 'Main');
|
||
if (!mainRoute) {
|
||
return;
|
||
}
|
||
|
||
// 获取现有的静态路由(仪表盘路由)
|
||
const staticChildren = mainRoute.children || [];
|
||
const dashboardRoute = staticChildren.find(r => r.name === 'Dashboard');
|
||
|
||
// 移除旧的主路由
|
||
if (router.hasRoute('Main')) {
|
||
router.removeRoute('Main');
|
||
}
|
||
|
||
// 重新添加主路由,仪表盘路由放在第一个,后面跟动态路由
|
||
const newMainRoute = {
|
||
...mainRoute,
|
||
redirect: "/dashboard", // 始终重定向到仪表盘
|
||
children: [
|
||
// 仪表盘静态路由永远放在第一个
|
||
...(dashboardRoute ? [dashboardRoute] : []),
|
||
// 动态路由跟在后面
|
||
...dynamicRoutes
|
||
]
|
||
};
|
||
|
||
router.addRoute(newMainRoute);
|
||
|
||
// 添加知识库的子路由(详情页和编辑页)
|
||
// 直接在 Main 路由下添加完整路径的子路由
|
||
router.addRoute('Main', {
|
||
path: 'apps/knowledge/detail/:id',
|
||
name: 'apps-knowledge-detail',
|
||
component: () => import('@/views/apps/knowledge/components/detail.vue'),
|
||
meta: {
|
||
requiresAuth: true,
|
||
title: '知识详情'
|
||
}
|
||
});
|
||
router.addRoute('Main', {
|
||
path: 'apps/knowledge/edit/:id',
|
||
name: 'apps-knowledge-edit',
|
||
component: () => import('@/views/apps/knowledge/components/edit.vue'),
|
||
meta: {
|
||
requiresAuth: true,
|
||
title: '编辑知识'
|
||
}
|
||
});
|
||
|
||
dynamicRoutesAdded = true;
|
||
}
|
||
|
||
// 查找第一个有效的路由(有组件的路由)
|
||
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();
|
||
// 如果路由加载后仍然未添加(API 失败)
|
||
if (!dynamicRoutesAdded) {
|
||
// 重新检查 token,如果 token 不存在或无效,跳转到登录页
|
||
const currentToken = localStorage.getItem('token');
|
||
if (!currentToken) {
|
||
next({ path: "/login", query: { redirect: to.path } });
|
||
return;
|
||
}
|
||
// 如果 token 存在但路由加载失败,可能是 token 过期或无效,清除 token 并跳转登录
|
||
localStorage.removeItem('token');
|
||
localStorage.removeItem('userInfo');
|
||
next({ path: "/login", query: { redirect: to.path } });
|
||
return;
|
||
}
|
||
|
||
// 路由加载完成后,等待一个tick确保Vue Router内部路由表已更新
|
||
await new Promise(resolve => setTimeout(resolve, 0));
|
||
|
||
// 重新解析路径检查是否能匹配
|
||
const resolved = router.resolve(to.path);
|
||
|
||
// 如果能匹配到路由,使用完整路径重新导航(确保Vue Router正确更新)
|
||
if (resolved.matched.length > 0 && resolved.name !== "NotFound") {
|
||
next({ path: to.fullPath || to.path, replace: true });
|
||
return;
|
||
}
|
||
|
||
// 如果无法匹配,使用原始路径重新导航
|
||
next({ path: to.fullPath || to.path, replace: true });
|
||
return;
|
||
}
|
||
|
||
// 3.5 如果路由已加载但当前路径未匹配,可能是刷新问题
|
||
// 注意:这里需要排除根路径和已知的静态路由
|
||
if (to.matched.length === 0 && to.name !== "NotFound" && to.path !== "/" && to.path !== "/dashboard") {
|
||
// 重新解析路径,看看是否能够匹配
|
||
const resolved = router.resolve(to.path);
|
||
|
||
if (resolved.matched.length > 0 && resolved.name !== "NotFound") {
|
||
// 能够匹配,说明路由表正常,使用路径重新导航
|
||
next({ path: to.path, 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; |