天天看点

我的框架开发记录--动态路由、菜单前言XpStart–2022.6.5

前言

好像已经两周没更新进度了。

因为这期间的进展没什么可值得用篇幅去记录的。

我大都在写页面。目前的话,一个很基础的后台管理基本成型了。

这一篇博客也是记录动态路由和菜单的。尤其是动态路由,把我急死了,好几次想锤桌子了。

话不多说,进入正题

XpStart–2022.6.5

先用一张图看看目前进度:

我的框架开发记录--动态路由、菜单前言XpStart–2022.6.5

写前端也是一个挺麻烦的事,我经常重构代码,原因就是想着如何复用组件、拆分组件、这个属性到底放到vuex中还是组件本身。

好在,基本摸清了吧,写得会比原来快不少。

动态菜单实现

动态菜单得实现还是很简单的。

用户登录成功后,将用户对应的角色的权限查出来(角色的权限就是拥有哪些菜单—菜单包括目录、菜单和按钮)。查出来的权限就是菜单id的集合,去数据库中查出这些菜单并返回给前端。

前端拿到菜单列表后,我的做法是session Storage中保存,vuex中也要有,就防止刷新丢失了(也不知道这种方式好不好)。然后就是左侧的菜单组件渲染数据,我实在不知道对于这种树结构如何在页面递归,所以只能写死,最多三层。这样动态菜单完成了。

<el-menu
          :default-active="this.$store.state.editableTabsValue"
          mode="vertical"
          class="el-menu-vertical-demo"
          @open="handleOpen"
          @close="handleClose"
          background-color="#545c64"
          text-color="#fff"
          active-text-color="#ffd04b"
          :router="true"
          :collapse="isCollapse">
          <el-menu-item index="/index" @click="changeMenu({menuName:'首页', routerName:'Index'})">
                <template slot="title">
                  <i class="el-icon-s-home"></i>
                  <span>首页</span>
                </template>
         </el-menu-item>
          <template v-for="menu in menuList">
            <el-submenu v-if="menu.menuType === '0'" :index="menu.menuPath" :key="menu.menuName"  @click="changeMenu(menu)">
            <template slot="title">
              <i class="el-icon-location"></i>
              <span>{{menu.menuName}}</span>
            </template>
            <template v-if="menu.children">
              <el-submenu :index="item.menuPath" :key="item.name" v-for="item in menu.children">
                <template slot="title">
                  <i class="el-icon-location"></i>
                  <span>{{item.menuName}}</span>
                </template>
                <el-menu-item :index="subItem.menuPath" :key="subItem.name" v-for="subItem in item.children" @click="changeMenu(subItem)">
                <template slot="title">
                  <i class="el-icon-location"></i>
                  <span>{{subItem.menuName}}</span>
                </template>
              </el-menu-item>
              </el-submenu>
            </template>
            <template v-else>
              <el-menu-item :index="item.menuPath" :key="item.name" v-for="item in menu.children" @click="changeMenu(item)">
                <template slot="title">
                  <i class="el-icon-location"></i>
                  <span>{{item.title}}</span>
                </template>
              </el-menu-item>
            </template>
          </el-submenu>
          <el-menu-item v-else :index="menu.menuPath" :key="menu.menuName"  @click="changeMenu(menu)">
            <template slot="title">
              <i class="el-icon-location"></i>
              <span>{{menu.menuName}}</span>
            </template>
          </el-menu-item>
          </template>
        </el-menu>
           

动态路由

因为用户拥有哪些菜单就意味着用户拥有哪些路由,所以需要将sessionStorage中的菜单列表转换成路由,并添加到路由表中。

在菜单管理页,也需要添加这些东西:

我的框架开发记录--动态路由、菜单前言XpStart–2022.6.5

动态路由实现的思路:

  1. 登录成功后,将菜单列表转化为路由,转化的路由存放在vuex中
  2. 在每次路由跳转前,判断页面是否刷新(页面刷新路由表也会回到初始状态,需要重新加)

1.菜单转路由:

登录成功后调用vuex中的

mutations

方法

login(){
	login(this.loginForm).then((res) => {
		if(res.data.code === 200) {
			// 转化并添加动态路由
            this.$store.commit("dynamicRouters", res.data.data);
            sessionStorage.setItem("token",res.headers.authentication);
            sessionStorage.setItem("permissions", JSON.stringify(res.data.data))
            this.$router.push("/index");
          } 
	})
}
           

操作如下:

因为所有的菜单都是Index的子路由,所以我构造了一个Index的路由,动态生成的路由会push到它的children中。

dynamicRouters(state, menus) {
      let routers = {
        path: '/index',
        name: 'Index',
        component: () => import('../views/IndexView.vue'),
        children: []
      }
      if (menus) {
        menus.forEach((menu) => {
          if (menu.children.length > 0) {
            menu.children.forEach((item) => {
              // 菜单类型为1才是菜单,0是目录,2是按钮
              if (item.menuType === '1' && item.routerComponent) {
                let newRouter = {
                  path: item.menuPath,
                  name: item.routerName,
                  meta: {
                    title: item.menuName
                  },
                  component: () => import(`@/views/${item.routerComponent}.vue`)
                };
                routers.children.push(newRouter);
              }
            })
          } else {
            // 菜单类型为1才是菜单,0是目录,2是按钮
            if (menu.menuType === '1' && menu.routerComponent) {
              let newRouter = {
                path: menu.menuPath,
                name: menu.routerName,
                meta: {
                  title: menu.menuName
                },
                component: () => import(`@/views/${menu.routerComponent}.vue`)
              }
              routers.children.push(newRouter);
            }
          }
        })
        //this.$store.state.isFresh = "a";
        state.dynamicRouters = routers;
        state.isFresh = "xp"
        router.addRoute(routers)
        sessionStorage.setItem("doIt", "yes")
      }
    }
           

这是静态路由(工共的路由,暂时只有这个登录):

const routes = [
  {
    path: '/',
    name: 'Login',
    component: () => import(/* webpackChunkName: "about" */ '../views/LoginView.vue')
  },
]
           

这样就实现了动态路由了。

但是!!!一旦页面刷新,路由表就只剩静态路由了。要解决这个问题,我们需要用到router的前置路由守卫:

在每次路由跳转之前,都会经过这个前置首位。

我们需要在守卫中判断页面是不是刷新了。可是如何判断页面刷新?

既然vuex在页面刷新时会清空数据,那么我们在vuex中设置一个标志,在守卫中判断这个标志的值不就可以实现对页面刷新否的判断了吗。我的做法是判断这个标志为不为空,默认值就是空,每次生成动态路由之后给它设置一个值。如果这个值为空则说明页面刷新了,需要重新生成路由。

router.beforeEach((to, from, next) => {
  if (store.state.isFresh === '') {
    store.commit("dynamicRouters", JSON.parse(sessionStorage.getItem("permissions")))
    if (to.name === "Login") {
      next();
    }
    else if (router.options.routes.length === 1) {
      next({ ...to, replace: true })
    }
  }
next()
})
           

记得将store引入:

import store from '../store'

store.state.isFresh

为空字符说明页面刷新,调用路由生成的方法。

如果将要跳转的路由是登录,那么应该放行,不然会造成死循环栈溢出。

如果没有下面这段代码,你永远也拿不到添加后的路由。

else if (router.options.routes.length === 1) {
      next({ ...to, replace: true })
    }
           

并且会报错说,没有这个新导航:(就是没有这个路由,这个问题把我心态搞崩了)

我的框架开发记录--动态路由、菜单前言XpStart–2022.6.5

next({ ...to, replace: true })

这段代码的作用类似于刷新路由表,让你刚刚添加的路由起作用。要使用这句代码,需要一个终止条件,不然也会栈溢出。这就像一个while循环,直到路由表刷新了才往下执行。

router.options.routes.length === 1

我这个条件就很简单,因为我静态路由就一个,当长度为1说明新路由还没加上,继续等待。

到此,页面刷新也能保证动态路由能恢复了。over。