Skip to content

路由和菜单

了解 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,
        },
      },
    ],
  },
];

动态路由根据用户权限从后端获取,主链路如下:

typescript
// src/store/modules/permission-store.ts
const data = await MenuAPI.getRoutes();
const dynamicRoutes = transformRoutes(data);
routes.value = [...constantRoutes, ...dynamicRoutes];

路由元信息

在路由的 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' // 激活的菜单路径(用于详情页等特殊场景)
  }
}
属性类型说明
titlestring菜单标题
iconstring菜单图标(支持 Element Plus 图标和自定义图标)
hiddenboolean是否隐藏菜单,默认 false
alwaysShowboolean是否总是显示根菜单,默认 false
keepAliveboolean是否缓存页面,默认 false
affixboolean是否固定在 tags-view,默认 false
breadcrumbboolean是否显示在面包屑,默认 true
activeMenustring激活的菜单路径

多级菜单

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' }
        }
      ]
    }
  ]
}

外链与内嵌

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' }
    }
  ]
}

隐藏路由

不在侧边栏显示,但可以访问(常用于详情页):

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' // 激活用户管理菜单
      }
    }
  ]
}

路由传参

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.id

页面缓存

使用 keep-alive 缓存页面,组件必须设置 name 属性且与路由 name 一致:

typescript
{
  path: '/system/user',
  name: 'User',
  component: () => import('@/views/system/user/index.vue'),
  meta: { title: '用户管理', keepAlive: true }
}
vue
<script setup lang="ts">
defineOptions({ name: "User" });
</script>

路由守卫

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. 前往「角色管理」为角色分配该菜单权限
字段说明示例
菜单名称建议用 i18n keyroute.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 配置

相关链接

基于 MIT 许可发布