路由和菜单
了解 vue3-element-admin 的路由配置和菜单管理。
路由配置
静态路由
在 src/router/routes.ts 中配置静态路由:
export const constantRoutes: RouteRecordRaw[] = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { hidden: true }
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index.vue'),
name: 'Dashboard',
meta: { title: '首页', icon: 'homepage', affix: true }
}
]
}
]2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
动态路由
动态路由根据用户权限从后端获取:
// store/modules/permission.ts
export const usePermissionStore = defineStore('permission', () => {
const routes = ref<RouteRecordRaw[]>([])
async function generateRoutes(roles: string[]) {
// 获取后端返回的路由
const res = await getRoutes()
const asyncRoutes = res.data
// 过滤有权限的路由
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
routes.value = constantRoutes.concat(accessedRoutes)
return accessedRoutes
}
return { routes, generateRoutes }
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
路由元信息
在路由的 meta 中配置菜单相关信息:
{
path: '/system',
component: Layout,
meta: {
title: '系统管理', // 菜单标题
icon: 'setting', // 菜单图标
hidden: false, // 是否隐藏菜单
alwaysShow: true, // 是否总是显示根菜单
roles: ['ADMIN'], // 允许访问的角色
perms: ['sys:menu'], // 需要的权限
keepAlive: true, // 是否缓存页面
affix: false, // 是否固定在 tags-view
noCache: false, // 是否不缓存
breadcrumb: true, // 是否显示面包屑
activeMenu: '/system/user' // 激活的菜单路径
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
meta 属性说明
| 属性 | 类型 | 说明 |
|---|---|---|
title | string | 菜单标题 |
icon | string | 菜单图标(支持 Element Plus 图标和自定义图标) |
hidden | boolean | 是否隐藏菜单,默认 false |
alwaysShow | boolean | 是否总是显示根菜单,默认 false |
roles | string[] | 允许访问的角色 |
perms | string[] | 需要的权限标识 |
keepAlive | boolean | 是否缓存页面,默认 false |
affix | boolean | 是否固定在 tags-view,默认 false |
noCache | boolean | 是否不缓存,默认 false |
breadcrumb | boolean | 是否显示在面包屑,默认 true |
activeMenu | string | 激活的菜单路径 |
菜单图标
Element Plus 图标
{
path: '/user',
meta: {
title: '用户管理',
icon: 'user' // Element Plus 图标名称
}
}2
3
4
5
6
7
自定义 SVG 图标
{
path: '/system',
meta: {
title: '系统管理',
icon: 'system' // 对应 src/assets/icons/system.svg
}
}2
3
4
5
6
7
多级菜单
二级菜单
{
path: '/system',
component: Layout,
redirect: '/system/user',
meta: { title: '系统管理', icon: 'setting' },
children: [
{
path: 'user',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '用户管理', icon: 'user' }
},
{
path: 'role',
component: () => import('@/views/system/role/index.vue'),
meta: { title: '角色管理', icon: 'role' }
}
]
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
三级菜单
{
path: '/nested',
component: Layout,
redirect: '/nested/menu1',
meta: { title: '多级菜单', icon: 'nested' },
children: [
{
path: 'menu1',
component: () => import('@/views/nested/menu1/index.vue'),
redirect: '/nested/menu1/menu1-1',
meta: { title: '菜单1', icon: 'menu' },
children: [
{
path: 'menu1-1',
component: () => import('@/views/nested/menu1/menu1-1/index.vue'),
meta: { title: '菜单1-1' }
}
]
}
]
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
外链菜单
外部链接
{
path: 'https://www.baidu.com',
meta: { title: '百度', icon: 'link' }
}2
3
4
内嵌 iframe
{
path: '/external',
component: Layout,
children: [
{
path: 'https://www.baidu.com',
meta: {
title: '百度',
icon: 'link',
iframe: true // 内嵌 iframe
}
}
]
}2
3
4
5
6
7
8
9
10
11
12
13
14
隐藏菜单
隐藏路由
不在侧边栏显示,但可以访问:
{
path: '/user',
component: Layout,
meta: { hidden: true },
children: [
{
path: 'profile',
component: () => import('@/views/user/profile.vue'),
meta: { title: '个人中心' }
}
]
}2
3
4
5
6
7
8
9
10
11
12
详情页面
详情页面通常隐藏在菜单中:
{
path: '/user',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/user/index.vue'),
meta: { title: '用户管理' }
},
{
path: 'detail/:id',
component: () => import('@/views/user/detail.vue'),
meta: {
title: '用户详情',
hidden: true,
activeMenu: '/user/index' // 激活用户管理菜单
}
}
]
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
路由传参
动态路由参数
// 路由配置
{
path: 'detail/:id',
component: () => import('@/views/user/detail.vue')
}
// 跳转
router.push({ path: '/user/detail/1' })
// 获取参数
const route = useRoute()
const id = route.params.id2
3
4
5
6
7
8
9
10
11
12
Query 参数
// 跳转
router.push({
path: '/user/detail',
query: { id: 1 }
})
// 获取参数
const route = useRoute()
const id = route.query.id2
3
4
5
6
7
8
9
页面缓存
使用 keep-alive 缓存页面:
{
path: '/user',
component: () => import('@/views/user/index.vue'),
meta: {
title: '用户管理',
keepAlive: true // 开启缓存
}
}2
3
4
5
6
7
8
注意:组件必须设置 name 属性,且与路由的 name 保持一致。
<script setup lang="ts">
defineOptions({
name: 'User' // 与路由 name 一致
})
</script>2
3
4
5
面包屑
面包屑自动根据路由生成:
{
path: '/system',
meta: { title: '系统管理', breadcrumb: true },
children: [
{
path: 'user',
meta: { title: '用户管理', breadcrumb: true }
}
]
}2
3
4
5
6
7
8
9
10
显示效果:系统管理 / 用户管理
路由守卫
全局前置守卫
router.beforeEach(async (to, from, next) => {
// 设置页面标题
document.title = to.meta.title || '默认标题'
// 判断是否登录
const token = getToken()
if (token) {
if (to.path === '/login') {
next({ path: '/' })
} else {
// 判断是否已获取用户信息
const hasRoles = store.state.user.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// 获取用户信息
await store.dispatch('user/getUserInfo')
// 生成动态路由
const accessRoutes = await store.dispatch('permission/generateRoutes')
// 动态添加路由
accessRoutes.forEach(route => {
router.addRoute(route)
})
next({ ...to, replace: true })
} catch (error) {
await store.dispatch('user/logout')
next({ path: '/login' })
}
}
}
} else {
// 白名单直接访问
if (whiteList.includes(to.path)) {
next()
} else {
next({ path: '/login', query: { redirect: to.path } })
}
}
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
菜单管理实践
说明
本节介绍如何在后台管理系统中操作菜单,包括新增菜单、一级菜单、多级菜单以及开启页面缓存功能。
新增菜单
在系统中,菜单通常呈现为目录 + 菜单的二级结构。首先需要创建一个目录,然后在该目录下添加菜单。
1. 新增目录
在系统管理 > 菜单管理页面,点击"新增"按钮,进行以下配置:
操作步骤:
- 选择顶级菜单作为父级菜单
- 设置菜单名称(例如:系统管理)
- 选择目录作为菜单类型
- 路由路径需要以
/开头(例如:/system) - 选择图标
- 设置排序值
- 其他使用默认设置即可

对应的路由配置:
{
path: '/system',
component: Layout,
meta: {
title: '系统管理',
icon: 'setting',
alwaysShow: true
}
}2
3
4
5
6
7
8
9
2. 新增菜单
在目录下新增菜单,选择刚添加的目录作为父级菜单。
操作步骤:
- 选择刚添加的目录作为父级菜单
- 填写菜单名称(例如:用户管理)
- 填写路由名称(例如:User)
- 填写路由路径(例如:user,不需要
/开头) - 填写组件路径(例如:system/user/index,与实际页面组件路径一致)
- 选择显示状态为"显示"
- 设置排序值

对应的路由配置:
{
path: '/system',
component: Layout,
children: [
{
path: 'user',
component: () => import('@/views/system/user/index.vue'),
name: 'User',
meta: {
title: '用户管理',
icon: 'user'
}
}
]
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意事项:
- ✅ 确保路由名称(
name)唯一 - ✅ 组件路径与页面组件的实际路径一致(不需要
@/views/前缀和.vue后缀) - ✅ 配置显示状态和排序值
- ✅ 如果是菜单(非目录),必须填写组件路径
新增一级菜单
系统支持创建一级菜单,但不直接支持将菜单作为根菜单。需要通过特殊配置实现。
核心技巧
通过将目录的"始终显示"设置为否,使其唯一的子菜单呈现为一级菜单。
操作步骤
1. 添加一级目录
在菜单管理页面创建一级目录,关键配置:
- 父级菜单:选择顶级菜单
- 菜单名称:随意填写(例如:首页目录)
- 菜单类型:选择目录
- 路由路径:例如
/dashboard - 始终显示:设置为否(关键配置)

2. 添加子菜单
在刚才创建的目录下添加子菜单:
- 父级菜单:选择刚创建的目录
- 菜单名称:例如"首页"
- 路由名称:例如 Dashboard
- 路由路径:例如 index
- 组件路径:例如 dashboard/index

3. 效果展示
最终效果:侧边栏只显示"首页",不显示父级目录,呈现为一级菜单。

对应的路由配置:
{
path: '/dashboard',
component: Layout,
redirect: '/dashboard/index',
meta: {
alwaysShow: false // 关键:不总是显示父菜单
},
children: [
{
path: 'index',
component: () => import('@/views/dashboard/index.vue'),
name: 'Dashboard',
meta: {
title: '首页',
icon: 'dashboard',
affix: true // 固定在标签栏
}
}
]
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
注意事项
- 一级目录下只能有一个子菜单,否则会显示为二级结构
- 必须将目录的
alwaysShow设置为false - 子菜单的图标和名称会直接显示在侧边栏
新增多级菜单
多级菜单的操作与普通的二级菜单类似,首先需要创建一级目录,然后增加二级目录,相对应的在多级目录下创建菜单即可。
操作步骤
1. 创建一级目录
- 父级菜单:顶级菜单
- 菜单类型:目录
- 示例:系统管理
2. 创建二级目录
- 父级菜单:选择刚创建的一级目录
- 菜单类型:目录
- 示例:权限管理
3. 创建三级菜单
- 父级菜单:选择二级目录
- 菜单类型:菜单
- 填写组件路径
- 示例:用户管理、角色管理
路由配置示例
三级菜单结构:
{
path: '/system',
component: Layout,
meta: {
title: '系统管理',
icon: 'setting'
},
children: [
{
path: 'permission',
redirect: '/system/permission/user',
meta: {
title: '权限管理',
icon: 'permission'
},
children: [
{
path: 'user',
component: () => import('@/views/system/permission/user/index.vue'),
name: 'PermissionUser',
meta: { title: '用户管理' }
},
{
path: 'role',
component: () => import('@/views/system/permission/role/index.vue'),
name: 'PermissionRole',
meta: { title: '角色管理' }
}
]
}
]
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
提示
- 多级菜单理论上支持无限层级,但建议不超过 3 级,以保持良好的用户体验
- 二级目录需要设置
redirect属性,指向默认显示的子菜单 - 每个菜单的
name必须唯一
开启页面缓存
项目支持菜单页面的缓存(keep-alive)功能,可以在切换菜单时保留页面状态,提升用户体验。
缓存原理
Vue 的 keep-alive 通过组件名称(name)来缓存组件实例。因此,路由名称必须与组件名称完全一致。
后台配置步骤
在菜单管理页面进行以下配置:
1. 开启缓存选项
在新增或编辑菜单时,勾选"是否缓存"选项:

2. 确保路由名称与组件名称一致
- 菜单的路由名称:例如
User - 组件的 name 属性:必须也是
User
代码配置
路由配置:
{
path: 'user',
component: () => import('@/views/system/user/index.vue'),
name: 'User', // 路由名称
meta: {
title: '用户管理',
keepAlive: true // 开启缓存
}
}2
3
4
5
6
7
8
9
组件配置:
<script setup lang="ts">
// 组件名称必须与路由名称一致
defineOptions({
name: 'User' // 与路由 name 保持一致
})
// 页面逻辑...
</script>
<template>
<div>
<!-- 页面内容 -->
</div>
</template>2
3
4
5
6
7
8
9
10
11
12
13
14
验证缓存是否生效
- 访问需要缓存的页面,进行一些操作(例如滚动、输入内容)
- 切换到其他页面
- 再次返回该页面,检查页面状态是否保留
注意事项
- ✅ 路由
name必须与组件defineOptions中的name完全一致(大小写敏感) - ✅ 组件名称建议使用大驼峰命名(PascalCase),例如:
UserManagement - ✅ 缓存的组件在切换时会保留表单输入、滚动位置等状态
- ⚠️ 如果名称不一致,缓存将不会生效
- ⚠️ 组件必须使用
defineOptions定义名称,export default方式不支持
常见问题
问:为什么我开启了缓存,但页面还是会重新加载?
答:请检查以下几点:
- 路由的
name和组件的name是否完全一致 - 组件是否使用了
defineOptions({ name: 'XXX' }) - 路由的
meta.keepAlive是否设置为true
问:缓存的组件如何刷新数据?
答:可以使用以下生命周期钩子:
import { onActivated } from 'vue'
// 每次激活时刷新数据
onActivated(() => {
loadData()
})2
3
4
5
6
常见问题
1. 菜单不显示
可能原因:
meta.hidden设置为true- 没有权限访问该菜单
- 路由配置错误
解决方案:
- 检查路由配置中的
hidden属性 - 确认用户角色是否有权限
- 验证路由路径和组件路径是否正确
2. 页面缓存不生效
可能原因:
- 路由
name与组件name不一致 - 未设置
keepAlive: true - 组件未使用
defineOptions定义名称
解决方案:
- 确保路由和组件的名称一致
- 在路由
meta中添加keepAlive: true - 使用
defineOptions定义组件名称
3. 面包屑显示异常
可能原因:
- 路由层级结构不正确
meta.title未配置
解决方案:
- 检查路由的父子关系
- 为每个路由配置
meta.title
