增加一些directives

This commit is contained in:
zhl 2021-01-20 15:49:11 +08:00
parent 50072ace8d
commit d2982d6428
13 changed files with 400 additions and 37 deletions

View File

@ -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}`,

View File

@ -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
}
}
}

View File

@ -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
}
}
}
}

5
src/directives/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from './permission'
export * from './el-draggable-dialog'
export * from './waves'
export * from './clipboard'
export * from './role'

View File

@ -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\']"')
}
}
}

View File

@ -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\']"')
}
}
}

View File

@ -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)
}
}

View File

@ -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;
}

View File

@ -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({

View File

@ -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)

View File

@ -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
}
}

View File

@ -7,27 +7,27 @@
>
<div>
<span
v-permission="['admin']"
v-permission="['app:read']"
class="permission-alert"
>
Only
<el-tag
class="permission-tag"
size="small"
>admin</el-tag> can see this
>app:read</el-tag> 1 can see this
</span>
<el-tag
v-permission="['admin']"
v-role="['admin']"
class="permission-sourceCode"
type="info"
>
v-permission="['admin']"
v-role="['admin']"
</el-tag>
</div>
<div>
<span
v-permission="['editor']"
v-role="['editor']"
class="permission-alert"
>
Only
@ -37,17 +37,17 @@
>editor</el-tag> can see this
</span>
<el-tag
v-permission="['editor']"
v-role="['editor']"
class="permission-sourceCode"
type="info"
>
v-permission="['editor']"
v-role="['editor']"
</el-tag>
</div>
<div>
<span
v-permission="['admin','editor']"
v-role="['admin','editor']"
class="permission-alert"
>
Both
@ -61,17 +61,17 @@
>editor</el-tag> can see this
</span>
<el-tag
v-permission="['admin','editor']"
v-role="['admin','editor']"
class="permission-sourceCode"
type="info"
>
v-permission="['admin','editor']"
v-role="['admin','editor']"
</el-tag>
</div>
</div>
<div
:key="'checkPermission'+key"
:key="'checkRole'+key"
style="margin-top:60px;"
>
<aside>
@ -84,7 +84,7 @@
style="width:550px;"
>
<el-tab-pane
v-if="checkPermission(['admin'])"
v-if="checkRole(['admin'])"
label="Admin"
>
Admin can see this
@ -92,12 +92,12 @@
class="permission-sourceCode"
type="info"
>
v-if="checkPermission(['admin'])"
v-if="checkRole(['admin'])"
</el-tag>
</el-tab-pane>
<el-tab-pane
v-if="checkPermission(['editor'])"
v-if="checkRole(['editor'])"
label="Editor"
>
Editor can see this
@ -105,12 +105,12 @@
class="permission-sourceCode"
type="info"
>
v-if="checkPermission(['editor'])"
v-if="checkRole(['editor'])"
</el-tag>
</el-tab-pane>
<el-tab-pane
v-if="checkPermission(['admin','editor'])"
v-if="checkRole(['admin','editor'])"
label="Admin-OR-Editor"
>
Both admin or editor can see this
@ -118,7 +118,19 @@
class="permission-sourceCode"
type="info"
>
v-if="checkPermission(['admin','editor'])"
v-if="checkRole(['admin','editor'])"
</el-tag>
</el-tab-pane>
<el-tab-pane
v-if="checkPermission(['app:read'])"
label="app_read"
>
Both admin or editor can see this
<el-tag
class="permission-sourceCode"
type="info"
>
v-if="checkPermission(['app:read'])"
</el-tag>
</el-tab-pane>
</el-tabs>
@ -128,7 +140,7 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { checkPermission } from '@/utils/permission' // Use permission directly
import { checkRole, checkPermission } from '@/utils/permission' // Use permission directly
import SwitchRoles from './components/SwitchRoles.vue'
@Component({
@ -139,6 +151,7 @@ import SwitchRoles from './components/SwitchRoles.vue'
})
export default class extends Vue {
private key = 1 //
private checkRole = checkRole
private checkPermission = checkPermission
private handleRolesChange() {

View File

@ -120,7 +120,7 @@ import { cloneDeep } from 'lodash'
import { Component, Vue } from 'vue-property-decorator'
import { RouteConfig } from 'vue-router'
import { Tree } from 'element-ui'
import { getRoutes, getRoles, createRole, deleteRole, updateRole } from '@/api/roles'
import { getRoutes, getRoles, saveRole, deleteRole } from '@/api/roles'
interface IRole {
key: number
@ -302,7 +302,7 @@ export default class extends Vue {
this.role.routes = this.generateTree(cloneDeep(this.serviceRoutes), '/', checkedKeys)
if (isEdit) {
await updateRole(this.role.key, { role: this.role })
await saveRole( { role: this.role })
for (let index = 0; index < this.rolesList.length; index++) {
if (this.rolesList[index].key === this.role.key) {
this.rolesList.splice(index, 1, Object.assign({}, this.role))
@ -310,7 +310,7 @@ export default class extends Vue {
}
}
} else {
const { data } = await createRole({ role: this.role })
const { data } = await saveRole({ role: this.role })
this.role.key = data.key
this.rolesList.push(this.role)
}