From 9b96868586541a305b5e3cb95288c6238679d51f Mon Sep 17 00:00:00 2001 From: iczer <1126263215@qq.com> Date: Sun, 2 Aug 2020 18:50:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20add=20authorize=20directive;=20:star2:?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=EF=BC=9A=E6=9D=83=E9=99=90=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E6=8C=87=E4=BB=A4=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mock/user/login.js | 4 +- src/plugins/authority-plugin.js | 134 ++++++++++++++++++++++++++++++++ src/plugins/index.js | 5 +- src/router/index.js | 4 +- src/store/modules/account.js | 17 ++-- src/theme/default/style.less | 4 + src/utils/routerUtil.js | 35 ++++++++- 7 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 src/plugins/authority-plugin.js diff --git a/src/mock/user/login.js b/src/mock/user/login.js index 932a406..364310f 100644 --- a/src/mock/user/login.js +++ b/src/mock/user/login.js @@ -22,8 +22,8 @@ Mock.mock('/login', 'post', ({body}) => { result.data.user = user result.data.token = 'Authorization:' + Math.random() result.data.expireAt = new Date(new Date().getTime() + 30 * 60 * 1000) - result.data.permissions = [{id: 'analysis', extra: ['add', 'edit', 'delete']}] - result.data.roles = [{id: 'admin', extra: ['add', 'edit', 'delete']}] + result.data.permissions = [{id: 'queryForm', operation: ['add', 'edit', 'delete']}] + result.data.roles = [{id: 'admin', operation: ['add', 'edit', 'delete']}] } return result }) diff --git a/src/plugins/authority-plugin.js b/src/plugins/authority-plugin.js new file mode 100644 index 0000000..64fe185 --- /dev/null +++ b/src/plugins/authority-plugin.js @@ -0,0 +1,134 @@ +/** + * 获取路由需要的权限 + * @param permissions + * @param route + * @returns {*} + */ +const getRoutePermission = (permissions, route) => permissions.find(item => item.id === route.meta.authority.permission) +/** + * 获取路由需要的角色 + * @param roles + * @param route + * @returns {*} + */ +const getRouteRole = (roles, route) => roles.find(item => item.id === route.meta.authority.role) +/** + * 判断是否已为方法注入权限认证 + * @param method + * @returns {boolean} + */ +const hasInjected = (method) => method.toString().indexOf('//--auth-inject') !== -1 + +/** + * 操作权限校验 + * @param authConfig + * @param permission + * @param role + * @param permissions + * @param roles + * @returns {boolean} + */ +const auth = function(authConfig, permission, role, permissions, roles) { + const {check, type} = authConfig + if (check && typeof check === 'function') { + return check(permission, role, permissions, roles) + } else { + if (type === 'permission') { + return permission && permission.operation && permission.operation.indexOf(check) !== -1 + } else if (type === 'role') { + return role && role.operation && role.operation.indexOf(check) !== -1 + } + } + return false +} + +/** + * 阻止的 click 事件监听 + * @param event + * @returns {boolean} + */ +const preventClick = function (event) { + event.stopPropagation() + return false +} + +const checkInject = function (el, binding,vnode) { + const type = binding.arg + const check = binding.value + const instance = vnode.componentInstance + const $auth = instance.$auth + if (!$auth || !$auth(check, type)) { + el.classList.add('disabled') + el.setAttribute('title', '无此权限') + el.addEventListener('click', preventClick, true) + } else { + el.classList.remove('disabled') + el.removeAttribute('title') + el.removeEventListener('click', preventClick, true) + } +} + +const AuthorityPlugin = { + install(Vue) { + Vue.directive('auth', { + bind(el, binding,vnode) { + checkInject(el, binding, vnode) + }, + update(el, binding,vnode) { + checkInject(el, binding, vnode) + } + }) + Vue.mixin({ + beforeCreate() { + if (this.$options.authorize) { + const authorize = this.$options.authorize + Object.keys(authorize).forEach(key => { + if (this.$options.methods[key]) { + const method = this.$options.methods[key] + if (!hasInjected(method)) { + let authConfig = authorize[key] + authConfig = (typeof authConfig === 'string') ? {check: authConfig} : authConfig + const {check, type, onFailure} = authConfig + this.$options.methods[key] = function () { + //--auth-inject + if (this.$auth(check, type)) { + return method(...arguments) + } else { + if (onFailure && typeof onFailure === 'function') { + this[`$${check}Failure`] = onFailure + return this[`$${check}Failure`](check) + } else { + this.$message.error(`对不起,您没有操作权限:${check}`) + } + return 0 + } + } + } + } + }) + } + }, + methods: { + /** + * 操作权限校验 + * @param check 需要校验的操作名 + * @param type 校验类型,通过 permission 校验,还是通过 role 校验。 + * 如未设置,则自动识别,如匹配到当前路由 permission 则 type = permission,否则 type = role + * @returns {boolean} 是否校验通过 + */ + $auth(check, type) { + const permissions = this.$store.getters['account/permissions'] + const roles = this.$store.getters['account/roles'] + const permission = getRoutePermission(permissions, this.$route) + const role = getRouteRole(roles, this.$route) + if (!type) { + type = permission ? 'permission' : 'role' + } + return auth({check, type}, permission, role, permissions, roles) + } + } + }) + } +} + +export default AuthorityPlugin diff --git a/src/plugins/index.js b/src/plugins/index.js index 0b04285..e3c9b3a 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -1,7 +1,10 @@ -import VueI18nPlugin from '@/plugins/i18n-extend'; +import VueI18nPlugin from '@/plugins/i18n-extend' +import AuthorityPlugin from '@/plugins/authority-plugin' + const Plugins = { install: function (Vue) { Vue.use(VueI18nPlugin) + Vue.use(AuthorityPlugin) } } export default Plugins diff --git a/src/router/index.js b/src/router/index.js index 5a68ccc..06b0ea3 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,11 +1,12 @@ import Vue from 'vue' import Router from 'vue-router' +import {formatAuthority} from '@/utils/routerUtil' Vue.use(Router) // 不需要登录拦截的路由配置 const loginIgnore = { - names: ['404'], //根据路由名称匹配 + names: ['404', '403'], //根据路由名称匹配 paths: ['/login'], //根据路由fullPath匹配 /** * 判断路由是否包含在该配置中 @@ -24,6 +25,7 @@ const loginIgnore = { */ function initRouter(isAsync) { const options = isAsync ? require('./config.async').default : require('./config').default + formatAuthority(options.routes) return new Router(options) } export {loginIgnore, initRouter} diff --git a/src/store/modules/account.js b/src/store/modules/account.js index 8735d18..ae80b9b 100644 --- a/src/store/modules/account.js +++ b/src/store/modules/account.js @@ -2,9 +2,9 @@ export default { namespaced: true, state: { user: undefined, - permissions: [], - roles: [], - routesConfig: [] + permissions: null, + roles: null, + routesConfig: null }, getters: { user: state => { @@ -19,10 +19,11 @@ export default { return state.user }, permissions: state => { - if (!state.permissions || state.permissions.length === 0) { + if (!state.permissions) { try { const permissions = localStorage.getItem(process.env.VUE_APP_PERMISSIONS_KEY) state.permissions = JSON.parse(permissions) + state.permissions = state.permissions ? state.permissions : [] } catch (e) { console.error(e.message) } @@ -30,10 +31,11 @@ export default { return state.permissions }, roles: state => { - if (!state.roles || state.roles.length === 0) { + if (!state.roles) { try { const roles = localStorage.getItem(process.env.VUE_APP_ROLES_KEY) state.roles = JSON.parse(roles) + state.roles = state.roles ? state.roles : [] } catch (e) { console.error(e.message) } @@ -41,10 +43,11 @@ export default { return state.roles }, routesConfig: state => { - if (!state.routesConfig || state.routesConfig.length === 0) { + if (!state.routesConfig) { try { const routesConfig = localStorage.getItem(process.env.VUE_APP_ROUTES_KEY) - state.routesConfig = eval(routesConfig) ? JSON.parse(routesConfig) : state.routesConfig + state.routesConfig = JSON.parse(routesConfig) + state.routesConfig = state.routesConfig ? state.routesConfig : [] } catch (e) { console.error(e.message) } diff --git a/src/theme/default/style.less b/src/theme/default/style.less index 3400e0a..3c05a8c 100644 --- a/src/theme/default/style.less +++ b/src/theme/default/style.less @@ -26,3 +26,7 @@ border-right: 1px solid rgba(98, 98, 98, 0.2); } } +.disabled{ + cursor: not-allowed; + opacity: 0.4; +} diff --git a/src/utils/routerUtil.js b/src/utils/routerUtil.js index 5e65bf6..d5940c2 100644 --- a/src/utils/routerUtil.js +++ b/src/utils/routerUtil.js @@ -64,6 +64,7 @@ function loadRoutes({router, store, i18n}, routesConfig) { if (asyncRoutes) { if (routesConfig && routesConfig.length > 0) { const routes = parseRoutes(routesConfig, routerMap) + formatAuthority(routes) const finalRoutes = mergeRoutes(router.options.routes, routes) router.options = {...router.options, routes: finalRoutes} router.matcher = new Router({...router.options, routes:[]}).matcher @@ -151,7 +152,37 @@ function hasRole(route, roles) { if (typeof authority === 'object') { required = authority.role } - return authority === '*' || (required && roles.findIndex(item => item === required || item.id === required) !== -1) + return authority === '*' || (required && roles && roles.findIndex(item => item === required || item.id === required) !== -1) } -export {parseRoutes, loadRoutes, loginGuard, authorityGuard} +/** + * 格式化路由的权限配置 + * @param routes + */ +function formatAuthority(routes) { + routes.forEach(route => { + const meta = route.meta + if (meta) { + let authority = {} + if (!meta.authority) { + authority.permission = '*' + }else if (typeof meta.authority === 'string') { + authority.permission = meta.authority + } else if (typeof meta.authority === 'object') { + authority = meta.authority + } else { + console.log(typeof meta.authority) + } + meta.authority = authority + } else { + route.meta = { + authority: {permission: '*'} + } + } + if (route.children) { + formatAuthority(route.children) + } + }) +} + +export {parseRoutes, loadRoutes, loginGuard, authorityGuard, formatAuthority}