VUE自定义弹窗,使用ant-design-vue图片预览功能后,关闭弹窗时为什么会报错?

我自己写了一个弹窗组件,在一种情况下会报错,但是找不到报错原因。
打开弹窗后,在弹窗中使用ant-design-vue的图片预览功能,然后关闭弹窗时会报错。
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

回到顶部