Files
vueadmin/src/router/index.js
T
2026-01-15 23:27:51 +08:00

236 lines
5.3 KiB
JavaScript

import { createRouter, createWebHistory } from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import config from '../config'
import userRoutes from '../config/routes'
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: createWebHistory(import.meta.env.BASE_URL),
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 目录加载
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,
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 staticMenus = userRoutes || []
// 获取后端返回的用户菜单
const backendMenus = userStore.getMenu()
// 合并静态菜单和后端菜单
// 如果后端菜单为空,只使用静态菜单
// 如果后端菜单不为空,合并两个菜单,后端菜单优先
let mergedMenus = [...staticMenus]
if (backendMenus && backendMenus.length > 0) {
// 创建菜单映射,用于去重(以路径为唯一标识)
const menuMap = new Map()
// 先添加静态菜单
staticMenus.forEach(menu => {
if (menu.path) {
menuMap.set(menu.path, menu)
}
})
// 添加后端菜单,如果路径重复则覆盖
backendMenus.forEach(menu => {
if (menu.path) {
menuMap.set(menu.path, menu)
}
})
// 转换为数组
mergedMenus = Array.from(menuMap.values())
}
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: createWebHistory(import.meta.env.BASE_URL),
routes: systemRoutes
})
router.matcher = newRouter.matcher
}
export default router