路由及菜单

框架的路由和菜单采用强绑定模式,菜单是读取的路由信息动态构建的。

为实现动态菜单及动态路由权限功能,不得不对路由的定义做一些限制,及对路由做一些特殊处理。

目录结构介绍

1、/src/router/ 目录下有 index.js、base.js、routerUtil.js 文件和 modules 目录

2、index.js 文件负责创建路由器,定义全局路由守卫及路由白名单。

3、base.js 中定义基础路由,如:登录、404等。基础路由在创建路由器时被加载到路由器。

4、routerUtil.js 定义了路由及菜单的处理函数。

5、modules 目录下定义动态路由,动态路由在登录系统后被程序动态加载。modules 下的 js 文件需要向外暴露一个路由对象或一个路由数组。

路由

路由处理说明

1、路由器创建时载入的只有 base.js 中暴露的 baseRoutes 的路由。

2、定义的路由前置守卫中规定了无用户认证信息只能访问白名单路由及 name为Login的路由,访问其它路由会被重定向到 Login

3、如果用户已经认证但是没有用户信息,则请求用户信息接口并将信息使用pinia存储,然后初始化路由。(token持久化到了localStorage,用户信息及菜单仅保存在pinia,刷新浏览器后,pinia 数据会重置,所以需要此步骤)

4、初始化路由提供两种策略

​ (1)、仅加载前端定义的路由:读取 /src/router/modules 目录下所有文件获取路由数据。

​ (2)、仅加载后端定义的路由:请求接口获取路由数据。

​ (3)、加载前端路由数据,加载后端菜单数据:这种策略暂没有考虑添加,需要考虑的细节太多,会使现有的逻辑有较大的改动,如果有需要自行改造。

获取到路由数据后对路由进行权限等处理后动态添加到路由器,并提取菜单信息处理后存储到pinia。(具体处理逻辑代码在 /src/router/routerUtil.js)

如果是加载后端来源的路由数据,排序及权限等建议能在后端处理的应该在后端处理,然后根据需要改造 routerUtil.js 中的处理逻辑,将已经在后端处理过的步骤跳过。

5、动态路由数据加载到路由器时会将 '/' 根路由及其子路由覆盖

白名单

作用:未认证也可以访问的页面,如系统欢迎页。

由于设计的路由与菜单的加载策略,白名单的路由要在base.js中定义并在index.jsWHITE_LIST数组中添加路由path,因为modules中的路由在未认证时不会被加载。

因为程序在路由器创建时并没有初始化菜单。如果白名单路由定义在Layout组件的路由下,可以访问此路由,但是进入Layout内并不会展示菜单。不建议这样使用,如果需要用户未认证也可以进入Layout,建议使用创建匿名/游客角色使用匿名/游客用户的方式。

/** /src/router/index.js */
// ......
// 路由白名单
const WHITE_LIST = ['/welcome']

/** /src/router/base.js */
// ......
// 基础路由
const baseRoutes = [
  {
    path: '/welcome',
    name: 'Welcome',
    component: () => import('@/views/welcome/index.vue'),
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/login/index.vue'),
  },
  {
    path: '/:path(.*)*',
    name: 'NotFound',
    component: () => import('@/views/error-page/NotFound.vue'),
  }
]

路由定义

/** /src/router/modules/dashboard.js  */
export default {
  path: '/dashboard',
  name: 'Dashboard',
  redirect: '/dashboard/analysis',
  meta: {
    title: 'menus.dashboard',
    isHide: false,
    icon: 'ant-design:dashboard-outlined',
    sortNo: 2,
  },
  children: [
    {
      path: '/dashboard/analysis',
      name: 'Analysis',
      meta: {
        title: 'menus.analysis',
        isHide: false,
        icon: 'ep:data-analysis',
      },
      component: () => import('@/views/dashboard/analysis/index.vue'),
    },
    {
      path: '/dashboard/workbench',
      name: 'Workbench',
      meta: {
        title: 'menus.workbench',
        isHide: false,
        icon: 'icon-park-outline:workbench',
      },
      component: () => import('@/views/dashboard/workbench/index.vue'),
    },
  ],
}

路由定义必须以 / 开头 不建议路径使用 '' 空字符代替父级的 redirect 效果,推荐显式定义。

Meta

在定义路由时建议定义metameta.title

{
    /**
     * 在菜单中显示的标题。可以是定义好的多语言模板。
     */
    title:String, 
    /**
     * 在菜单中显示的图标。使用的自定义的 SvgIcon 组件渲染,可以是本地图标或网络中的资源。
     */
    icon:String, 
    /**
     * 是否在菜单中隐藏。(默认为 false)
     */
    isHide: Boolean, 
    /**
     * 是否在菜单中隐藏所有子级(默认为 false)。
     * 当为true时,所有子级的isHide和hideChildren都会被动态赋值为true
     */
    hideChildren: Boolean, 
    /**
     * 如果有值并以 http: 或 https: 开头,则以外链内嵌方式打开(默认无)
     */
    iframeLink:String, 
    /**
     * 在 layout 框架外打开(默认为 false)。
     * 如数据大屏等。
     * 具有传递性,影响子路由。
     */
    isOuter:Boolean, 
    /**
     * 可以访问的角色编码(默认无)。
     * 为 [] 时所有角色都不可以访问,为空值时所有角色都可以访问
     * 具有传递性,影响子路由。
     * 当没有权限访问时,此路由的isHide和hideChildren会被动态赋值为true
     */
    roles:Array|String, 
    /**
     * 是否缓存组件实例(默认为 false)。
     * 注意:组件名要与对应路由的 name 相同
     */
    isKeep:Boolean, 
    /**
     * 多标签页模式下是否固定在标签页(默认 false)。
     */
    fixedTab: Boolean,
    /**
     * 单独配置此页面的过渡动画(默认无)。
     */
    transition:String, 
    /**
     *  排序号(默认无)
     */
    sortNo:Number,
}

外链路由

只需要将路由的pathhttp:https:开头即可,不要以/开头。

{
  // 默认以浏览器新标签页方式跳转,如果有 `,_self`则在此标签页打开链接
  path: 'http://xxx.xxxxx,_self', 
  name: 'Xxx',
  meta: {
    title: 'XXXX',
  },
},

外链内嵌

需要将 meta.iframeLink 设置为需要嵌套的链接地址

{
  path: '/test',
  name: 'Test',
  meta: {
    title: '测试外链内嵌',
    iframeLink: 'https://localhost:8888',
  },
  /** component 可以省略
   * 路由在由程序加载时根据 meta.iframeLink 判断此路由有内嵌地址
   * 会自动赋值 component 为 @/layout/main/LayoutIframe.vue 组件
   */
  // component: () => import('@/layout/main/LayoutIframe.vue'),
}

内嵌路由在切换页面后无法缓存

Layout外部页面

如数据大屏需要在 Layout 布局外显示的页面,需要将meta.isOuter设置为true

{
  path: '/screen',
  name: 'BigScreen',
  meta: {
    title: '数据大屏展示',
    isOuter: true,
  },
  component: () => import('@/views/demo/data-big-screen/BigScreen1.vue'),
},

通过路由父子级关系实现,动态加载路由时将meta.isOuter true的路由提取。

最后使用router.addRoute()添加到路由以覆盖根路由下的此路由

所以isOuter属性是有传递性的,meta.isOutertrue的路由及子路由都会在 Layout 外渲染

路由权限

meta.roles中规定哪些角色可以访问

roles的值可以是单个角色值的字符串也可以是多个角色的角色值数组

{
  path: '/demo/permission/page-admin',
  name: 'PermissionPageAdmin',
  meta: {
    title: '路由权限',
    // 可以使用通配符匹配,表示已 '-admin' 结尾的角色都可以访问此路由
    roles: ['*-admin'],
  },
  component: () => import('@/views/demo/permission/PagePermAdmin.vue'),
},

首页

首页是当用户登录成功之后默认跳转的页面,或者在地址栏输入根路径被重定向到的页面。

默认首页可全局配置或用户单独配置,用户配置优先。(业务开发中可根据需要扩展根据角色或部门分配默认首页)

在 .env 文件中全局配置

// .env

VITE_APP_HOME_PATH = /home

为用户单独配置只需要设置用户信息的homePath属性值即可

刷新当前页面

import { refreshPage } from '@/utils/tools'
refreshPage()

菜单

菜单是使用ElementPlus的菜单组件实现。

菜单处理说明

菜单初始化是在用户信息及动态路由数据初始化后,对路由数据进行处理并生成菜单数据,具体处理逻辑在/src/router/routerUtil.js文件中。

菜单处理过程中会根据路由数据中meta中的配置过滤数据。

不在菜单中显示的条件有:

1、路由的meta.isHidetrue

2、父级路由的meta.hideChildrentrue

3、路由的meta.roles与用户信息的roles没有匹配项

4、父级路由的meta.roles与用户信息的roles没有匹配项

当路由的meta.isHidetrue并且父级路由的meta.hideChildren不为true时,此路由的子级将在树结构中被提升一个层级。

混合布局

在混合布局中有主菜单和子菜单之分,程序运行时会侦听布局及路由的变化,会根据选择的主菜单项动态修改子菜单的数据及控制是否显示子菜单,当选择的主菜单数据中没有子级时会隐藏子菜单的显示。

Last Updated:
Contributors: zhangyuge