问题:
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> 直接复制出来,再进行相应的一些小修改调整。