目录
前言
一、项目介绍
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一下,就可以运行了。如果这篇文章有幸帮助到你,希望读者大大们可以给作者给个三连呀😶🌫️😶🌫️😶🌫️