自定义指令的一些实用例子
使用时记得先引入Vue,
import Vue from 'vue'
。
1. el-dialog可拖拽
// directive代码
Vue.directive('el-drag-dialog', {
bind(el, binding, vnode) {
const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog')
dialogHeaderEl.style.cssText += ';cursor:move;'
dragDom.style.cssText += ';top:0px;'
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const getStyle = (function() {
if (window.document.currentStyle) {
return (dom, attr) => dom.currentStyle[attr]
} else {
return (dom, attr) => getComputedStyle(dom, false)[attr]
}
})()
dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft
const disY = e.clientY - dialogHeaderEl.offsetTop
const dragDomWidth = dragDom.offsetWidth
const dragDomHeight = dragDom.offsetHeight
const screenWidth = document.body.clientWidth
const screenHeight = document.body.clientHeight
const minDragDomLeft = dragDom.offsetLeft
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
const minDragDomTop = dragDom.offsetTop
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
// 获取到的值带px 正则匹配替换
let styL = getStyle(dragDom, 'left')
let styT = getStyle(dragDom, 'top')
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
} else {
styL = +styL.replace(/\px/g, '')
styT = +styT.replace(/\px/g, '')
}
document.onmousemove = function(e) {
// 通过事件委托,计算移动的距离
let left = e.clientX - disX
let top = e.clientY - disY
// 边界处理
if (-(left) > minDragDomLeft) {
left = -minDragDomLeft
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft
}
if (-(top) > minDragDomTop) {
top = -minDragDomTop
} else if (top > maxDragDomTop) {
top = maxDragDomTop
}
// 移动当前元素
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
// emit onDrag event
vnode.child.$emit('dragDialog')
}
document.onmouseup = function(e) {
document.onmousemove = null
document.onmouseup = null
}
}
}
})
在vue文件中使用
<el-dialog
v-el-drag-dialog
:visible.sync="dialogTableVisible"
title="Shipping address"
@dragDialog="handleDrag"
>
<h1>可拖拽的dialog</h1>
</el-dialog>
2. v-clipboard复制文本内容
需要使用npm插件clipboard。 首先安装插件npm i clipboard -S
。 然后注册全局directive,记得引入clipboard。最后在vue文件中使用。复制成功的回调可写可不写。
// 全局注册clipboard
const Clipboard = require('clipboard');
Vue.directive('clipboard', {
bind(el, binding) {
if (binding.arg === 'success') {
el._v_clipboard_success = binding.value
} else if (binding.arg === 'error') {
el._v_clipboard_error = binding.value
} else {
const clipboard = new Clipboard(el, {
text() { return binding.value },
action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
})
clipboard.on('success', e => {
const callback = el._v_clipboard_success
callback && callback(e) // eslint-disable-line
})
clipboard.on('error', e => {
const callback = el._v_clipboard_error
callback && callback(e) // eslint-disable-line
})
el._v_clipboard = clipboard
}
},
update(el, binding) {
if (binding.arg === 'success') {
el._v_clipboard_success = binding.value
} else if (binding.arg === 'error') {
el._v_clipboard_error = binding.value
} else {
el._v_clipboard.text = function() { return binding.value }
el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
}
},
unbind(el, binding) {
if (binding.arg === 'success') {
delete el._v_clipboard_success
} else if (binding.arg === 'error') {
delete el._v_clipboard_error
} else {
el._v_clipboard.destroy()
delete el._v_clipboard
}
}
})
在.vue文件中使用
<template>
<div class="app-container">
<el-button
v-clipboard="testVal"
v-clipboard:success="clipboardSuccess"
type="primary"
icon="el-icon-document"
>
复制内容
</el-button>
</div>
</template>
<script>
export default {
data() {
return {
testVal: 'O(∩_∩)O哈哈~'
}
},
methods: {
clipboardSuccess(val) {
this.$message({
message: `内容(${val.text})复制成功`,
type: 'success'
})
}
}
}
</script>
3. el-input关于价格的注册
需求: 价格相关的input。默认居右显示,默认有placeholder请输入。聚焦后在左侧修改。 只允许输入最多2位小数。 输入完后失焦居右显示千分位数字。 难点: 失焦后居右显示千分位数字。 v-model不支持filter, 所以本例子的思路是,新建一个input, 隐藏之前的input。 update钩子里也要写逻辑, 否则报错。
Vue.directive('price', {
bind: function(el, { value = 2 }) {
el = el.nodeName === "INPUT" ? el : el.children[0];
el.placeholder = "请输入";
el.style.textAlign = "right";
var RegStr = value === 0 ? `^[\\+\\-]?\\d+\\d{0,0}` : `^[\\+\\-]?\\d+\\.?\\d{0,${value}}`;
el.addEventListener('keyup', function() {
el.value = el.value.match(new RegExp(RegStr, 'g'));
el.dispatchEvent(new Event('input'))
})
el.addEventListener('focus', function() {
const newAddIpt = el.parentNode.querySelector('.thousand-ipt');
if (newAddIpt) {
el.parentNode.removeChild(newAddIpt);
}
el.style.textAlign = "left";
el.style.opacity = 1;
el.style.position = '';
})
el.addEventListener('blur', function() {
if (el.value === '') {
el.value = '0.00';
} else {
el.value = Number(el.value).toFixed(2);
}
el.style.opacity = 0;
el.style.position = 'absolute';
el.dispatchEvent(new Event('input'))
const newIpt = document.createElement("input"); // 新建input节点
newIpt.value = parseFloat(el.value).toFixed(2).replace(/\d(?=(?:\d{3})+\b)/g, `$&,`)
newIpt.className = 'el-input__inner thousand-ipt';
newIpt.style.textAlign = "right";
el.parentNode.appendChild(newIpt);
})
},
update: function(el) {
if (el.children[1] && el.children[1].value === undefined) {
const newAddIpt = el.parentNode.querySelector('.thousand-ipt');
if (newAddIpt) {
el.removeChild(newAddIpt);
el.children[0].style.textAlign = "left";
el.children[0].style.opacity = 1;
el.children[0].style.position = '';
}
}
}
})
4. el-input自动聚焦
/**
* 自动聚焦
*/
Vue.directive("focus", {
// 获取光标在inserted中操作,此时元素已经插入到父节点了
inserted(el) {
el = el.nodeName === "INPUT" ? el : el.children[0];
el.focus();
},
bind(el) {
el = el.nodeName === "INPUT" ? el : el.children[0];
el.focus();
}
});