Skip to content

路由(导航)

默认配置下,导航菜单通过路由数据自动生成。

项目路由存放在 /src/router/modules/ 目录下,每一个 ts 文件会被视为一个路由模块。所有路由模块最终会在 /src/router/routes.ts 文件里引入并放到不同的主导航下。

注意

让路由配置从 /src/router/modules/ 中导出,在 /src/router/routes.ts 中统一引入,是预留的一个扩展点,基于此后续才能以应用为级别去复用, 统一的路由管理文件同时也方便我们对路由进行统一的处理

基本配置

二级路由

一个最常见的路由模块可参考以下结构:

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/example',
  component: () => import('@/layouts/index.vue'),
  redirect: '/example/index',
  name: 'Example',
  meta: {
    title: '演示',
  },
  children: [
    {
      path: 'index',
      name: 'ExampleIndex',
      component: () => import('@/views/example/index.vue'),
      meta: {
        title: '演示页面',
      },
    },
  ],
}

export default routes

注意

  • 所有路由的 name 请确保唯一,不能重复
  • 一级路由的 component 需设置为 () => import('@/layouts/index.vue') ,并且 path 前面需要加 /,其余子路由都不要以 / 开头

多级路由

说明

多级路由最终都会转成二级路由并注册,但多级嵌套的层级结构会在导航菜单和面包屑导航中得到保留。

多级路由的中间层级,无需设置 component

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/example',
  component: () => import('@/layouts/index.vue'),
  redirect: '/example/level/index',
  name: 'Example',
  meta: {
    title: '演示',
  },
  children: [
    {
      path: 'level',
      // 无需设置 componment
      meta: {
        title: '中间层级',
      },
      children: [
        {
          path: 'index',
          name: 'ExampleIndex',
          component: () => import('@/views/example/index.vue'),
          meta: {
            title: '演示页面',
          },
        },
      ],
    },
  ],
}

export default routes

主导航

主导航并非路由的一部分,它只是将路由模块进行归类,这么做的目的是方便调整单个路由模块的展示位置,并且不会影响路由路径。

/src/router/routes.ts 里进行设置:

ts
const asyncRoutes: Route.recordMainRaw[] = [
  {
    meta: {
      title: '演示',
      icon: 'menu-default',
    },
    children: [
      MultilevelMenuExample,
      BreadcrumbExample,
      KeepAliveExample,
    ],
  },
  {
    meta: {
      title: '其它',
      icon: 'menu-other',
    },
    children: [
      ComponentExample,
      PermissionExample,
    ],
  },
]

主导航只需设置 metachildren 两个参数,其中 meta 接受 titlei18niconactiveIconauth 这 5 个参数,children 则是存放不同的路由模块。

导航配置

框架的核心是通过路由数据生成导航菜单,所以除了路由的基本配置外,框架还提供了针对导航的自定义配置,这些配置都存放在 meta 对象里。

title

  • 类型 string | function

导航、面包屑导航以及页面中展示的标题

设置 title 会改变导航菜单的显示名称,同时也会影响标签栏、面包屑导航的显示,如果开启 app.enableDynamicTitle (启动态标题)也会影响页面title的值。

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/role',
  component: () => import('@/views/role/index.vue'),
  name: 'Role',
  meta: {
    title: '角色管理',
  },
}

export default routes

i18n

  • 类型 string

标题国际化对应的 key 值,需要引入 $t 函数,然后在 meta 对象里设置 i18n 参数,$t 没有实际作用只是为了调起 vscode 的插件提示。

ts
import type { RouteRecordRaw } from 'vue-router'
import { $t } from '@/locales'

const routes: RouteRecordRaw = {
  path: '/role',
  component: () => import('@/views/role/index.vue'),
  name: 'Role',
  meta: {
    title: '角色管理',
    i18n: $t('route.rbac.role'),
  },
}

export default routes

提示

详细可阅读《国际化》。

icon

  • 类型 string

导航中显示的图标,icon 参数用于设置导航菜单中的图标,如果开启了 tabbar.enableIcon (是否开启标签栏图标显示),则会影响标签栏图标的显示。

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/role',
  component: () => import('@/views/role/index.vue'),
  name: 'Role',
  meta: {
    title: '角色管理',
    icon: 'i-fluent-tag-multiple-16-regular',
  },
}

export default routes

提示

该项配置最终会通过 <SvgIcon /> 组件进行展示,意味着你可以使用自定义图标,也可使用 Iconify 提供的图标,详细可阅读《SVG 图标》。

activeIcon

  • 类型 string

导航激活时显示的图标

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/role',
  component: () => import('@/views/role/index.vue'),
  name: 'Role',
  meta: {
    title: '角色管理',
    icon: 'i-fluent-tag-multiple-16-regular',
    activeIcon: 'i-fluent-tag-multiple-16-regular',
  },
}

export default routes

提示

该项配置最终会通过 <SvgIcon /> 组件进行展示,意味着你可以使用自定义图标,也可使用 Iconify 提供的图标,详细可阅读《SVG 图标》。

defaultOpened

  • 类型 boolean
  • 默认值 false

次导航是否默认展开,在父级路由上设置后,会默认展开并显示下一级的子路由

注意

该特性仅在 顶部模式 / 侧边栏模式(含主导航) / 侧边栏模式(无主导航) 下生效。

使用该特性时,建议在应用配置中关闭 menu.subMenuUniqueOpened 设置,否则可能无法看到效果。

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/role',
  component: () => import('@/views/role/index.vue'),
  name: 'Role',
  meta: {
    title: '角色管理',
    icon: 'i-fluent-tag-multiple-16-regular',
    defaultOpened: true,
  },
}

export default routes

permanent

  • 类型 'boolean'
  • 默认值 false

是否常驻标签页,使用该特性时,需要在应用配置中开启 tabbar.enable 设置,同时需注意,请勿在带有参数的路由上设置该特性。

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/role',
  component: () => import('@/views/role/index.vue'),
  name: 'Role',
  meta: {
    title: '角色管理',
    icon: 'i-fluent-tag-multiple-16-regular',
    permanent: true
  },
}

export default routes

auth

  • 类型 string | string[]

该路由访问权限,如果不设置则没有权限校验,支持多个权限叠加,只要满足一个,即可进入,用户在访问路由时,会判断当前路由是否具备访问权限,不具备访问权限则会显示 403 页面,详细可阅读《权限 - 路由权限》。

为避免多级路由同时设置 auth 可能会导致逻辑冲突,框架会以最先设置的 auth 为准:

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/system',
  meta: {
    title: '系统管理',
  },
  children: [
    {
      path: 'department',
      meta: {
        title: '部门管理',
        auth: 'a',
      },
      children: [
        {
          path: 'job',
          meta: {
            title: '职位管理',
          },
          children: [
            {
              path: 'member',
              meta: {
                title: '人员管理',
                auth: 'b', // 无效设置,该路由的访问权限会以 auth: 'a' 为准
              },
            },
          ],
        },
      ],
    },
  ],
}

export default routes
  • 类型 boolean
  • 默认值 true

该路由是否在菜单导航中展示,当子路由里没有可展示的路由时,在菜单导航中则只会显示父级路由,例如:

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/permission',
  component: () => import('@/layouts/index.vue'),
  meta: {
    title: '权限验证',
  },
  children: [
    {
      path: '',
      component: () => import('@/views/permission.vue'),
      meta: {
        title: '权限验证',
        sidebar: false,
      },
    },
  ],
}

export default routes

activeMenu

  • 类型 'string'

指定高亮的菜单导航,需要设置完整路由地址,该参数常与 sidebar: false 一起使用,因为路由不在菜单导航显示,会导致进入该路由后,菜单导航高亮效果失效,所以需要手动指定。

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/news',
  component: () => import('@/layouts/index.vue'),
  meta: {
    title: '新闻管理',
  },
  children: [
    {
      path: 'list',
      component: () => import('@/views/news/list.vue'),
      meta: {
        title: '新闻列表',
      },
    },
    {
      path: 'detail',
      component: () => import('@/views/news/detail.vue'),
      meta: {
        title: '新闻详情',
        sidebar: false,
        activeMenu: '/news/list',
      },
    },
  ],
}

export default routes

singleMenu beta

  • 类型 boolean
  • 默认值 false

是否为单个一级导航菜单,该配置用于简化只想展示一级,没有二级菜单导航的路由配置。

需注意,该配置只能在一级路由上设置,且只在一级路由上生效。

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/test',
  component: () => import('@/views/test/index.tsx'),
  name: 'test',
  meta: {
    title: '测试页面',
    singleMenu: true,
  },
}

export default routes

以上路由配置等同于以下配置:

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/test',
  component: () => import('@/layouts/index.vue'),
  name: 'test',
  meta: {
    title: '测试页面',
  },
  children: [
    {
      path: '',
      component: () => import('@/views/test/index.tsx'),
      meta: {
        title: '测试页面',
        sidebar: false,
        breadcrumb: false,
      },
    },
  ],
}

export default routes
  • 类型 boolean
  • 默认值 true

该路由是否在面包屑导航中展示

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/role',
  component: () => import('@/views/role/index.vue'),
  name: 'Role',
  meta: {
    title: '角色管理',
    icon: 'i-fluent-tag-multiple-16-regular',
    breadcrumb: false
  },
}

export default routes

cache

  • 类型 boolean | string | string[]

是否对该页面进行缓存

  • boolean 设置为 true 时,该路由页面会被一直缓存
  • string 设置某个目标路由的 name ,表示当前路由页面跳转到设置的 name 对应的路由页面时,则将当前路由页面进行缓存,否则不缓存
  • string[] ,可设置一个目标路由的 name 数组

提示

当类型为 stringstring[] 时,可以更精细的去控制页面缓存的逻辑。例如从列表页进入详情页,则需要将列表页进行缓存;而从列表页进入其它页面,则无需将列表页进行缓存。详细可阅读《页面缓存》。

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/role',
  component: () => import('@/views/role/index.vue'),
  name: 'Role',
  meta: {
    title: '角色管理',
    icon: 'i-fluent-tag-multiple-16-regular',
    // boolean
    cache: true,
    // string
    cache: 'RoleDetail',
    // string[]
    cache: ['RoleDetail', 'RoleEdit'],
  },
}

export default routes

noCache

  • 类型 string | string[]

是否对该页面清除缓存,须设置 cache 才会生效

  • string 设置某个目标路由的 name ,表示当前路由页面跳转到设置的 name 对应的路由页面时,则将当前路由页面清除缓存,否则不清除缓存
  • string[] ,可设置一个目标路由的 name 数组

提示

该属性通常在启用标签栏合并时会使用到。详细可阅读《页面缓存 - 标签栏开启且合并》。

badge

  • 类型 boolean | number | string

导航标记,设置不同的类型值,展示效果也会不同。

  • boolean 展示形式为点,当值为 false 时隐藏
  • number 展示形式为文本,当值小于等于 0 时隐藏
  • string 展示形式为文本,当值为空时隐藏

如果标记需要动态更新,请设置为箭头函数形式,并返回外部变量,例如搭配 pinia 一起使用。

ts
badge: () => globalStore.number
  • 类型 string

外部网页链接,会在新窗口访问该链接。

外部网页无需设置 component ,但需设置 redirectname 属性。

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/xxx',
  component: () => import('@/layouts/index.vue'),
  redirect: '/xxx/link',
  meta: {
    title: '外部网页',
  },
  children: [
    {
      path: 'link',
      redirect: '',
      name: 'Link',
      meta: {
        title: '百度',
        link: 'www.baidu.com',
      },
    },
  ],
}

export default routes

iframe

  • 类型 string | function

内嵌网页链接,会启用一个 <iframe> 并载入该链接。

内嵌网页无需设置 component ,但需设置 redirectname 属性,如果同时设置了 meta.linkmeta.link 优先级更高。

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/xxx',
  component: () => import('@/layouts/index.vue'),
  redirect: '/xxx/iframe',
  meta: {
    title: '内嵌网页',
  },
  children: [
    {
      path: 'iframe',
      redirect: '',
      name: 'Iframe',
      meta: {
        title: '百度',
        iframe: 'www.baidu.com',
      },
    },
  ],
}

export default routes

iframe 传入一个函数的时候,参数传入 userStore.user,你也可以根据自己的在函数内调用其他 store 的数据。

提示

iframe 传入函数是为了更灵活的处理内嵌网页的链接,例如 iframe 内嵌页面做免登处理等逻辑

ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw = {
  path: '/xxx',
  component: () => import('@/layouts/index.vue'),
  redirect: '/xxx/iframe',
  meta: {
    title: '内嵌网页',
  },
  children: [
    {
      path: 'iframe',
      redirect: '',
      name: 'Iframe',
      meta: {
        title: '百度',
        iframe: (user) => {
          return `www.baidu.com/user/${user.userId}`
        },
      },
    },
  ],
}

export default routes

内嵌网页同样支持使用 meta.cachemeta.noCache 属性来开启页面缓存,但考虑到 <iframe> 本身的性能问题,框架默认提供最大缓存数量为 3 个,超过 3 个则会自动清除最早的缓存页面。

如果需要修改最大缓存数量,请在应用配置中设置:

ts
const globalSettings: Settings.all = {
  mainPage: {
    iframeCacheMax: 3,
  },
}
  • 类型 boolean

该路由是否显示底部版权信息,该参数比应用配置里的 copyright.enable 优先级高,不设置则继承应用配置里的设置。

paddingBottom

  • 类型 string

该路由是否需要空出距离底部距离,当使用类似 <FixedActionBar /> 这类通过 position: fixed 固定在底部的组件时,需要手动设置该参数,目的是为了防止页面底部可能被遮挡。

ts
paddingBottom: '80px'

whiteList

  • 类型 boolean

是否开启白名单,开启后无需登录即可访问,这个属性比较特殊,因为基于后台框架的页面基本上都是需要登录后才能访问,如果希望增加免登录的页面(白名单页面),也就是脱离框架本身,相对独立的页面,你可以按照下面的方式处理:

首先在 /src/router/routes.tsconstantRoutes 配置免登录页面的路由,然后在 meta 对象里设置 whiteList: true ,例子如下:

ts
const constantRoutes: RouteRecordRaw[] = [
  {
    path: '/no/login/example',
    name: 'noLoginExample',
    component: () => import('@/views/no-login-example.vue'),
    meta: {
      title: '免登录页面',
      whiteList: true,
    },
  },
]

需要注意,请勿在系统路由动态路由上设置该属性,因为这里面的路由,它们的一级路由调用的是 Layout 组件,而 Layout 组件是必须登录才能正常使用。

isDev

  • 类型 boolean

当为 true 的时候,点击会提示 暂未上线,敬请期待!

其他路由生成方式

TIP

本章节提供另外两种路由生成方式,如果你对 技术底座 提供的权限管理系统有较大改造需求,比如想要通过后端配置路由,或者想要通过文件系统生成路由,可以参考下面的内容。

技术底座 目前提供的权限管理方式参考权限章节

后端生成 beta

不推荐!

导航后端配置并生成这个需求,基本上是后端开发者刚接触前端后台项目开发时最常问的一个问题,但当真正做过前端开发并了解 Vue 的开发模式后,会意识到这个需求是多此一举的,并且会导致框架提供的部分导航特性无法使用,是得不偿失的做法。

其根本原因在于,导航是通过路由生成的,而路由是与页面组件直接挂钩,即一个路由则对应着一个 .vue 的页面组件文件。即便将路由数据通过后端配置,也要确保路由对应的页面组件在项目中存在,这就导致在开发环境下,通过后端配置好路由数据后,还需要在项目中新增对应的 .vue 文件写业务代码,并不是后端新增一个路由,前端就能直接访问到该路由页面,这也就意味着在生产环境中不能随意修改路由数据,可能会导致导航无法访问,因为很多路由设置和业务逻辑是高度耦合的,例如页面跳转用到的 namepath 不能随意修改,component 不能设置不存在的页面组件。交给后端动态配置的会让前端开发处于及其疲劳状态,你甚至在没有启动服务器的情况下都不能看到页面,让开发流程复杂化,是得不偿失的一种做法。

如果你执意使用该特性,请确保你了解该特性在上面所说的优缺点,并谨慎使用!

在应用配置中设置:

ts
const globalSettings: Settings.all = {
  app: {
    routeBaseOn: 'backend'
  }
}

开启后在 /src/api/modules/app.ts 文件里找到 routeList() 这个函数,并修改这个函数的请求地址,请求返回的数据就是路由数据,你可以在 /src/mock/app.ts 里查看 mock 数据。

开启后端生成后,路由权限有两种做法,第一种是返回全部的路由数据,让框架自行处理(推荐),第二种是后端直接返回用户具备访问权限的路由数据。

两种做法的区别在于第一种做法对后端相对轻松,无需对数据做任何处理,并且对于无权限访问的路由,框架会以 403 页面进行展示;而第二种做法需要后端对处理进行处理,并且无需在 meta 对象里设置 auth 参数,但由于直接在数据源上就过滤掉了无权限的路由,所以在访问没有权限的路由时会直接显示 404 页面。

文件系统的路由 beta

基于文件系统的路由是除后端生成路由外,另一种路由生成方式。如果你有后端生成路由的需求,不妨先了解下文件系统路由这一特性,它的优势在于将路由和导航菜单进行了解耦,后端无需再关心路由数据,只需提供导航菜单数据即可,详细可阅读《基于文件系统的路由》。