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 };

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM