Vue+Element UI 开发后台管理系统之动态路由相关问题总结
摘要
这段时间一直在搭建移动端APP基础信息管理的PC端后台管理系统,主要功能含 人员基础信息维护 / 人员组织维护 / 人员权限管理 / APP应用数据导入导出 等。由于项目刚上线,正好抽出半天空闲时间(主要还是因为今天刚好是周末🤣)将开发中遇到的一些问题作个简要的归纳和总结。
正如题目说述,这篇文章主要记录在使用vue+element UI 开发后台管理系统时在动态路由和路由权限处理时遇到的问题和踩过的坑。
关键词 :addRoutes / page 404 / 路由重复 / 权限控制
技术栈: Vue2.0 / Element UI / vuex /vue-router / localStorage | sessionStorage
路由权限控制
为防止歧义,这里先声明一下本文所指的动态路由和静态路由的概念:
- 动态路由:指根据用户角色从后台动态获取的路由(和用户的权限和角色相关)
- 静态路由:指公共开放的路由,也就是所有人都能访问的页面(与权限角色无关)这部分路由由前端处理无需从后台获取
权限分为页面级权限和按钮级权限两种,其中页面级权限通过控制路由展示与否的方式控制,按钮级权限通过用户的身份角色控制。
路由权限控制主要分两种思路:
- 一种是前端保存完整的路由信息,该信息除了记录正常的路由页面外还包含了可访问该路由的角色信息,然后根据登录用户的身份角色和路由信息的角色信息判断需要展示的路由信息。elment-admin就是通过这种方式实现的,具体可以参考elemnt-admin权限控制和手摸手,带你用vue撸后台 系列二(登录权限篇)。这里不再过多的叙述
- 第二种方式是权限通过后端控制,用户登录成功后会随即向后台发送一个请求,返回该用户可访问的路由表信息即前文提及的动态路由。然后再用前端将动态路由和静态路由合并到一起,生成完整的用户路由表展示给用户。
本项目选用的是第二种权限控制方式,之所以选用第二种方式,其一是处于安全的考虑,其二是因为本项目涉及到多个APP系统的管理。不同的用户管理的APP子系统是不一样的。如果使用前端进行路由控制的话随着APP应用增多势必导致用户角色变得庞大和难以维护。
动态生成用户的路由表信息
- 用户的路由信息由两部分组成静态路由和动态路由。
1.首先需要将后端返回的路由数据转换为路由表:
- 后台返回的数据格式参考如下
[
{
funcode: null
funid: "JN001001"
funlevel: null
funname: "组织架构"
funpath: "ORGStructure"
funtype: 0
funtype_: null
funurl: "views/SYSManage/ORGStructure.vue"
icourl: "el-icon-c-scale-to-original"
isshow: 1
isshow_: null
parentid: "JN001"
systemid: "JN"
}
]
-此时前端需要根据该路由数据生成路由表。主要是获取component
- 前端路由转换函数参考
function generateRouter(authTree){
let asyncRouterMap = authTree.map((list)=>{
return {
path:`/${list.funpath}`,
name:list.funpath,
component:Layout,
redirect:`/${list.funpath}`,
hidden:list.isshow==1,
meta:{title:list.funname,icon:list.icourl},
children:list.children.map((child)=>{
return {
path:`/${child.funpath}`,
name:child.funpath,
meta:{title:child.funname,icon:child.icourl},
component:()=>import(`@/${child.funurl}`) //重要
}
})
}
})
return asyncRouterMap; //返回动态路由表
}
转换函数需要根据后台返回的数据进行调整,实际开发时需要前后端一起约定数据格式,此处仅作参考。
- 监听全局路由导航Router.beforeEach使用addRoutes合并路由信息。
let asyncRouter = null; //储存动态路由,也是判断动态路由是否建立的重要标志
router.beforeEach((to, from, next) => {
if(to.path=="/login"){
asyncRouter = null; //跳转到登录页面时对asyncRouter 初始化
next();
}
/* 首先判断token是否存在 */
else if(!localStorage.getItem("Token")){
Message({
showClose: true,
type: 'error',
message:"请先登录!"
});
next("/login")
}
/*判断动态路由是否已经建立*/
else if(!asyncRouter){
/*如果找不到路由信息,提示用户重新登录 */
if(!localStorage.getItem("router")){
Message({
showClose: true,
type: 'error',
message:"登录信息已过期,请重新登录!"
});
next("/login")
}
/*合并路由 */
else{
/* 调用前面定义的路由生成函数 */
asyncRouter =generateRouter(JSON.parse(localStorage.getItem('router')));
store.commit("setRouter",asyncRouter);
/** 解决路由重复问题 */
router.matcher = createRouter().matcher;
router.addRoutes(asyncRouter);
/**
* 注意:一定要把404页面放到路由的最后面,否则页面刷新时会优先被重定向到404页面
*/
let router404 = [{
path:"*",
redirect: '/404'
}];
router.addRoutes(router404);
next({ ...to, replace: true });
}
}
/*一切正常,直接跳转*/
else{
next();
}
})
重点关注的问题
完整的代码如上所示,逻辑其实比较简单但是却有几个地方需要特别注意(已注释说明)
-
路由信息在用户登录获取后,同时存储到store和localStorage中,避免页面刷新导致路由丢失
-
建立变量
let asyncRouter = null;
保存动态路由信息和判断路由信息是否存在/*判断动态路由是否已经建立*/else if(!asyncRouter)
否则将会导致全局导航循环执行,最终浏览器堆栈溢出。这个也是自己亲自踩过的坑,想详细了解的可以搜索router.beforeEach() 动态加载路由出现死循环
在这方面踩坑的人不再少数。 -
404页面问题:如注释中所述。404页面需要放到所有页面之后。否则在任何一个页面刷新都会跳转到404页面。因为404页面优先被处理了。当然如果你使用的是
history
模式还应该参考vue官方教程,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。vue-router history 模式配置说明 -
在路由每次导航至login页面时记得重置asyncRouter。这个也是自己在实际开发中踩过的坑。现在有这样一种场景,用户A登录后退出登录(但未关闭网页),此时页面导航至登录页面使用用户B的账号登录,你会惊奇的发现B居然渲染的是A的路由表信息。
这是因为用户A在推出登录时asyncRouter
仍保留着A的动态路由表,此时B登录进来时会跳过生成过程直接将A的路由表合并成B的路由信息。因此在每次导航到login页面时需要重置asyncRouter以保证加载正确的路由信息。
if(to.path=="/login"){
asyncRouter = null; //跳转到登录页面时对asyncRouter 初始化
next();
}
顺便说一下上述判断可能会导致另一个错误提醒(不是致命的)此时可在main.js中(整个路由导航都应该在main.js中执行)加入如下语句即可修复。
const routerPush = Router.prototype.push
Router.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error=> error)
}
- 路由重复添加问题
路由重复主要是因为
addRoutes
只负责添加路由,不负责过滤已经存在的路由😢
当用户A推出登录(未关闭网页)此时用户B接着登录。如果用户A和用户B有相同的路由信息,那么控制台就会出现警告路由重复添加
【此前重置变量asyncRouter只是后端返回的路由信息,而真实生成的路由信息并每页重置,因此会出现路由重复问题】,路由重复问题可通过下述代码解决,通过router.matcher
实现路由的初始化
/** 解决路由重复添加问题 */
router.matcher = createRouter().matcher;
router.addRoutes(asyncRouter);
/*---------------------------------------------*/
/*createRouter 函数*/
export const createRouter = () => new VueRouter({
routes:initRouterMap, //由前端管理的静态路由
base: process.env.BASE_URL,
})
以上就是近期Vue+Elment UI 搭建后台管理系统关于动态路由控制方面遇到的问题的归纳和总结
(完)