From d2982d64285089c28d5c06f6ed174505c6c5e8b0 Mon Sep 17 00:00:00 2001 From: zhl Date: Wed, 20 Jan 2021 15:49:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=80=E4=BA=9Bdirectives?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/roles.ts | 9 +-- src/directives/clipboard/index.ts | 60 +++++++++++++++++ src/directives/el-draggable-dialog/index.ts | 75 +++++++++++++++++++++ src/directives/index.ts | 5 ++ src/directives/permission/index.ts | 55 +++++++++++++++ src/directives/role/index.ts | 21 ++++++ src/directives/waves/index.ts | 46 +++++++++++++ src/directives/waves/waves.css | 26 +++++++ src/main.ts | 9 ++- src/store/modules/user.ts | 28 ++++++-- src/utils/permission.ts | 48 ++++++++++++- src/views/permission/directive.vue | 49 +++++++++----- src/views/permission/role.vue | 6 +- 13 files changed, 400 insertions(+), 37 deletions(-) create mode 100644 src/directives/clipboard/index.ts create mode 100644 src/directives/el-draggable-dialog/index.ts create mode 100644 src/directives/index.ts create mode 100644 src/directives/permission/index.ts create mode 100644 src/directives/role/index.ts create mode 100644 src/directives/waves/index.ts create mode 100644 src/directives/waves/waves.css diff --git a/src/api/roles.ts b/src/api/roles.ts index c8621d2..ba22185 100644 --- a/src/api/roles.ts +++ b/src/api/roles.ts @@ -7,20 +7,13 @@ export const getRoles = (params: any) => params }) -export const createRole = (data: any) => +export const saveRole = (data: any) => request({ url: '/roles', method: 'post', data }) -export const updateRole = (id: number, data: any) => - request({ - url: `/roles/${id}`, - method: 'put', - data - }) - export const deleteRole = (id: number) => request({ url: `/roles/${id}`, diff --git a/src/directives/clipboard/index.ts b/src/directives/clipboard/index.ts new file mode 100644 index 0000000..8ff0b67 --- /dev/null +++ b/src/directives/clipboard/index.ts @@ -0,0 +1,60 @@ +// Inspired by https://github.com/Inndy/vue-clipboard2 +import Clipboard from 'clipboard' +import { DirectiveOptions } from 'vue' + +if (!Clipboard) { + throw new Error('you should npm install `clipboard` --save at first ') +} + +let successCallback: Function | null +let errorCallback: Function | null +let clipboardInstance: Clipboard | null + +export const clipboard: DirectiveOptions = { + bind(el, binding) { + if (binding.arg === 'success') { + successCallback = binding.value + } else if (binding.arg === 'error') { + errorCallback = binding.value + } else { + clipboardInstance = new Clipboard(el, { + text() { return binding.value }, + action() { return binding.arg === 'cut' ? 'cut' : 'copy' } + }) + clipboardInstance.on('success', e => { + const callback = successCallback + callback && callback(e) + }) + clipboardInstance.on('error', e => { + const callback = errorCallback + callback && callback(e) + }) + } + }, + + update(el, binding) { + if (binding.arg === 'success') { + successCallback = binding.value + } else if (binding.arg === 'error') { + errorCallback = binding.value + } else { + clipboardInstance = new Clipboard(el, { + text() { return binding.value }, + action() { return binding.arg === 'cut' ? 'cut' : 'copy' } + }) + } + }, + + unbind(_, binding) { + if (binding.arg === 'success') { + successCallback = null + } else if (binding.arg === 'error') { + errorCallback = null + } else { + if (clipboardInstance) { + clipboardInstance.destroy() + } + clipboardInstance = null + } + } +} diff --git a/src/directives/el-draggable-dialog/index.ts b/src/directives/el-draggable-dialog/index.ts new file mode 100644 index 0000000..d33da40 --- /dev/null +++ b/src/directives/el-draggable-dialog/index.ts @@ -0,0 +1,75 @@ +import { DirectiveOptions } from 'vue' + +export const elDraggableDialog: DirectiveOptions = { + bind(el, _, vnode) { + const dragDom = el.querySelector('.el-dialog') as HTMLElement + const dialogHeaderEl = el.querySelector('.el-dialog__header') as HTMLElement + dragDom.style.cssText += ';top:0px;' + dialogHeaderEl.style.cssText += ';cursor:move;' + + dialogHeaderEl.onmousedown = (e) => { + const disX = e.clientX - dialogHeaderEl.offsetLeft + const disY = e.clientY - dialogHeaderEl.offsetTop + + const dragDomWidth = dragDom.offsetWidth + const dragDomHeight = dragDom.offsetHeight + + const screenWidth = document.body.clientWidth + const screenHeight = document.body.clientHeight + + const minDragDomLeft = dragDom.offsetLeft + const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth + + const minDragDomTop = dragDom.offsetTop + const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight + + const styleLeftStr = getComputedStyle(dragDom).left + const styleTopStr = getComputedStyle(dragDom).top + if (!styleLeftStr || !styleTopStr) return + let styleLeft: number + let styleTop: number + + // Format may be "##%" or "##px" + if (styleLeftStr.includes('%')) { + styleLeft = +document.body.clientWidth * (+styleLeftStr.replace(/%/g, '') / 100) + styleTop = +document.body.clientHeight * (+styleTopStr.replace(/%/g, '') / 100) + } else { + styleLeft = +styleLeftStr.replace(/px/g, '') + styleTop = +styleTopStr.replace(/px/g, '') + } + + document.onmousemove = (e) => { + let left = e.clientX - disX + let top = e.clientY - disY + + // Handle edge cases + if (-(left) > minDragDomLeft) { + left = -minDragDomLeft + } else if (left > maxDragDomLeft) { + left = maxDragDomLeft + } + if (-(top) > minDragDomTop) { + top = -minDragDomTop + } else if (top > maxDragDomTop) { + top = maxDragDomTop + } + + // Move current element + dragDom.style.cssText += `;left:${left + styleLeft}px;top:${top + styleTop}px;` + + // Emit on-dialog-drag event + // See https://stackoverflow.com/questions/49264426/vuejs-custom-directive-emit-event + if (vnode.componentInstance) { + vnode.componentInstance.$emit('on-dialog-drag') + } else if (vnode.elm) { + vnode.elm.dispatchEvent(new CustomEvent('on-dialog-drag')) + } + } + + document.onmouseup = () => { + document.onmousemove = null + document.onmouseup = null + } + } + } +} diff --git a/src/directives/index.ts b/src/directives/index.ts new file mode 100644 index 0000000..1bd79d0 --- /dev/null +++ b/src/directives/index.ts @@ -0,0 +1,5 @@ +export * from './permission' +export * from './el-draggable-dialog' +export * from './waves' +export * from './clipboard' +export * from './role' diff --git a/src/directives/permission/index.ts b/src/directives/permission/index.ts new file mode 100644 index 0000000..9c9cb2d --- /dev/null +++ b/src/directives/permission/index.ts @@ -0,0 +1,55 @@ +import { DirectiveOptions } from 'vue' +import { UserModule } from '@/store/modules/user' + +export const permission: DirectiveOptions = { + inserted(el, binding) { + const { value } = binding + const permissions = UserModule.permissions + let hasPermission = false + + if (value && value instanceof Array && value.length > 0) { + for (const sub of value) { + if (sub === '*') { + hasPermission = true + break + } + const subArr = sub.split(':') + if (subArr[0] === '*') { + for (const p of permissions) { + if (p[1] === '*' || p[1] === subArr[1]) { + hasPermission = true + break + } + } + } else if (subArr[1] === '*') { + for (const p of permissions) { + if (p[0] === '*' || p[0] === subArr[0]) { + hasPermission = true + break + } + } + } else { + for (const p of permissions) { + if ((p[0] === '*' && p[1] === '*') || + (p[0] === '*' && p[1] === subArr[1]) || + (p[0] === subArr[0] && p[1] === '*') || + (p[0] === subArr[0] && p[1] === subArr[1])) { + hasPermission = true + break + } + } + } + if (hasPermission) { + break + } + } + + if (!hasPermission) { + el.style.display = 'none' + } + } else { + throw new Error('need permissions! Like v-permission="[\'role:read\',\'news:read\']"') + } + } + +} diff --git a/src/directives/role/index.ts b/src/directives/role/index.ts new file mode 100644 index 0000000..75777ef --- /dev/null +++ b/src/directives/role/index.ts @@ -0,0 +1,21 @@ +import { DirectiveOptions } from 'vue' +import { UserModule } from '@/store/modules/user' + +export const role: DirectiveOptions = { + inserted(el, binding) { + const { value } = binding + const roles = UserModule.roles + if (value && value instanceof Array && value.length > 0) { + const permissionRoles = value + const hasRole = roles.some(role => { + return permissionRoles.includes(role) + }) + if (!hasRole) { + el.style.display = 'none' + } + } else { + throw new Error('need roles! Like v-role="[\'admin\',\'editor\']"') + } + } +} + diff --git a/src/directives/waves/index.ts b/src/directives/waves/index.ts new file mode 100644 index 0000000..7ff0fca --- /dev/null +++ b/src/directives/waves/index.ts @@ -0,0 +1,46 @@ +import './waves.css' +import { DirectiveOptions } from 'vue' + +export const waves: DirectiveOptions = { + bind(el, binding) { + el.addEventListener('click', e => { + const customOpts = Object.assign({}, binding.value) + const opts = Object.assign({ + ele: el, // 波纹作用元素 + type: 'hit', // hit 点击位置扩散 center中心点扩展 + color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 + }, customOpts) + const target: HTMLElement = opts.ele + if (target) { + target.style.position = 'relative' + target.style.overflow = 'hidden' + const rect = target.getBoundingClientRect() + let ripple = target.querySelector('.waves-ripple') as HTMLElement + if (!ripple) { + ripple = document.createElement('span') + ripple.className = 'waves-ripple' + ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px' + target.appendChild(ripple) + } else { + ripple.className = 'waves-ripple' + } + switch (opts.type) { + case 'center': + ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px' + ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px' + break + default: + ripple.style.top = + (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || + document.body.scrollTop) + 'px' + ripple.style.left = + (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || + document.body.scrollLeft) + 'px' + } + ripple.style.backgroundColor = opts.color + ripple.className = 'waves-ripple z-active' + return false + } + }, false) + } +} diff --git a/src/directives/waves/waves.css b/src/directives/waves/waves.css new file mode 100644 index 0000000..af7a7ef --- /dev/null +++ b/src/directives/waves/waves.css @@ -0,0 +1,26 @@ +.waves-ripple { + position: absolute; + border-radius: 100%; + background-color: rgba(0, 0, 0, 0.15); + background-clip: padding-box; + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); + opacity: 1; +} + +.waves-ripple.z-active { + opacity: 0; + -webkit-transform: scale(2); + -ms-transform: scale(2); + transform: scale(2); + -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; + transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; + transition: opacity 1.2s ease-out, transform 0.6s ease-out; + transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index e3c5df6..85e43a5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import Vue from 'vue' +import Vue, { DirectiveOptions } from 'vue' import 'normalize.css' import ElementUI from 'element-ui' @@ -13,8 +13,11 @@ import router from '@/router' import i18n from '@/lang' import '@/icons/components' import '@/permission' +import { AppModule } from '@/store/modules/app' +import * as directives from '@/directives' Vue.use(ElementUI, { + size: AppModule.size, i18n: (key: string, value: string) => i18n.t(key, value) }) Vue.use(SvgIcon, { @@ -23,6 +26,10 @@ Vue.use(SvgIcon, { defaultHeight: '1em' }) +Object.keys(directives).forEach(key => { + Vue.directive(key, (directives as { [key: string ]: DirectiveOptions })[key]) +}) +// Register global directives Vue.config.productionTip = false new Vue({ diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 4b9e703..be2f2ad 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -8,9 +8,9 @@ import { import { getUserInfo, login, logout } from '@/api/users' import { getToken, removeToken, setToken } from '@/utils/cookies' import store from '@/store' -import router, { resetRouter } from "@/router" -import { PermissionModule } from "@/store/modules/permission" -import { TagsViewModule } from "@/store/modules/tags-view" +import router, { resetRouter } from '@/router' +import { PermissionModule } from '@/store/modules/permission' +import { TagsViewModule } from '@/store/modules/tags-view' export interface IUserState { token: string @@ -19,6 +19,7 @@ export interface IUserState { introduction: string roles: string[] email: string + permissions: string[][] } @Module({ dynamic: true, store, name: 'user' }) @@ -28,6 +29,7 @@ class User extends VuexModule implements IUserState { public avatar = '' public introduction = '' public roles: string[] = [] + public permissions: string[][] = [] public email = '' @Action @@ -35,7 +37,6 @@ class User extends VuexModule implements IUserState { let { username, password } = userInfo username = username.trim() const { data } = await login({ username, password }) - console.log(data); setToken(data.token) this.SET_TOKEN(data.token) } @@ -45,6 +46,7 @@ class User extends VuexModule implements IUserState { removeToken() this.SET_TOKEN('') this.SET_ROLES([]) + this.SET_PERMISSIONS([]) } @Action @@ -53,11 +55,10 @@ class User extends VuexModule implements IUserState { throw Error('GetUserInfo: token is undefined!') } const { data } = await getUserInfo({ /* Your params here */ }) - console.log(data); if (!data) { throw Error('Verification failed, please Login again.') } - const { roles, showname, avatar, introduction } = data + const { roles, showname, avatar, introduction, permissions } = data // roles must be a non-empty array if (!roles || roles.length <= 0) { throw Error('GetUserInfo: roles must be a non-null array!') @@ -66,6 +67,7 @@ class User extends VuexModule implements IUserState { this.SET_NAME(showname) this.SET_AVATAR(avatar) this.SET_INTRODUCTION(introduction) + this.SET_PERMISSIONS(permissions) } @Action @@ -93,6 +95,7 @@ class User extends VuexModule implements IUserState { removeToken() this.SET_TOKEN('') this.SET_ROLES([]) + this.SET_PERMISSIONS([]) } @Mutation @@ -119,6 +122,19 @@ class User extends VuexModule implements IUserState { private SET_ROLES(roles: string[]) { this.roles = roles } + + @Mutation + private SET_PERMISSIONS(permissions: string[]) { + const results: string[][] = [] + for (const permission of permissions) { + if (permission === '*') { + results.push(['*', '*']) + } else { + results.push(permission.split(':')) + } + } + this.permissions = results + } } export const UserModule = getModule(User) diff --git a/src/utils/permission.ts b/src/utils/permission.ts index 1a9624e..1563bfe 100644 --- a/src/utils/permission.ts +++ b/src/utils/permission.ts @@ -1,6 +1,6 @@ import { UserModule } from '@/store/modules/user' -export const checkPermission = (value: string[]): boolean => { +export const checkRole = (value: string[]): boolean => { if (value && value instanceof Array && value.length > 0) { const roles = UserModule.roles const permissionRoles = value @@ -13,3 +13,49 @@ export const checkPermission = (value: string[]): boolean => { return false } } + +export const checkPermission = (value: string[]): boolean => { + if (value && value instanceof Array && value.length > 0) { + const permissions = UserModule.permissions + let hasPermission = false + for (const sub of value) { + if (sub === '*') { + hasPermission = true + break + } + const subArr = sub.split(':') + if (subArr[0] === '*') { + for (const p of permissions) { + if (p[1] === '*' || p[1] === subArr[1]) { + hasPermission = true + break + } + } + } else if (subArr[1] === '*') { + for (const p of permissions) { + if (p[0] === '*' || p[0] === subArr[0]) { + hasPermission = true + break + } + } + } else { + for (const p of permissions) { + if ((p[0] === '*' && p[1] === '*') || + (p[0] === '*' && p[1] === subArr[1]) || + (p[0] === subArr[0] && p[1] === '*') || + (p[0] === subArr[0] && p[1] === subArr[1])) { + hasPermission = true + break + } + } + } + if (hasPermission) { + break + } + } + return hasPermission + } else { + console.error('need roles! Like v-permission="[\'admin\',\'editor\']"') + return false + } +} diff --git a/src/views/permission/directive.vue b/src/views/permission/directive.vue index 5c4cc51..78eb807 100644 --- a/src/views/permission/directive.vue +++ b/src/views/permission/directive.vue @@ -7,27 +7,27 @@ >
Only admin can see this + >app:read 1 can see this - v-permission="['admin']" + v-role="['admin']"
Only @@ -37,17 +37,17 @@ >editor can see this - v-permission="['editor']" + v-role="['editor']"
Both @@ -61,17 +61,17 @@ >editor can see this - v-permission="['admin','editor']" + v-role="['admin','editor']"