前言
好像已经两周没更新进度了。
因为这期间的进展没什么可值得用篇幅去记录的。
我大都在写页面。目前的话,一个很基础的后台管理基本成型了。
这一篇博客也是记录动态路由和菜单的。尤其是动态路由,把我急死了,好几次想锤桌子了。
话不多说,进入正题
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中的菜单列表转换成路由,并添加到路由表中。
在菜单管理页,也需要添加这些东西:
动态路由实现的思路:
- 登录成功后,将菜单列表转化为路由,转化的路由存放在vuex中
- 在每次路由跳转前,判断页面是否刷新(页面刷新路由表也会回到初始状态,需要重新加)
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 })
}
并且会报错说,没有这个新导航:(就是没有这个路由,这个问题把我心态搞崩了)
next({ ...to, replace: true })
这段代码的作用类似于刷新路由表,让你刚刚添加的路由起作用。要使用这句代码,需要一个终止条件,不然也会栈溢出。这就像一个while循环,直到路由表刷新了才往下执行。
router.options.routes.length === 1
我这个条件就很简单,因为我静态路由就一个,当长度为1说明新路由还没加上,继续等待。
到此,页面刷新也能保证动态路由能恢复了。over。