我自己写了一个弹窗组件,在一种情况下会报错,但是找不到报错原因。
打开弹窗后,在弹窗中使用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后,使用了预览图片功能,之后就会报错。
希望有人帮忙找出报错原因,及解决办法
问题已根据“唯一丶”大神的方法解决。
更新一波自定义弹窗组件代码
<!-- 使用方法<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>
试了一下,这样似乎是可以的。
<!-- 使用方法<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>
我为了避免重复设置主题直接套在app上可行吗?是否有更好的方法呢? 我大体看了下,除非修改theme,不然对子元素的操作应当只有一次。但我简单测试了下,这对首次渲染响应似乎有一点影响。
本文向大家介绍vue弹窗组件使用方法,包括了vue弹窗组件使用方法的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了vue弹窗组件的具体代码,供大家参考,具体内容如下 弹窗是一个项目必备的复用利器,所以封装起来,保证项目ui一致,是很有必要的。学了一段时间vue,想想还是用vue写一下吧。用的很小白,但是会写出来了,说明我也有进步一丢丢了。继续加油…. 代码贴图如下,样式比较丑,不要介
dialog组件使用visible这个prop开控制显示,监听visible赋值给visibleMe v-if=“visibleMe”的方式打开关闭弹窗 关闭的时候令visibleMe=false,但是因为visible是prop,不可以在这个组件内更改,所以visible还是true 所以下次调用它的组件再把visible改成true的时候,并不会触发watch 怎么通过只改dialog组件的代
本文向大家介绍vue实现图片上传预览功能,包括了vue实现图片上传预览功能的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了vue实现图片上传预览的具体代码,供大家参考,具体内容如下 效果图 html结构 css样式 关于vue.js组件的教程,请大家点击专题vue.js组件学习教程进行学习。 更多vue学习教程请阅读专题《vue实战教程》 以上就是本文的全部内容,希望对大家的学习有所
本文向大家介绍很棒的vue弹窗组件,包括了很棒的vue弹窗组件的使用技巧和注意事项,需要的朋友参考一下 弹窗是一个项目必备的复用利器,所以封装起来,保证项目ui一致,是很有必要的。学了一段时间vue,想想还是用vue写一下吧。用的很小白,但是会写出来了,说明我也有进步一丢丢了。继续加油…. 代码贴图如下,样式比较丑,不要介意… 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐
本文向大家介绍Vue 实现一个命令式弹窗组件功能,包括了Vue 实现一个命令式弹窗组件功能的使用技巧和注意事项,需要的朋友参考一下 前言 在日常工作中弹窗组件是很常用的组件,但用得多还是别人的,空闲时间就自己来简单实现一个弹窗组件 涉及知识点:extend、$mount、$el 使用方式: 目录结构 index.vue:组件布局、样式、交互逻辑 index.js:挂载组件、暴露方法 知识点 在此之