Vue3 改造后的生命周期钩子变成了七个,而且名称变得比较好记了。
前端获取到操作权限数据之后,进行转换并存储在sessionStorage中,转换后的权限数据大概如下
{
"UserMgr": ["add", "edit"],
"RoleMgr": ["add", "edit"]
}
通过 vue-router 的路由守卫,在进入指定的路由前先获取to路由的路由名,然后存储在localStorage中
自定义指令先从localStorage中获取to路由名,然后通过这个to路由名,找到对应的功能权限标识
自定义指令将第 2 步获取到操作权限标识数组和传入的操作权限标识比较,有相同的值,则有权限,否则表示没有权限
<script lang="ts">
export default {
name: 'PermissionDirective01',
}
</script>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { Directive, nextTick, ref } from 'vue'
import MyButton from './MyButton.vue'
let hasPermissions = ref<string[]>([])
interface CustomHTMLElement extends HTMLElement {
_clickCall?: (evt: MouseEvent) => boolean | undefined
// 存储原始的display方式
originalDisplay?: string
}
function doPermissionValidate(el: CustomHTMLElement, permissionFlag: string, arg: string) {
let clickCall = el._clickCall as (evt: MouseEvent) => boolean | undefined
if (!clickCall) {
/**
* 自定义的点击事件必须在一开始的时候就添加上去,保证鉴权事件在所有事件之前
*
*/
clickCall = (evt: MouseEvent) => {
if (hasPermissions.value.indexOf(permissionFlag) < 0) {
/*
阻止事件冒泡且同时阻止同类事件的触发(如:原本可能绑定了多个click事件,
stopImmediatePropagation执行之后能确保当前click事件之后的其他click事件都不会被触发)
*/
evt.stopImmediatePropagation()
evt.stopPropagation()
evt.preventDefault()
if (arg === 'tips') {
ElMessage({
showClose: true,
message: '没权限执行该操作',
type: 'warning',
})
}
return false
}
}
el.addEventListener('click', clickCall, false)
el._clickCall = clickCall
}
if (hasPermissions.value.indexOf(permissionFlag) < 0) {
// 之所以在nextTick中执行,是为了解决el-button这种UI库组件,样式设置不上去的问题
nextTick(() => {
if (!el.classList.contains('is-disabled')) {
el.classList.add('is-disabled')
}
})
switch (arg) {
case 'hidden': {
el.originalDisplay = el.style.display
el.style.display = 'none'
break
}
case 'disabled': {
// 之所以在nextTick中执行,是为了解决el-button这种UI库组件,属性设置不上去的问题
nextTick(() => {
el.setAttribute('disabled', 'disabled')
})
}
}
} else {
if (el.style.display === 'none') {
el.style.display = 'block'
}
if (el.getAttribute('disabled')) {
el.removeAttribute('disabled')
}
el.classList.remove('is-disabled')
}
}
const vPermission: Directive<CustomHTMLElement, string> = {
created(el, binding, vnode, prevVnode) {
const { value: permissionFlag, arg = 'disabled' } = binding
doPermissionValidate(el, permissionFlag, arg)
},
updated(el, binding, vnode, prevVnode) {
const { value: permissionFlag, arg = 'disabled' } = binding
doPermissionValidate(el, permissionFlag, arg)
},
beforeUnmount(el, binding, vnode, prevVnode) {
el._clickCall && el.removeEventListener('click', el._clickCall, false)
},
}
function sayHello() {
alert('你好')
}
function addOrDelPermission(type: 'add' | 'del') {
hasPermissions.value = []
if (type === 'add') {
hasPermissions.value.push('permissionFlag')
}
}
function containerClick() {
alert('背景点击')
}
</script>
<template>
<div>
<button @click="addOrDelPermission('add')">添加权限</button>
<button @click="addOrDelPermission('del')">去除权限</button>
<div>hasPermissions: {{ hasPermissions }}</div>
<div>原生按钮button(事件通过vue的@click添加)</div>
<div @click="containerClick" style="background-color: lightpink">
<div>
<button v-permission="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则禁用)</button>
</div>
<div>
<button v-permission:hidden="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则隐藏)</button>
</div>
<div>
<button v-permission:tips="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限给出tips提示)</button>
</div>
</div>
<div>原生超链接a(事件通过vue的@click添加)</div>
<div @click="containerClick" style="background-color: lightpink">
<div>
<a v-permission="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则禁用)</a>
</div>
<div>
<a v-permission:hidden="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则隐藏)</a>
</div>
<div>
<a v-permission:tips="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限给出tips提示)</a>
</div>
</div>
<div>原生span(事件通过vue的@click添加)</div>
<div @click="containerClick" style="background-color: lightpink">
<div>
<span v-permission="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则禁用)</span>
</div>
<div>
<span v-permission:hidden="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限则隐藏)</span>
</div>
<div>
<span v-permission:tips="'permissionFlag'" @click.native.stop="sayHello">权限按钮(没权限给出tips提示)</span>
</div>
</div>
<div>el-button</div>
<div @click="containerClick" style="background-color: lightpink">
<div>
<el-button v-permission="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限则禁用)</el-button>
</div>
<div>
<el-button v-permission:hidden="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限则隐藏)</el-button>
</div>
<div>
<el-button v-permission:tips="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限给出tips提示)</el-button>
</div>
</div>
<div>自封装组件</div>
<div @click="containerClick" style="background-color: lightpink">
<div>
<MyButton v-permission="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限则禁用)</MyButton>
</div>
<div>
<MyButton v-permission:hidden="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限则隐藏)</MyButton>
</div>
<div>
<MyButton v-permission:tips="'permissionFlag'" @click.stop="sayHello">权限按钮(没权限给出tips提示)</MyButton>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
button {
background-color: lightseagreen;
padding: 10px;
border: 1px solid #333;
border-radius: 5px;
}
button[disabled],
a[disabled],
span[disabled] {
background-color: #666;
}
</style>
var ownPermission = ['user', 'order'];
function toolPermission(el, permission) {
if (permission && !ownPermission.includes(permission)) {
el.parentNode && el.parentNode.removeChild(el); // 关键代码, 没有权限则删除元素
}
}
app.directive('permission', {
mounted(el, binding) {
toolPermission(el, binding.value)
},
updated(el, binding) {
toolPermission(el, binding.value)
}
})
具体使用
<div>
<button v-permission="'user'">用户模块</button>
<button v-permission="'order'">订单模块</button>
<button v-permission="'goods'">商品模块</button>
</div>
批量注册指令
新建 directives/directive.js 文件
// 导入指令定义文件
import debounce from './debounce'
import throttle from './throttle'
// 集成一起
const directives = {
debounce,
throttle,
}
//批量注册
export default {
install(Vue) {
Object.keys(directives).forEach((key) => {
Vue.directive(key, directives[key])
})
},
}
在 main.js 引入,并Vue.use() 调用完成批量注册。
import Vue from 'vue'
import Directives from './directives/directive.js'
Vue.use(Directives)
vue3 main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import lazyPlugin from 'vue3-lazy'
import loadingDirective from '@/components/base/loading/directive'
import noResultDirective from '@/components/base/no-result/directive'
import { load, saveAll } from '@/assets/js/array-store'
import { FAVORITE_KEY, PLAY_KEY } from '@/assets/js/constant'
import { processSongs } from '@/service/song'
createApp(App).use(store).use(router).use(lazyPlugin, {
loading: require('@/assets/images/default.png') // lazyPlugin 配置默认图片 基于当前的懒加载插件
}).directive('loading', loadingDirective).directive('no-result', noResultDirective).mount('#app') // 使用directive API 做全局注册 //
// 相当于上面有两个指令的注册 //
// 这里分别注册了 load 和 404页面的 指令 //
v-clickoutside
<script lang="ts">
export default {
name: 'ClickoutsideDemo',
}
</script>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { Directive, ref } from 'vue'
interface ClickoutsideHTMLElement extends HTMLElement {
_clickEvt: (e: MouseEvent) => void
}
const vClickoutside: Directive<ClickoutsideHTMLElement> = {
created(el, binding) {
function clickEvt(e: MouseEvent) {
if (!el.contains(e.target as Node)) {
el.style.display = 'none'
binding.value && binding.value()
}
}
el._clickEvt = clickEvt
document.addEventListener('click', clickEvt, false)
},
beforeUnmount(el) {
el._clickEvt && document.removeEventListener('click', el._clickEvt)
},
}
const testRef = ref<HTMLDivElement>()
function showAlert() {
ElMessage('点在了ClickoutsideDemo之外的地方,ClickoutsideDemo将隐藏,3秒后会重新显示')
setTimeout(() => {
if (testRef.value) {
testRef.value.style.display = 'inline-block'
}
}, 3000)
}
</script>
<template>
<div v-clickoutside="showAlert" class="test" ref="testRef">ClickoutsideDemo</div>
</template>
<style lang="scss" scoped>
.test {
display: inline-block;
border: 1px solid red;
padding: 10px;
border-radius: 5px;
}
</style>