import { createRouter, createWebHashHistory } from 'vue-router' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import config from '../config' import { useUserStore } from '../stores/modules/user' import systemRoutes from './systemRoutes' // 配置 NProgress NProgress.configure({ showSpinner: false, trickleSpeed: 200, minimum: 0.3 }) /** * 404 路由 */ const notFoundRoute = { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('../layouts/other/404.vue'), meta: { title: '404', hidden: true } } // 创建路由实例 const router = createRouter({ history: createWebHashHistory(), routes: systemRoutes }) /** * 组件导入映射 */ const modules = import.meta.glob('../pages/**/*.vue') /** * 动态加载组件 * @param {string} componentPath - 组件路径 * @returns {Promise} 组件 */ function loadComponent(componentPath) { // 如果组件路径以 'views/' 或 'pages/' 开头,则从相应目录加载 if (componentPath.startsWith('views/')) { const path = componentPath.replace('views/', '../pages/') return modules[`${path}.vue`] } // 如果是简单的组件名称,从 pages 目录加载 if (componentPath.endsWith('index')){ return modules[`../pages/${componentPath}.vue`] } else { return modules[`../pages/${componentPath}/index.vue`] } } /** * 将后端菜单转换为路由格式 * @param {Array} menus - 后端返回的菜单数据 * @returns {Array} 路由数组 */ function transformMenusToRoutes(menus) { if (!menus || !Array.isArray(menus)) { return [] } return menus .filter(menu => menu && menu.path) .map(menu => { const route = { path: menu.path, name: menu.name || menu.path.replace(/\//g, '-'), meta: { title: menu.meta?.title || menu.title, icon: menu.meta?.icon || menu.icon, hidden: menu.hidden || menu.meta?.hidden, keepAlive: menu.meta?.keepAlive || false, affix: menu.meta?.affix || 0, role: menu.meta?.role || [] } } // 处理组件 if (menu.component) { route.component = loadComponent(menu.component) } // 处理子路由 if (menu.children && menu.children.length > 0) { route.children = transformMenusToRoutes(menu.children) } // 处理重定向 if (menu.redirect) { route.redirect = menu.redirect } return route }) } /** * 路由守卫 */ let isDynamicRouteLoaded = false router.beforeEach(async (to, from, next) => { // 开始进度条 NProgress.start() // 设置页面标题 document.title = to.meta.title ? `${to.meta.title} - ${config.APP_NAME}` : config.APP_NAME const userStore = useUserStore() const isLoggedIn = userStore.isLoggedIn() const whiteList = config.whiteList || [] // 1. 如果在白名单中,直接放行 if (whiteList.includes(to.path)) { next() return } // 2. 如果未登录,跳转到登录页 if (!isLoggedIn) { // 保存目标路由,登录后跳转 next({ path: '/login', query: { redirect: to.fullPath } }) return } // 3. 已登录情况 // 如果访问登录页,重定向到首页 if (to.path === '/login') { next({ path: config.DASHBOARD_URL }) return } // 4. 动态路由加载 if (!isDynamicRouteLoaded) { try { // 获取后端返回的用户菜单 const mergedMenus = userStore.getMenu() if (mergedMenus && mergedMenus.length > 0) { // 将合并后的菜单转换为路由 const dynamicRoutes = transformMenusToRoutes(mergedMenus) // 添加动态路由到 Layout 的子路由 dynamicRoutes.forEach(route => { router.addRoute('Layout', route) }) // 添加 404 路由(必须在最后添加) router.addRoute(notFoundRoute) isDynamicRouteLoaded = true // 重新导航,确保新添加的路由被正确匹配 next({ ...to, replace: true }) } else { // 如果没有菜单数据,重置并跳转到登录页 userStore.logout() next({ path: '/login', query: { redirect: to.fullPath } }) } } catch (error) { console.error('动态路由加载失败:', error) // 加载失败,清除用户信息并跳转到登录页 userStore.logout() next({ path: '/login', query: { redirect: to.fullPath } }) } } else { // 动态路由已加载,直接放行 next() } }) router.afterEach(() => { // 结束进度条 NProgress.done() }) /** * 重置路由(用于登出时) */ export function resetRouter() { // 移除所有动态添加的路由 isDynamicRouteLoaded = false // 重置为初始路由 const newRouter = createRouter({ history: createWebHashHistory(), routes: systemRoutes }) router.matcher = newRouter.matcher } export default router