根据预处理完毕的数据进行菜单渲染
<el-row class="tac">
<el-col :span="24">
<h5 class="mb-2">Default colors</h5>
<el-menu class="el-menu-vertical-demo">
<el-sub-menu :index="menu.id + ''" v-for="(menu) in newMenus" :key="menu.id">
<template #title>
<span>{{menu.title}}</span>
</template>
<template v-for="(subMenu) in menu.children" >
<el-menu-item v-if="!subMenu.hidden" :key="subMenu.id">{{subMenu.title}}</el-menu-item>
</template>
</el-sub-menu>
</el-menu>
</el-col>
</el-row>
hidden字段决定了二级菜单的展示与否,因此使用v-if去控制二级菜单
但是v-for和v-if不能同时使用,则借助template
:index=”menu.id + ‘’” 是因为子菜单的展示的名称必须是字符串类型,进行强制数据类型转换
但是现在遇到新问题?每个二级菜单都可以同时展示
项目需求应该是点开某个二级菜单时,其他二级菜单全部收起来。
找到elementPlus文档
给菜单添加上 :unique-opened=”true” 实现单一子菜单打开
考虑后续会添加路由,因此也加上属性
全局前置导航守卫
现在的问题是,刷新页面后,菜单的数据依然会丢失
使用全局前置导航守卫解决这一点,在每一次进入页面前都查询是否有token,并且查询menus是否为空(长度为0),如果是,则发送请求重新获取数据
顺便安装nprogress
yarn add nprogress
yarn add @types/nprogress -D
import router from '../../router'
import nProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { useUserStore } from '..'
import Cookies from 'js-cookie'
import { getAdminInfoApi } from "../../request/api";
nProgress.configure({
showSpinner: false
})
// 全局前置守卫
router.beforeEach(async (to, from) => {
nProgress.start()
const User = useUserStore();
const token = Cookies.get('token');
if(token && User.menus.length === 0){
const res = await getAdminInfoApi();
if (res.code === 200) {
User.menus = res.data.menus;
console.log(User.menus);
}
}
return true
})
// 全局后置钩子
router.afterEach(() => {
nProgress.done(true)
})
需要在main.ts里引入
二级路由规则
首先,点击菜单某一项后需要进行跳转,但是注意,跳转后,依然需要左侧菜单栏存在。
其路由规则如下:
{
path: '/pms',
name: 'pms',
component: () => import('../views/home/home.vue'),
children:[
{
path: 'addProduct',
name: 'addProduct',
component: () => import('../views/pms/addProduct.vue')
}
]
}
因此一级路由的组件依然是home组件
二级路由才是一个新的具体的vue组件
需要在home中设置路由出口
效果
如果按这样的方式写固定的路由,代码臃肿且不利于维护
需要动态生成路由
在全局路由前置守卫里动态生成路由
if (token && User.menus.length === 0) {
const res = await getAdminInfoApi();
if (res.code === 200) {
User.menus = res.data.menus;
}
//循环菜单,生成并注册动态路由
const menus = User.getNewMenus;
for(let key in menus) {
const newRoute: RouteRecordRaw ={
path: '/'+menus[key].name,
name: menus[key].name,
component: () => import('../../views/home/home.vue'),
children:[]
};
for(let i = 0;i<menus[key].children.length;++i){
newRoute.children?.push({
path: menus[key].children[i].name,
name: menus[key].children[i].name,
component: () => import(`../../views/${menus[key].name}/${menus[key].children[i].name}.vue`)
})
}
router.addRoute(newRoute)
}
}
router.addRoute(newRoute)可以添加路由
借助开发者工具查看生成的路由
重定向
考虑当用户登录完之后,直接跳转到首页。
因此路由里需要添加“首页”,但是得在登录完成之后
同样在全局路由守卫里添加
//动态添加首页
const route: RouteRecordRaw= {
path: "/",
name: "home",
component: () => import("../../views/home/home.vue"),
redirect: "/index",
children: [
{
path: "index",
name: "index",
component: () => import("../../views/index/index.vue"),
},
],
}
router.addRoute(route)
使用重定向,访问/时也跳转到/index
同理,在访问一级菜单时,直接使其跳转到二级菜单路由
解决刷新之后页面空白问题
本质是一位路由守卫的机制
进入路由守卫后,进行了token和menus长度判断,if判断式,但是没有考虑到不需要判断的情况
综上,对路由守卫进行升级
router.beforeEach(async (to, from,next) => {
nProgress.start();
const User = useUserStore();
const token = Cookies.get("token");
if (token && User.menus.length === 0) {
const res = await getAdminInfoApi();
if (res.code === 200) {
User.menus = res.data.menus;
}
//循环菜单,生成并注册动态路由
const menus = User.getNewMenus;
for (let key in menus) {
const newRoute: RouteRecordRaw = {
path: "/" + menus[key].name,
name: menus[key].name,
component: () => import("../../views/home/home.vue"),
redirect: "/" + menus[key].name + "/" + menus[key].children[0].name,
children: [],
};
for (let i = 0; i < menus[key].children.length; ++i) {
newRoute.children?.push({
path: menus[key].children[i].name,
name: menus[key].children[i].name,
component: () =>
import(
`../../views/${menus[key].name}/${menus[key].children[i].name}.vue`
),
});
}
//动态生成菜单路由
router.addRoute(newRoute);
}
//动态添加首页
const route: RouteRecordRaw= {
path: "/",
name: "home",
component: () => import("../../views/home/home.vue"),
redirect: "/index",
children: [
{
path: "index",
name: "index",
component: () => import("../../views/index/index.vue"),
},
],
}
router.addRoute(route);
next(to.path)
}else{
next()
}
});
使用next,让登录成功的用户得以继续访问
守卫的小bug
已经登录且token有效的情况下,不应该允许去往登录页
token失效的情况下,必须跳转到登录页
router.beforeEach(async (to, from,next) => {
nProgress.start();
const User = useUserStore();
const token = Cookies.get("token");
if (token && User.menus.length === 0) {
const res = await getAdminInfoApi();
if (res.code === 200) {
User.menus = res.data.menus;
}
//循环菜单,生成并注册动态路由
const menus = User.getNewMenus;
for (let key in menus) {
const newRoute: RouteRecordRaw = {
path: "/" + menus[key].name,
name: menus[key].name,
component: () => import("../../views/home/home.vue"),
redirect: "/" + menus[key].name + "/" + menus[key].children[0].name,
children: [],
};
for (let i = 0; i < menus[key].children.length; ++i) {
newRoute.children?.push({
path: menus[key].children[i].name,
name: menus[key].children[i].name,
component: () =>
import(
`../../views/${menus[key].name}/${menus[key].children[i].name}.vue`
),
});
}
//动态生成菜单路由
router.addRoute(newRoute);
}
//动态添加首页
const route: RouteRecordRaw= {
path: "/",
name: "home",
component: () => import("../../views/home/home.vue"),
redirect: "/index",
children: [
{
path: "index",
name: "index",
component: () => import("../../views/index/index.vue"),
},
],
}
router.addRoute(route);
next(to.path)
}else{
if(token && to.path === '/login'){
next(from)
}else if(!token && to.path !== '/login'){
next('/login')
}else next()
}
});