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 相关文章推荐
THREE.JS入门教程(5)你应当知道的十件事
Jan 24 Javascript
JavaScript中读取和保存文件实例
May 08 Javascript
javascript使用shift+click实现选择和反选checkbox的方法
May 04 Javascript
超级给力的JavaScript的React框架入门教程
Jul 02 Javascript
javascript实现判断鼠标的状态
Jul 10 Javascript
Bootstrap每天必学之警告框插件
Apr 26 Javascript
jQuery Easyui快速入门教程
Aug 21 Javascript
AngularJs  E2E Testing 详解
Sep 02 Javascript
AngularJS页面传参的5种方式
Apr 01 Javascript
webpack2.0配置postcss-loader的方法
Aug 17 Javascript
Vue实现页面添加水印功能
Nov 09 Javascript
搭建Vue从Vue-cli到router路由护卫的实现
Nov 14 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
DSP接收机前端设想
2021/03/02 无线电
php选择排序法实现数组排序实例分析
2015/02/16 PHP
PHP5.6.8连接SQL Server 2008 R2数据库常用技巧分析总结
2019/05/06 PHP
php设计模式之职责链模式实例分析【星际争霸游戏案例】
2020/03/27 PHP
PHP autoload使用方法及步骤详解
2020/09/05 PHP
ExtJs使用IFrame的实现代码
2010/03/24 Javascript
jQueryUI的Dialog的简单封装
2010/06/07 Javascript
(jQuery,mootools,dojo)使用适合自己的编程别名命名
2010/09/14 Javascript
div模拟滚动条效果示例代码
2013/10/16 Javascript
自制的文件上传JS控件可支持IE、chrome、firefox etc
2014/04/18 Javascript
JS+CSS实现可拖拽的漂亮圆角特效弹出层完整实例
2015/02/13 Javascript
jquery任意位置浮动固定层插件用法实例
2015/05/29 Javascript
简介JavaScript中的push()方法的使用
2015/06/09 Javascript
jQuery超精致图片轮播幻灯片特效代码分享
2015/09/10 Javascript
基于Vue2实现的仿手机QQ单页面应用功能(接入聊天机器人 )
2017/03/30 Javascript
layer弹窗插件操作方法详解
2017/05/19 Javascript
利用node.js制作命令行工具方法教程(一)
2017/06/22 Javascript
在Vue中使用echarts的实例代码(3种图)
2017/07/10 Javascript
webpack中使用iconfont字体图标的方法
2018/02/22 Javascript
详解使用vue-admin-template的优化历程
2018/05/20 Javascript
微信小程序实现点击按钮后修改颜色
2019/12/05 Javascript
Openlayers+EasyUI Tree动态实现图层控制
2020/09/28 Javascript
[01:35]2018完美盛典章节片——共竞
2018/12/17 DOTA
Python中文件操作简明介绍
2015/04/13 Python
Python中splitlines()方法的使用简介
2015/05/20 Python
python操作列表的函数使用代码详解
2017/12/28 Python
Python基于opencv的图像压缩算法实例分析
2018/05/03 Python
python3 tkinter实现点击一个按钮跳出另一个窗口的方法
2019/06/13 Python
TripAdvisor印尼站:全球领先的旅游网站
2018/03/15 全球购物
澳大利亚个性化儿童礼品网站:Bright Star Kids
2019/06/14 全球购物
计算机操作自荐信
2013/12/07 职场文书
高中运动会广播稿
2014/01/21 职场文书
党支部对照检查材料
2014/08/25 职场文书
如何用JavaScript实现一个数组惰性求值库
2021/05/05 Javascript
Python数据处理的三个实用技巧分享
2022/04/01 Python
Python开发五子棋小游戏
2022/05/02 Python