设计一个简单的Vue模态组件

886 阅读1分钟

在ElementUI组件库中,MessageBox组件(包括注入原型链中的$confirm方法,实际上就是MessageBox实例)和Dialog组件本质上都是模态,但ElementUI并未提供一个统一的模态组件,虽然Dialog组件可以充当这一角色,但是在使用时容易踩坑,比如我之前的一篇文章深入了解el-dialog组件中插槽的生命周期中说到的子组件生命周期的问题,又或者没有title时样式错位问题等,遂自己写造轮子,写了一个模态组件。

设计

一个模态框的整体设计如下: 界面:包括5个部分:遮罩层shadow,标题title,关闭按钮close,内容body,页脚footer; 操作:鼠标点击关闭按钮关闭模态,鼠标点击遮罩层关闭模态; 灵活性:提供标题title,页脚footer和内容body的插槽slot,也可以通过title属性和body属性传入。

页面结构如下

考虑点击遮罩层关闭的效果,采用点击遮罩层非内容区域关闭遮罩层的三种思路四种方案中的方案一。

<template>
<!-- 模态框 -->
  <div id="modal-wrap" class="modal-wrap" v-if="selfVisible" @click="handleClickShadow">
    <div class="modal-content" :style="{ width: width || '500px' }">
      <i class="modal-content-icon__close el-icon-close" v-if="showClose" @click="close"></i>
      <div :class="{ 'modal-header': title || $slots.title }">
        <slot name="title">{{title}}</slot>
      </div>
      <div class="modal-body">
        <slot>{{body}}</slot>
      </div>
      <div :class="{ 'modal-footer': $slots.footer }">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</template>

组件内部逻辑

主要包含三部分:props,data和method,其中需要监听visible属性修改模态内部selfVisible状态。

<script>
export default {
  name: 'Modal',

  props: {
    // 标题
    title:{
      type: String,
      default: ''
    },
    // 内容
    body: {
      type: String,
      default: ''
    },
    // 是否显示模态框
    visible: {
      type: Boolean,
      default: true
    },
    // 展示关闭按钮
    showClose: {
      type: Boolean,
      default: true
    },
    // 弹框宽度
    width: {
      type: String,
      default: '500px'
    },
    // 点击遮罩层关闭模态
    closeOnClickShadow: {
      type: Boolean,
      default: false
    }
  },

  data () {
    return {
      selfVisible: false
    }
  },

  watch: {
    visible: {
      handler (newVal) {
        if (this.visible) {
          this.open()
        } else {
          this.close()
        }
      },
      immediate: true
    }
  },

  methods: {
    // 打开
    open () {
      this.selfVisible = true
      this.$emit('opened')
    },
    // 关闭
    close () {
      this.selfVisible = false
      this.$emit('closed')
      this.$emit('update:visible', false)
    },
    // 点击遮罩层
    handleClickShadow (e) {
      if (this.closeOnClickShadow && e.target.id === 'modal-wrap') {
        this.close()
      }
    },
  }
}
</script>

样式

包裹元素和遮罩层fixed定位,内容区域absolute定位,其余样式主要就是美化。

<style lang="less">
.modal-wrap {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  margin: 0;
  z-index: 9999;
  background-color: rgba(0,0,0,.5);
  overflow-y: scroll;
  overflow-x: auto;

  .modal-content {
    background-color: #fff;
    box-shadow: 2px 2px 20px 1px;
    overflow-x: auto;
    border-radius: 16px;
    margin: 10vh auto;
    position: relative;

    .modal-content-icon__close {
      cursor: pointer;
      position: absolute;
      top: 8px;
      right: 8px;
      line-height: 20px;
      height: 20px;
      z-index: 10001;
      font-size: 18px;
    }

    .modal-content-icon__close:hover {
      color: #66b1ff;
      animation: rotate360 0.5s;

      @keyframes rotate360 {
        from {
          transform: rotate(0deg);
        }
        to {
          transform: rotate(360deg);
        }
      }
    }

    .modal-header {
      border-bottom: 1px solid #eee;
      color: #313131;
      padding: 15px;
      font-size: 18px;
      text-align: left;
    }

    .modal-body {
      position: relative;
      padding: 20px;
      font-size: 16px;
      text-align: center;
    }

    .modal-footer {
      border-top: 1px solid #eee;
      padding: 15px;
    }
  }
}
</style>