详解基于vue,vue-router, vuex以及addRoutes进行权限控制
基于vuex, vue-router,vuex的权限控制教程,完整代码地址见https://github.com/linrunzheng/vue-permission-control
接下来让我们模拟一个普通用户打开网站的过程,一步一步的走完整个流程。
首先从打开本地的服务localhost:8080开始,我们知道打开后会进入login页面,那么判断的依据是什么。
首先是token。
没有登陆的用户是获取不到token的,而登陆后的角色我们会将token存到local或者seesionStorage 因此,根据当前有没有token即可知道是否登陆。
为了存取token并且方便我们操作,可以配和vuex实现
/* state.js */
export default {
get UserToken() {
return localStorage.getItem('token')
},
set UserToken(value) {
localStorage.setItem('token', value)
}
}
/* mutation.js */
export default {
LOGIN_IN(state, token) {
state.UserToken = token
},
LOGIN_OUT(state) {
state.UserToken = ''
}
}
拦截的判断
- 没有token进入需要权限的页面:redirect到login页面
- 由于我们路由是动态挂载的,包括 ' ' 和404,所以当匹配不到路由时,也重定向到login
router.beforeEach((to, from, next) => {
if (!store.state.UserToken) {
if (
to.matched.length > 0 &&
!to.matched.some(record => record.meta.requiresAuth)
) {
next()
} else {
next({ path: '/login' })
}
}
})
好了,此时用户打开localhost:8080,默认匹配的是''路径,此时我们并没有挂载路由,也没有token,所以来到了login。
输入用户名密码后,有token了,通过store触发* commit('LOGIN_IN')* 来设置token。
但是还是没有路由,目前最开始只有login路由
/* 初始路由 */
export default new Router({
routes: [
{
path: '/login',
component: Login
}
]
})
/* 准备动态添加的路由 */
export const DynamicRoutes = [
{
path: '',
component: Layout,
name: 'container',
redirect: 'home',
meta: {
requiresAuth: true,
name: '首页'
},
children: [
{
path: 'home',
component: Home,
name: 'home',
meta: {
name: '首页'
}
}
]
},
{
path: '/403',
component: Forbidden
},
{
path: '*',
component: NotFound
}
]
我们要根据当前用户的token去后台获取权限。
由于权限这块逻辑还挺多,所以在vuex添加了一个permission模块来处理权限。
为了判断是已有路由列表,需要在vuex的permission模块存一个state状态permissionList用来判断,假如permissionList不为null,即已经有路由,如果不存在,就需要我们干活了。
router.beforeEach((to, from, next) => {
if (!store.state.UserToken) {
...
} else {
/* 现在有token了 */
if (!store.state.permission.permissionList) {
/* 如果没有permissionList,真正的工作开始了 */
store.dispatch('permission/FETCH_PERMISSION').then(() => {
next({ path: to.path })
})
} else {
if (to.path !== '/login') {
next()
} else {
next(from.fullPath)
}
}
}
})
来看一下 store.dispatch('permission/FETCH_PERMISSION') 都干了什么
actions: {
async FETCH_PERMISSION({ commit, state }) {
/* 获取后台给的权限数组 */
let permissionList = await fetchPermission()
/* 根据后台权限跟我们定义好的权限对比,筛选出对应的路由并加入到path=''的children */
let routes = recursionRouter(permissionList, dynamicRouter)
let MainContainer = DynamicRoutes.find(v => v.path === '')
let children = MainContainer.children
children.push(...routes)
/* 生成左侧导航菜单 */
commit('SET_MENU', children)
setDefaultRoute([MainContainer])
/* 初始路由 */
let initialRoutes = router.options.routes
/* 动态添加路由 */
router.addRoutes(DynamicRoutes)
/* 完整的路由表 */
commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes])
}
}
首先,await fetchPermission()获取后台给的权限数组,格式大概如下
{
"code": 0,
"message": "获取权限成功",
"data": [
{
"name": "订单管理",
"children": [
{
"name": "订单列表"
},
{
"name": "生产管理",
"children": [
{
"name": "生产列表"
}
]
},
{
"name": "退货管理"
}
]
}
]
}
其次根据我们写好的路由数组,进行对比,过滤得到我们要的路由
/* 这里是我们写好的需要权限判断的路由 */
const dynamicRoutes = [
{
path: '/order',
component: Order,
name: 'order-manage',
meta: {
name: '订单管理'
},
children: [
{
path: 'list',
name: 'order-list',
component: OrderList,
meta: {
name: '订单列表'
}
},
{
path: 'product',
name: 'product-manage',
component: ProductManage,
meta: {
name: '生产管理'
},
children: [
{
path: 'list',
name: 'product-list',
component: ProductionList,
meta: {
name: '生产列表'
}
},
{
path: 'review',
name: 'review-manage',
component: ReviewManage,
meta: {
name: '审核管理'
}
}
]
},
{
path: 'returnGoods',
name: 'return-goods',
component: ReturnGoods,
meta: {
name: '退货管理'
}
}
]
}
]
export default dynamicRoutes
为了对比,我写好了一个递归函数,用name和meta.name进行对比 ,根据这个函数就可以得到我们想要的结果
/**
*
* @param {Array} userRouter 后台返回的用户权限json
* @param {Array} allRouter 前端配置好的所有动态路由的集合
* @return {Array} realRoutes 过滤后的路由
*/
export function recursionRouter(userRouter = [], allRouter = []) {
var realRoutes = []
allRouter.forEach((v, i) => {
userRouter.forEach((item, index) => {
if (item.name === v.meta.name) {
if (item.children && item.children.length > 0) {
v.children = recursionRouter(item.children, v.children)
}
realRoutes.push(v)
}
})
})
return realRoutes
}
得到过滤后的数组后,加入到path为''的children下面
{
path: '',
component: Layout,
name: 'container',
redirect: 'home',
meta: {
requiresAuth: true,
name: '首页'
},
children: [
{
path: 'home',
component: Home,
name: 'home',
meta: {
name: '首页'
}
},
<!-- 将上面得到的东西加入到这里 -->
...
]
}
这个时候,path为''的children就是我们左侧的导航菜单了,存到state的sidebarMenu待用。加入到children后,这时DynamicRoutes就可以加入到路由了。
/* 动态添加路由 */
router.addRoutes(DynamicRoutes)
/* 初始路由 */
let initialRoutes = router.options.routes
/* 合并起来,就是完整的路由了 */
commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes])
路由添加完了,也就是action操作完毕了,即可在action.then里面调用 next({ path: to.path })进去路由,这里要注意, next里面要传参数即要进入的页面的路由信息,因为next传参数后,当前要进入的路由会被废止,转而进入参数对应的路由,虽然是同一个路由,这么做主要是为了确保addRoutes生效了。
进入路由后,要开始生成左侧菜单,之前我们已经存到sidebarMenu了,现在需要做的只是递归生成菜单而已,虽然用了element的导航菜单,但是为了递归路由,还需要自己封装一下。这里核心的地方是组件的name,在组件里面有children的地方,又再次使用自己,从而遍历整个tree结构的路由。
<template>
<div class="menu-container">
<template v-for="v in menuList">
<el-submenu :index="v.name" v-if="v.children&&v.children.length>0" :key="v.name">
<template slot="title">
<i class="iconfont icon-home"></i>
<span>{{v.meta.name}}</span>
</template>
<el-menu-item-group>
<my-nav :menuList="v.children"></my-nav>
</el-menu-item-group>
</el-submenu>
<el-menu-item :key="v.name" :index="v.name" @click="gotoRoute(v.name)" v-else>
<i class="iconfont icon-home"></i>
<span slot="title">{{v.meta.name}}</span>
</el-menu-item>
</template>
</div>
</template>
<script>
export default {
name: 'my-nav',
props: {
menuList: {
type: Array,
default: function() {
return []
}
}
},
methods: {
gotoRoute(name) {
this.$router.push({ name })
}
}
}
</script>
刷新页面后,根据我们router.beforeEach的判断,有token但是没permissionList,我们是会重新触发action去获取路由的,所以无需担心。但是导航菜单active效果会不见。不过我们已经把el-menu-item的key设置为路由的name,那么我们只要在刷新后,在afterEach把当前路由的name赋值给el-menu default-active即可。同理,在afterEach阶段获取所有matched的路由,即可实现面包屑导航。
if (!store.state.permission.permissionList) {
store.dispatch('permission/FETCH_PERMISSION').then(() => {
next({ path: to.path })
})
}
...
router.afterEach((to, from, next) => {
var routerList = to.matched
store.commit('setCrumbList', routerList)
store.commit('permission/SET_CURRENT_MENU', to.name)
})
退出登陆后,需要刷新页面,因为我们是通过addRoutes添加的,router没有deleteRoutes这个api,所以清除token,清除permissionList等信息,刷新页面是最保险的。
最后还有一点,每次请求得带上token, 可以对axios封装一下来处理
var instance = axios.create({
timeout: 30000,
baseURL
})
// 添加请求拦截器
instance.interceptors.request.use(
function(config) {
// 请求头添加token
if (store.state.UserToken) {
config.headers.Authorization = store.state.UserToken
}
return config
},
function(error) {
return Promise.reject(error)
}
)
/* axios请求二次封装 */
instance.get = function(url, data, options) {
return new Promise((resolve, reject) => {
axios
.get(url, data, options)
.then(
res => {
var response = res.data
if (response.code === 0) {
resolve(response.data)
} else {
Message.warning(response.message)
/* reject(response.message) */
}
},
error => {
if (error.response.status === 401) {
Message.warning({
message: '登陆超时,请重新登录'
})
store.commit('LOGIN_OUT')
window.location.reload()
} else {
Message.error({
message: '系统异常'
})
}
reject(error)
}
)
.catch(e => {
console.log(e)
})
})
}
export default instance
详解基于vue,vue-router, vuex以及addRoutes进行权限控制的更多相关文章
- HashMap实现详解 基于JDK1.8
HashMap实现详解 基于JDK1.8 1.数据结构 散列表:是一种根据关键码值(Key value)而直接进行访问的数据结构.采用链地址法处理冲突. HashMap采用Node<K,V> ...
- nrf52——DFU升级USB/UART升级方式详解(基于SDK开发例程)
摘要:在前面的nrf52--DFU升级OTA升级方式详解(基于SDK开发例程)一文中我测试了基于蓝牙的OTA,本文将开始基于UART和USB(USB_CDC_)进行升级测试. 整体升级流程: 整个过程 ...
- 基于spring security 实现前后端分离项目权限控制
前后端分离的项目,前端有菜单(menu),后端有API(backendApi),一个menu对应的页面有N个API接口来支持,本文介绍如何基于spring security实现前后端的同步权限控制. ...
- MapReduce过程详解(基于hadoop2.x架构)
本文基于hadoop2.x架构详细描述了mapreduce的执行过程,包括partition,combiner,shuffle等组件以及yarn平台与mapreduce编程模型的关系. mapredu ...
- 详解基于linux环境MySQL搭建与卸载
本篇文章将从实际操作的层面,讲解基于linux环境的mysql的搭建和卸载. 1 搭建mysql 1.1 官网下载mysql压缩包 下载压缩包时,可以先把安装包下载到本地,再上传到服务器,也可以在 ...
- nrf52——DFU升级OTA升级方式详解(基于SDK开发例程)
在我们开始前,默认你已经安装好了一些基础工具,如nrfutil,如果你没有安装过请根据官方中文博客去安装好这些基础工具,连接如下:Nordic nRF5 SDK开发环境搭建(nRF51/nRF52芯片 ...
- Linux常用命令详解—基于CentOS7
## Linux 目录- /:根目录,一般只存放目录,不存放文件- /bin -> /usr/bin:可执行二进制文件的目录,也是常用命令目录,如常用的命令 ls.cat.mv 等- /boot ...
- 图文详解基于角色的权限控制模型RBAC
我们开发一个系统,必然面临权限控制的问题,即不同的用户具有不同的访问.操作.数据权限.形成理论的权限控制模型有:自主访问控制(DAC: Discretionary Access Control).强制 ...
- Springboot + Vue + shiro 实现前后端分离、权限控制
本文总结自实习中对项目对重构.原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelA ...
随机推荐
- 6.3、Android Studio的CPU Monitor
Android Monitor包含一个CPU Monitor,可以让你非常方便的监测你的应用的CPU的使用.它显示试试的CPU使用. 在CPU Monitor显示正在运行的应用 1. 打开一个项目 2 ...
- JDBC存储和读取二进制数据
以下JSP文件用common-fileupload组件实现文件上传,并将文件以二进制文件的形式存入数据库 <% if("POST".equalsIgnoreCase(requ ...
- Android初级教程通过简要分析“土司”源码,来自实现定义土司理论探讨
由于系统自带的土司瞬间即逝,而且非常难看.因此我们就希望自定义自己的土司风格.有些实例就是基于自定义土司完成的,例如金山卫士的火箭发射,基本原理就是个土司.但是在做出自己的土司风格之前,还是要简要分析 ...
- C++编写ATM
偶然看到的一段代码,代码虽然简单,但是挺有意思: #include <iostream> #include <conio.h> //全局变量 float sum_m ...
- C++_友元函数
1.为什么要引入友元函数:在实现类之间数据共享时,减少系统开销,提高效率 具体来说:为了使其他类的成员函数直接访问该类的私有变量 即:允许外面的类或函数去访问类的私有变量和保护 ...
- android scrollview嵌套listview计算高度的问题
ScrollView中只能放一个控件,一般都放LinearLayout,orientation属性值为vertical.在LinearLayout中放需要呈现的内容.ListView也在其中,List ...
- ROS_Kinetic_18 使用V-Rep3.3.1和Matlab2015b(vrep_ros_bridge)续
ROS_Kinetic_18 使用V-Rep3.3.1和Matlab2015b(vrep_ros_bridge)续 上一节配置的v-rep在ros kinetic中是可以看图像,并订阅主题的,但是无法 ...
- 深度剖析malloc、free和new、delete
1.malloc,free是C语言的函数,而new,delete是操作符,属于C++的语法,一定注意这两个不再是函数了,而是操作符. 2.malloc和new对于分配基础类型变量和数组变量,它们除了语 ...
- 基于easyui框架中input 类型的checkbox拼接成字符串存入数据库和读取选中---善良公社项目
项目中我做修改用户个人资料的时候,有一个需求是帮助人员的帮助类型如图下所示: 当初想如果是asp.net控件的话应该很简单实现,如果不是基于easyUI框架那就太简单了,现在是受框架的限制与是前端ht ...
- (四十四)TabBarController和NagivationController配合
如果既要使用TabBar分页,又要使用Nagivation导航,那么只能是TabBar为根,Nagivation为TabBar子视图,每一个TabBar对应一个Nagivation导航的一系列页面. ...