vue3常用指令以及注册

vue3 常用指令以及注册

简单梳理一些 vue3 常用指令以及用法

vue3 工程化搭建

vue3 工程化搭建,这里用的是 vite 作为构建工具,搭建步骤如下:

导入各个模块路由

router.js 文件

js 复制代码
// 引入各模块路由
let moduleRouters = []
const rc = import.meta.globEager('./modules/*.js');
moduleRouters = Object.keys(rc).reduce((modules, key) => modules.concat(rc[key].default), []);
const routes = [
  ...moduleRouters
];
export default routes;


// modules文件夹
// F下引入各模块路由
const rc = import.meta.globEager('../../modules/*/router/index.js')
const router = Object.keys(rc).reduce((modules, key) => {
	const name = key.replace(/(\..\/\..\/modules\/)|(\/router\/index\.js$)/g, '')
	return modules.concat(rc[key].default.map(i => {
		i.path = `/${name}${i.path}`
		return i
	}))
}, [])

export default router

应用入口文件

main.js 文件

js 复制代码
import WebInit from "./webInit";
import router from "./router"; // 引入路由配置文件,各个模块的路由配置文件
const web = new WebInit({ router });
web.init();

main.js 使用初始化实例

webInit.js 文件

js 复制代码
//引入elementui plus组件
import { resetMessage } from "./msg";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import ElementPlus from "element-plus";

//引入自定义公共组件
import GlobalCpn from "./global/globalCpn";
// 引入自定义mixin方法
import mixins from "./mixins";
// 引入全局工具方法
import GlobalFn from "./tools/fn";
// 全局store
import store from "./store/index";
//全局指令
import directiveList from "./directive";
// 权限控制,路由守卫,判断用户权限,动态添加路由
import "./permission";
//静态路由
import router from "./router";
import "element-plus/dist/index.css";
//引入全局SCSS
import "./styles/index.scss";
//引入icon
import "./assets/iconfonts/iconfont.css";
import "./assets/iconfonts/iconfont.js";
import "./assets/icons/index.js";
//引入字体
import "./assets/font/font.css";
//国际化
import en from "./assets/langs/en.js";
import zh_cn from "./assets/langs/zh-CN.js";
import zhCn from "element-plus/es/locale/lang/zh-cn";
import { createI18n } from "vue-i18n";

import { createApp } from "vue";
import App from "./views/App.vue";
import "virtual:svg-icons-register";

const i18n = new createI18n({
  // 设置默认语言
  locale: "zh_cn", // 语言标识
  // 添加多语言(每一个语言标示对应一个语言文件)
  messages: {
    zh_cn,
    en,
  },
});

export default class WebInit {
  constructor({ router }) {
    this.app = createApp(App);
    this.routes = router;
  }
  async init() {
    this.routes.forEach((i) => router.addRoute(i)); //动态添加路由配置文件中的路由规则到router实例中
    this.app
      .mixin(mixins) //全局mixin注册
      .use(router) //注册路由实例到vue中
      .use(store) //注册store实例到vue中
      .use(ElementPlus, { locale: zhCn })
      .use(GlobalCpn) //全局组件注册
      .use(i18n); //国际化注册
    // 注册element-plus图标组件
    for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
      this.app.component(key, component);
    }
    //注册指令
    for (let j in directiveList) {
      this.app.directive(j, directiveList[j]);
    }
    // 全局方法注册
    this.app.config.globalProperties.$fn = GlobalFn;
    // 挂载到 #app 上
    this.app.mount("#app");
  }
}

全局组件注册文件

全局组件注册文件,自动导入 global 目录下所有 index.vue 文件,并以 g-开头命名注册到 Vue 中,GlobalCpn.js 文件示例如下:

vue 复制代码
<!-- global/Echarts/index.vue -->
<template>
  <div
    :style="props.style ? props.style : { zoom: 1 / zoom }"
    :class="props.style ? '' : 'echart w100 h100'"
    ref="chartDom"
  ></div>
</template>

<script setup name="echarts">
import { debounce } from "./fn";
import {
  ref,
  onMounted,
  onBeforeUnmount,
  watch,
  nextTick,
  onBeforeMount,
} from "vue";
import * as echarts from "echarts";

const props = defineProps({
  style: {
    type: Object,
    default: null,
  },
  option: {
    type: Object,
    default: null,
  },
});

const emit = defineEmits(["imageUrl"]);
const chartDom = ref(); //图表容器dom元素
let myChart = null; //图表实例
const zoom = ref(1); //缩放比例

// 防抖
function debounce(fn, delay) {
  let timer = null;
  return (e) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn(e);
    }, delay);
  };
}
// 节流
function throttle(fn, delay = 100) {
  let flag = false;
  return (e) => {
    if (flag) return;
    fn(e);
    setTimeout(() => {
      flag = true;
    }, delay);
  };
}
//重绘图表函数
const resizeHandler = () => {
  myChart.resize();
};
// 获取echart图表的图片
const getImageUrl = () => {
  let imgSrc = myChart.getDataURL();
  emit("imageUrl", imgSrc);
};
//设置防抖,保证无论拖动窗口大小,只执行一次获取浏览器宽高的方法
const cancalDebounce = debounce(resizeHandler, 300);
//页面成功渲染,开始绘制图表
onMounted(() => {
  //配置为 svg 形式,预防页面缩放而出现模糊问题;图表过于复杂时建议使用 Canvas
  myChart = echarts.init(chartDom.value, null, { renderer: "svg" });
  myChart.setOption(props.option, true);
  window.addEventListener("resize", cancalDebounce);
});
onBeforeMount(() => {
  zoom.value = document.getElementsByTagName("body")[0].style.zoom;
  // console.log(zoom.value, 'zoom');
});
//页面销毁前,销毁事件和实例
onBeforeUnmount(() => {
  window.removeEventListener("resize", cancalDebounce);
  myChart.dispose();
});
//监听图表数据时候变化,重新渲染图表
watch(
  () => props.option,
  () => {
    myChart.setOption(props.option, true);
    myChart.resize();
  },
  { deep: true }
);

defineExpose({
  getImageUrl,
  cancalDebounce,
  resizeHandler,
});
</script>
js 复制代码
const requireContext = import.meta.globEager("./**/index.vue");

const modules = Object.keys(requireContext).reduce((total, i) => {
  const name = i.replace(/(\.\/.)|(\/index\.vue$)|(\/.)|[A-Z]/g, (str) => {
    return /(\/index\.vue$)/.test(str) ? "" : `-${str.slice(-1).toLowerCase()}`;
  });
  total[`g${name}`] = requireContext[i].default;
  return total;
}, {});
export default {
  install(vue) {
    Object.keys(modules).forEach((i) => {
      vue.component(i, modules[i]);
    });
  },
};
// global目录下为全局组件,只注册目录下所有index.vue
// 使用规则: 以./global/preview/index.vue 为例, 用<g-preview/>

指令文件

例如:utils/directives.js,代码示例如下:

js 复制代码
//指令
import { isJSON } from "./fn";
import { resetMessage } from "./msg.js";
import { Drag } from "./drag";

//-------元素里面的input框聚焦------------
const focus = {
  mounted(el) {
    let item;
    if (el.tagName != "INPUT") {
      item = el.querySelector("input");
    } else {
      item = el;
    }
    item.focus();
  },
};

//-----点击除自己本身的元素外的空白处触发事件---------
/**
 * 传入一个函数
 */
const clickoutside = {
  // 初始化指令
  beforeMount(el, binding, vnode) {
    function documentHandler(e) {
      // 这里判断点击的元素是否是本身,是本身,则返回
      if (el.contains(e.target)) {
        return false;
      }
      // 判断指令中是否绑定了函数
      if (typeof binding.value == "function") {
        // 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
        binding.value(e);
      }
    }
    // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
    el.__vueClickOutside__ = documentHandler;
    document.addEventListener("click", documentHandler);
  },
  unmounted(el, binding) {
    // 解除事件监听
    document.removeEventListener("click", el.__vueClickOutside__);
    delete el.__vueClickOutside__;
  },
};

//-----------复制元素------------------
/**
 * 传入要复制的内容
 */
const copy = {
  mounted(el, { value }) {
    el.$value = value;

    // el控件定义 onclick 事件
    el.onclick = () => {
      if (!el.$value) {
        console.log("无复制内容");
        return;
      }
      // 动态创建 textarea 标签
      const textarea = document.createElement("textarea");
      // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
      textarea.readOnly = "readonly";
      textarea.style.position = "absolute";
      textarea.style.left = "-9999px";
      // 将要 copy 的值赋给 textarea 标签的 value 属性
      if (isJSON(el.$value) || typeof el.$value == "object") {
        textarea.value = JSON.stringify(el.$value);
      } else {
        textarea.value = el.$value;
      }
      // 将 textarea 插入到 body 中
      document.body.appendChild(textarea);
      // 选中值并复制
      textarea.select();
      const result = document.execCommand("Copy");
      if (result) {
        resetMessage({
          type: "success",
          message: "已复制",
        });
        console.log("复制成功");
      }
      document.body.removeChild(textarea);
    };
    // 绑定点击事件,就是所谓的一键 copy 啦
    el.addEventListener("click", el.handler);
  },
  // 当传进来的值更新的时候触发
  beforeUpdate(el, { value }) {
    el.$value = value;
  },
  // 指令与元素解绑的时候,移除事件绑定
  unmounted(el) {
    el.removeEventListener("click", el.handler);
  },
};

//-----------将子项变为可拖拽------------------
/**
 * 传入true or false
 */
const drag = {
  mounted(el, { value }, vnode) {
    // 指令绑定到元素时调用
    if (!!value === true) {
      const onDragChange = vnode.props["on:dragChange"] ?? (() => {});
      el.drag = new Drag(el);
      el.drag.ondragend = onDragChange;
    }
  },
  updated(el, { value }, vnode) {
    // 元素更新前调用
    if (!!value === false && el.drag) {
      el.drag?.distroy();
      el.drag = null;
    } else if (!!value === true) {
      const onDragChange = vnode.props["on:dragChange"] ?? (() => {});
      el.drag = new Drag(el);
      el.drag.ondragend = onDragChange;
    }
  },
  beforeUnmount(el) {
    // 元素解绑时调用
    el.drag?.distroy();
    el.drag = null;
  },
};

/**
 * 只能输入正整数
 */
const int = {
  mounted(el) {
    el.addEventListener("keypress", function (e) {
      e = e || window.event;
      let charcode = typeof e.charCode == "number" ? e.charCode : e.keyCode;
      let re = /\d/;
      if (
        !re.test(String.fromCharCode(charcode)) &&
        charcode > 9 &&
        !e.ctrlKey
      ) {
        if (e.preventDefault) {
          e.preventDefault();
        } else {
          e.returnValue = false;
        }
      }
    });
  },
};

/**
 * 只能输入正数(包含小数)
 */
const float = {
  mounted(el) {
    el.addEventListener("keypress", function (e) {
      e = e || window.event;
      let charcode = typeof e.charCode == "number" ? e.charCode : e.keyCode;
      let re = /^[-+]?\d*$/;
      if (charcode == 46) {
        if (el.value.includes(".")) {
          e.preventDefault();
        }
        return;
      } else if (
        !re.test(String.fromCharCode(charcode)) &&
        charcode > 9 &&
        !e.ctrlKey
      ) {
        if (e.preventDefault) {
          e.preventDefault();
        } else {
          e.returnValue = false;
        }
      }
    });
  },
};
const draggable = {
  mounted(el) {
    el.style.pointerEvents = null;
    document.body.style.userSelect = "auto";
    const header = el.querySelector(".header");
    const container = el.querySelector(".container");

    header.style.cssText += ";cursor:move;";
    let isDragging = false; // 是否正在拖拽

    // 鼠标点击事件的回调
    function mouseDown(e) {
      let X = e.clientX - el.offsetLeft;
      let Y = e.clientY - el.offsetTop;
      function mouseMove(e) {
        isDragging = true; // 正在拖拽

        // el.style.pointerEvents = 'none'
        document.body.style.userSelect = "none";
        el.style.left = e.clientX - X + "px";
        // 水平边界判断
        if (el.offsetLeft < 0) el.style.left = 0 + "px";
        if (
          el.offsetLeft >
          document.documentElement.clientWidth - el.offsetWidth
        )
          el.style.left =
            document.documentElement.clientWidth - el.offsetWidth + "px";

        // 垂直边界判断
        el.style.top = e.clientY - Y + "px";
        if (el.offsetTop < 0) el.style.top = 0 + "px";
        if (
          el.offsetTop >
          document.documentElement.clientHeight - el.offsetHeight
        )
          el.style.top =
            document.documentElement.clientHeight - el.offsetHeight + "px";
      }

      // 注意以下是用 document,而不是 el 本身
      // 这是为了防止 el 跟不上鼠标移动的速度从而导致鼠标脱离了 el 但又没有释放

      // 添加鼠标移动事件
      document.addEventListener("mousemove", mouseMove);

      // 添加鼠标松开事件
      document.addEventListener("mouseup", (e) => {
        el.style.pointerEvents = null;
        document.body.style.userSelect = "auto";

        // 使用延时器在鼠标释放后的一小段时间内保持 isDragging 为 true
        setTimeout(() => {
          isDragging = false;
        }, 50);

        document.removeEventListener("mousemove", mouseMove);
      });
    }

    // 添加鼠标按下事件
    // el.addEventListener('mousedown', mouseDown);
    header.addEventListener("mousedown", mouseDown); // 只有头部可拖拽

    el.addEventListener(
      "click",
      (e) => {
        if (isDragging) {
          e.stopImmediatePropagation();
          e.preventDefault();
        }
      },
      true
    );
  },
};

export default {
  focus,
  clickoutside,
  copy,
  drag,
  int,
  float,
  draggable,
};
相关推荐
陈随易16 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
Dream-Y.ocean18 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn19 小时前
性能监控
前端
Highcharts.js19 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn20 小时前
构建工具
前端
FRing0320 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷21 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen1 天前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒1 天前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫1 天前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库