3

本文主要讲下如何修改ElementUI的Dialog和MessageBox的默认属性。主要包括以下几个方面:

  1. Dialog和MessageBox简介;
  2. 修改单次调用的默认属性;
  3. 全局修改默认属性:
    3.1 Dialog全局修改默认属性;
    3.2 MessageBox全局修改默认属性。

以下例子以修改close-on-click-modal属性为例进行讲解。这个属性的默认值是true,也就是点击蒙层的时候弹框关闭。在实际项目中,我们一般不想要这个默认行为,所以拿这个属性举例。

Dialog和MessageBox简介

Dialog是一个组件Component
MessageBox这个名字虽然咋一看也像是组件名,但是却是一个方法名
我试着猜测了下MessageBox没有做成组件的原因。根据官网的介绍:

A set of modal boxes simulating system message box, mainly for alerting information, confirm operations and prompting messages.

MessageBox主要是为了模仿系统原生的弹框,而系统原生弹框的API是window.alertwindow.confirmwindow.prompt,这些都是方法。所以,可能是为了和原生API保持一致的使用方式,所以把MessageBox做成了对应的方法:MessageBox.alert,MessageBox.confirm和MessageBox.prompt。此外,还有一个基础的MessageBox方法也可以调用。这四个方法也挂载到了Vue原型上,分别是$alert,$confirm,$prompt和$msgbox:

原生APIMessageBox方法名Vue实例方法名
-MessageBox$msgbox
window.alertMessageBox.alert$alert
window.confirmMessageBox.confirm$confirm
window.promptMessageBox.prompt$prompt

正因为Dialog和MessageBox本质是不一样的,所以修改默认属性的方式也就不一样了,下面会针对这两种类型分别进行说明。

修改单次调用的默认属性

修改单次调用的默认属性就比较简单了,Dialog组件通过属性的方式修改:close-on-click-modal

<el-dialog
  ...
  :close-on-click-modal="false">
  ...
</el-dialog>

MessageBox通过传参的方式修改:

// $alert, $confirm, $prompt方法与下面类似
this.$msgbox({
  ...
  closeOnClickModal: false
})

全局修改默认属性

在这里,首先说下我解决这个问题的思路:

  1. 直接搜索,大部分问题都能搜到解决方案;
  2. 搜不到或者别人的方法不适用时,查看源码。

我有一个习惯,如果开发时间还算宽裕的话,即使搜到了现成的解决方案,我还是会对照源码探索下为什么这个方案是可行的。下面的解决方案都对照源码验证过,所以就直接对照源码讲解决方案以及对应源码的位置。

本文涉及到的源码以v2.15.1版本为例。

Dialog全局修改默认属性

Dialog组件源码位于/packages/dialog文件夹下面github地址
查看/packages/dialog/component.vue文件会发现closeOnClickModal是一个props:

props: {
  // ...
  closeOnClickModal: {
    type: Boolean,
    default: true // 默认值是true
  },
  // ...
}

全局修改方式如下:

import Element from 'element-ui'
Element.Dialog.props.closeOnClickModal.default = false

MessageBox全局修改默认属性

首先看下/src/index.js文件中和MessageBox相关的代码:

// ...
import MessageBox from '../packages/message-box/index.js';
// ...
const install = function(Vue, opts = {}) {
  // ...
  Vue.prototype.$msgbox = MessageBox; // 对应于上面表格中的对应关系:MessageBox => $msgbox
  Vue.prototype.$alert = MessageBox.alert; // 对应于上面表格中的对应关系:MessageBox.alert => $alert
  Vue.prototype.$confirm = MessageBox.confirm; // 对应于上面表格中的对应关系:Message.confirm => $confirm
  Vue.prototype.$prompt = MessageBox.prompt; // 对应于上面表格中的对应关系:MessageBox.prompt => $prompt
  // ...
}
export default {
  // ...
  MessageBox,
  // ...
}

接下来看下MessageBox的定义文件/packages/message-box/src/main.js,MessageBox接受两个参数:optionscallback,字面含义应该就是配置对象和回调函数:

const MessageBox = function(options, callback) {
  // ...
};

我使用了$msgbox,所以我的第一思路是修改$msgbox方法,调用MessageBox的时候添加自定义参数:

import Element from 'element-ui' 
import Vue from 'vue'

Vue.prototype.$msgbox = function (options, ...args) {
  options = Object.assign({
    closeOnClickModal: false
  }, options)
  Element.MessageBox(options, ...args)
}

测试了下,没有问题,但是仔细看了下源码,发现options并不一定是对象,也可以是字符串:

const MessageBox = function(options, callback) {
  if (Vue.prototype.$isServer) return;
  if (typeof options === 'string' || isVNode(options)) { // 第一个参数是字符串的时候,当作message
    options = {
      message: options
    };
    if (typeof arguments[1] === 'string') { // 第二个参数是字符串的时候,当作title
      options.title = arguments[1];
    }
  } else if (options.callback && !callback) {
    callback = options.callback;
  }
  // ...
}

我测试没有问题,是因为我调用的时候options都是当对象使用的。所以这个方案是有限定范围的解决方案,并不是通用的。主要有以下问题:

  1. options必须是对象的形式;
  2. 如果使用了$alert$confirm$prompt等方法,都需要做类似的处理,代码比较啰嗦;
  3. 只能通过$msgbox的方式修改,如果你是直接调用的MessageBox,那就不适用了,因为我并没有修改MessageBox

基于问题1,你可以像MessageBox源码里面分析参数那样,创建一个合适的options,然后传给MessageBox。基于问题3,暂时没有想到好的解决方案。

考虑到项目是团队合作的结果,万一有同学调用$msgbox的时候第一个参数不是对象,或者直接通过MessageBox调用,我就在考虑是否有更好的解决方案。仔细查看源码,发现一个更简单的API:

MessageBox.setDefaults = defaults => {
  MessageBox.defaults = defaults;
};

再重新看下MessageBox函数的源码:

const MessageBox = function(options, callback) {
  // ...
  if (typeof Promise !== 'undefined') {
    return new Promise((resolve, reject) => { // eslint-disable-line
      msgQueue.push({
        options: merge({}, defaults, MessageBox.defaults, options), // 注意这一行
        callback: callback,
        resolve: resolve,
        reject: reject
      });

      showNextMsg();
    });
  } else {
    msgQueue.push({
      options: merge({}, defaults, MessageBox.defaults, options), // 注意这一行
      callback: callback
    });

    showNextMsg();
  }
};

通过上面最终options参数的计算方法,我们可以给options排下优先级:

函数调用时传入的options > MessageBox.defaults > 组件默认值options

所以,修改MessageBox.defaults可以实现我们的目的:

import Element from 'element-ui'

Element.MessageBox.setDefaults({
  closeOnClickModal: false
})

当然,该方法也并不是十分完美的,问题如下:

  1. 未在官方文档中发现该API的介绍,这种非官方API严重依赖当前使用的版本,官方应该在后续迭代中不保证这种API的稳定性和正确性;
  2. MessageBox.alert在调用Message的时候,默认覆盖了几个默认属性,这几个属性通过MessageBox.setDefaults方法修改不生效,比如设置alert框的closeOnClickModal为true:

    MessageBox.alert = (message, title, options) => {
      // ...
      return MessageBox(merge({
        // ...
        closeOnPressEscape: false,
        closeOnClickModal: false // 调用MessageBox时传入的options会覆盖MessageBox.defaults
      }, options));
    };

探索到这里,对于我来说,MessageBox.setDefaults已经算是比较优的解决方案了,所以就不再继续探索了。
如果你还有更高的要求,比如修复问题2,简单点,你可以覆盖这个默认方法或者探索更适合你的解决方案。

总结

希望大家能有所收获。如有错误,欢迎留言讨论。

参考文档

  1. ElementUI 如何全局配置组件默认属性
  2. 全局修改elementUI的$message默认显示时间的方法

luckness
6.2k 声望5.1k 粉丝