Vue 動(dòng)態(tài)路由的實(shí)現(xiàn)及 Springsecurity 按鈕級(jí)別的權(quán)限控制
思路 :
動(dòng)態(tài)路由實(shí)現(xiàn):在導(dǎo)航守衛(wèi)中判斷用戶是否有用戶信息, 通過調(diào)用接口,拿到后臺(tái)根據(jù)用戶角色生成的菜單樹, 格式化菜單樹結(jié)構(gòu)信息并遞歸生成層級(jí)路由表并 使用Vuex保存,通過 router.addRoutes 動(dòng)態(tài)掛載到 router 上,按鈕級(jí)別的權(quán)限控制,則需使用自定義指令去實(shí)現(xiàn)。
實(shí)現(xiàn):
導(dǎo)航守衛(wèi)代碼:
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(() => {
// 根據(jù)roles生成可訪問的路由表
// 動(dòng)態(tài)添加可訪問路由表
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 {
// 跳轉(zhuǎn)到目的路由
next({ path: redirect })
}
})
})
.catch(() => {
notification.error({
message: '錯(cuò)誤',
description: '請(qǐng)求用戶信息失敗,請(qǐng)重試'
})
store.dispatch('Logout').then(() => {
next({ path: '/user/login', query: { redirect: to.fullPath } })
})
})
} else {
next()
}
}
} else {
if (whiteList.includes(to.name)) {
// 在免登錄白名單,直接進(jìn)入
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()
})
})
}
}
}
路由工具,訪問后端接口獲得菜單樹,然后對(duì)菜單樹進(jìn)行處理,把菜單樹的組件字符串進(jìn)行轉(zhuǎn)換為前端的組件如:
userlist: () => import('@/views/other/UserList'),這樣生成的路由就是我們所要的了。
import { axios } from '@/utils/request'
import { UserLayout, BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts'
// 前端路由表
const constantRouterComponents = {
// 基礎(chǔ)頁面 layout 必須引入
BasicLayout: BasicLayout,
BlankLayout: BlankLayout,
RouteView: RouteView,
PageView: PageView,
// 需要?jiǎng)討B(tài)引入的頁面組件
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. 調(diào)用 getRouterByUser() 訪問后端接口獲得路由結(jié)構(gòu)數(shù)組
* 2. 調(diào)用
* @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)
})
})
}
/**
* 格式化 后端 結(jié)構(gòu)信息并遞歸生成層級(jí)路由表
*
* @param routerMap
* @param parent
* @returns {*}
*/
export const generator = (routerMap, parent) => {
return routerMap.map(item => {
const currentRouter = {
// 路由地址 動(dòng)態(tài)拼接生成如 /dashboard/workplace
path: `${item && item.path || ''}`,
// 路由名稱,建議唯一
name: item.name || item.key || '',
// 該路由對(duì)應(yīng)頁面的 組件
component: constantRouterComponents[item.component || item.key],
// meta: 頁面標(biāo)題, 菜單圖標(biāo), 頁面權(quán)限(供指令權(quán)限用,可去掉)
meta: { title: item.name, icon: item.icon || undefined, permission: item.key && [ item.key ] || null }
}
// 為了防止出現(xiàn)后端返回結(jié)果不規(guī)范,處理有可能出現(xiàn)拼接出兩個(gè) 反斜杠
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
})
}
后端菜單樹生成工具類
/**
* 構(gòu)造菜單樹工具類
* @author dang
*
*/
public class TreeUtil {
protected TreeUtil() {
}
private final static Long TOP_NODE_ID = (long) 1;
/**
* 構(gòu)造前端路由
* @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;
}
}
菜單實(shí)體 (使用了lombok插件)
/**
* 菜單實(shí)體
* @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<>();
}
}
路由菜單是根據(jù)用戶的角色去獲得的,一個(gè)用戶具有多個(gè)角色,一個(gè)角色具有多個(gè)菜單
思路:
說下按鈕權(quán)限控制的實(shí)現(xiàn):前端vue主要用自定義指令實(shí)現(xiàn)控制按鈕的顯示與隱藏,后端我用的是SpringSecurity框架,所以使用的是 @PreAuthorize注解, 在菜單實(shí)體的 perms屬性記錄權(quán)限的標(biāo)識(shí),如:sys:user:add,記錄有權(quán)限標(biāo)識(shí)的菜單其 parentId 應(yīng)為上級(jí)菜單,然后獲取用戶的perms集合,在用戶登錄的時(shí)候傳給前端并用Vuex保存,在自定義指令中去比較用戶是否含有按鈕所需要的權(quán)限。
實(shí)現(xiàn):
獲取用戶信息的時(shí)候,把權(quán)限存到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)
})
})
}
前端自定義指令
// 定義一些和權(quán)限有關(guān)的 Vue指令
// 必須包含列出的所有權(quán)限,元素才顯示
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)
}
}
}
})
}
}
// 當(dāng)不包含列出的權(quán)限時(shí),渲染該元素
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)
}
}
}
})
}
}
// 只要包含列出的任意一個(gè)權(quán)限,元素就會(huì)顯示
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)
}
}
}
})
}
}
// 只要包含列出的任意一個(gè)角色,元素就會(huì)顯示
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)
這樣就可以在按鈕中使用自定義指令,沒有權(quán)限時(shí),按鈕自動(dòng)隱藏,使用Postman工具測試也會(huì)拒絕訪問
<a-button type="primary" @click="handleAddUser()" v-hasPermission="['sys:user:add']" icon="plus"
總結(jié)
以上所述是小編給大家介紹的Vue 動(dòng)態(tài)路由的實(shí)現(xiàn)以及 Vue 動(dòng)態(tài)路由的實(shí)現(xiàn)及 Springsecurity 按鈕級(jí)別的權(quán)限控制Springsecurity 按鈕級(jí)別的權(quán)限控制,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
相關(guān)文章
vue動(dòng)態(tài)添加store、路由和國際化配置方式
這篇文章主要介紹了vue動(dòng)態(tài)添加store、路由和國際化配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Vue.js 利用v-for中的index值實(shí)現(xiàn)隔行變色
這篇文章主要介紹了Vue.js 利用v-for中的index值實(shí)現(xiàn)隔行變色效果,首先定義好樣式,利用v-for中的index值,然后綁定樣式來實(shí)現(xiàn)隔行變色,需要的朋友可以參考下2018-08-08
element中Steps步驟條和Tabs標(biāo)簽頁關(guān)聯(lián)的解決
這篇文章主要介紹了element中Steps步驟條和Tabs標(biāo)簽頁關(guān)聯(lián)的解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
vuejs element table 表格添加行,修改,單獨(dú)刪除行,批量刪除行操作
這篇文章主要介紹了vuejs element table 表格添加行,修改,單獨(dú)刪除行,批量刪除行操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07
Vue Object 的變化偵測實(shí)現(xiàn)代碼
這篇文章主要介紹了Vue Object的變化偵測實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04

