路由及菜单
框架的路由和菜单采用强绑定模式,菜单是读取的路由信息动态构建的。
为实现动态菜单及动态路由权限功能,不得不对路由的定义做一些限制,及对路由做一些特殊处理。
目录结构介绍
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.js
的WHITE_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
在定义路由时建议定义meta
和meta.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,
}
外链路由
只需要将路由的path
以http:
或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.isOuter
为true
的路由及子路由都会在 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.isHide
为true
2、父级路由的meta.hideChildren
为true
3、路由的meta.roles
与用户信息的roles
没有匹配项
4、父级路由的meta.roles
与用户信息的roles
没有匹配项
当路由的meta.isHide
为true
并且父级路由的meta.hideChildren
不为true
时,此路由的子级将在树结构中被提升一个层级。
混合布局
在混合布局中有主菜单和子菜单之分,程序运行时会侦听布局及路由的变化,会根据选择的主菜单项动态修改子菜单的数据及控制是否显示子菜单,当选择的主菜单数据中没有子级时会隐藏子菜单的显示。