關於 Element-ui Dialog 引用時,在彈窗上 mousedown 後再在遮罩層 mouseup 引發的意外彈窗關閉問題的處理方案

Azer.Chen發表於2020-11-27

問題:

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> 直接複製出來,再進行相應的一些小修改調整。

相關文章