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,
};
相关推荐
helloweilei7 小时前
Vue 3 中 <script setup>顶层 await与 <Suspense>的结合使用
vue.js
AeahKa7 小时前
ztree 依赖问题解决记录
前端·webpack
子兮曰7 小时前
AI Coding 为什么全选了 TUI?从 Claude Code 到 Codex CLI,终端架构的底层逻辑
前端·后端·ai编程
ji_shuke7 小时前
前端请求/authapi/auth/permissions 实际发给后端 /api/auth/permissions 本地和线上配置
运维·前端·nginx
可乐泡枸杞7 小时前
《我用AI,一个月做出学了吗APP》
前端·人工智能·后端
韭菜炒大葱7 小时前
详解:useMemo 和useCallback
前端·react.js·面试
ZC跨境爬虫8 小时前
跟着 MDN 学 HTML day_62:(HTML调试与常见错误修复指南)
java·前端·javascript·ui·html·媒体
REDcker8 小时前
Playwright详解 Web自动化与E2E测试 架构原理与实战入门
前端·架构·自动化
用户86022504674728 小时前
从入门到进阶的 React Native 实战指南
android·前端