Ideas :
Dynamic routing implemented: the navigation guard determined whether the user has the user information by calling the interface, the background to get the user's character generation menu tree, Format menu tree structure information and the recursion level route table and use Vuex saved by router.addRoutes
dynamic linked loaded into router
the button-level access control, you need to use custom commands to achieve.
achieve:
Navigation guard Code:
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 generated according to a routing table accessible
// add the dynamic routing table accessible
router.addRoutes (store.getters.addRouters)
const = decodeURIComponent the redirect (from.query.redirect || to.path)
IF (to.path === the redirect) {
// Method to ensure hack addRoutes has been completed, the SET Replace: the SO to true Will Not Leave A Navigation History Record
Next ({... to, Replace: to true})
} the else {
// jump to the destination route
Next ({path: the redirect})
}
})
})
.catch (() => {
notification.error ({
message: 'error',
Description: 'requesting user information failed. Please try again "
})
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 save 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()
})
})
}
}
}
Routing tool, get access to the backend interface menu tree, and then processes the menu tree, the menu tree component string by converting components such as a front end:
userlist: () => import ( '@ / views / other / UserList'), is thus generated route we want a.
{} from Axios Import '@ / utils / Request' Import {UserLayout,, BasicLayout, RouteView, BlankLayout, PageView} from '@ / Layouts' // distal routing table const constantRouterComponents = { // underlying page layout to be introduced BasicLayout: BasicLayout, BlankLayout: BlankLayout, RouteView: RouteView, PageView: PageView, // dynamically introduced page components Analysis: () => Import ( '@ / views / Dashboard / the Analysis'), Workplace: () => Import (' @ / views / Dashboard / Workplace '), Monitor: () => Import (' @ / views / Dashboard / Monitor '), UserList: () => Import (' @ / views / OTHER / the UserList ') // ... More } // distal route page not found (no change fixed) const = {notFoundRouter path: '*', the redirect:'/404', hidden: true } / ** * Get the rear end of the API routing information Axios * Promise @Returns {} * / Export getRouterByUser = const (Parameter) => { return Axios ({ URL: '/ mENU /' + parameter.username, Method: 'GET' }) } / ** * menu obtaining routing information * * 1. call getRouterByUser () to get access to the routing structure of the backend interface array * 2. calling * @ {Promise Returns < the any > } * / Export generatorDynamicRouter = const (Data) => { return new new Promise ((Resolve, Reject) => { // Ajax getRouterByUser (Data) .then (RES =>{ // const result = res.result Generator Routers = const (RES) routers.push (notFoundRouter) Resolve (Routers) }). the catch (ERR => { Reject (ERR) }) }) } / ** * formatted configuration information and the rear end recursion level route table * * @param routerMap * @param parent * @Returns {*} * / Export = const Generator (routerMap, parent ) => { return routerMap.map (Item => { const currentRouter = { // routing address dynamically generated as splicing / Dashboard / Workplace path: $ { `Item && item.path || ''`}, // route name , the only recommended name: item.name item.key || || '', // the route corresponding components of the page the component: constantRouterComponents [item.component || item.key], // Meta: page title, menu icons, page permissions (permission instruction for use, can be removed) Meta: {title: item.name, for, icon: item.icon || undefined, permission: item.key && [item.key] || null} } // return the result in order to prevent the rear end is not standardized, there may be processed splicing two backslash currentRouter.path = currentRouter.path.replace ( '//', '/') // redirection item.redirect && (currentRouter.redirect = item.redirect) // if there are sub-menu, and recursively processed IF (item.children && item.children.length> 0) { // the recursion currentRouter.children = Generator (item.children, currentRouter) } return currentRouter }) }
Back-end menu tree generation tools
/ ** * Tools menu tree structure * @author Dang * * / public class TreeUtil { protected TreeUtil () { } Private Final static Long TOP_NODE_ID = ( Long ). 1 ; / * * distal routing configured * @param routes * @ return * / public static the 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; } }
Menu entities (using lombok plug-in)
/** * 菜单实体 * @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<>(); } }
Routing menu is based on the user's role to get in, a user has multiple roles, a role with more menus
Ideas:
Said access control achieved under the button: Front vue main display with a custom instruction to achieve control buttons and hide, I use the back-end SpringSecurity framework, it is used @PreAuthorize注解,
在菜单实体的
to identify perms property rights records, such as: sys: user: add , recorded menu marked its authority should be parentId the previous menu, and then get perms collection of users, passed to the front when the user is logged and saved with Vuex, in a custom command to compare the user is required to contain the permissions button.
achieve:
When acquiring the user information, the authority to save the 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)
})
})
}
Custom distal end instruction
// define permissions and instructions related Vue
// must contain all of the listed rights, before the display elements
Export const = {the hasPermission
the install (Vue) {
Vue.directive ( 'the hasPermission', {
the bind (EL, Binding, the vnode) {
const = vnode.context Permissions. $ store.state.user.permissions
const per = []
for (V const of Permissions) {
per.push (v.authority)
}
const value = binding.value
the let = In Flag to true
for ( value of V const) {
IF (! per.includes (V)) {
In Flag to false =
}
}
IF (! In 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)) {
In Flag to false =
}
}
IF (! In Flag) {
IF (! el.parentNode) {
el.style.display = 'none'
} the else {
el.parentNode.removeChild (EL)
}
}
}
})
}
}
// long as it contains any privileges listed elements displayed
Export const = {hasAnyPermission
the install (Vue) {
Vue.directive ( 'hasAnyPermission', {
the bind (EL, Binding, the vnode) {
const = vnode.context Permissions. $ store.state.user.permissions
const per = []
for (V const of Permissions) {
per.push (v.authority)
}
const value = binding.value
the let = In Flag to false
for (value of V const) {
IF (per.includes (V)) {
In Flag to true =
}
}
IF (! In Flag) {
IF ( ! el.parentNode) {
el.style.display = 'none'
} the else {
el.parentNode.removeChild (EL)
}
}
}
})
}
}
// must include all of the listed characters, display elements only
export const hasRole = {
the install (Vue) {
Vue.directive ( 'the 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)
}
}
}
})
}
}
// long as it contains any of the listed character element is displayed
Export const = {hasAnyRole
the install (Vue) {
Vue.directive ( 'hasAnyRole', {
the bind (EL, Binding, the vnode) {
const = Permissions the vnode. context. $ store.state.user.roles
const per = []
for (V const of Permissions) {
per.push (v.authority)
}
const value = binding.value
the let = In Flag to false
for (value of V const) {
IF (per.includes (V)) {
In Flag to true =
}
}
IF (! In Flag) {
IF (! el.parentNode) {
el.style.display = 'none'
} else {
el.parentNode.removeChild(el)
}
}
}
})
}
}
Introducing the custom instruction 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">新建</a-button>