vue.js之自定义指令

🔮 欢迎点赞 👍٩( ´︶` )( ´︶` )۶ 收藏 🌟留言 💌 欢迎讨论!💕
🔮 本文由 【第四人称Alice】 原创,首发于 CSDN ✨✨✨
🌍 由于博主还属于前期的前端开发小白,欢迎大家留言提出更好的意见,大家共同进步!💭

声明:博主的项目是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自定义指令-CSDN博客

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

至此基本案例结束,拓展场景案例后期会再开一篇博。

项目参考地址:vite-base-Framework: vite基础配置框架

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值