element message-box源码
本文参考https://www.cnblogs.com/fangnianqin/p/10769949.html
src/main.vue
<template> <transition name="msgbox-fade"> <!--包裹弹框的div--> <div class="el-message-box__wrapper" tabindex="-1" v-show="visible" @click.self="handleWrapperClick" role="dialog" aria-modal="true" :aria-label="title || 'dialog'"> <!--中间的弹框--> <div class="el-message-box" :class="[customClass, center && 'el-message-box--center']"> <!--弹窗头部,包含:标题和关闭按钮;title必须设置,如果不设置不显示头部信息--> <div class="el-message-box__header" v-if="title !== null"> <!--头部标题--> <div class="el-message-box__title"> <!--center为true时,为居中布局,可设置图标,图标和标题居中显示--> <div :class="['el-message-box__status', icon]" v-if="icon && center"> </div> <span>{{ title }}</span> </div> <button type="button" class="el-message-box__headerbtn" aria-label="Close" v-if="showClose" @click="handleAction(distinguishCancelAndClose ? 'close' : 'cancel')" @keydown.enter="handleAction(distinguishCancelAndClose ? 'close' : 'cancel')"> <i class="el-message-box__close el-icon-close"></i> </button> </div> <!--弹框内容部分--> <div class="el-message-box__content"> <!--消息类型的图标--> <div :class="['el-message-box__status', icon]" v-if="icon && !center && message !== ''"> </div> <!--弹框的主要内容--> <div class="el-message-box__message" v-if="message !== ''"> <slot> <!--dangerouslyUseHTMLString是否将 message 属性作为 HTML 片段处理,如果该字段不存在时,直接显示message--> <p v-if="!dangerouslyUseHTMLString">{{ message }}</p> <!--如果存在将message作为HTML处理--> <p v-else v-html="message"></p> </slot> </div> <!--输入框部分,根据设置的showInput显示--> <div class="el-message-box__input" v-show="showInput"> <el-input v-model="inputValue" :type="inputType" @keydown.enter.native="handleInputEnter" :placeholder="inputPlaceholder" ref="input"></el-input> <!--检验错误的提示信息--> <div class="el-message-box__errormsg" :style="{ visibility: !!editorErrorMessage ? 'visible' : 'hidden' }">{{ editorErrorMessage }}</div> </div> </div> <!--弹框底部,包含:确认、取消按钮--> <div class="el-message-box__btns"> <el-button :loading="cancelButtonLoading" :class="[ cancelButtonClasses ]" v-if="showCancelButton" :round="roundButton" size="small" @click.native="handleAction('cancel')" @keydown.enter="handleAction('cancel')"> {{ cancelButtonText || t('el.messagebox.cancel') }} </el-button> <el-button :loading="confirmButtonLoading" ref="confirm" :class="[ confirmButtonClasses ]" v-show="showConfirmButton" :round="roundButton" size="small" @click.native="handleAction('confirm')" @keydown.enter="handleAction('confirm')"> {{ confirmButtonText || t('el.messagebox.confirm') }} </el-button> </div> </div> </div> </transition> </template> <script type="text/babel"> import Popup from 'element-ui/src/utils/popup'; import Locale from 'element-ui/src/mixins/locale'; import ElInput from 'element-ui/packages/input'; import ElButton from 'element-ui/packages/button'; import { addClass, removeClass } from 'element-ui/src/utils/dom'; import { t } from 'element-ui/src/locale'; import Dialog from 'element-ui/src/utils/aria-dialog'; let messageBox; //定义的图标类型 let typeMap = { success: 'success', info: 'info', warning: 'warning', error: 'error' }; export default { mixins: [Popup, Locale], props: { modal: { default: true }, // 是否在 MessageBox 出现时将 body 滚动锁定 lockScroll: { default: true }, // MessageBox 是否显示右上角关闭按钮 showClose: { type: Boolean, default: true }, // 是否可通过点击遮罩关闭 MessageBox boolean — true(以 alert 方式调用时为 false) closeOnClickModal: { default: true }, // 是否可通过按下 ESC 键关闭 MessageBox boolean — true(以 alert 方式调用时为 false) closeOnPressEscape: { default: true }, // 是否在 hashchange 时关闭 MessageBox boolean — true closeOnHashChange: { default: true }, // 是否居中布局 center: { default: false, type: Boolean }, // 是否使用圆角按钮 roundButton: { default: false, type: Boolean } }, components: { ElInput, ElButton }, computed: { icon() { const { type, iconClass } = this; //如果用户设置了自定义图标的类名,就显示自定义图标;如果没有就显示设置的type图标,否则就不显示图标 return iconClass || (type && typeMap[type] ? `el-icon-${ typeMap[type] }` : ''); }, //添加确定按钮的自定义类名 confirmButtonClasses() { return `el-button--primary ${ this.confirmButtonClass }`; }, //添加取消按钮的自定义类名 cancelButtonClasses() { return `${ this.cancelButtonClass }`; } }, methods: { // 关闭 getSafeClose() { const currentId = this.uid; return () => { this.$nextTick(() => { if (currentId === this.uid) this.doClose(); }); }; }, // 执行关闭事件 doClose() { if (!this.visible) return; this.visible = false; this._closing = true; this.onClose && this.onClose(); messageBox.closeDialog(); // 解绑 // 如果是弹出框出来锁定body if (this.lockScroll) { setTimeout(this.restoreBodyStyle, 200); } this.opened = false; this.doAfterClose(); setTimeout(() => { if (this.action) this.callback(this.action, this); }); }, //点击弹框时,根据closeOnClickModal来是否可通过点击遮罩关闭 MessageBox handleWrapperClick() { // 如果closeOnClickModal设置为true if (this.closeOnClickModal) { //判断是否将取消(点击取消按钮)与关闭(点击关闭按钮或遮罩层、按下 ESC 键)进行区分 //如果区分则this.handleAction('close');否则this.handleAction('cancel'); this.handleAction(this.distinguishCancelAndClose ? 'close' : 'cancel'); } }, // 输入框键盘enter事件 handleInputEnter() { if (this.inputType !== 'textarea') { // 确认 return this.handleAction('confirm'); } }, handleAction(action) { // 如果当前是this.$prompt并且点击了确认按钮并且没有验证通过 if (this.$type === 'prompt' && action === 'confirm' && !this.validate()) { return; } this.action = action; //判断beforeClose是否是函数,也就是用户是否定义了beforeClose函数 if (typeof this.beforeClose === 'function') { this.close = this.getSafeClose(); /** MessageBox 关闭前的回调,会暂停实例的关闭 function(action, instance, done),action 的值为'confirm', 'cancel'或'close';instance 为 MessageBox 实例,可以通过它访问实例上的属性和方法;done 用于关闭 MessageBox 实例 */ this.beforeClose(action, this, this.close); } else { //如果用户没有定义beforeClose,就调doClose直接关闭弹框 this.doClose(); } }, //该方法主要是用于用户在调用$prompt方法打开消息提示时,校验input输入框的值 validate() { //$prompt方法即可打开消息提示,它模拟了系统的 prompt if (this.$type === 'prompt') { //获取用户自己定义的匹配模式 const inputPattern = this.inputPattern; //如果用户自己定义了匹配模式,并且用户输入校验不通过 if (inputPattern && !inputPattern.test(this.inputValue || '')) { //显示用户自己定义的校验不通过时的提示信息;当用户未定义校验不通过的提示信息时,t('el.messagebox.error')输出提示:输入的数据不合法! this.editorErrorMessage = this.inputErrorMessage || t('el.messagebox.error'); //这里主要是在校验不通过时,给input加上的类名中加上invalid,变成class="el-input__inner invalid" //通过.el-message-box__input input.invalid{border-color: #f56c6c;}改变input的border为红色 addClass(this.getInputElement(), 'invalid'); return false; } //输入框的校验函数;可以返回布尔值或字符串,若返回一个字符串, 则返回结果会被赋值给 inputErrorMessage const inputValidator = this.inputValidator; //如果校验函数存在 if (typeof inputValidator === 'function') { const validateResult = inputValidator(this.inputValue); //校验不通过,显示校验不通过的红色提示信息 if (validateResult === false) { this.editorErrorMessage = this.inputErrorMessage || t('el.messagebox.error'); addClass(this.getInputElement(), 'invalid'); return false; } //若返回一个字符串, 则返回结果会被赋值给 inputErrorMessage if (typeof validateResult === 'string') { this.editorErrorMessage = validateResult; addClass(this.getInputElement(), 'invalid'); return false; } } } //如果校验通过,则不显示错误提示,并删除类名invalid this.editorErrorMessage = ''; removeClass(this.getInputElement(), 'invalid'); return true; }, // 获取第一个聚焦元素 getFirstFocus() { const btn = this.$el.querySelector('.el-message-box__btns .el-button'); const title = this.$el.querySelector('.el-message-box__btns .el-message-box__title'); return btn || title; }, // 获取输入框元素 getInputElement() { const inputRefs = this.$refs.input.$refs; return inputRefs.input || inputRefs.textarea; } }, watch: { // 监听输入值得变化 inputValue: { immediate: true,//立即触发 handler(val) { //自定义函数 this.$nextTick(_ => { if (this.$type === 'prompt' && val !== null) { this.validate(); } }); } }, // 是否显示messagebox框 visible(val) { if (val) { this.uid++; // 如果是this.$alert或者this.$confirm if (this.$type === 'alert' || this.$type === 'confirm') { this.$nextTick(() => { // 确认按钮聚焦 this.$refs.confirm.$el.focus(); }); } // document.activeElement -> 当前获取焦点的元素 this.focusAfterClosed = document.activeElement; messageBox = new Dialog(this.$el, this.focusAfterClosed, this.getFirstFocus()); } // prompt if (this.$type !== 'prompt') return; if (val) { setTimeout(() => { if (this.$refs.input && this.$refs.input.$el) { this.getInputElement().focus(); } }, 500); } else { this.editorErrorMessage = ''; removeClass(this.getInputElement(), 'invalid'); } } }, mounted() { this.$nextTick(() => { if (this.closeOnHashChange) { //如果需要,则在元素挂载之后,给window添加hashchange事件 window.addEventListener('hashchange', this.close); } }); }, beforeDestroy() { // 组件销毁时移除hashchange事件 if (this.closeOnHashChange) { window.removeEventListener('hashchange', this.close); } setTimeout(() => { messageBox.closeDialog(); //解绑 }); }, data() { return { uid: 1, title: undefined, //MessageBox 标题 message: '', //MessageBox 消息正文内容 type: '', //消息类型,用于显示图标 iconClass: '', //自定义图标的类名,会覆盖 type customClass: '', //MessageBox 的自定义类名 showInput: false, //是否显示输入框 inputValue: null, //输入框的初始文本 inputPlaceholder: '', //输入框的占位符 inputType: 'text', //输入框的类型 inputPattern: null, //输入框的校验表达式 inputValidator: null,//输入框的校验函数。可以返回布尔值或字符串,若返回一个字符串, 则返回结果会被赋值给 inputErrorMessage inputErrorMessage: '', //校验未通过时的提示文本 showConfirmButton: true, //是否显示确定按钮 showCancelButton: false, //是否显示取消按钮 action: '', //操作确认还是取消 confirmButtonText: '',//确定按钮的文本内容 cancelButtonText: '',//取消按钮的文本内容 confirmButtonLoading: false, //确认按钮的loading状态 cancelButtonLoading: false,//取消按钮的loading状态 confirmButtonClass: '',//确认按钮的类名 confirmButtonDisabled: false,//确认按钮是否禁用 cancelButtonClass: '',//取消按钮的类名 editorErrorMessage: null,//错误信息提示 callback: null,//回调函数 dangerouslyUseHTMLString: false,// 是否将 message 属性作为 HTML 片段处理 focusAfterClosed: null, isOnComposition: false, distinguishCancelAndClose: false//是否将取消(点击取消按钮)与关闭(点击关闭按钮或遮罩层、按下 ESC 键)进行区分 }; } }; </script>
src/main.js
const defaults = { title: null,//MessageBox 标题 message: '',//MessageBox 消息正文内容 type: '',//消息类型,用于显示图标 iconClass: '',//自定义图标的类名,会覆盖 type showInput: false, //是否显示输入框 showClose: true,//MessageBox 是否显示右上角关闭按钮 modalFade: true, lockScroll: true,//是否在 MessageBox 出现时将 body 滚动锁定 closeOnClickModal: true,//是否可通过点击遮罩关闭 MessageBox closeOnPressEscape: true,//是否可通过按下 ESC 键关闭 MessageBox closeOnHashChange: true,//是否在 hashchange 时关闭 MessageBox inputValue: null,//输入框的初始文本 inputPlaceholder: '',//输入框的占位符 inputType: 'text',//输入框的类型 inputPattern: null, //输入框的校验表达式 inputValidator: null,//输入框的校验函数。可以返回布尔值或字符串,若返回一个字符串, 则返回结果会被赋值给 inputErrorMessage inputErrorMessage: '',//校验未通过时的提示文本 showConfirmButton: true,//是否显示确定按钮 showCancelButton: false,//是否显示取消按钮 confirmButtonPosition: 'right', confirmButtonHighlight: false, cancelButtonHighlight: false, confirmButtonText: '', //确定按钮的文本内容 cancelButtonText: '',//取消按钮的文本内容 confirmButtonClass: '',//确定按钮的自定义类名 cancelButtonClass: '',//取消按钮的自定义类名 customClass: '',//MessageBox 的自定义类名 beforeClose: null,//MessageBox 关闭前的回调,会暂停实例的关闭 dangerouslyUseHTMLString: false,//是否将 message 属性作为 HTML 片段处理 center: false,//是否居中布局 roundButton: false,//是否使用圆角按钮 distinguishCancelAndClose: false//是否将取消(点击取消按钮)与关闭(点击关闭按钮或遮罩层、按下 ESC 键)进行区分 }; import Vue from 'vue'; import msgboxVue from './main.vue'; import merge from 'element-ui/src/utils/merge'; import { isVNode } from 'element-ui/src/utils/vdom'; //创建MessageBox的构造器,包含msgboxVue组件选项的对象作为Vue.extend的参数,返回一个VueComponent类,VueComponent类是Vue类的子类 //Vue.extend是一个类构造器,用来创建一个子类vue并返回构造函数,而Vue.component它的任务是将给定的构造函数与字符串ID相关联,以便Vue.js可以在模板中接收它。 const MessageBoxConstructor = Vue.extend(msgboxVue); let currentMsg, instance; let msgQueue = []; const defaultCallback = action => { if (currentMsg) { let callback = currentMsg.callback; if (typeof callback === 'function') { if (instance.showInput) { callback(instance.inputValue, action); } else { callback(action); } } if (currentMsg.resolve) { // 点击确定或者去下关闭按钮时,在此处调对应的方法进行处理 if (action === 'confirm') { //执行确认后的回调方法 if (instance.showInput) { currentMsg.resolve({ value: instance.inputValue, action }); } else { currentMsg.resolve(action); } } else if (currentMsg.reject && (action === 'cancel' || action === 'close')) { //执行取消和关闭后的回调方法 currentMsg.reject(action); } } } }; const initInstance = () => { //instance为messageBox的实例 instance = new MessageBoxConstructor({ el: document.createElement('div') }); instance.callback = defaultCallback; }; const showNextMsg = () => { if (!instance) { // 调用initInstance初始化实例,返回messageBox的实例对象 initInstance(); } instance.action = ''; if (!instance.visible || instance.closeTimer) { if (msgQueue.length > 0) { currentMsg = msgQueue.shift(); //将用户设置的属性和方法挂载到messageBox的实例对象instance上去 let options = currentMsg.options; for (let prop in options) { if (options.hasOwnProperty(prop)) { instance[prop] = options[prop]; } } //当用户未设置callback时,将defaultCallback赋值给instance.callback if (options.callback === undefined) { instance.callback = defaultCallback; } let oldCb = instance.callback; instance.callback = (action, instance) => { oldCb(action, instance); showNextMsg(); }; if (isVNode(instance.message)) { instance.$slots.default = [instance.message]; instance.message = null; } else { delete instance.$slots.default; } // 默认设置为true ['modal', 'showClose', 'closeOnClickModal', 'closeOnPressEscape', 'closeOnHashChange'].forEach(prop => { if (instance[prop] === undefined) { instance[prop] = true; } }); document.body.appendChild(instance.$el); Vue.nextTick(() => { instance.visible = true; }); } } }; const MessageBox = function (options, callback) { if (Vue.prototype.$isServer) return; if (typeof options === 'string' || isVNode(options)) { options = { message: options }; // 第二个参数设置为title if (typeof arguments[1] === 'string') { options.title = arguments[1]; } } else if (options.callback && !callback) { callback = options.callback; } if (typeof Promise !== 'undefined') { return new Promise((resolve, reject) => { // eslint-disable-line // options合并默认的所有参数和用户设置的参数 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(); } }; MessageBox.setDefaults = defaults => { MessageBox.defaults = defaults; }; //this.$alert方法 MessageBox.alert = (message, title, options) => { //如果title的类型为object时,用户可能没传title,则将title的值赋值给options if (typeof title === 'object') { options = title; title = ''; } else if (title === undefined) { title = ''; } //合并用户传的参数,并调用MessageBox方法 return MessageBox(merge({ title: title, message: message, $type: 'alert', closeOnPressEscape: false, closeOnClickModal: false }, options)); }; //this.$confirm方法,分析同MessageBox.alert方法 MessageBox.confirm = (message, title, options) => { if (typeof title === 'object') { options = title; title = ''; } else if (title === undefined) { title = ''; } return MessageBox(merge({ title: title, message: message, $type: 'confirm', showCancelButton: true }, options)); }; //this.$prompt方法,分析同MessageBox.alert方法 MessageBox.prompt = (message, title, options) => { if (typeof title === 'object') { options = title; title = ''; } else if (title === undefined) { title = ''; } return MessageBox(merge({ title: title, message: message, showCancelButton: true, showInput: true, $type: 'prompt' }, options)); }; // 关闭 MessageBox.close = () => { instance.doClose(); instance.visible = false; msgQueue = []; currentMsg = null; }; export default MessageBox; export { MessageBox };