dynamicRoutes.js是动态路由生成工具,根据用户权限和菜单配置动态生成路由,实现基于权限的路由控制。
文件位置: src/utils/dynamicRoutes.js
- 根据菜单数据生成路由
- 权限验证和过滤
- 组件懒加载
- 路由元信息配置
- 嵌套路由处理
- 重定向路由生成
/**
* 根据菜单数据生成动态路由
* @param {Array} menuList - 菜单列表
* @param {Array} userPermissions - 用户权限列表
* @returns {Array} 生成的路由配置
*/
export function generateRoutes(menuList, userPermissions = []) {
if (!Array.isArray(menuList) || menuList.length === 0) {
return []
}
const routes = []
menuList.forEach(menu => {
// 检查权限
if (!hasPermission(menu, userPermissions)) {
return
}
const route = createRoute(menu)
// 处理子菜单
if (menu.children && menu.children.length > 0) {
route.children = generateRoutes(menu.children, userPermissions)
}
routes.push(route)
})
return routes
}/**
* 创建单个路由配置
* @param {Object} menu - 菜单项
* @returns {Object} 路由配置对象
*/
function createRoute(menu) {
const route = {
path: menu.path,
name: menu.name || generateRouteName(menu.path),
meta: {
title: menu.title,
icon: menu.icon,
requiresAuth: menu.requiresAuth !== false,
permissions: menu.permissions || [],
roles: menu.roles || [],
hidden: menu.hidden || false,
keepAlive: menu.keepAlive || false,
breadcrumb: menu.breadcrumb !== false,
activeMenu: menu.activeMenu,
noCache: menu.noCache || false,
affix: menu.affix || false
}
}
// 设置组件
if (menu.component) {
route.component = loadComponent(menu.component)
} else if (menu.children && menu.children.length > 0) {
// 如果有子路由但没有组件,使用布局组件
route.component = () => import('@/components/Layout/LayoutContainer.vue')
}
// 设置重定向
if (menu.redirect) {
route.redirect = menu.redirect
} else if (menu.children && menu.children.length > 0) {
// 自动重定向到第一个子路由
const firstChild = menu.children.find(child => !child.hidden)
if (firstChild) {
route.redirect = firstChild.path
}
}
return route
}/**
* 动态加载组件
* @param {String} componentPath - 组件路径
* @returns {Function} 组件加载函数
*/
function loadComponent(componentPath) {
// 处理不同的组件路径格式
if (componentPath.startsWith('/')) {
componentPath = componentPath.slice(1)
}
// 添加文件扩展名
if (!componentPath.endsWith('.vue')) {
componentPath += '.vue'
}
// 使用动态导入
return () => import(`@/views/${componentPath}`)
.catch(error => {
console.error(`Failed to load component: ${componentPath}`, error)
// 返回404组件作为降级
return import('@/views/NotFound.vue')
})
}/**
* 检查用户是否有访问菜单的权限
* @param {Object} menu - 菜单项
* @param {Array} userPermissions - 用户权限列表
* @returns {Boolean} 是否有权限
*/
function hasPermission(menu, userPermissions) {
// 如果菜单没有设置权限要求,默认允许访问
if (!menu.permissions || menu.permissions.length === 0) {
return true
}
// 检查用户是否有任一所需权限
return menu.permissions.some(permission =>
userPermissions.includes(permission)
)
}/**
* 根据路径生成路由名称
* @param {String} path - 路由路径
* @returns {String} 路由名称
*/
function generateRouteName(path) {
return path
.split('/')
.filter(segment => segment)
.map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))
.join('')
}/**
* 扁平化路由结构
* @param {Array} routes - 路由数组
* @returns {Array} 扁平化后的路由数组
*/
export function flattenRoutes(routes) {
const flattened = []
function flatten(routeList, parentPath = '') {
routeList.forEach(route => {
const fullPath = parentPath + route.path
flattened.push({
...route,
path: fullPath,
children: undefined
})
if (route.children && route.children.length > 0) {
flatten(route.children, fullPath + '/')
}
})
}
flatten(routes)
return flattened
}/**
* 根据路径查找路由
* @param {Array} routes - 路由数组
* @param {String} path - 路径
* @returns {Object|null} 找到的路由对象
*/
export function findRouteByPath(routes, path) {
for (const route of routes) {
if (route.path === path) {
return route
}
if (route.children && route.children.length > 0) {
const found = findRouteByPath(route.children, path)
if (found) {
return found
}
}
}
return null
}/**
* 获取路由所需的权限
* @param {Object} route - 路由对象
* @returns {Array} 权限数组
*/
export function getRoutePermissions(route) {
const permissions = []
if (route.meta && route.meta.permissions) {
permissions.push(...route.meta.permissions)
}
return [...new Set(permissions)]
}/**
* 创建路由守卫
* @param {Object} options - 守卫配置
* @returns {Function} 路由守卫函数
*/
export function createRouteGuard(options = {}) {
const {
requiresAuth = true,
checkPermissions = true,
redirectTo = '/login'
} = options
return (to, from, next) => {
// 检查认证
if (requiresAuth && to.meta.requiresAuth) {
const token = localStorage.getItem('token')
if (!token) {
next(redirectTo)
return
}
}
// 检查权限
if (checkPermissions && to.meta.permissions) {
const userPermissions = getUserPermissions()
const hasRequiredPermission = to.meta.permissions.some(permission =>
userPermissions.includes(permission)
)
if (!hasRequiredPermission) {
next('/403')
return
}
}
next()
}
}/**
* 获取当前用户权限
* @returns {Array} 用户权限数组
*/
function getUserPermissions() {
try {
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
return userInfo.permissions || []
} catch (error) {
console.error('Failed to get user permissions:', error)
return []
}
}/**
* 路由缓存管理类
*/
export class RouteCache {
constructor() {
this.cache = new Map()
this.maxSize = 50
}
/**
* 设置路由缓存
* @param {String} key - 缓存键
* @param {Object} route - 路由对象
*/
set(key, route) {
if (this.cache.size >= this.maxSize) {
// 删除最旧的缓存
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(key, {
route,
timestamp: Date.now()
})
}
/**
* 获取路由缓存
* @param {String} key - 缓存键
* @returns {Object|null} 缓存的路由对象
*/
get(key) {
const cached = this.cache.get(key)
if (!cached) {
return null
}
// 检查缓存是否过期(1小时)
const isExpired = Date.now() - cached.timestamp > 60 * 60 * 1000
if (isExpired) {
this.cache.delete(key)
return null
}
return cached.route
}
/**
* 清除所有缓存
*/
clear() {
this.cache.clear()
}
/**
* 删除指定缓存
* @param {String} key - 缓存键
*/
delete(key) {
this.cache.delete(key)
}
}
// 创建全局路由缓存实例
export const routeCache = new RouteCache()/**
* 标准化路由配置
* @param {Array} routes - 原始路由数组
* @returns {Array} 标准化后的路由数组
*/
export function normalizeRoutes(routes) {
return routes.map(route => {
const normalized = { ...route }
// 确保路径以/开头
if (normalized.path && !normalized.path.startsWith('/')) {
normalized.path = '/' + normalized.path
}
// 确保meta对象存在
if (!normalized.meta) {
normalized.meta = {}
}
// 设置默认值
normalized.meta.requiresAuth = normalized.meta.requiresAuth !== false
normalized.meta.hidden = normalized.meta.hidden || false
normalized.meta.keepAlive = normalized.meta.keepAlive || false
// 处理子路由
if (normalized.children && normalized.children.length > 0) {
normalized.children = normalizeRoutes(normalized.children)
}
return normalized
})
}/**
* 验证路由配置
* @param {Array} routes - 路由数组
* @returns {Object} 验证结果
*/
export function validateRoutes(routes) {
const errors = []
const warnings = []
function validate(routeList, parentPath = '') {
routeList.forEach((route, index) => {
const routePath = `${parentPath}[${index}]`
// 检查必需字段
if (!route.path) {
errors.push(`${routePath}: Missing required field 'path'`)
}
if (!route.name && !route.component) {
warnings.push(`${routePath}: Route should have either 'name' or 'component'`)
}
// 检查路径格式
if (route.path && !route.path.startsWith('/') && parentPath === '') {
warnings.push(`${routePath}: Root route path should start with '/'`)
}
// 检查组件
if (route.component && typeof route.component !== 'function') {
errors.push(`${routePath}: Component should be a function`)
}
// 递归检查子路由
if (route.children && route.children.length > 0) {
validate(route.children, `${routePath}.children`)
}
})
}
validate(routes)
return {
isValid: errors.length === 0,
errors,
warnings
}
}import { generateRoutes } from '@/utils/dynamicRoutes'
// 菜单数据
const menuList = [
{
path: '/dashboard',
title: '仪表盘',
icon: 'Dashboard',
component: 'Dashboard',
permissions: ['dashboard:view']
},
{
path: '/user',
title: '用户管理',
icon: 'User',
permissions: ['user:view'],
children: [
{
path: '/user/list',
title: '用户列表',
component: 'user/index',
permissions: ['user:list']
}
]
}
]
// 用户权限
const userPermissions = ['dashboard:view', 'user:view', 'user:list']
// 生成路由
const routes = generateRoutes(menuList, userPermissions)import { createRouter, createWebHistory } from 'vue-router'
import { generateRoutes, createRouteGuard } from '@/utils/dynamicRoutes'
const router = createRouter({
history: createWebHistory(),
routes: [
// 静态路由
{
path: '/login',
component: () => import('@/views/Login.vue')
}
]
})
// 动态添加路由
export async function setupDynamicRoutes() {
try {
// 获取菜单数据
const menuList = await getMenuList()
const userPermissions = await getUserPermissions()
// 生成动态路由
const dynamicRoutes = generateRoutes(menuList, userPermissions)
// 添加到路由器
dynamicRoutes.forEach(route => {
router.addRoute(route)
})
// 添加路由守卫
const routeGuard = createRouteGuard({
requiresAuth: true,
checkPermissions: true,
redirectTo: '/login'
})
router.beforeEach(routeGuard)
} catch (error) {
console.error('Failed to setup dynamic routes:', error)
}
}import { routeCache } from '@/utils/dynamicRoutes'
// 缓存路由
routeCache.set('user-routes', userRoutes)
// 获取缓存
const cachedRoutes = routeCache.get('user-routes')
// 清除缓存
routeCache.clear()import { validateRoutes, normalizeRoutes } from '@/utils/dynamicRoutes'
// 验证路由配置
const validation = validateRoutes(routes)
if (!validation.isValid) {
console.error('Route validation errors:', validation.errors)
}
// 标准化路由
const normalizedRoutes = normalizeRoutes(routes)const menuItem = {
path: '/user', // 路由路径
name: 'User', // 路由名称(可选)
title: '用户管理', // 菜单标题
icon: 'User', // 菜单图标
component: 'user/index', // 组件路径
permissions: ['user:view'], // 所需权限
roles: ['admin'], // 所需角色
hidden: false, // 是否隐藏
keepAlive: true, // 是否缓存
requiresAuth: true, // 是否需要认证
breadcrumb: true, // 是否显示面包屑
activeMenu: '/user/list', // 激活的菜单项
redirect: '/user/list', // 重定向路径
children: [] // 子菜单
}const guardOptions = {
requiresAuth: true, // 是否检查认证
checkPermissions: true, // 是否检查权限
redirectTo: '/login', // 重定向路径
whiteList: ['/login'], // 白名单路径
beforeEnter: (to, from) => {}, // 自定义前置守卫
afterEnter: (to, from) => {} // 自定义后置守卫
}- 组件路径: 确保组件路径正确,支持相对路径和绝对路径
- 权限检查: 权限检查基于数组包含关系,支持多权限验证
- 路由缓存: 合理使用路由缓存,避免内存泄漏
- 错误处理: 组件加载失败时会降级到404页面
- 性能优化: 大量路由时考虑懒加载和分批处理
最后更新时间:2025-09-19