根据预处理完毕的数据进行菜单渲染
<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()}});
