yunzer_go/pc/src/router/index.js

276 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;