当前位置: 首页 > 知识库问答 >
问题:

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

濮献
2024-01-11

我自己写了一个弹窗组件,在一种情况下会报错,但是找不到报错原因。
打开弹窗后,在弹窗中使用ant-design-vue的图片预览功能,然后关闭弹窗时会报错。
image.png
弹窗代码如下:

<!-- 使用方法<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后,使用了预览图片功能,之后就会报错。
希望有人帮忙找出报错原因,及解决办法

共有2个答案

贺善
2024-01-11

问题已根据“唯一丶”大神的方法解决。
更新一波自定义弹窗组件代码

<!-- 使用方法<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>
公冶浩慨
2024-01-11

试了一下,这样似乎是可以的。

  • 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>
 类似资料:
  • 我为了避免重复设置主题直接套在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:挂载组件、暴露方法 知识点 在此之