VUE自定义弹窗,使用ant-design-vue图片预览功能后,关闭弹窗时为什么会报错?
我自己写了一个弹窗组件,在一种情况下会报错,但是找不到报错原因。
打开弹窗后,在弹窗中使用ant-design-vue的图片预览功能,然后关闭弹窗时会报错。
弹窗代码如下:
<!-- 使用方法<m-dialog v-model:visible="_d.visible"
title="产品信息编辑"
width="800px"
:okLoading="_d.loading"
@ok="onSubmit"
@closed="() => resetFields()">
</m-dialog>
-->
<template>
<div v-if="_d.show1" ref="mDialogWrapperRef"
class="m-dialog-wrapper"
:class="{
show: _d.show3,
'is-drag': props.draggable
}"
:style="{
display: _d.show2 ? 'block' : 'none',
zIndex: _d.zIndex
}"
@click="maskClick()">
<div class="m-dialog-modal">
<div class="m-dialog-cell">
<div class="m-dialog-container"
:style="{
width: _d.width,
maxWidth: _d.maxWidth,
transform: `translate(${_d.drag.resultX}px, ${_d.drag.resultY}px)`,
}">
<div class="m-dialog"
ref="mDialogRef"
@click.stop>
<div class="m-dialog-title"
@mousedown="dragDown">
<template v-if="title">{{title}}</template>
<slot v-else name="title"></slot>
<div class="m-dialog-close" @click="handleCancel()">
<svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg>
</div>
</div>
<div class="m-dialog-content" :style="bodyStyle">
<slot></slot>
</div>
<div class="m-dialog-footer">
<button v-if="props.showCancel" class="btn" @click="handleCancel()">{{cancelText}}</button>
<button v-if="props.showOk" :loading="props.okLoading" class="btn" type="primary" @click="handleOk()">{{okText}}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, watch, onBeforeMount, onBeforeUnmount } from 'vue'
const mDialogWrapperRef = ref();
const mDialogRef = ref();
const emit = defineEmits(['update:visible', 'ok', 'cancel', 'closed'])
const props = defineProps({
visible: {
type: Boolean,
default: false
},
width: {
type: String,
default: ''
},
maxWidth: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
footer: {
type: Boolean,
default: true
},
showOk: {
type: Boolean,
default: true
},
showCancel: {
type: Boolean,
default: true
},
okText: {
type: String,
default: '确认修改'
},
cancelText: {
type: String,
default: '暂不修改'
},
// 确认按钮-加载中
okLoading: {
type: Boolean,
default: false
},
// 点击蒙层是否允许关闭
maskClosable: {
type: Boolean,
default: true
},
// 内容样式
bodyStyle: {
type: Object,
default: () => ({})
},
// 层级 - 如果设置了就是固定层级,如果不设置从最多层级1000开始往上加
zIndex: {
type: Number,
default: 0
},
// 是否加到body节点下
appendToBody: {
type: Boolean,
default: true
},
// 当关闭 Dialog 时,销毁其中的元素
destroyOnClose: {
type: Boolean,
default: true
},
// 为 Dialog 启用可拖拽功能
draggable: {
type: Boolean,
default: false
}
})
const _d = reactive({
show1: false, // 控制v-if
show2: false, // 控制display
show3: false, // 控制动画
zIndex: 1000, // 最低层级
width: !props.width && !props.maxWidth ? '400px' : props.width, // 宽度
maxWidth: props.maxWidth, // 最大宽度
// 拖动参数
drag: {
// 是否点下
isDown: false,
// 整个页面你最大宽高
pageWidth: 0,
pageHeight: 0,
// 弹窗宽高
dialogWidth: 0,
dialogHeight: 0,
// 左右最大移动距离
minX: 0,
maxX: 0,
minY: 0,
maxY: 0,
// 移动结果
resultX: 0,
resultY: 0,
// 鼠标点下是历史移动距离
startX: 0,
startY: 0,
// 鼠标点下时相对于page的位置
downX: 0,
downY: 0,
// 鼠标移动位置
moveX: 0,
moveY: 0,
}
})
watch(() => props.visible, (newVal) => {
setVisible();
})
onBeforeMount(() => {
setVisible();
// 绑定鼠标事件
window.addEventListener('mousemove', dragMove);
window.addEventListener('mouseup', dragUp);
})
onBeforeUnmount(() => {
// 如果appendToBody属性为true, 移除掉插入到body上面的弹框组件,别说这一点我都忘了,严谨
let $el = mDialogWrapperRef.value;
if (props.appendToBody && $el && $el.parentNode) {
$el.parentNode.removeChild($el);
}
// 解绑鼠标事件
window.removeEventListener('mousemove', dragMove);
window.removeEventListener('mouseup', dragUp);
})
// 设置显示状态
function setVisible () {
if(props.visible) {
new Promise((resolve, reject) => {
if(!_d.show1) { // 第一次通过v-if添加节点到dom树
setZIndex();
_d.show1 = true;
_d.show2 = true;
setTimeout(() => {
// 判断是否加到body节点下
if(props.appendToBody) {
let lastChild =
document.body.appendChild(mDialogWrapperRef.value);
}
setTimeout(() => {
resolve();
}, 15);
}, 15);
} else { // 关闭后不删除节点,再显示弹窗
_d.show2 = true;
setTimeout(() => {
resolve();
}, 30);
}
}).then(() => {
document.body.style.overflow = "hidden";
_d.show3 = true;
})
} else { // 关闭弹窗
_d.show3 = false; // 关闭时动画效果
setTimeout(() => {
_d.show2 = false; // 设置display:none
if(props.destroyOnClose) {
_d.show1 = false; // 通过v-if销毁节点
}
document.body.style.overflow = "";
emit('closed');
}, 250)
}
}
// 显示的时候设置z-index
function setZIndex() {
let max = 1000;
// 判断是否有自定义的层级
if(props.zIndex) {
max = props.zIndex;
} else {
let aMd = [...document.querySelectorAll('.m-dialog.show')];
aMd.forEach(obj => {
let num = getStyle(obj, 'z-index');
if(max <= num) {
max = num+1;
}
})
}
_d.zIndex = max;
}
// 获取样式
function getStyle (obj, name) {
if(window.getComputedStyle) {
return getComputedStyle(obj, null)[name];
} else {
return obj.currentStyle[name];
}
}
// 确认
function handleOk() {
emit('ok');
}
// 取消
function handleCancel() {
emit('update:visible', false);
emit('cancel');
}
// 蒙层点击
function maskClick () {
if(props.maskClosable) {
emit('update:visible', false);
}
}
// 拖动 ----------------------------------------------------
function dragDown(e) {
if(!props.draggable) {
return;
}
// 是否按下
_d.drag.isDown = true;
// 页面宽高
_d.drag.pageWidth = mDialogWrapperRef.value.clientWidth;
_d.drag.pageHeight = mDialogWrapperRef.value.clientHeight;
// 弹窗宽高
_d.drag.dialogWidth = mDialogRef.value.clientWidth;
_d.drag.dialogHeight = mDialogRef.value.clientHeight;
// 获取弹窗到页面左上角的距离
let dis = getDistanceBody(mDialogRef.value);
// 计算边界
let minX = -dis.left;
let minY = -dis.top;
let maxX = _d.drag.pageWidth - _d.drag.dialogWidth - dis.left;
let maxY = _d.drag.pageHeight - _d.drag.dialogHeight - dis.top;
if(minX > 0) {
minX = 0;
}
if(minY > 0) {
minY = 0;
}
if(maxX > _d.drag.pageWidth - _d.drag.dialogWidth) {
maxX = _d.drag.pageWidth - _d.drag.dialogWidth;
}
if(maxY > _d.drag.pageHeight - _d.drag.dialogHeight) {
maxY = _d.drag.pageHeight - _d.drag.dialogHeight;
}
_d.drag.minX = minX;
_d.drag.maxX = maxX;
_d.drag.minY = minY;
_d.drag.maxY = maxY;
// 开始位置
_d.drag.startX = _d.drag.resultX;
_d.drag.startY = _d.drag.resultY;
// 鼠标点下时相对于page的位置
_d.drag.downX = e.pageX;
_d.drag.downY = e.pageY;
}
function dragMove(e) {
if(!props.draggable) {
return;
}
if(!_d.drag.isDown) {
return;
}
// 移动距离
_d.moveX = e.pageX-_d.drag.downX;
_d.moveY = e.pageY-_d.drag.downY;
// 涉及结果位置
let resultX = _d.drag.startX+_d.moveX;
let resultY = _d.drag.startY+_d.moveY;
if(resultX < _d.drag.minX) {
resultX = _d.drag.minX;
}
if(resultX > _d.drag.maxX) {
resultX = _d.drag.maxX;
}
if(resultY < _d.drag.minY) {
resultY = _d.drag.minY;
}
if(resultY > _d.drag.maxY) {
resultY = _d.drag.maxY;
}
_d.drag.resultX = resultX;
_d.drag.resultY = resultY;
}
// 鼠标抬起
function dragUp(e) {
if(!props.draggable) {
return;
}
if(!_d.drag.isDown) {
return;
}
_d.drag.isDown = false;
}
// 获取节点到body的高度
function getDistanceBody (obj) {
var oBody = document.body;
var oHtml = document.documentElement;
var oParent = null;
var oDistance = {
top: 0,
left: 0
}
do {
oDistance.top += obj.offsetTop;
oDistance.left += obj.offsetLeft;
oParent = obj.offsetParent;
obj = oParent;
} while(oParent && oParent != oBody && oParent != oHtml)
return oDistance;
}
</script>
<style lang="less" scoped>
.m-dialog-wrapper {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0,0,0,0.25);
transition: opacity .25s;
opacity: 0;
overflow: auto;
&.show {
opacity: 1;
.m-dialog {
transform: scale(1);
}
}
&.is-drag {
-moz-user-select:none; /*火狐*/
-webkit-user-select:none; /*webkit浏览器*/
-ms-user-select:none; /*IE10*/
-khtml-user-select:none; /*早期浏览器*/
user-select:none;
.m-dialog-title {
cursor: move;
}
}
.m-dialog-modal {
display: table;
padding: 20px 0 40px;
min-height: 100%;
min-width: 100%;
box-sizing: border-box;
}
.m-dialog-cell {
display: table-cell;
vertical-align: middle;
}
.m-dialog-container {
margin: 0 auto;
}
.m-dialog {
position: relative;
z-index: 2;
border-radius: 2px;
overflow: hidden;
background: #fff;
transition: transform .25s;
transform: scale(0.9);
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
.m-dialog-title {
position: relative;
box-sizing: border-box;
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
color: #000;
font-size: 16px;
line-height: 22px;
word-wrap: break-word;
min-height: 55px;
.m-dialog-close {
position: absolute;
top: 50%;
right: 0;
margin-top: -27px;
width: 54px;
height: 54px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
color: #666;
cursor: pointer;
&:hover {
color: #333;
}
}
}
.m-dialog-content {
padding: 24px;
min-height: 100px;
}
.m-dialog-footer {
padding: 10px 16px;
text-align: right;
background: transparent;
border-top: 1px solid #f0f0f0;
border-radius: 0 0 2px 2px;
.btn {
padding: 4px 15px;
font-size: 14px;
color: #333;
border: 1px solid #d9d9d9;
background: #fff;
height: 32px;
font-weight: 400;
cursor: pointer;
outline: medium;
border-radius: 2px;
&:not(:last-child) {
margin-right: 8px;
}
}
}
}
}
</style>
项目地址:codesandbox上测试项目
如果将配置项(:appendToBody="false")设置为false,不添加到body下,使用是没有问题的。设置为true,添加到body后,使用了预览图片功能,之后就会报错。
希望有人帮忙找出报错原因,及解决办法
回答:
试了一下,这样似乎是可以的。
- src/components/mDialog/index.vue
<!-- 使用方法<m-dialog v-model:visible="_d.visible"
title="产品信息编辑"
width="800px"
:okLoading="_d.loading"
@ok="onSubmit"
@closed="() => resetFields()">
</m-dialog>
-->
<template>
<teleport :disabled="appendToBody">
<div v-if="_d.show1" ref="mDialogWrapperRef"
class="m-dialog-wrapper"
:class="{
show: _d.show3,
'is-drag': props.draggable
}"
:style="{
display: _d.show2 ? 'block' : 'none',
zIndex: _d.zIndex
}"
@click="maskClick()">
<div class="m-dialog-modal">
<div class="m-dialog-cell">
<div class="m-dialog-container"
:style="{
width: _d.width,
maxWidth: _d.maxWidth,
transform: `translate(${_d.drag.resultX}px, ${_d.drag.resultY}px)`,
}">
<div class="m-dialog"
ref="mDialogRef"
@click.stop>
<div class="m-dialog-title"
@mousedown="dragDown">
<template v-if="title">{{title}}</template>
<slot v-else name="title"></slot>
<div class="m-dialog-close" @click="handleCancel()">
<svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg>
</div>
</div>
<div class="m-dialog-content" :style="bodyStyle">
<slot></slot>
</div>
<div class="m-dialog-footer">
<button v-if="props.showCancel" class="btn" @click="handleCancel()">{{cancelText}}</button>
<button v-if="props.showOk" :loading="props.okLoading" class="btn" type="primary" @click="handleOk()">{{okText}}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</teleport>
</template>
<script setup>
import { ref, reactive, watch, onBeforeMount, onBeforeUnmount } from 'vue'
const mDialogWrapperRef = ref();
const mDialogRef = ref();
const emit = defineEmits(['update:visible', 'ok', 'cancel', 'closed'])
const props = defineProps({
visible: {
type: Boolean,
default: false
},
width: {
type: String,
default: ''
},
maxWidth: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
footer: {
type: Boolean,
default: true
},
showOk: {
type: Boolean,
default: true
},
showCancel: {
type: Boolean,
default: true
},
okText: {
type: String,
default: '确认修改'
},
cancelText: {
type: String,
default: '暂不修改'
},
// 确认按钮-加载中
okLoading: {
type: Boolean,
default: false
},
// 点击蒙层是否允许关闭
maskClosable: {
type: Boolean,
default: true
},
// 内容样式
bodyStyle: {
type: Object,
default: () => ({})
},
// 层级 - 如果设置了就是固定层级,如果不设置从最多层级1000开始往上加
zIndex: {
type: Number,
default: 0
},
// 是否加到body节点下
appendToBody: {
type: Boolean,
default: true
},
// 当关闭 Dialog 时,销毁其中的元素
destroyOnClose: {
type: Boolean,
default: true
},
// 为 Dialog 启用可拖拽功能
draggable: {
type: Boolean,
default: false
}
})
const _d = reactive({
show1: false, // 控制v-if
show2: false, // 控制display
show3: false, // 控制动画
zIndex: 1000, // 最低层级
width: !props.width && !props.maxWidth ? '400px' : props.width, // 宽度
maxWidth: props.maxWidth, // 最大宽度
// 拖动参数
drag: {
// 是否点下
isDown: false,
// 整个页面你最大宽高
pageWidth: 0,
pageHeight: 0,
// 弹窗宽高
dialogWidth: 0,
dialogHeight: 0,
// 左右最大移动距离
minX: 0,
maxX: 0,
minY: 0,
maxY: 0,
// 移动结果
resultX: 0,
resultY: 0,
// 鼠标点下是历史移动距离
startX: 0,
startY: 0,
// 鼠标点下时相对于page的位置
downX: 0,
downY: 0,
// 鼠标移动位置
moveX: 0,
moveY: 0,
}
})
watch(() => props.visible, (newVal) => {
setVisible();
})
onBeforeMount(() => {
setVisible();
// 绑定鼠标事件
window.addEventListener('mousemove', dragMove);
window.addEventListener('mouseup', dragUp);
})
onBeforeUnmount(() => {
// 解绑鼠标事件
window.removeEventListener('mousemove', dragMove);
window.removeEventListener('mouseup', dragUp);
})
// 设置显示状态
function setVisible () {
if(props.visible) {
new Promise((resolve, reject) => {
if(!_d.show1) { // 第一次通过v-if添加节点到dom树
setZIndex();
_d.show1 = true;
_d.show2 = true;
setTimeout(() => {
setTimeout(() => {
resolve();
}, 15);
}, 15);
} else { // 关闭后不删除节点,再显示弹窗
_d.show2 = true;
setTimeout(() => {
resolve();
}, 30);
}
}).then(() => {
document.body.style.overflow = "hidden";
_d.show3 = true;
})
} else { // 关闭弹窗
_d.show3 = false; // 关闭时动画效果
setTimeout(() => {
_d.show2 = false; // 设置display:none
if(props.destroyOnClose) {
_d.show1 = false; // 通过v-if销毁节点
}
document.body.style.overflow = "";
emit('closed');
}, 250)
}
}
// 显示的时候设置z-index
function setZIndex() {
let max = 1000;
// 判断是否有自定义的层级
if(props.zIndex) {
max = props.zIndex;
} else {
let aMd = [...document.querySelectorAll('.m-dialog.show')];
aMd.forEach(obj => {
let num = getStyle(obj, 'z-index');
if(max <= num) {
max = num+1;
}
})
}
_d.zIndex = max;
}
// 获取样式
function getStyle (obj, name) {
if(window.getComputedStyle) {
return getComputedStyle(obj, null)[name];
} else {
return obj.currentStyle[name];
}
}
// 确认
function handleOk() {
emit('ok');
}
// 取消
function handleCancel() {
emit('update:visible', false);
emit('cancel');
}
// 蒙层点击
function maskClick () {
if(props.maskClosable) {
emit('update:visible', false);
}
}
// 拖动 ----------------------------------------------------
function dragDown(e) {
if(!props.draggable) {
return;
}
// 是否按下
_d.drag.isDown = true;
// 页面宽高
_d.drag.pageWidth = mDialogWrapperRef.value.clientWidth;
_d.drag.pageHeight = mDialogWrapperRef.value.clientHeight;
// 弹窗宽高
_d.drag.dialogWidth = mDialogRef.value.clientWidth;
_d.drag.dialogHeight = mDialogRef.value.clientHeight;
// 获取弹窗到页面左上角的距离
let dis = getDistanceBody(mDialogRef.value);
// 计算边界
let minX = -dis.left;
let minY = -dis.top;
let maxX = _d.drag.pageWidth - _d.drag.dialogWidth - dis.left;
let maxY = _d.drag.pageHeight - _d.drag.dialogHeight - dis.top;
if(minX > 0) {
minX = 0;
}
if(minY > 0) {
minY = 0;
}
if(maxX > _d.drag.pageWidth - _d.drag.dialogWidth) {
maxX = _d.drag.pageWidth - _d.drag.dialogWidth;
}
if(maxY > _d.drag.pageHeight - _d.drag.dialogHeight) {
maxY = _d.drag.pageHeight - _d.drag.dialogHeight;
}
_d.drag.minX = minX;
_d.drag.maxX = maxX;
_d.drag.minY = minY;
_d.drag.maxY = maxY;
// 开始位置
_d.drag.startX = _d.drag.resultX;
_d.drag.startY = _d.drag.resultY;
// 鼠标点下时相对于page的位置
_d.drag.downX = e.pageX;
_d.drag.downY = e.pageY;
}
function dragMove(e) {
if(!props.draggable) {
return;
}
if(!_d.drag.isDown) {
return;
}
// 移动距离
_d.moveX = e.pageX-_d.drag.downX;
_d.moveY = e.pageY-_d.drag.downY;
// 涉及结果位置
let resultX = _d.drag.startX+_d.moveX;
let resultY = _d.drag.startY+_d.moveY;
if(resultX < _d.drag.minX) {
resultX = _d.drag.minX;
}
if(resultX > _d.drag.maxX) {
resultX = _d.drag.maxX;
}
if(resultY < _d.drag.minY) {
resultY = _d.drag.minY;
}
if(resultY > _d.drag.maxY) {
resultY = _d.drag.maxY;
}
_d.drag.resultX = resultX;
_d.drag.resultY = resultY;
}
// 鼠标抬起
function dragUp(e) {
if(!props.draggable) {
return;
}
if(!_d.drag.isDown) {
return;
}
_d.drag.isDown = false;
}
// 获取节点到body的高度
function getDistanceBody (obj) {
var oBody = document.body;
var oHtml = document.documentElement;
var oParent = null;
var oDistance = {
top: 0,
left: 0
}
do {
oDistance.top += obj.offsetTop;
oDistance.left += obj.offsetLeft;
oParent = obj.offsetParent;
obj = oParent;
} while(oParent && oParent != oBody && oParent != oHtml)
return oDistance;
}
</script>
<style lang="less" scoped>
.m-dialog-wrapper {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0,0,0,0.25);
transition: opacity .25s;
opacity: 0;
overflow: auto;
&.show {
opacity: 1;
.m-dialog {
transform: scale(1);
}
}
&.is-drag {
-moz-user-select:none; /*火狐*/
-webkit-user-select:none; /*webkit浏览器*/
-ms-user-select:none; /*IE10*/
-khtml-user-select:none; /*早期浏览器*/
user-select:none;
.m-dialog-title {
cursor: move;
}
}
.m-dialog-modal {
display: table;
padding: 20px 0 40px;
min-height: 100%;
min-width: 100%;
box-sizing: border-box;
}
.m-dialog-cell {
display: table-cell;
vertical-align: middle;
}
.m-dialog-container {
margin: 0 auto;
}
.m-dialog {
position: relative;
z-index: 2;
border-radius: 2px;
overflow: hidden;
background: #fff;
transition: transform .25s;
transform: scale(0.9);
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
.m-dialog-title {
position: relative;
box-sizing: border-box;
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
color: #000;
font-size: 16px;
line-height: 22px;
word-wrap: break-word;
min-height: 55px;
.m-dialog-close {
position: absolute;
top: 50%;
right: 0;
margin-top: -27px;
width: 54px;
height: 54px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
color: #666;
cursor: pointer;
&:hover {
color: #333;
}
}
}
.m-dialog-content {
padding: 24px;
min-height: 100px;
}
.m-dialog-footer {
padding: 10px 16px;
text-align: right;
background: transparent;
border-top: 1px solid #f0f0f0;
border-radius: 0 0 2px 2px;
.btn {
padding: 4px 15px;
font-size: 14px;
color: #333;
border: 1px solid #d9d9d9;
background: #fff;
height: 32px;
font-weight: 400;
cursor: pointer;
outline: medium;
border-radius: 2px;
&:not(:last-child) {
margin-right: 8px;
}
}
}
}
}
</style>
回答:
问题已根据“唯一丶”大神的方法解决。
更新一波自定义弹窗组件代码
<!-- 使用方法<m-dialog v-model:visible="_d.visible"
title="产品信息编辑"
width="800px"
:okLoading="_d.loading"
@ok="onSubmit"
@closed="() => resetFields()">
</m-dialog>
-->
<template>
<teleport to="body" :disabled="!props.appendToBody">
<div v-if="_d.show1" ref="mDialogWrapperRef"
class="m-dialog-wrapper"
:class="{
show: _d.show3,
'is-drag': props.draggable
}"
:style="{
display: _d.show2 ? 'block' : 'none',
zIndex: _d.zIndex
}"
@click="maskClick()">
<div class="m-dialog-modal">
<div class="m-dialog-cell">
<div class="m-dialog-container"
:style="{
width: _d.width,
maxWidth: _d.maxWidth,
transform: `translate(${_d.drag.resultX}px, ${_d.drag.resultY}px)`,
}">
<div class="m-dialog"
ref="mDialogRef"
@click.stop>
<div class="m-dialog-title"
@mousedown="dragDown">
<template v-if="title">{{title}}</template>
<slot v-else name="title"></slot>
<div class="m-dialog-close" @click="handleCancel()">
<svg focusable="false" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg>
</div>
</div>
<div class="m-dialog-content" :style="bodyStyle">
<slot></slot>
</div>
<div class="m-dialog-footer">
<button v-if="props.showCancel" class="btn" @click="handleCancel()">{{cancelText}}</button>
<button v-if="props.showOk" :loading="props.okLoading" class="btn" type="primary" @click="handleOk()">{{okText}}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</teleport>
</template>
<script setup>
import { ref, reactive, watch, onBeforeMount, onBeforeUnmount } from 'vue'
const mDialogWrapperRef = ref();
const mDialogRef = ref();
const emit = defineEmits(['update:visible', 'ok', 'cancel', 'closed'])
const props = defineProps({
visible: {
type: Boolean,
default: false
},
width: {
type: String,
default: ''
},
maxWidth: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
footer: {
type: Boolean,
default: true
},
showOk: {
type: Boolean,
default: true
},
showCancel: {
type: Boolean,
default: true
},
okText: {
type: String,
default: '确认修改'
},
cancelText: {
type: String,
default: '暂不修改'
},
// 确认按钮-加载中
okLoading: {
type: Boolean,
default: false
},
// 点击蒙层是否允许关闭
maskClosable: {
type: Boolean,
default: true
},
// 内容样式
bodyStyle: {
type: Object,
default: () => ({})
},
// 层级 - 如果设置了就是固定层级,如果不设置从最多层级1000开始往上加
zIndex: {
type: Number,
default: 0
},
// 是否加到body节点下
appendToBody: {
type: Boolean,
default: true
},
// 当关闭 Dialog 时,销毁其中的元素
destroyOnClose: {
type: Boolean,
default: true
},
// 为 Dialog 启用可拖拽功能
draggable: {
type: Boolean,
default: false
}
})
const _d = reactive({
show1: false, // 控制v-if
show2: false, // 控制display
show3: false, // 控制动画
zIndex: 1000, // 最低层级
width: !props.width && !props.maxWidth ? '400px' : props.width, // 宽度
maxWidth: props.maxWidth, // 最大宽度
// 拖动参数
drag: {
// 是否点下
isDown: false,
// 整个页面你最大宽高
pageWidth: 0,
pageHeight: 0,
// 弹窗宽高
dialogWidth: 0,
dialogHeight: 0,
// 左右最大移动距离
minX: 0,
maxX: 0,
minY: 0,
maxY: 0,
// 移动结果
resultX: 0,
resultY: 0,
// 鼠标点下是历史移动距离
startX: 0,
startY: 0,
// 鼠标点下时相对于page的位置
downX: 0,
downY: 0,
// 鼠标移动位置
moveX: 0,
moveY: 0,
}
})
watch(() => props.visible, (newVal) => {
setVisible();
})
onBeforeMount(() => {
setVisible();
// 绑定鼠标事件
window.addEventListener('mousemove', dragMove);
window.addEventListener('mouseup', dragUp);
})
onBeforeUnmount(() => {
// 如果appendToBody属性为true, 移除掉插入到body上面的弹框组件
let $el = mDialogWrapperRef.value;
if (props.appendToBody && $el && $el.parentNode) {
$el.parentNode.removeChild($el);
}
// 解绑鼠标事件
window.removeEventListener('mousemove', dragMove);
window.removeEventListener('mouseup', dragUp);
})
// 设置显示状态
function setVisible () {
if(props.visible) {
new Promise((resolve, reject) => {
if(!_d.show1) { // 第一次通过v-if添加节点到dom树
setZIndex();
_d.show1 = true;
_d.show2 = true;
setTimeout(() => {
resolve();
}, 30);
} else { // 关闭后不删除节点,再显示弹窗
_d.show2 = true;
setTimeout(() => {
resolve();
}, 30);
}
}).then(() => {
document.body.style.overflow = "hidden";
_d.show3 = true;
})
} else { // 关闭弹窗
_d.show3 = false; // 关闭时动画效果
setTimeout(() => {
_d.show2 = false; // 设置display:none
if(props.destroyOnClose) {
_d.show1 = false; // 通过v-if销毁节点
}
document.body.style.overflow = "";
emit('closed');
}, 250)
}
}
// 显示的时候设置z-index
function setZIndex() {
let max = 1000;
// 判断是否有自定义的层级
if(props.zIndex) {
max = props.zIndex;
} else {
let aMd = [...document.querySelectorAll('.m-dialog.show')];
aMd.forEach(obj => {
let num = getStyle(obj, 'z-index');
if(max <= num) {
max = num+1;
}
})
}
_d.zIndex = max;
}
// 获取样式
function getStyle (obj, name) {
if(window.getComputedStyle) {
return getComputedStyle(obj, null)[name];
} else {
return obj.currentStyle[name];
}
}
// 确认
function handleOk() {
emit('ok');
}
// 取消
function handleCancel() {
emit('update:visible', false);
emit('cancel');
}
// 蒙层点击
function maskClick () {
if(props.maskClosable) {
emit('update:visible', false);
}
}
// 拖动 ----------------------------------------------------
function dragDown(e) {
if(!props.draggable) {
return;
}
// 是否按下
_d.drag.isDown = true;
// 页面宽高
_d.drag.pageWidth = mDialogWrapperRef.value.clientWidth;
_d.drag.pageHeight = mDialogWrapperRef.value.clientHeight;
// 弹窗宽高
_d.drag.dialogWidth = mDialogRef.value.clientWidth;
_d.drag.dialogHeight = mDialogRef.value.clientHeight;
// 获取弹窗到页面左上角的距离
let dis = getDistanceBody(mDialogRef.value);
// 计算边界
let minX = -dis.left;
let minY = -dis.top;
let maxX = _d.drag.pageWidth - _d.drag.dialogWidth - dis.left;
let maxY = _d.drag.pageHeight - _d.drag.dialogHeight - dis.top;
if(minX > 0) {
minX = 0;
}
if(minY > 0) {
minY = 0;
}
if(maxX > _d.drag.pageWidth - _d.drag.dialogWidth) {
maxX = _d.drag.pageWidth - _d.drag.dialogWidth;
}
if(maxY > _d.drag.pageHeight - _d.drag.dialogHeight) {
maxY = _d.drag.pageHeight - _d.drag.dialogHeight;
}
_d.drag.minX = minX;
_d.drag.maxX = maxX;
_d.drag.minY = minY;
_d.drag.maxY = maxY-1;// 完成相等会出现滚动条
// 开始位置
_d.drag.startX = _d.drag.resultX;
_d.drag.startY = _d.drag.resultY;
// 鼠标点下时相对于page的位置
_d.drag.downX = e.pageX;
_d.drag.downY = e.pageY;
}
function dragMove(e) {
if(!props.draggable) {
return;
}
if(!_d.drag.isDown) {
return;
}
// 移动距离
_d.moveX = e.pageX-_d.drag.downX;
_d.moveY = e.pageY-_d.drag.downY;
// 涉及结果位置
let resultX = _d.drag.startX+_d.moveX;
let resultY = _d.drag.startY+_d.moveY;
if(resultX < _d.drag.minX) {
resultX = _d.drag.minX;
}
if(resultX > _d.drag.maxX) {
resultX = _d.drag.maxX;
}
if(resultY < _d.drag.minY) {
resultY = _d.drag.minY;
}
if(resultY > _d.drag.maxY) {
resultY = _d.drag.maxY;
}
_d.drag.resultX = resultX;
_d.drag.resultY = resultY;
}
// 鼠标抬起
function dragUp(e) {
if(!props.draggable) {
return;
}
if(!_d.drag.isDown) {
return;
}
_d.drag.isDown = false;
}
// 获取节点到body的高度
function getDistanceBody (obj) {
var oBody = document.body;
var oHtml = document.documentElement;
var oParent = null;
var oDistance = {
top: 0,
left: 0
}
do {
oDistance.top += obj.offsetTop;
oDistance.left += obj.offsetLeft;
oParent = obj.offsetParent;
obj = oParent;
} while(oParent && oParent != oBody && oParent != oHtml)
return oDistance;
}
</script>
<style lang="less" scoped>
.m-dialog-wrapper {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0,0,0,0.25);
transition: opacity .25s;
opacity: 0;
overflow: auto;
&.show {
opacity: 1;
.m-dialog {
transform: scale(1);
}
}
&.is-drag {
-moz-user-select:none; /*火狐*/
-webkit-user-select:none; /*webkit浏览器*/
-ms-user-select:none; /*IE10*/
-khtml-user-select:none; /*早期浏览器*/
user-select:none;
.m-dialog-title {
cursor: move;
}
}
.m-dialog-modal {
display: table;
padding: 20px 0 40px;
min-height: 100%;
min-width: 100%;
box-sizing: border-box;
}
.m-dialog-cell {
display: table-cell;
vertical-align: middle;
}
.m-dialog-container {
margin: 0 auto;
}
.m-dialog {
position: relative;
z-index: 2;
border-radius: 2px;
overflow: hidden;
background: #fff;
transition: transform .25s;
transform: scale(0.9);
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
.m-dialog-title {
position: relative;
box-sizing: border-box;
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
color: #000;
font-size: 16px;
line-height: 22px;
word-wrap: break-word;
min-height: 55px;
.m-dialog-close {
position: absolute;
top: 50%;
right: 0;
margin-top: -27px;
width: 54px;
height: 54px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
color: #666;
cursor: pointer;
&:hover {
color: #333;
}
}
}
.m-dialog-content {
padding: 24px;
min-height: 100px;
}
.m-dialog-footer {
padding: 10px 16px;
text-align: right;
background: transparent;
border-top: 1px solid #f0f0f0;
border-radius: 0 0 2px 2px;
.btn {
padding: 4px 15px;
font-size: 14px;
color: #333;
border: 1px solid #d9d9d9;
background: #fff;
height: 32px;
font-weight: 400;
cursor: pointer;
outline: medium;
border-radius: 2px;
&:not(:last-child) {
margin-right: 8px;
}
}
}
}
}
</style>
以上是 VUE自定义弹窗,使用ant-design-vue图片预览功能后,关闭弹窗时为什么会报错? 的全部内容, 来源链接: utcz.com/p/935310.html