vue超好用的自定义指令封装

一、指令封装

目录结构:

vue超好用的自定义指令封装_第1张图片

index.ts 统一注册

import { App, Directive } from "vue";
import auth from "./modules/auth";
import copy from "./modules/copy";
import waterMarker from "./modules/waterMarker";
import draggable from "./modules/draggable";
import debounce from "./modules/debounce";
import throttle from "./modules/throttle";
import longpress from "./modules/longpress";

const directivesList: { [key: string]: Directive } = {
  auth,
  copy,
  waterMarker,
  draggable,
  debounce,
  throttle,
  longpress
};

const directives = {
  install: function (app: App) {
    Object.keys(directivesList).forEach(key => {
      app.directive(key, directivesList[key]);
    });
  }
};

export default directives;

记得use

二、自定义指令

1.防抖 v-debounce

/**
 * v-debounce
 * 按钮防抖指令,可自行扩展至input
 * 接收参数:function类型
 */
import type { Directive, DirectiveBinding } from "vue";
interface ElType extends HTMLElement {
  __handleClick__: () => any;
}
const debounce: Directive = {
  mounted(el: ElType, binding: DirectiveBinding) {
    if (typeof binding.value !== "function") {
      throw "callback must be a function";
    }
    let timer: NodeJS.Timeout | null = null;
    el.__handleClick__ = function () {
      if (timer) {
        clearInterval(timer);
      }
      timer = setTimeout(() => {
        binding.value();
      }, 500);
    };
    el.addEventListener("click", el.__handleClick__);
  },
  beforeUnmount(el: ElType) {
    el.removeEventListener("click", el.__handleClick__);
  }
};

export default debounce;

2.节流 v-throttle

/*
  需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。

  思路:
    1、第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮
    2、将需要触发的方法绑定在指令上
  
  使用:给 Dom 加上 v-throttle 及回调函数即可
  
*/
import type { Directive, DirectiveBinding } from "vue";
interface ElType extends HTMLElement {
  __handleClick__: () => any;
  disabled: boolean;
}
const throttle: Directive = {
  mounted(el: ElType, binding: DirectiveBinding) {
    if (typeof binding.value !== "function") {
      throw "callback must be a function";
    }
    let timer: NodeJS.Timeout | null = null;
    el.__handleClick__ = function () {
      if (timer) {
        clearTimeout(timer);
      }
      if (!el.disabled) {
        el.disabled = true;
        binding.value();
        timer = setTimeout(() => {
          el.disabled = false;
        }, 1000);
      }
    };
    el.addEventListener("click", el.__handleClick__);
  },
  beforeUnmount(el: ElType) {
    el.removeEventListener("click", el.__handleClick__);
  }
};

export default throttle;

3.复制 v-copy

/**
 * v-copy
 * 复制某个值至剪贴板
 * 接收参数:string类型/Ref类型/Reactive类型
 */
import type { Directive, DirectiveBinding } from "vue";
import { ElMessage } from "element-plus";
interface ElType extends HTMLElement {
  copyData: string | number;
  __handleClick__: any;
}
const copy: Directive = {
  mounted(el: ElType, binding: DirectiveBinding) {
    el.copyData = binding.value;
    el.addEventListener("click", handleClick);
  },
  updated(el: ElType, binding: DirectiveBinding) {
    el.copyData = binding.value;
  },
  beforeUnmount(el: ElType) {
    el.removeEventListener("click", el.__handleClick__);
  }
};

function handleClick(this: any) {
  const input = document.createElement("input");
  input.value = this.copyData.toLocaleString();
  document.body.appendChild(input);
  input.select();
  document.execCommand("Copy");
  document.body.removeChild(input);
  ElMessage({
    type: "success",
    message: "复制成功"
  });
}

export default copy;

4.长按 v-longpress

/**
 * v-longpress
 * 长按指令,长按时触发事件
 */
import type { Directive, DirectiveBinding } from "vue";

const directive: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    if (typeof binding.value !== "function") {
      throw "callback must be a function";
    }
    // 定义变量
    let pressTimer: any = null;
    // 创建计时器( 2秒后执行函数 )
    const start = (e: any) => {
      if (e.button) {
        if (e.type === "click" && e.button !== 0) {
          return;
        }
      }
      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          handler(e);
        }, 1000);
      }
    };
    // 取消计时器
    const cancel = () => {
      if (pressTimer !== null) {
        clearTimeout(pressTimer);
        pressTimer = null;
      }
    };
    // 运行函数
    const handler = (e: MouseEvent | TouchEvent) => {
      binding.value(e);
    };
    // 添加事件监听器
    el.addEventListener("mousedown", start);
    el.addEventListener("touchstart", start);
    // 取消计时器
    el.addEventListener("click", cancel);
    el.addEventListener("mouseout", cancel);
    el.addEventListener("touchend", cancel);
    el.addEventListener("touchcancel", cancel);
  }
};

export default directive;

5.拖拽 v-draggable

/*
	需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。

	思路:
		1、设置需要拖拽的元素为absolute,其父元素为relative。
		2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
		3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
		4、鼠标松开(onmouseup)时完成一次拖拽

	使用:在 Dom 上加上 v-draggable 即可
	
*/ import type { Directive } from "vue"; interface ElType extends HTMLElement { parentNode: any; } const draggable: Directive = { mounted: function (el: ElType) { el.style.cursor = "move"; el.style.position = "absolute"; el.onmousedown = function (e) { let disX = e.pageX - el.offsetLeft; let disY = e.pageY - el.offsetTop; document.onmousemove = function (e) { let x = e.pageX - disX; let y = e.pageY - disY; let maxX = el.parentNode.offsetWidth - el.offsetWidth; let maxY = el.parentNode.offsetHeight - el.offsetHeight; if (x < 0) { x = 0; } else if (x > maxX) { x = maxX; } if (y < 0) { y = 0; } else if (y > maxY) { y = maxY; } el.style.left = x + "px"; el.style.top = y + "px"; }; document.onmouseup = function () { document.onmousemove = document.onmouseup = null; }; }; } }; export default draggable;

6.水印 v-waterMarker

/*
  需求:给整个页面添加背景水印。

  思路:
    1、使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
    2、将其设置为背景图片,从而实现页面或组件水印效果
  
  使用:设置水印文案,颜色,字体大小即可
  
*/ import type { Directive, DirectiveBinding } from "vue"; const addWaterMarker: Directive = (str: string, parentNode: any, font: any, textColor: string) => { // 水印文字,父元素,字体,文字颜色 let can: HTMLCanvasElement = document.createElement("canvas"); parentNode.appendChild(can); can.width = 205; can.height = 140; can.style.display = "none"; let cans = can.getContext("2d") as CanvasRenderingContext2D; cans.rotate((-20 * Math.PI) / 180); cans.font = font || "16px Microsoft JhengHei"; cans.fillStyle = textColor || "rgba(180, 180, 180, 0.3)"; cans.textAlign = "left"; cans.textBaseline = "Middle" as CanvasTextBaseline; cans.fillText(str, can.width / 10, can.height / 2); parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")"; }; const waterMarker = { mounted(el: DirectiveBinding, binding: DirectiveBinding) { addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor); } }; export default waterMarker;

7.按钮权限 v-auth

/**
 * v-auth
 * 按钮权限指令 (根据需求而定)
 */
import { useAuthStore } from "@/stores/modules/auth";
import type { Directive, DirectiveBinding } from "vue";

const auth: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const { value } = binding;
    const authStore = useAuthStore();
    const currentPageRoles = authStore.authButtonListGet[authStore.routeName] ?? [];
    if (value instanceof Array && value.length) {
      const hasPermission = value.every(item => currentPageRoles.includes(item));
      if (!hasPermission) el.remove();
    } else {
      if (!currentPageRoles.includes(value)) el.remove();
    }
  }
};

export default auth;

8.旋转 v-rotate

// 自定义指令,点击旋转 v-rotate
const rotate = {
  beforeMount(el: any) {
    el.addEventListener("click", function () {
      console.log(el.style.transform);

      el.style.transition = "all 0.3s";
      if (el.style.transform) {
        let str = el.style.transform;
        let deg = str.substring(str.indexOf("(") + 1, str.indexOf("d"));
        el.style.transform = `rotate(${Number(deg) + 180}deg)`;
      } else {
        el.style.transform = "rotate(180deg)";
      }
    });
  }
};

export default rotate;

你可能感兴趣的:(vue.js,javascript,前端)