当我们需要去统一修改项目中对话框的样式的时候,我们可以去修改全局的样式,但是还存在另外一种方式就是去二次封装我们的eldialogl,而为了避免我们在使用自己二次封装的对话框时没有对应的文档,所以我尽量使用官方文档对应的属性,经量做到只需要修改组件名称然后根据官方文档就能使用的效果.
关于 :visible.sync 属性的修改
看了很多篇的文章都是需要 使用 类似与 :centerDialogVisible.sync="visible" 这种需要重新定义一个属性去做开关,然后关闭使用计算属性去触发
computed: {
centerDialogVisible: {
get() {
return this.value;
},
set(val) {
this.$emit("update:value", val);
}
}
},
父组件通过接受这个自定义事件的回调去关闭,这个和官方文档比起来麻烦了不少 难道我们就不能和官方文档一样使用 双向绑定吗?
然后优化后的代码就是利用双向绑定的方法去做开关,你只需要在父组件传递开关对应的字段名就ok了.
<m-dialog
title="提示"
:visble.sync="dialogVisible"
width="400px"
append-to-body
destroy-on-close
>
<Form />
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false"
>确 定</el-button
>
</div>
</m-dialog>
上面就是使用案例 将官方的修改成 v-model,和elementPlus的使用一样了,
子组件如何兼容 父组件的:visble.sync
如图所示,我们的props下的value就对应着 v-model 传递下来的值,使用的话就是如下图
这样我们就将:visible.sync上对应的值给el-dialog 上面的值对应起来了,所以实现很简单
组件属性和官方如何对应
实现方式也很简单,那就是和官方的属性值一样不就ok 了,实现如下:
props: {
title: {
type: String,
default: "标题"
},
visble: {
type: Boolean,
default() {
return false;
}
},
width: {
type: String,
default() {
return "430px";
}
},
top: {
type: String,
default() {
return "15vh";
}
},
closeOnClickModal: {
type: Boolean,
default: true
},
center: {
type: Boolean,
default: false
}
},
那el-dialog 上的效果是啥样的呢 ,如下:
<el-dialog
class="comn_dialog"
:title="title"
:visible.sync="dialogVisible"
:width="width"
:top="top"
:close-on-click-modal="closeOnClickModal"
@close="Cancel"
append-to-body
destroy-on-close
:center="center"
v-if='dialogVisible'
>
<slot>
<p>弹窗内容自定义</p>
</slot>
<div slot="footer">
<slot name="footer" />
</div>
</el-dialog>
完整代码
<template>
<!-- v-dialogDrag -->
<el-dialog
class="comn_dialog"
:title="title"
:visible.sync="dialogVisible"
:width="width"
:top="top"
:close-on-click-modal="closeOnClickModal"
:before-close="beforeClose"
append-to-body
:center="center"
v-if="dialogVisible"
v-dialogDrag="draggable"
>
<slot>
<p>弹窗内容自定义</p>
</slot>
<div slot="footer">
<slot name="footer" />
</div>
</el-dialog>
</template>
<script>
export default {
props: {
title: {
type: String,
default: "标题"
},
visble: {
type: Boolean,
default() {
return false;
}
},
width: {
type: String,
default() {
return "430px";
}
},
top: {
type: String,
default() {
return "15vh";
}
},
closeOnClickModal: {
type: Boolean,
default: false
},
center: {
type: Boolean,
default: false
},
draggable: {
type: Boolean,
default: true
}
},
methods: {
Cancel() {
this.$emit("close");
},
beforeClose(done) {
done();
}
},
computed: {
dialogVisible: {
get() {
return this.visble;
},
set(val) {
// this.$emit("updateVisible", val);
this.$emit("update:visble", val);
}
}
},
created() {},
directives: {
dialogDrag: {
bind(el, binding) {
console.log(131231, el, binding);
// 自定义属性,判断是否可拖拽
if (!binding.value) return;
const dialogHeaderEl = el.querySelector(".el-dialog__header");
const dragDom = el.querySelector(".el-dialog");
dialogHeaderEl.style.cursor = "move";
// 禁止拖拽时选中标题中文本内容
dialogHeaderEl.style.userSelect = "none";
// 获取原有属性
const sty = (function() {
if (document.body.currentStyle) {
// 在 IE 下兼容写法
return (dom, attr) => dom.currentStyle[attr];
}
return (dom, attr) => getComputedStyle(dom, null)[attr];
})();
dialogHeaderEl.onmousedown = e => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
const screenWidth = document.body.clientWidth; // body当前宽度
const screenHeight = document.documentElement.clientHeight; // 可见区域高度
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
const dragDomHeight = dragDom.offsetHeight; // 对话框高度
const minDragDomLeft = dragDom.offsetLeft;
const maxDragDomLeft =
screenWidth - dragDom.offsetLeft - dragDomWidth;
const minDragDomTop = dragDom.offsetTop;
const maxDragDomTop =
screenHeight - dragDom.offsetTop - dragDomHeight;
// 获取到的值带px 正则匹配替换
let styL = sty(dragDom, "left");
if (styL === "auto") styL = "0px";
let styT = sty(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.left = `${left + styL}px`;
dragDom.style.top = `${top + styT}px`;
};
document.onmouseup = function() {
document.onmousemove = null;
document.onmouseup = null;
};
return false; // 防止文本选中
};
}
}
}
};
</script>
<style lang="scss">
.comn_dialog {
.el-dialog__body {
padding: 20px 20px 0px;
}
}
</style>
组件全局定义
在main.js 文件中引入我们的二次疯转组件 如下
import MDialog from "./components/Dialog";
Vue.component("MDialog", MDialog);
组件使用
使用就是 将 前面的 el 变成 m 就ok啦, 快关绑定换成 v-model
<m-dialog
title="提示"
:visible.sync="dialogVisible"
width="400px"
append-to-body
destroy-on-close
></m-dialog>
和官方属性一样就行了, 没有的自己加!
适配对话框拖动
本部分参考了文章 链接https://blog.csdn.net/jiciqiang/article/details/139731640
文章中的使用时全局指令,由于我只在dialog.vue内部进行使用,所以我就定义成布局的啦,详细的流程可以去看上方大佬的详细文档,我就提供下落实后的完整方法:
方案1:(存在很多细节部分处理不是很好)
directives: {
dialogDrag: {
bind: el => {
// 获取弹框标题区域DOM节点
const headerDOM = el.querySelector(".el-dialog__header");
// 修改鼠标图标样式
headerDOM.style.cursor = "move";
// 禁止拖拽时选中标题中文本内容
headerDOM.style.userSelect = "none";
// 获取弹框区域的DOM节点
const dialogDOM = el.querySelector(".el-dialog");
let isDown = false; //是否按下
// 鼠标按下时坐标位置
let clientX = 0;
let clientY = 0;
// 按下时弹框位置
let dialogLeft = 0;
let dialogTop = 0;
// 定义函数判断当前是否在可见范围内
function boundingRange() {
const bounding = dialogDOM.getBoundingClientRect();
return {
top: bounding.top >= 0, // 表示顶部在可见范围
left: bounding.left >= 0, // 表示左侧在可见范围
right: bounding.left < window.innerWidth - bounding.width, // 表示右侧在指定范围
bottom: bounding.top < window.innerHeight - bounding.height // 表示底部在指定范围
};
}
// 更新数据
function update(e) {
// 获取当前鼠标按钮位置坐标
clientX = e.clientX;
clientY = e.clientY;
// 获取弹框位置(默认情况弹框样式left和top可能不存在,当为NaN时初始化为0)
dialogLeft = isNaN(parseFloat(dialogDOM.style.left))
? 0
: parseFloat(dialogDOM.style.left);
dialogTop = isNaN(parseFloat(dialogDOM.style.top))
? 0
: parseFloat(dialogDOM.style.top);
}
// 监听鼠标按下事件
headerDOM.onmousedown = e => {
isDown = true;
update(e);
// 获取当前鼠标按钮位置坐标
clientX = e.clientX;
clientY = e.clientY;
// 获取弹框位置(默认情况弹框样式left和top可能不存在,当为NaN时初始化为0)
dialogLeft = isNaN(parseFloat(dialogDOM.style.left))
? 0
: parseFloat(dialogDOM.style.left);
dialogTop = isNaN(parseFloat(dialogDOM.style.top))
? 0
: parseFloat(dialogDOM.style.top);
};
// 监听鼠标移动事件
dialogDOM.onmousemove = e => {
// 不按下的时候,执行移动操作
if (isDown) {
// 获取DOM边界范围
const range = boundingRange();
// 获取当前移动到的位置坐标,与按下位置坐标进行计算,获取移动距离
const distX = e.clientX - clientX;
const distY = e.clientY - clientY;
// 判断左侧或右侧是否可移动
if ((range.left && distX < 0) || (range.right && distX >= 0))
dialogDOM.style.left = dialogLeft + distX + "px";
// 判断顶部或者底部是否可移动
if ((range.top && distY < 0) || (range.bottom && distY >= 0))
dialogDOM.style.top = dialogTop + distY + "px";
// 更新起始位数据
update(e);
}
};
// 监听鼠标松开事件
headerDOM.onmouseup = e => (isDown = false);
window.onmouseup = () => (isDown = false);
}
}
}
直接放在created methods通级下,然后再el-dialog下进行使用
方案2:(觉得挺好用)
使用方法是 v-drags="true" 第二个参数用于判断是否可拖动
drags: {
bind(el, binding) {
console.log(131231, el, binding);
// 自定义属性,判断是否可拖拽
if (!binding.value) return;
const dialogHeaderEl = el.querySelector(".el-dialog__header");
const dragDom = el.querySelector(".el-dialog");
dialogHeaderEl.style.cursor = "move";
// 禁止拖拽时选中标题中文本内容
dialogHeaderEl.style.userSelect = "none";
// 获取原有属性
const sty = (function() {
if (document.body.currentStyle) {
// 在 IE 下兼容写法
return (dom, attr) => dom.currentStyle[attr];
}
return (dom, attr) => getComputedStyle(dom, null)[attr];
})();
dialogHeaderEl.onmousedown = e => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
const screenWidth = document.body.clientWidth; // body当前宽度
const screenHeight = document.documentElement.clientHeight; // 可见区域高度
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
const dragDomHeight = dragDom.offsetHeight; // 对话框高度
const minDragDomLeft = dragDom.offsetLeft;
const maxDragDomLeft =
screenWidth - dragDom.offsetLeft - dragDomWidth;
const minDragDomTop = dragDom.offsetTop;
const maxDragDomTop =
screenHeight - dragDom.offsetTop - dragDomHeight;
// 获取到的值带px 正则匹配替换
let styL = sty(dragDom, "left");
if (styL === "auto") styL = "0px";
let styT = sty(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.left = `${left + styL}px`;
dragDom.style.top = `${top + styT}px`;
};
document.onmouseup = function() {
document.onmousemove = null;
document.onmouseup = null;
};
return false; // 防止文本选中
};
}
}
vue3 版本的:
const vDrags = {
mounted(el, binding) {
// 自定义属性,判断是否可拖拽
if (!binding.value) return;
const dialogHeaderEl = el.querySelector(".dialog_header");
const dragDom = el.querySelector(".dialog_content");
dialogHeaderEl.style.cssText += ";cursor:move;";
// dragDom.style.cssText += ';bottom:0px;'
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const sty = (function () {
if (document.body.currentStyle) {
// 在ie下兼容写法
return (dom, attr) => dom.currentStyle[attr];
}
return (dom, attr) => getComputedStyle(dom, null)[attr];
})();
dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
const screenWidth = document.body.clientWidth; // body当前宽度
const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
const dragDomheight = dragDom.offsetHeight; // 对话框高度
const minDragDomLeft = dragDom.offsetLeft;
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
const minDragDomTop = dragDom.offsetTop;
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
// 获取到的值带px 正则匹配替换
let styL = sty(dragDom, "left");
// 为兼容ie
if (styL === "auto") styL = "0px";
let styT = sty(dragDom, "top");
// console.log(styL)
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
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;`;
ChangeTop(top + styT);
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
return false;
};
},
};