菜单配置
温馨提示
菜单控制支持2种方式:
前端控制:/@/router/route.ts 修改菜单数据
后端控制:需先去 /src/stores/themeConfig.ts 下开启
isRequestRoutes: true
,然后去 /@/api/menu/index.ts 中修改接口拿菜单数据
菜单格式
在项目 /@/router/route.ts 文件中。这里需要注意,菜单数据内容必须嵌套进顶级节点(作为顶级路由出口)的 children
字段里
{
// 顶级菜单路径
path: '/',
// 顶级菜单 name
name: '/',
// 顶级路由出口
component: () => import('/@/layout/index.vue'),
// 顶级菜单重定向路径
redirect: '/home',
// 顶级附加自定义数据
meta: {
// 顶级菜单是否缓存
isKeepAlive: true,
},
// 顶级菜单的子级菜单数据
children: [
// 新增的菜单对象写在这里
{
...
}
]
}
附加自定义数据参数说明
代码位置:/@/router/route.ts
菜单路由中的字段说明
{
// 菜单路径,用于跳转
path: '/home',
// 菜单 name,用于界面 keep-alive 路由缓存。
// 此 name 需要与 component 组件中的 name 值相同(唯一)
name: 'home',
// 组件路径
component: () => import('/@/views/home/index.vue'),
// 附加自定义数据
meta: {
// 菜单标题(国际化写法)
title: 'message.router.home',
// 菜单外链链接
// 开启外链条件,`1、isLink: true 2、链接地址不为空(meta.isLink) 3、isIframe: false`
isLink: '',
// 菜单是否隐藏(菜单不显示在界面,但可以进行跳转)
isHide: false,
// 菜单是否缓存
isKeepAlive: true,
// 菜单是否固定(固定在 tagsView 中,不可进行关闭),右键菜单无 `关闭` 项
isAffix: true,
// 是否内嵌
// 开启条件,`1、isIframe: true 2、链接地址不为空(meta.isLink)`
isIframe: false,
// 当前路由权限标识,取角色管理。控制路由显示、隐藏。超级管理员:admin 普通角色:common
// 之前 auth 取用户(角色下有多个用户)
roles: ['admin', 'common'],
// 菜单图标
icon: 'iconfont icon-shouye',
// 自行再添加
...
},
}
路径格式
1. 面包屑多级显示
在项目 /@/router/route.ts 文件中,观察 path
字段,有 children
时,path
字段是基于上一级继续拼接。
如下所示:/params/xxx
,这样做是为了 breadcrumb-面包屑
的显示问题。
{
path: '/params',
redirect: '/params/common',
...,
children: [
{
// 面包屑:首页 / 路由参数 / 普通路由
path: '/params/common',
...,
},
{
// 面包屑:首页 / 路由参数 / 普通路由 / 普通路由详情
path: '/params/common/details',
...
},
]
}
2. 面包屑单级显示
children
里的 path
不基于上级 path
,注意高亮部分代码的 path
{
path: '/params',
redirect: '/params/common',
...,
children: [
{
// 面包屑:首页 / 路由参数 / 普通路由
path: '/params/common',
...,
},
{
// 面包屑:首页 / 路由参数 / 普通路由详情
path: '/params/details',
...
},
{
// 面包屑:首页
path: '/params1/common1/details',
...
},
]
}
路由的缓存
路由菜单中的 name
需与组件的 name
相同且 唯一
,还有 meta.isKeepAlive
设为 true
1. 页面
- index.vue,注意
name
值需与 /@/router/route.ts 中的name
值一致,否则实现不了路由的缓存(keep-alive)
<template>
<div class="personal">
personal
...
</div>
</template>
<script setup lang="ts" name="personal">
import { reactive, toRefs } from 'vue';
// 定义变量内容
const state = reactive({});
<style scoped lang="scss">
.personal {}
</style>
2. 路由代码
在项目 /@/router/route.ts 文件中,需写在 children
字段里(因为布局分为,左菜单,右内容,顶部,底部,侧部分才是我们切换的页面路由),比如我们上面添加个人中心界面,新增如下代码:
{
path: '/',
name: '/',
component: () => import('/@/layout/index.vue'),
redirect: '/home',
meta: {
isKeepAlive: true,
},
children: [
// 新增的菜单对象写在这里
{
path: '/personal',
name: 'personal',
component: () => import('/@/views/personal/index.vue'),
meta: {
title: 'message.router.personal',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
roles: ['admin', 'common'],
icon: 'iconfont icon-gerenzhongxin',
},
},
]
}
二级菜单
二级菜单与一级菜单的区别:(只要子级里有 children
)
1、redirect:顶级设置重定向
2、component:顶级为
component: () => import('/@/layout/routerView/parent.vue')
写死路径。component: () => import('/@/layout/routerView/parent.vue')
为路由出口
1. 新建文件夹(同一级菜单)
我们按照建
一级菜单
的步骤建二级菜单
。/@/views
下新增system
文件夹。system
文件夹下新增menu、user
等文件夹为了方便管理,我们在
/@/views/system/menu
或/@/views/system/user
中都添加index.vue
组件
<template>
<div class="system-menu-container">
systemMenu
...
</div>
</template>
<script setup lang="ts" name="systemMenu">
import { reactive } from 'vue';
// 定义变量内容
const state = reactive({});
<style scoped lang="scss">
.system-menu-container {}
</style>
路由数据模式
- 创建路由函数
router/index.ts
创建是加载一些默认的路由
export const router = createRouter({
history: createWebHashHistory(),
/**
* 说明:
* 1、notFoundAndNoPower 默认添加 404、401 界面,防止一直提示 No match found for location with path 'xxx'
* 2、backEnd.ts(后端控制路由)、frontEnd.ts(前端控制路由) 中也需要加 notFoundAndNoPower 404、401 界面。
* 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
*/
routes: [
//错误页面路由
...notFoundAndNoPower,
//静态固定路由(login)
...staticRoutes
],
});
- 路由加载前
router/index.ts
NProgress
第三方进度条库- 当进入
login
页面,必须是未登录失效,其它页面进入,必须是已登录状态,当token
失效进入登录页 - 已登录时进入
login
自动进入控制台页面,其它页面则进行动态在pinia
中获取路由
import { useRoutesList } from '/@/stores/routesList';
//路由缓存列表
const storesRoutesList = useRoutesList(pinia);
- 当缓存数据为空时,进行 前端控制/后端控制 的方式加载数据,读取
/src/stores/themeConfig.ts
是否开启后端控制路由配置。
//store主题配置缓存
const storesThemeConfig = useThemeConfig(pinia);
//themeConfig对象
const { themeConfig } = storeToRefs(storesThemeConfig);
//themeConfig-isRequestRoutes 属性
const { isRequestRoutes } = themeConfig.value;
前端模式
isRequestRoutes:false
后端模式
isRequestRoutes:true
router.beforeEach(async (to, from, next) => {
NProgress.configure({ showSpinner: false });
if (to.meta.title) NProgress.start();
const token = Session.get('token');
if (to.path === '/login' && !token) {
next();
NProgress.done();
} else {
if (!token) {
next(`/login?redirect=${to.path}¶ms=${JSON.stringify(to.query ? to.query : to.params)}`);
Session.clear();
NProgress.done();
} else if (token && to.path === '/login') {
next('/home');
NProgress.done();
} else {
const storesRoutesList = useRoutesList(pinia);
//将状态存储在 store 中转换为 ref 对象的辅助函数(保持响应式更新)
const { routesList } = storeToRefs(storesRoutesList);
if (routesList.value.length === 0) {
if (isRequestRoutes) {
// 后端控制路由:路由数据初始化,防止刷新时丢失
await initBackEndControlRoutes();
// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
// to.query 防止页面刷新时,普通路由带参数时,参数丢失。动态路由(xxx/:id/:name")isDynamic 无需处理
next({ path: to.path, query: to.query });
} else {
// 前端控制路由
await initFrontEndControlRoutes();
next({ path: to.path, query: to.query });
}
} else {
next();
}
}
}
});