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 相关文章推荐
js通过googleAIP翻译PHP系统的语言配置的实现代码
Oct 17 Javascript
js局部刷新页面时间具体实现
Jul 04 Javascript
在Iframe中获取父窗口中表单的值(示例代码)
Nov 22 Javascript
jquery实现tr元素的上下移动示例代码
Dec 20 Javascript
javascript引擎长时间独占线程造成卡顿的解决方案
Dec 03 Javascript
JS实现仿Windows经典风格的选项卡Tab切换代码
Oct 20 Javascript
JavaScript必看小技巧(必看)
Jun 07 Javascript
详解Node.js如何开发命令行工具
Aug 14 Javascript
jQuery EasyUI结合zTree树形结构制作web页面
Sep 01 jQuery
vue.js  父向子组件传参的实例代码
Oct 29 Javascript
vue.js实现的全选与全不选功能示例【基于elementui】
Dec 03 Javascript
详解Vue源码之数据的代理访问
Dec 11 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
一个可查询所有表的“通用”查询分页类
2006/10/09 PHP
洪恩在线成语词典小偷程序php版
2012/04/20 PHP
Smarty中常用变量操作符汇总
2014/10/27 PHP
php解析字符串里所有URL地址的方法
2015/04/03 PHP
PHP 读取大文件并显示的简单实例(推荐)
2016/08/12 PHP
PHP实现链式操作的三种方法详解
2017/11/16 PHP
基于jquery的图片的切换(以数字的形式)
2011/02/14 Javascript
jQuery+CSS 实现随滚动条增减的汽水瓶中的液体效果
2011/09/26 Javascript
JS中表单的使用小结
2014/01/11 Javascript
单击和双击事件的冲突处理示例代码
2014/04/03 Javascript
全面解析Bootstrap中nav、collapse的使用方法
2016/05/22 Javascript
bootstrap和jQuery.Gantt的css冲突 如何解决
2016/05/29 Javascript
JS实现的跨浏览器解析XML文件实例
2016/06/21 Javascript
jquery Form轻松实现文件上传
2017/05/24 jQuery
详解Vue2.0配置mint-ui踩过的那些坑
2018/04/23 Javascript
ES6之模版字符串的具体使用
2018/05/17 Javascript
[01:04:14]VP vs TNC 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
Python实现冒泡排序的简单应用示例
2017/12/11 Python
python处理csv中的空值方法
2018/06/22 Python
对python产生随机的二维数组实例详解
2018/12/13 Python
对Python3 * 和 ** 运算符详解
2019/02/16 Python
解决Django加载静态资源失败的问题
2019/07/28 Python
PyQt5+Caffe+Opencv搭建人脸识别登录界面
2019/08/28 Python
Python中如何将一个类方法变为多个方法
2019/12/30 Python
Python使用Excel将数据写入多个sheet
2020/05/16 Python
python解释器安装教程的方法步骤
2020/07/02 Python
pycharm 使用tab跳出正在编辑的括号(){}{}等问题
2021/02/26 Python
纯css3实现思维导图样式示例
2018/11/01 HTML / CSS
SneakerStudio英国:最佳运动鞋商店
2019/05/22 全球购物
党员个人思想汇报
2013/12/28 职场文书
护士自我鉴定怎么写
2014/02/07 职场文书
运动会演讲稿300字
2014/08/25 职场文书
上市公司董事长岗位职责
2015/04/16 职场文书
讲座通知范文
2015/04/23 职场文书
2015年秋季开学典礼校长致辞
2015/07/16 职场文书
60条职场经典语录,总有一条能触动你的心
2019/08/21 职场文书