Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制


Posted in Javascript onSeptember 05, 2019

思路 :

动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用Vuex保存,通过  router.addRoutes  动态挂载到  router  上,按钮级别的权限控制,则需使用自定义指令去实现。

实现:

导航守卫代码:

router.beforeEach((to, from, next) => {
 NProgress.start() // start progress bar
 to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
 if (getStore('ACCESS_TOKEN')) {
 /* has token */
 if (to.path === '/user/login') {
  next({ path: '/other/list/user-list' })
  NProgress.done()
 } else {
  if (store.getters.roles.length === 0) {
  store
   .dispatch('GetInfo')
   .then(res => {
   const username = res.principal.username
   store.dispatch('GenerateRoutes', { username }).then(() => {
    // 根据roles生成可访问的路由表
    // 动态添加可访问路由表
    router.addRoutes(store.getters.addRouters)
    const redirect = decodeURIComponent(from.query.redirect || to.path)
    if (to.path === redirect) {
    // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
    next({ ...to, replace: true })
    } else {
    // 跳转到目的路由
    next({ path: redirect })
    }
   })
   })
   .catch(() => {
   notification.error({
    message: '错误',
    description: '请求用户信息失败,请重试'
   })
   store.dispatch('Logout').then(() => {
    next({ path: '/user/login', query: { redirect: to.fullPath } })
   })
   })
  } else {
  next()
  }
 }
 } else {
 if (whiteList.includes(to.name)) {
  // 在免登录白名单,直接进入
  next()
 } else {
  next({ path: '/user/login', query: { redirect: to.fullPath } })
  NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
 }
 }
})

Vuex保存routers

const permission = {
 state: {
 routers: constantRouterMap,
 addRouters: []
 },
 mutations: {
 SET_ROUTERS: (state, routers) => {
  state.addRouters = routers
  state.routers = constantRouterMap.concat(routers)
 }
 },
 actions: {
 GenerateRoutes ({ commit }, data) {
  return new Promise(resolve => {
  generatorDynamicRouter(data).then(routers => {
   commit('SET_ROUTERS', routers)
   resolve()
  })
  })
 }
 }
}

路由工具,访问后端接口获得菜单树,然后对菜单树进行处理,把菜单树的组件字符串进行转换为前端的组件如:

userlist: () => import('@/views/other/UserList'),这样生成的路由就是我们所要的了。

import { axios } from '@/utils/request'
import { UserLayout, BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts'
// 前端路由表
const constantRouterComponents = {
 // 基础页面 layout 必须引入
 BasicLayout: BasicLayout,
 BlankLayout: BlankLayout,
 RouteView: RouteView,
 PageView: PageView,
 // 需要动态引入的页面组件
 analysis: () => import('@/views/dashboard/Analysis'),
 workplace: () => import('@/views/dashboard/Workplace'),
 monitor: () => import('@/views/dashboard/Monitor'),
 userlist: () => import('@/views/other/UserList')
 // ...more
}
// 前端未找到页面路由(固定不用改)
const notFoundRouter = {
 path: '*', redirect: '/404', hidden: true
}
/**
 * 获取后端路由信息的 axios API
 * @returns {Promise}
 */
export const getRouterByUser = (parameter) => {
 return axios({
 url: '/menu/' + parameter.username,
 method: 'get'
 })
}
/**
 * 获取路由菜单信息
 *
 * 1. 调用 getRouterByUser() 访问后端接口获得路由结构数组
 * 2. 调用
 * @returns {Promise<any>}
 */
export const generatorDynamicRouter = (data) => {
 return new Promise((resolve, reject) => {
 // ajax
 getRouterByUser(data).then(res => {
  // const result = res.result
  const routers = generator(res)
  routers.push(notFoundRouter)
  resolve(routers)
 }).catch(err => {
  reject(err)
 })
 })
}
/**
 * 格式化 后端 结构信息并递归生成层级路由表
 *
 * @param routerMap
 * @param parent
 * @returns {*}
 */
export const generator = (routerMap, parent) => {
 return routerMap.map(item => {
 const currentRouter = {
  // 路由地址 动态拼接生成如 /dashboard/workplace
  path: `${item && item.path || ''}`,
  // 路由名称,建议唯一
  name: item.name || item.key || '',
  // 该路由对应页面的 组件
  component: constantRouterComponents[item.component || item.key],
  // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
  meta: { title: item.name, icon: item.icon || undefined, permission: item.key && [ item.key ] || null }
 }
 // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
 currentRouter.path = currentRouter.path.replace('//', '/')
 // 重定向
 item.redirect && (currentRouter.redirect = item.redirect)
 // 是否有子菜单,并递归处理
 if (item.children && item.children.length > 0) {
  // Recursion
  currentRouter.children = generator(item.children, currentRouter)
 }
 return currentRouter
 })
}

后端菜单树生成工具类

/**
 * 构造菜单树工具类
 * @author dang
 *
 */
public class TreeUtil {

 protected TreeUtil() {

 }

 private final static Long TOP_NODE_ID = (long) 1;
 /**
  * 构造前端路由
  * @param routes
  * @return
  */
 public static ArrayList<MenuEntity> buildVueRouter(List<MenuEntity> routes) {
  if (routes == null) {
   return null;
  }
  List<MenuEntity> topRoutes = new ArrayList<>();
  routes.forEach(route -> {
   Long parentId = route.getParentId();
   if (TOP_NODE_ID.equals(parentId)) {
    topRoutes.add(route);
    return;
   }
   for (MenuEntity parent : routes) {
    Long id = parent.getId();
    if (id != null && id.equals(parentId)) {
     if (parent.getChildren() == null) {
      parent.initChildren();
     }
     parent.getChildren().add(route);
     return;
    }
   }
  });

  ArrayList<MenuEntity> list = new ArrayList<>();
  MenuEntity root = new MenuEntity();
  root.setName("首页");
  root.setComponent("BasicLayout");
  root.setPath("/");
  root.setRedirect("/other/list/user-list");
  root.setChildren(topRoutes);
  list.add(root);
  return list;
 }
}

菜单实体 (使用了lombok插件)

/**
 * 菜单实体
 * @author dang
 *
 */
public class MenuEntity extends CoreEntity {
 private static final long serialVersionUID = 1L;
 @TableField("FParentId")
 private Long parentId;
 @TableField("FNumber")
 private String number;
 @TableField("FName")
 private String name;
 @TableField("FPerms")
 private String perms;
 @TableField("FType")
 private int type;
 @TableField("FLongNumber")
 private String longNumber;
 @TableField("FPath")
 private String path;
 @TableField("FComponent")
 private String component;
 @TableField("FRedirect")
 private String redirect;
 @TableField(exist = false)
 private List<MenuEntity> children;
 @TableField(exist = false)
 private MenuMeta meta;
 @TableField(exist = false)
 private List<PermissionEntity> permissionList;
 @Override
 public int hashCode() {
  return number.hashCode();
 }
 @Override
 public boolean equals(Object obj) {
  return super.equals(obj(obj);
 }
 public void initChildren() {
  this.children = new ArrayList<>();
 }
}

路由菜单是根据用户的角色去获得的,一个用户具有多个角色,一个角色具有多个菜单

思路:

说下按钮权限控制的实现:前端vue主要用自定义指令实现控制按钮的显示与隐藏,后端我用的是SpringSecurity框架,所以使用的是 @PreAuthorize注解, 在菜单实体的 perms属性记录权限的标识,如:sys:user:add,记录有权限标识的菜单其 parentId 应为上级菜单,然后获取用户的perms集合,在用户登录的时候传给前端并用Vuex保存,在自定义指令中去比较用户是否含有按钮所需要的权限。

实现:

获取用户信息的时候,把权限存到Vuex中   commit('SET_PERMISSIONS', result.authorities)

// 获取用户信息
 GetInfo ({ commit }) {
  return new Promise((resolve, reject) => {
  getInfo().then(response => {
   const result = response
   if (result.authorities) {
   commit('SET_PERMISSIONS', result.authorities)
   commit('SET_ROLES', result.principal.roles)
   commit('SET_INFO', result)
   } else {
   reject(new Error('getInfo: roles must be a non-null array !'))
   }
   commit('SET_NAME', { name: result.principal.displayName, welcome: welcome() })
   commit('SET_AVATAR', result.principal.avatar)
   resolve(response)
  }).catch(error => {
   reject(error)
  })
  })
 }

前端自定义指令

// 定义一些和权限有关的 Vue指令
// 必须包含列出的所有权限,元素才显示
export const hasPermission = {
 install (Vue) {
 Vue.directive('hasPermission', {
  bind (el, binding, vnode) {
  const permissions = vnode.context.$store.state.user.permissions
  const per = []
  for (const v of permissions) {
   per.push(v.authority)
  }
  const value = binding.value
  let flag = true
  for (const v of value) {
   if (!per.includes(v)) {
   flag = false
   }
  }
  if (!flag) {
   if (!el.parentNode) {
   el.style.display = 'none'
   } else {
   el.parentNode.removeChild(el)
   }
  }
  }
 })
 }
}
// 当不包含列出的权限时,渲染该元素
export const hasNoPermission = {
 install (Vue) {
 Vue.directive('hasNoPermission', {
  bind (el, binding, vnode) {
  const permissions = vnode.context.$store.state.user.permissions
  const per = []
  for (const v of permissions) {
   per.push(v.authority)
  }
  const value = binding.value
  let flag = true
  for (const v of value) {
   if (per.includes(v)) {
   flag = false
   }
  }
  if (!flag) {
   if (!el.parentNode) {
   el.style.display = 'none'
   } else {
   el.parentNode.removeChild(el)
   }
  }
  }
 })
 }
}
// 只要包含列出的任意一个权限,元素就会显示
export const hasAnyPermission = {
 install (Vue) {
 Vue.directive('hasAnyPermission', {
  bind (el, binding, vnode) {
  const permissions = vnode.context.$store.state.user.permissions
  const per = []
  for (const v of permissions) {
   per.push(v.authority)
  }
  const value = binding.value
  let flag = false
  for (const v of value) {
   if (per.includes(v)) {
   flag = true
   }
  }
  if (!flag) {
   if (!el.parentNode) {
   el.style.display = 'none'
   } else {
   el.parentNode.removeChild(el)
   }
  }
  }
 })
 }
}
// 必须包含列出的所有角色,元素才显示
export const hasRole = {
 install (Vue) {
 Vue.directive('hasRole', {
  bind (el, binding, vnode) {
  const permissions = vnode.context.$store.state.user.roles
  const per = []
  for (const v of permissions) {
   per.push(v.authority)
  }
  const value = binding.value
  let flag = true
  for (const v of value) {
   if (!per.includes(v)) {
   flag = false
   }
  }
  if (!flag) {
   if (!el.parentNode) {
   el.style.display = 'none'
   } else {
   el.parentNode.removeChild(el)
   }
  }
  }
 })
 }
}
// 只要包含列出的任意一个角色,元素就会显示
export const hasAnyRole = {
 install (Vue) {
 Vue.directive('hasAnyRole', {
  bind (el, binding, vnode) {
  const permissions = vnode.context.$store.state.user.roles
  const per = []
  for (const v of permissions) {
   per.push(v.authority)
  }
  const value = binding.value
  let flag = false
  for (const v of value) {
   if (per.includes(v)) {
   flag = true
   }
  }
  if (!flag) {
   if (!el.parentNode) {
   el.style.display = 'none'
   } else {
   el.parentNode.removeChild(el)
   }
  }
  }
 })
 }
}

在main.js中引入自定义指令

import Vue from 'vue'
import { hasPermission, hasNoPermission, hasAnyPermission, hasRole, hasAnyRole } from './utils/permissionDirect'

Vue.use(hasPermission)
Vue.use(hasNoPermission)
Vue.use(hasAnyPermission)
Vue.use(hasRole)
Vue.use(hasAnyRole)

这样就可以在按钮中使用自定义指令,没有权限时,按钮自动隐藏,使用Postman工具测试也会拒绝访问

<a-button type="primary" @click="handleAddUser()" v-hasPermission="['sys:user:add']" icon="plus"

总结

以上所述是小编给大家介绍的Vue 动态路由的实现以及 Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制Springsecurity 按钮级别的权限控制,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
javascript实现面向对象类的功能书写技巧
Mar 07 Javascript
Jquery+JSon 无刷新分页实现代码
Apr 01 Javascript
js实现兼容IE6与IE7的DIV高度
May 13 Javascript
javascript生成不重复的随机数
Jul 17 Javascript
VUEJS实战之利用laypage插件实现分页(3)
Jun 13 Javascript
js对字符串进行编码的方法总结(推荐)
Nov 10 Javascript
Vue.js实现价格计算器功能
Mar 30 Javascript
vue中实现methods一个方法调用另外一个方法
Feb 08 Javascript
JQuery Ajax执行跨域请求数据的解决方案
Dec 10 jQuery
vue cli 3.x 项目部署到 github pages的方法
Apr 17 Javascript
微信小程序开发实现消息推送
Nov 18 Javascript
详解vue中多个有顺序要求的异步操作处理
Oct 29 Javascript
layui 对弹窗 form表单赋值的实现方法
Sep 04 #Javascript
layui-table对返回的数据进行转变显示的实例
Sep 04 #Javascript
layui table数据修改的回显方法
Sep 04 #Javascript
Vue实现商品详情页的评价列表功能
Sep 04 #Javascript
Layui 数据表格批量删除和多条件搜索的实例
Sep 04 #Javascript
解决layui表格的表头不滚动的问题
Sep 04 #Javascript
解决layui数据表格table的横向滚动条显示问题
Sep 04 #Javascript
You might like
用PHP的ob_start();控制您的浏览器cache!
2006/11/25 PHP
PHP中鲜为人知的10个函数
2014/02/28 PHP
PHP文件大小格式化函数合集
2014/03/10 PHP
Laravel框架模板加载,分配变量及简单路由功能示例
2018/06/11 PHP
Laravel Validator自定义错误返回提示消息并在前端展示
2019/05/09 PHP
dojo 之基础篇(二)之从服务器读取数据
2007/03/24 Javascript
给网站上的广告“加速”显示的方法
2007/04/08 Javascript
Prototype Selector对象学习
2009/07/23 Javascript
document.body.scrollTop 值总为0的解决方法 比较常见的标准问题
2009/11/30 Javascript
jQuery 三击事件实现代码
2013/09/11 Javascript
Js+Ajax,Get和Post在使用上的区别小结
2016/06/08 Javascript
jquery Banner轮播选项卡
2016/12/26 Javascript
详解nodejs微信公众号开发——1.接入微信公众号
2017/04/10 NodeJs
详解vue-resource promise兼容性问题
2017/06/20 Javascript
AngularJS动态绑定ng-options的ng-model实例代码
2017/06/21 Javascript
vue 粒子特效的示例代码
2017/09/19 Javascript
微信小程序多音频播放进度条问题
2018/08/28 Javascript
layui自定义验证,用ajax查询后台是否有重复数据,form.verify的例子
2019/09/06 Javascript
使用 js 简单的实现 bind、call 、aplly代码实例
2019/09/07 Javascript
在 Vue 中编写 SVG 图标组件的方法
2020/02/24 Javascript
vue的$http的get请求要加上params操作
2020/11/12 Javascript
[45:14]Optic vs VP 2018国际邀请赛淘汰赛BO3 第二场 8.24
2018/08/25 DOTA
python 解析XML python模块xml.dom解析xml实例代码
2014/02/07 Python
Python 两个列表的差集、并集和交集实现代码
2016/09/21 Python
python编写朴素贝叶斯用于文本分类
2017/12/21 Python
python获取网页中所有图片并筛选指定分辨率的方法
2018/03/31 Python
Python socket套接字实现C/S模式远程命令执行功能案例
2018/07/06 Python
python 操作hive pyhs2方式
2019/12/21 Python
Docker部署Python爬虫项目的方法步骤
2020/01/19 Python
您的网上新华书店:文轩网
2016/08/24 全球购物
党员干部承诺书
2014/03/25 职场文书
党员自我对照检查材料
2014/08/19 职场文书
2014会计年终工作总结
2014/12/20 职场文书
爱心捐助活动总结
2015/05/09 职场文书
党风廉洁教育心得体会
2016/01/20 职场文书
Requests什么的通通爬不了的Python超强反爬虫方案!
2021/05/20 Python