Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制
思路 :
动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用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 按钮级别的权限控制,希望对大家有所帮助