目录
前言
一、项目介绍
1.开发环境
2.功能
3.项目运行截图
二、实现
1.动态路由如何实现
2.项目目录介绍
3.核心代码
4.坑和知识点
小结
前言
最近在学权限相关的管理项目,前端用到了动态路由,就是根据用户角色显示不同的菜单,动态添加路由。说着好像很简单,但看了很多教程,也跟着教程写了很多代码,但最后都跑不通(很多原因,比如:版本问题,教程没有全部代码,只有核心代码片段等)。😭😭😭所以,我只能狠下心,写下这篇博客,方便自己总结,也为了避免大家踩坑。
一、项目介绍
项目下载
gitee:https://gitee.com/wusupweilgy/springboot-vue.git(点个star呀😎)
1.开发环境
前端:vue2+element-ui组件
2.功能
根据用户的账号密码,生成不同的菜单,动态添加路由
3.项目运行截图
二、实现
1.动态路由如何实现
流程:
- 登录的时候,根据登录用户返回此角色可以访问的页面的路由和token
- 前端将路由存储到sessionStorage和vuex中(因为vuex存储的数据一刷就没了,所以需要配合sessionStorage)
- 在路由前置守卫处动态添加拿到的路由,对页面进行渲染。
2.项目目录介绍
- assets:存放图片和样式
- router:路由配置
- dynamicRoutes.js:动态路由模板
- index.js:静态路由
- permission.js:路由前置守卫
- store:vuex存储全局的状态变量
- util:工具类
- index.js:动态添加路由工具类
- request.js:axios请求工具类
- views:存放页面
3.核心代码
1)/views/Login.vue:登录(onSubmit方法)成功时,根据用户名密码区分角色,根据角色将不同路由和token写入vuex,dynamicRoutes.js中有两个路由模板,分别是user和admin,然后调用generateRoutes()方法向vue-router中动态添加路由
import {admin,user} from '@/router/dynamicRoutes' ... onSubmit(formName) { //为表单绑定验证功能 this.$refs [formName].validate((valid) => { if (valid) { if(this.form.username==="user"&&this.form.password==="user"){ store.commit('SET_MENULIST', user); store.commit('SET_TOKEN', 'user'); }else if(this.form.username==="admin"&&this.form.password==="admin"){ store.commit('SET_MENULIST', admin); store.commit('SET_TOKEN', 'admin'); }else{ this.$message.error("登录失败") return } this.$message.success("登录成功") generateRoutes() this.$router.replace('/') } else { this.dialogVisible = true; return false; } }); },
复制
2)/store/index.js:vuex状态管理(配合sessionStorage)
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { token:'', menuList:[], isLoadRouters:false }, getters: { GET_TOKEN:state => { state.token = sessionStorage.getItem("token") return state.token }, GET_MENULIST:state => { state.menuList = JSON.parse(sessionStorage.getItem("menuList")||'[]') return state.menuList }, GET_ISLOADROUTERS:state=>{ return state.isLoadRouters } }, mutations: { SET_TOKEN:(state,token)=>{ state.token = token sessionStorage.setItem("token",token); }, SET_MENULIST:(state,menuList)=>{ state.menuList = menuList sessionStorage.setItem("menuList",JSON.stringify(menuList)); }, SET_ISLOADROUTERS:(state,isLoadRouters)=>{ state.isLoadRouters = isLoadRouters } }, })
复制
3)/util/index.js:方法动态添加路由工具
import router from '../router' import store from '@/store' //动态添加路由 export function generateRoutes() { const _asyncRoutes = store.getters.GET_MENULIST if(_asyncRoutes==null) return _asyncRoutes.forEach(menu => { if (menu.children) { menu.children.forEach(m => { let route = menuToRoute(m, menu.name); if (route) { router.addRoute("Home",route) } }) } }) } //将菜单转换成router可以识别的路由 const menuToRoute = (menu, parentName) => { if (!menu.component) { return null; } else { let route = { name: menu.name, path: menu.path, meta: { parentName: parentName } } route.component = () => import('@/views/' + menu.component + '.vue'); return route; } }
复制
4)/router/permission.js:前置路由守卫,判断是否登录和是否已经添加过路由
import router from "@/router/index" import store from "@/store" import {generateRoutes} from "@/util"; // 检查是否存在于免登陆白名单 function inWhiteList(toPath) { const whiteList = ['/login', '/404'] const path = whiteList.find((value) => { // 使用正则匹配 const reg = new RegExp('^' + value) return reg.test(toPath) }) return !!path } router.beforeEach((to, from, next) => { console.group('%c%s', 'color:blue', `${new Date().getTime()} ${to.path} 的全局前置守卫----------`) console.log('所有活跃的路由记录列表', router.getRoutes()) console.groupEnd() const token = store.getters.GET_TOKEN let isLoadRouters = store.state.isLoadRouters if (inWhiteList(to.path)) { next() } else { //用户已登录 if (token && JSON.stringify(store.getters.GET_MENULIST) !== '[]') { if (isLoadRouters) { // console.log('路由已添加,直接跳转到目标页面'); next() } else { //解决刷新页面空白 //console.log('重新加载路由,并跳转到目标页'); let menuList = store.getters.GET_MENULIST store.commit('SET_ISLOADROUTERS', true) //添加动态路由 generateRoutes(menuList) next({...to, replace: true}) } } else { // console.log('无登录信息,跳转到登录页'); store.commit('SET_ISLOADROUTERS', false) next(`/login`) } } })
复制
4.坑和知识点
1)遇到的坑
- 这里有个大坑,就是页面刷新的情况,页面一旦刷新,动态添加的路由也会清空,只剩静态路由,所有还要判断页面刷新的操作,这里通过一个vuex的变量解决,因为刷新操作,vuex的值也会刷新,所以我在vuex中设置了isLoadRouters:false这个变量,如果刷新,这个值就是默认的false
- 第二个坑就是,在addRoute()之后第一次访问被添加的路由会白屏,这是因为刚刚addRoute()就立刻访问被添加的路由,然而此时addRoutes()没有执行结束,因而找不到刚刚被添加的路由导致白屏。因此需要从新访问一次路由才行,所以需要next({...to, replace: true}),来保证路由添加完了再进入页面 (可以理解为重进一次)。小伙伴可以试试把它换成next(),看会不会出现白屏。是
这两个坑就已经卡了我很长时间了,更何况还有其他小坑(哭死我了)
-
用户点击退出后,需要重置路由,不然你添加过路由了,再通过addRoute()方法添加路由就会造成重复添加路由,浏览器的控制台会有警告,不重置也没事,就是看着有点不舒服。
// 重置路由 export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher }
复制
2)学到的知识点
-
js语法:两个感叹号,将非布尔型变量转为布尔值
// 检查是否存在于免登陆白名单 function inWhiteList(toPath) { const whiteList = ['/login', '/404'] const path = whiteList.find((value) => { // 使用正则匹配 const reg = new RegExp('^' + value) return reg.test(toPath) }) return !!path }
复制
- component: () => import("@/views/Index") 实现路由组件懒加载。vue这种单页面应用单页面应用单页面应用,如果我们不去做路由懒加载,打包之后的文件将会异常的大,就会造成进入首页时,需要加载的内容过多,时间过长,会出现长时间的白屏,不利于用户体验,运用懒加载就可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时。
- next({...to, replace: true}) 重新访问一次路由,避免路由还没添加完成就访问路
- router.matcher = newRouter.matcher相当于重置路由,避免重复添加路由
小结
本文介绍了使用Vue实现动态路由。关于这个东西,我网上找的很多教程都不怎么靠谱,给我挖了很多坑,所以希望我的这篇教程可以帮助你,少走点弯路,快速掌握这技术,代码的下载地址在文章开头,记得npm install一下,就可以运行了。如果这篇文章有幸帮助到你,希望读者大大们可以给作者给个三连呀😶🌫️😶🌫️😶🌫️