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,
};