🔮 欢迎点赞 👍٩( ´︶` )( ´︶` )۶ 收藏 🌟留言 💌 欢迎讨论!💕
🔮 本文由 【第四人称Alice】 原创,首发于 优快云 ✨✨✨
🌍 由于博主还属于前期的前端开发小白,欢迎大家留言提出更好的意见,大家共同进步!💭
声明:博主的项目是vue3+ts,node版本18.18.2
一、前言
1、官网:自定义指令 | Vue.js
2、使用场景:(待完善)
2.1 表单校验
2.2 一键Copy功能
2.3 按钮级别权限控制
2.4 防抖
2.5 相对时间转换
2.6 点击外部区域关闭弹窗的功能
二、指令介绍
1、钩子函数
// 在绑定元素的 attribute 前,或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件,以及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件,以及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
2、参数含义
el
:指令所绑定的元素,可以用来直接操作 DOM。
binding
:一个对象,包含以下属性。
value
:传递给指令的值。oldValue
:之前的值,仅在beforeUpdate
和updated
中可用。无论值是否更改,它都可用。arg
:传递给指令的参数 (如果有的话)。modifiers
:一个包含修饰符的对象 (如果有的话)。instance
:使用该指令的组件实例。dir
:指令的定义对象。
vnode
:代表绑定元素的底层 VNode。
prevVnode
:代表之前的渲染中指令所绑定元素的 VNode。仅在beforeUpdate
和updated
钩子中可用。
3、实际应用
3.1 局部自定义指令(私有自定义指令)
3.1.1 局部使用时通常会接收两个参数:el和binding
el:表示被绑定的当前实例元素
binding:表示绑定时传入的参数或者函数
3.1.2 案例:防止用户连续点击按钮,实现类似节流功能
<template>
<div>
<el-button type="primary" v-click-once="time">仅点击一次</el-button>
</div>
</template>
<script setup lang="ts">
import { Directive, DirectiveBinding, ref } from "vue";
let time = ref(1000);
interface HTMLElementWithDisabled extends HTMLElement {
disabled: boolean;
}
const vClickOnce: Directive = {
mounted(el: HTMLElementWithDisabled, binding: DirectiveBinding) {
el.addEventListener("click", () => {
if (!el.disabled) {
el.disabled = true;
setTimeout(() => {
el.disabled = false;
}, binding.value || 1000);
}
});
},
};
</script>
3.1.3 拖拽
参考地址:Vue中 实现自定义指令(directive)生命周期及应用场景_vue directive自定义指令-优快云博客
<script setup lang="ts">
/**
* Element.firstElementChild:只读属性,返回对象第一个子元素,没有则返回Null
* Element.clientX:只读属性,元素距离视口左边的距离(中心点)
* Element.offsetLeft:只读属性,元素左上角距离视口左边的距离
* Element.offsetWidth:元素宽度
* Element.offsetHeight:元素高度
* window.innerWidth:可视窗宽度
* window.innerHeight:可视窗高度
*/
import {Directive, DirectiveBinding} from "vue";
const vDrea:Directive<any,void> = (el:HTMLElement,binding:DirectiveBinding)=>{
let gap = 10
let moveElement:HTMLDivElement = el.firstElementChild as HTMLDivElement
const mouseDown = (e:MouseEvent)=>{
console.log(window.innerHeight)
let X = e.clientX - el.offsetLeft
let Y = e.clientY - el.offsetTop
const move = (e:MouseEvent)=>{
let x = e.clientX - X
let y = e.clientY - Y
//超出边界判断
if (x<=gap){
x = 0
}
if (y<=gap){
y = 0
}
if (x>= window.innerWidth -el.offsetWidth -gap){
x = window.innerWidth -el.offsetWidth
}
if (y>= window.innerHeight - el.offsetHeight-gap){
y = window.innerHeight - el.offsetHeight
}
el.style.left = x + 'px'
el.style.top = y + 'px'
}
// 鼠标移动
document.addEventListener('mousemove',move)
//松开鼠标
document.addEventListener('mouseup',()=>{
//清除移动事件
document.removeEventListener('mousemove',move)
})
}
//鼠标按下
moveElement.addEventListener('mousedown',mouseDown)
}
</script>
<template>
<div v-drea class="box">
<div class="header"></div>
<div>内容</div>
</div>
</template>
<style lang="less" scoped>
.box{
position: fixed;
width: 300px;
height: 250px;
border: solid 1px black;
.header{
height: 30px;
background-color: black;
}
}
</style>
3.2 全局自定义指令
3.2.1 注册单个全局自定义指令
3.2.1.1 编写需要注册的单个全局指令文件
/**
* ./src/directive/sizeDirective.ts
* 注册单个指令
*/
import { Directive, DirectiveBinding } from "vue";
export default <Directive>{
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
el.addEventListener("click", () => {
console.log("添加事件监听成功");
});
if (value) {
el.style.fontSize = value + "px";
}
},
};
3.2.1.2 在main.ts文件中注册单个全局指令
import { createApp, Directive } from "vue";
import sizeDirective from "@/directive/sizeDirective";
const app = createApp(App);
// 注册单个自定义指令
app.directive("size", sizeDirective as Directive);
3.2.1.3 在模版中使用全局指令
<template>
<div>
<div v-size="100">注册单个全局自定义指令</div>
</div>
</div>
</template>
<style lang="less" scoped>
.box {
width: 600px;
height: 300px;
border: 3px dashed #7643d4;
margin: 0 auto;
}
</style>
3.2.2 注册多个全局自定义指令
3.2.2.1 其他情景支撑
①进度条安装
npm i nprogress
npm i --save-dev @types/nprogress
3.2.2.2 编写需要注册的全局指令文件
①颜色指令
/**
* ./src/directive/modules/colorDirective.ts
* @description 权限指令
*/
import { Directive, DirectiveBinding } from "vue";
export default <Directive>{
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
el.style.color = value;
},
};
②按钮权限指令
/**
* ./src/directive/modules/permission.ts
* @description 权限指令
*/
import { Directive, DirectiveBinding } from "vue";
import useUserStore from "@/store/modules/user";
export const hasPer: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
const all_permission = "*:*:*";
const store = useUserStore();
const permissions = store.permissions;
if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value;
const hasPermissions = permissions.some((permission: string) => {
return all_permission === permission || permissionFlag.includes(permission);
});
if (!hasPermissions) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(`请设置操作权限标签值`);
}
},
};
3.2.2.3 公共状态库获取用户信息
import { defineStore } from "pinia";
import { parse, stringify } from "zipson";
import { getInfo } from "@/api/getList";
const useUserStore = defineStore("user", {
state: (): { info: object; roles: Array<string>; permissions: Array<string> } => ({
info: {
name: "user",
age: 18,
sex: "男",
address: "中国",
},
roles: [],
permissions: [],
}),
actions: {
setUserInfo(userInfo: any) {
this.info = userInfo;
},
getInfo() {
return new Promise((resolve, reject) => {
getInfo({ role: "financial" })
.then((res: any) => {
if (res.data.code === 200) {
const data = res.data.data;
if (data.roles && data.roles.length > 0) {
this.roles = data.roles;
this.permissions = data.permissions;
} else {
this.roles = ["ROLE_DEFAULT"];
}
}
resolve(res);
})
.catch(err => reject(err));
});
},
},
persist: {
key: "user",
storage: sessionStorage,
paths: ["info"],
serializer: {
deserialize: parse,
serialize: stringify,
},
},
});
export default useUserStore;
3.2.2.4调用useUserStore中的getInfo
/**
* ./permission.ts
* @description 路由守卫
*/
import useUserStore from "./store/modules/user";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import router from "./router";
NProgress.configure({ showSpinner: false }); // 显示右上角螺旋加载提示
router.beforeEach((to, from, next) => {
NProgress.start(); //开启进度条
/**
* 路由守卫待完善
* 获取用户信息,若没有则获取用户信息
*/
if (useUserStore().roles.length === 0) {
useUserStore()
.getInfo()
.then((res: any) => {
//这里可以写动态路由的判断,目前待完善
if (res.status === 200) {
NProgress.done();
next();
}
})
// 若在获取用户信息时发生错误,
.catch(async err => {
// 做退出登录操作,并跳转到登录页面
});
} else {
next();
}
});
router.afterEach(() => {
NProgress.done(); //完成进度条
});
3.2.2.5 main.ts注册指令引入路由守卫文件
import "./permission";
import * as directive from "@/directive";
const app = createApp(App);
Object.keys(directive).forEach(key => {
app.directive(key, (directive as { [key: string]: Directive })[key]);
});
3.2.2.6 模板中使用
<template>
<div>
<div>permissionExample</div>
<div class="box">
<div v-size="30">注册单个全局指令</div>
<div>
<p><span v-size="30">多个全局指令(权限按钮)</span></p>
<el-button type="success" v-hasPer="['business:apply:edit']">编辑</el-button>
<el-button plain v-hasPer="['business:apply:delete']">删除</el-button>
<p type="primary" v-colorD="'#409EFF'">无权限内容</p>
</div>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped>
.box {
width: 600px;
height: 300px;
border: 3px dashed #7643d4;
margin: 0 auto;
}
</style>
至此基本案例结束,拓展场景案例后期会再开一篇博。