关于 Element-ui Dialog 引用时,在弹窗上 mousedown 后再在遮罩层 mouseup 引发的意外弹窗关闭问题的处理方案

问题:

Element-UI 作为当前较为成熟的 Vue 框架的 UI 组件,能有效的提高特异性不强的项目的界面开发。但是,作为一个要兼备处理多方面问题的第三方库,Element-UI 也存在着一些问题。比如 <el-dialog>。

在引用 <el-dialog> 时,尤其是将其作为编辑弹窗时,常常会有一个问题,便是在使用鼠标滑动选取弹窗中做文本选择时,若不注意将光标滑到弹窗窗体之外的遮罩层处,此时松开鼠标,就会导致弹窗被关闭掉的现象,十分影响使用体验。

当然,这种情况也有一个折衷的方案,那就是通过官方开放的接口 close-on-click-modal 设置为 false。但是,这显然并不完全符合我们的目的,我们要的是保留点击遮罩层的时候,能够关闭弹窗,同时的,当我们在使用鼠标选取弹窗内文本时,即便光标不慎滑到谈窗外也不会触发弹窗的关闭。

查找所得相关方案:

[vue][element-ui]mousedown在Dialog上 mouseup在遮罩上时自动关闭弹窗的问题总结——丹哥不是哥

仍存在问题:

按查找所得的方案,及通过修改 element-ui 的库来实现。但是,这种方式存在一个问题,那就是修改后的 element-ui 库需要作为静态资源保存在项目中,并且要提交到团队的代码仓库中,其他成员使用时也必须同步修改后的 element-ui 库。

这样的做法并不符合现下的开发模式和库的引用方式。

最终决定方案:

创建新的组件,将 element-ui 的 <el-dialog> 复制出来后,对其再进行修改,然后作为自己的自定义组件来使用。如下:

<template>
    <transition
            name="dialog-fade"
            @after-enter="afterEnter"
            @after-leave="afterLeave">
        <div
                v-show="visible"
                class="el-dialog__wrapper else close-on-mousedown"
                <!-- 此处,添加了两个类名,作为调试时的识别 -->
                @mousedown.self="handleWrapperClick">
                <!-- 此处将原来组件中的 @click 替换为 @mousedown,如此一来,在遮罩层中 mouseup 时也不会意外触发弹窗的关闭 -->
            <div
                    role="dialog"
                    :key="key"
                    aria-modal="true"
                    :aria-label="title || 'dialog'"
                    :class="['el-dialog', { 'is-fullscreen': fullscreen, 'el-dialog--center': center }, customClass]"
                    ref="dialog"
                    :style="style">
                <div class="el-dialog__header">
                    <slot name="title">
                        <span class="el-dialog__title">{{ title }}</span>
                    </slot>
                    <button
                            type="button"
                            class="el-dialog__headerbtn"
                            aria-label="Close"
                            v-if="showClose"
                            @click="handleClose">
                        <i class="el-dialog__close el-icon el-icon-close"></i>
                    </button>
                </div>
                <div class="el-dialog__body" v-if="rendered"><slot></slot></div>
                <div class="el-dialog__footer" v-if="$slots.footer">
                    <slot name="footer"></slot>
                </div>
            </div>
        </div>
    </transition>
</template>

<script>
    import Popup from 'element-ui/src/utils/popup';
    import Migrating from 'element-ui/src/mixins/migrating';
    import emitter from 'element-ui/src/mixins/emitter';

    export default {
        name: 'ElseDialog',

        mixins: [Popup, emitter, Migrating],

        props: {
            title: {
                type: String,
                default: ''
            },

            modal: {
                type: Boolean,
                default: true
            },

            modalAppendToBody: {
                type: Boolean,
                default: true
            },

            appendToBody: {
                type: Boolean,
                default: false
            },

            lockScroll: {
                type: Boolean,
                default: true
            },

            closeOnClickModal: {
                type: Boolean,
                default: true
            },

            closeOnPressEscape: {
                type: Boolean,
                default: true
            },

            showClose: {
                type: Boolean,
                default: true
            },

            width: String,

            fullscreen: Boolean,

            customClass: {
                type: String,
                default: ''
            },

            top: {
                type: String,
                default: '15vh'
            },
            beforeClose: Function,
            center: {
                type: Boolean,
                default: false
            },

            destroyOnClose: Boolean
        },

        data() {
            return {
                closed: false,
                key: 0
            };
        },

        watch: {
            visible(val) {
                if (val) {
                    this.closed = false;
                    this.$emit('open');
                    this.$el.addEventListener('scroll', this.updatePopper);
                    this.$nextTick(() => {
                        this.$refs.dialog.scrollTop = 0;
                    });
                    if (this.appendToBody) {
                        document.body.appendChild(this.$el);
                    }
                } else {
                    this.$el.removeEventListener('scroll', this.updatePopper);
                    if (!this.closed) this.$emit('close');
                    if (this.destroyOnClose) {
                        this.$nextTick(() => {
                            this.key++;
                        });
                    }
                }
            }
        },

        computed: {
            style() {
                let style = {};
                if (!this.fullscreen) {
                    style.marginTop = this.top;
                    if (this.width) {
                        style.width = this.width;
                    }
                }
                return style;
            }
        },

        methods: {
            getMigratingConfig() {
                return {
                    props: {
                        'size': 'size is removed.'
                    }
                };
            },
            handleWrapperClick() {
                if (!this.closeOnClickModal) return;
                this.handleClose();
            },
            handleClose() {
                if (typeof this.beforeClose === 'function') {
                    this.beforeClose(this.hide);
                } else {
                    this.hide();
                }
            },
            hide(cancel) {
                if (cancel !== false) {
                    this.$emit('update:visible', false);
                    this.$emit('close');
                    this.closed = true;
                }
            },
            updatePopper() {
                this.broadcast('ElSelectDropdown', 'updatePopper');
                this.broadcast('ElDropdownMenu', 'updatePopper');
            },
            afterEnter() {
                this.$emit('opened');
            },
            afterLeave() {
                this.$emit('closed');
            }
        },

        mounted() {
            if (this.visible) {
                this.rendered = true;
                this.open();
                if (this.appendToBody) {
                    document.body.appendChild(this.$el);
                }
            }
        },

        destroyed() {
            // if appendToBody is true, remove DOM node after destroy
            if (this.appendToBody && this.$el && this.$el.parentNode) {
                this.$el.parentNode.removeChild(this.$el);
            }
        }
    };
</script>

以上的组件,事实上就是将 element-ui 的 <el-dialog> 直接复制出来,再进行相应的一些小修改调整。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值