根据预处理完毕的数据进行菜单渲染

  1. <el-row class="tac">
  2. <el-col :span="24">
  3. <h5 class="mb-2">Default colors</h5>
  4. <el-menu class="el-menu-vertical-demo">
  5. <el-sub-menu :index="menu.id + ''" v-for="(menu) in newMenus" :key="menu.id">
  6. <template #title>
  7. <span>{{menu.title}}</span>
  8. </template>
  9. <template v-for="(subMenu) in menu.children" >
  10. <el-menu-item v-if="!subMenu.hidden" :key="subMenu.id">{{subMenu.title}}</el-menu-item>
  11. </template>
  12. </el-sub-menu>
  13. </el-menu>
  14. </el-col>
  15. </el-row>

hidden字段决定了二级菜单的展示与否,因此使用v-if去控制二级菜单
但是v-for和v-if不能同时使用,则借助template

:index=”menu.id + ‘’” 是因为子菜单的展示的名称必须是字符串类型,进行强制数据类型转换

但是现在遇到新问题?每个二级菜单都可以同时展示
项目需求应该是点开某个二级菜单时,其他二级菜单全部收起来。

找到elementPlus文档
image.png
给菜单添加上 :unique-opened=”true” 实现单一子菜单打开
考虑后续会添加路由,因此也加上属性

全局前置导航守卫

现在的问题是,刷新页面后,菜单的数据依然会丢失
使用全局前置导航守卫解决这一点,在每一次进入页面前都查询是否有token,并且查询menus是否为空(长度为0),如果是,则发送请求重新获取数据
顺便安装nprogress
yarn add nprogress
yarn add @types/nprogress -D

  1. import router from '../../router'
  2. import nProgress from 'nprogress'
  3. import 'nprogress/nprogress.css'
  4. import { useUserStore } from '..'
  5. import Cookies from 'js-cookie'
  6. import { getAdminInfoApi } from "../../request/api";
  7. nProgress.configure({
  8. showSpinner: false
  9. })
  10. // 全局前置守卫
  11. router.beforeEach(async (to, from) => {
  12. nProgress.start()
  13. const User = useUserStore();
  14. const token = Cookies.get('token');
  15. if(token && User.menus.length === 0){
  16. const res = await getAdminInfoApi();
  17. if (res.code === 200) {
  18. User.menus = res.data.menus;
  19. console.log(User.menus);
  20. }
  21. }
  22. return true
  23. })
  24. // 全局后置钩子
  25. router.afterEach(() => {
  26. nProgress.done(true)
  27. })

需要在main.ts里引入

二级路由规则

首先,点击菜单某一项后需要进行跳转,但是注意,跳转后,依然需要左侧菜单栏存在。
其路由规则如下:

  1. {
  2. path: '/pms',
  3. name: 'pms',
  4. component: () => import('../views/home/home.vue'),
  5. children:[
  6. {
  7. path: 'addProduct',
  8. name: 'addProduct',
  9. component: () => import('../views/pms/addProduct.vue')
  10. }
  11. ]
  12. }

因此一级路由的组件依然是home组件
二级路由才是一个新的具体的vue组件
需要在home中设置路由出口
image.png
效果
image.png

如果按这样的方式写固定的路由,代码臃肿且不利于维护
需要动态生成路由
在全局路由前置守卫里动态生成路由

  1. if (token && User.menus.length === 0) {
  2. const res = await getAdminInfoApi();
  3. if (res.code === 200) {
  4. User.menus = res.data.menus;
  5. }
  6. //循环菜单,生成并注册动态路由
  7. const menus = User.getNewMenus;
  8. for(let key in menus) {
  9. const newRoute: RouteRecordRaw ={
  10. path: '/'+menus[key].name,
  11. name: menus[key].name,
  12. component: () => import('../../views/home/home.vue'),
  13. children:[]
  14. };
  15. for(let i = 0;i<menus[key].children.length;++i){
  16. newRoute.children?.push({
  17. path: menus[key].children[i].name,
  18. name: menus[key].children[i].name,
  19. component: () => import(`../../views/${menus[key].name}/${menus[key].children[i].name}.vue`)
  20. })
  21. }
  22. router.addRoute(newRoute)
  23. }
  24. }

router.addRoute(newRoute)可以添加路由

借助开发者工具查看生成的路由
image.png

重定向

考虑当用户登录完之后,直接跳转到首页。
因此路由里需要添加“首页”,但是得在登录完成之后
同样在全局路由守卫里添加

  1. //动态添加首页
  2. const route: RouteRecordRaw= {
  3. path: "/",
  4. name: "home",
  5. component: () => import("../../views/home/home.vue"),
  6. redirect: "/index",
  7. children: [
  8. {
  9. path: "index",
  10. name: "index",
  11. component: () => import("../../views/index/index.vue"),
  12. },
  13. ],
  14. }
  15. router.addRoute(route)

使用重定向,访问/时也跳转到/index
同理,在访问一级菜单时,直接使其跳转到二级菜单路由

解决刷新之后页面空白问题

本质是一位路由守卫的机制
进入路由守卫后,进行了token和menus长度判断,if判断式,但是没有考虑到不需要判断的情况
综上,对路由守卫进行升级

  1. router.beforeEach(async (to, from,next) => {
  2. nProgress.start();
  3. const User = useUserStore();
  4. const token = Cookies.get("token");
  5. if (token && User.menus.length === 0) {
  6. const res = await getAdminInfoApi();
  7. if (res.code === 200) {
  8. User.menus = res.data.menus;
  9. }
  10. //循环菜单,生成并注册动态路由
  11. const menus = User.getNewMenus;
  12. for (let key in menus) {
  13. const newRoute: RouteRecordRaw = {
  14. path: "/" + menus[key].name,
  15. name: menus[key].name,
  16. component: () => import("../../views/home/home.vue"),
  17. redirect: "/" + menus[key].name + "/" + menus[key].children[0].name,
  18. children: [],
  19. };
  20. for (let i = 0; i < menus[key].children.length; ++i) {
  21. newRoute.children?.push({
  22. path: menus[key].children[i].name,
  23. name: menus[key].children[i].name,
  24. component: () =>
  25. import(
  26. `../../views/${menus[key].name}/${menus[key].children[i].name}.vue`
  27. ),
  28. });
  29. }
  30. //动态生成菜单路由
  31. router.addRoute(newRoute);
  32. }
  33. //动态添加首页
  34. const route: RouteRecordRaw= {
  35. path: "/",
  36. name: "home",
  37. component: () => import("../../views/home/home.vue"),
  38. redirect: "/index",
  39. children: [
  40. {
  41. path: "index",
  42. name: "index",
  43. component: () => import("../../views/index/index.vue"),
  44. },
  45. ],
  46. }
  47. router.addRoute(route);
  48. next(to.path)
  49. }else{
  50. next()
  51. }
  52. });

使用next,让登录成功的用户得以继续访问

守卫的小bug

已经登录且token有效的情况下,不应该允许去往登录页
token失效的情况下,必须跳转到登录页

  1. router.beforeEach(async (to, from,next) => {
  2. nProgress.start();
  3. const User = useUserStore();
  4. const token = Cookies.get("token");
  5. if (token && User.menus.length === 0) {
  6. const res = await getAdminInfoApi();
  7. if (res.code === 200) {
  8. User.menus = res.data.menus;
  9. }
  10. //循环菜单,生成并注册动态路由
  11. const menus = User.getNewMenus;
  12. for (let key in menus) {
  13. const newRoute: RouteRecordRaw = {
  14. path: "/" + menus[key].name,
  15. name: menus[key].name,
  16. component: () => import("../../views/home/home.vue"),
  17. redirect: "/" + menus[key].name + "/" + menus[key].children[0].name,
  18. children: [],
  19. };
  20. for (let i = 0; i < menus[key].children.length; ++i) {
  21. newRoute.children?.push({
  22. path: menus[key].children[i].name,
  23. name: menus[key].children[i].name,
  24. component: () =>
  25. import(
  26. `../../views/${menus[key].name}/${menus[key].children[i].name}.vue`
  27. ),
  28. });
  29. }
  30. //动态生成菜单路由
  31. router.addRoute(newRoute);
  32. }
  33. //动态添加首页
  34. const route: RouteRecordRaw= {
  35. path: "/",
  36. name: "home",
  37. component: () => import("../../views/home/home.vue"),
  38. redirect: "/index",
  39. children: [
  40. {
  41. path: "index",
  42. name: "index",
  43. component: () => import("../../views/index/index.vue"),
  44. },
  45. ],
  46. }
  47. router.addRoute(route);
  48. next(to.path)
  49. }else{
  50. if(token && to.path === '/login'){
  51. next(from)
  52. }else if(!token && to.path !== '/login'){
  53. next('/login')
  54. }else next()
  55. }
  56. });