路由和菜单
了解 vue3-element-admin 的路由配置和菜单管理。
路由配置
在 src/router/index.ts 中配置静态路由:
typescript
export const Layout = () => import("@/layouts/index.vue");
export const constantRoutes: RouteRecordRaw[] = [
{
path: "/login",
component: () => import("@/views/login/index.vue"),
meta: { hidden: true },
},
{
path: "/",
name: "/",
component: Layout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
name: "Dashboard",
component: () => import("@/views/dashboard/index.vue"),
meta: {
title: "dashboard",
icon: "homepage",
affix: true,
keepAlive: true,
},
},
],
},
];1
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
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
动态路由根据用户权限从后端获取,主链路如下:
typescript
// src/store/modules/permission-store.ts
const data = await MenuAPI.getRoutes();
const dynamicRoutes = transformRoutes(data);
routes.value = [...constantRoutes, ...dynamicRoutes];1
2
3
4
2
3
4
路由元信息
在路由的 meta 中配置菜单相关信息:
typescript
{
path: '/system',
component: Layout,
meta: {
title: '系统管理', // 菜单标题
icon: 'setting', // 菜单图标(支持 Element Plus 图标和自定义 SVG)
hidden: false, // 是否隐藏菜单
alwaysShow: true, // 是否总是显示根菜单
keepAlive: true, // 是否缓存页面
affix: false, // 是否固定在 tags-view
breadcrumb: true, // 是否显示面包屑
activeMenu: '/system/user' // 激活的菜单路径(用于详情页等特殊场景)
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
| 属性 | 类型 | 说明 |
|---|---|---|
title | string | 菜单标题 |
icon | string | 菜单图标(支持 Element Plus 图标和自定义图标) |
hidden | boolean | 是否隐藏菜单,默认 false |
alwaysShow | boolean | 是否总是显示根菜单,默认 false |
keepAlive | boolean | 是否缓存页面,默认 false |
affix | boolean | 是否固定在 tags-view,默认 false |
breadcrumb | boolean | 是否显示在面包屑,默认 true |
activeMenu | string | 激活的菜单路径 |
多级菜单
typescript
// 二级菜单
{
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' }
}
]
}
// 三级菜单
{
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' }
}
]
}
]
}1
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
41
42
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
41
42
外链与内嵌
typescript
// 外部链接
{
path: 'https://www.baidu.com',
meta: { title: '百度', icon: 'link' }
}
// 内嵌 iframe
{
path: '/external',
component: Layout,
children: [
{
path: 'https://www.baidu.com',
meta: { title: '百度', icon: 'link' }
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
隐藏路由
不在侧边栏显示,但可以访问(常用于详情页):
typescript
{
path: '/system',
component: Layout,
children: [
{
path: 'user',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '用户管理', icon: 'user' }
},
{
path: 'detail/:id(\\d+)',
component: () => import('@/views/demo/detail.vue'),
name: 'DemoDetail',
meta: {
title: '详情页',
hidden: true,
keepAlive: true,
activeMenu: '/system/user' // 激活用户管理菜单
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
路由传参
typescript
// 动态路由参数
router.push({ path: '/user/detail/1' })
const id = route.params.id
// Query 参数
router.push({ path: '/user/detail', query: { id: 1 } })
const id = route.query.id1
2
3
4
5
6
7
2
3
4
5
6
7
页面缓存
使用 keep-alive 缓存页面,组件必须设置 name 属性且与路由 name 一致:
typescript
{
path: '/system/user',
name: 'User',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '用户管理', keepAlive: true }
}1
2
3
4
5
6
2
3
4
5
6
vue
<script setup lang="ts">
defineOptions({ name: "User" });
</script>1
2
3
2
3
路由守卫
typescript
// src/router/guards/permission.ts
export function setupPermissionGuard() {
const whiteList = ["/login"];
router.beforeEach(async (to, from, next) => {
const isLoggedIn = useUserStore().isLoggedIn();
if (!isLoggedIn) {
whiteList.includes(to.path) ? next() : next(`/login?redirect=${encodeURIComponent(to.fullPath)}`);
return;
}
if (to.path === "/login") {
next({ path: "/" });
return;
}
const permissionStore = usePermissionStore();
if (!permissionStore.isRouteGenerated) {
const dynamicRoutes = await permissionStore.generateRoutes();
dynamicRoutes.forEach((route) => router.addRoute(route));
next({ ...to, replace: true });
return;
}
next();
});
}1
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
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
后台菜单配置
在后台管理界面配置菜单:
- 登录后台 → 系统管理 → 菜单管理
- 点击新增,选择类型:目录 / 菜单 / 按钮
- 填写字段并保存
- 前往「角色管理」为角色分配该菜单权限
| 字段 | 说明 | 示例 |
|---|---|---|
| 菜单名称 | 建议用 i18n key | route.document |
| 路径 (path) | 路由地址 | /system/menu |
| 组件 (component) | 前端组件路径 | system/menu/index |
| 图标 (icon) | 菜单图标 | el-icon-menu |
| 权限标识 | 按钮权限标识 | sys:menu:add |
菜单国际化:菜单名称填写 i18n key(如 route.document),详见 FAQ - 如何配置菜单国际化。
常见问题
菜单添加后前端看不到?
- 确认角色是否已分配该菜单
- 重新登录刷新权限缓存
- 检查菜单是否设置为隐藏
路由跳转报错找不到组件?
- 检查
component字段格式:目录/文件名(不含src/views/前缀和.vue后缀)
菜单高亮/面包屑不正确?
- 检查路由 meta 中的
activeMenu配置
