基于vue,vue-router, vuex及addRoutes进行权限控制问题


Posted in Javascript onMay 02, 2018

基于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进行权限控制问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
[Web]防止用户复制页面内容和另存页面的方法
Feb 06 Javascript
JavaScript利用正则表达式去除日期中的“-”
Jul 01 Javascript
非常漂亮的相册集 使用jquery制作相册集
Apr 28 Javascript
JS利用正则表达式实现简单的密码强弱判断实例
Jun 16 Javascript
vue cli升级webapck4总结
Apr 04 Javascript
使用node搭建自动发图文微博机器人的方法
Mar 22 Javascript
Promise扫盲贴
Jun 24 Javascript
Vue实现简单计算器案例
Feb 25 Javascript
Vue 实现监听窗口关闭事件,并在窗口关闭前发送请求
Sep 01 Javascript
Openlayers实现点闪烁扩散效果
Sep 24 Javascript
three.js显示中文字体与tween应用详析
Jan 04 Javascript
Vue包大小优化的实现(从1.72M到94K)
Feb 18 Vue.js
用ES6写全屏滚动插件的示例代码
May 02 #Javascript
详解Vue中watch的高级用法
May 02 #Javascript
Vue.js中关于侦听器(watch)的高级用法示例
May 02 #Javascript
Vue SSR 组件加载问题
May 02 #Javascript
基于jquery实现左右上下移动效果
May 02 #jQuery
关于Vue在ie10下空白页的debug小结
May 02 #Javascript
解析Json字符串的三种方法日常常用
May 02 #Javascript
You might like
PHP 和 MySQL 开发的 8 个技巧
2007/01/02 PHP
删除及到期域名的查看(抢域名必备哦)
2008/05/14 PHP
php设计模式 Interpreter(解释器模式)
2011/06/26 PHP
php调用C代码的实现方法
2014/03/11 PHP
使用GDB调试PHP代码,解决PHP代码死循环问题
2015/03/02 PHP
利用“多说”制作留言板、评论系统
2015/07/14 PHP
PHP实现搜索地理位置及计算两点地理位置间距离的实例
2016/01/08 PHP
[原创]PHP实现SQL语句格式化功能的方法
2017/07/28 PHP
php获取用户真实IP和防刷机制的实例代码
2018/11/28 PHP
JQuery 学习笔记 选择器之一
2009/07/23 Javascript
层序遍历在ExtJs的TreePanel中的应用
2009/10/16 Javascript
javascript面向对象入门基础详细介绍
2012/09/05 Javascript
改变隐藏的input中value值的方法
2014/03/19 Javascript
微信小程序加载更多 点击查看更多
2016/11/29 Javascript
WEB开发之注册页面验证码倒计时代码的实现
2016/12/15 Javascript
Node.js搭建小程序后台服务
2018/01/03 Javascript
详解小程序rich-text对富文本支持方案
2018/11/28 Javascript
BootStrap模态框闪退问题实例代码详解
2018/12/10 Javascript
每天学点Vue源码之vm.$mount挂载函数
2019/03/11 Javascript
Python中用format函数格式化字符串的用法
2015/04/08 Python
python使用7z解压apk包的方法
2015/04/18 Python
浅谈python新手中常见的疑惑及解答
2016/06/14 Python
详解Python利用random生成一个列表内的随机数
2019/08/21 Python
Python中flatten( ),matrix.A用法说明
2020/07/05 Python
在 Python 中使用 7zip 备份文件的操作
2020/12/11 Python
python简单实现插入排序实例代码
2020/12/16 Python
Marks & Spencer爱尔兰:英国马莎百货
2016/04/20 全球购物
Parfumdreams英国:香水和化妆品
2019/05/10 全球购物
Michael Kors澳大利亚官网:世界知名的奢侈饰品和成衣设计师
2020/02/13 全球购物
车贷收入证明范本
2014/01/09 职场文书
五好关工委申报材料
2014/05/31 职场文书
审计班子对照检查材料
2014/08/27 职场文书
正风肃纪剖析材料
2014/09/30 职场文书
党员群众路线学习心得体会
2014/11/04 职场文书
Python中的 Set 与 dict
2022/03/13 Python
科学家测试在太空中培育人造肉,用于未来太空旅行
2022/04/29 数码科技