Vue3从0到1组件开发-系统组件:Modal对话框

770 阅读3分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

前言

在项目开发中,无论是移动端还是PC端,Modal对话框大概是绕不过去的一个功能性组件吧?

常用于重要消息提示,例如网络请求失败等信息,授权访问类功能等情况的信息提示。

相较于上文的Message消息组件Modal对话框组件的信息提示则更显正式,也更醒目。

默认情况下,我们可以使用window.alert()方法弹出信息提示,或者使用window.prompt()方法弹出输入框和用户进行交互。如下图。

image.png

image.png

但是这都1202年了,用这样的框不但显得很有时代气息,同时弹框会阻拦系统进程,用于生产环境时怕是会烦死人吧。

所以,时至今日,成熟的组件化早就替代了上个时代的默认系统框了,即便并非自己开发的,市场也有大量成熟的组件库任君采撷。

从结构开始

即便是从默认的系统组件我们也能看出,一个合格的对话框组件,最基本的信息是包含标题内容操作按钮的。并且对比两框,取消按钮是可选的,需要状态控制。

并且参考市面上组件库的开发方案,可选的还有是否有蒙版背景,是否有关闭按钮,是否支持点击非组件去关闭的交互。这里也会一并解析。

先布局基本结构。

这一块主要是样式写布局,没什么大的注意事项,把前面说的几个点都考虑一下, 布局就比较随心的。即便是工作中也无非是遵循产品主题。

block content
div(style="display:inline-block")
  teleport(
    to="body"
    :disabled="!teleport"
  )
    transition(name="fade" appear)
      div(
        class="yx-mask"
        @click="maskCancel"
        v-if="isShow"
      )
    div(
      class="yx-modal"
      :class="{confirm: type !== '' }"
    )
      transition(
        name="scale"
        @before-enter="setOrigin"
        @before-leave="setOrigin"
        @after-leave="afterLeave"
        appear
      )
        div(
          class="yx-modal-content"
          v-show="isShow"
          :class="{'yx-modal-confirm-wrap': type !== ''}"
          :style="modalStyle"
        )
          div(
            class="yx-modal-close"
            v-if="closable"
            @click="cancel"
          )
            i.yx-icon-x
          div.yx-modal-head
            template(v-if="!$slots.head")
              i(
                v-if="type !== ''"
                :class="iconType[type]"
              )
              | {{title}}
            slot(
              v-else
              name="head"
            )
          div.yx-modal-body
            template(v-if="type !== ''") {{content}}
            slot(v-else)
          div.yx-modal-footer
            template(v-if="!$slots.footer")
              yx-button(
                class="yx-modal-btn"
                v-if="!((type !== '') & (type !== 'confirm'))"
                plain
                @click="cancel"
              ) {{cancelText}}
              yx-button(
                class="yx-modal-btn"
                type="primary"
                :loading="loading"
                @click="confirm"
              ) {{confirmText}}
            slot(v-else name="footer")

这里的按钮用的是之前的Button按钮组件,也可以直接用button标签。

布局随意看看就好,并没有什么固定的说法,按照产品主题或者设计走就行了。

逻辑部分

几个主要的事件

// 是否点击遮罩关闭弹框, maskClosable来自props
const {maskClosable} = toRefs(props);
const maskcancel = () => {
  if(maskClosable.value) cancel()
}
// 关闭组件事件,并且调用父级的事件
const cancel = () => {
  isShow.value = false;
  emits('update:modelValue', isShow.value);
  emits('cancel');
  props.onCancel && props.onCancel()
}
// 确认事件,同上
const confirm = () => {
  emits('confirm');
  props.onConfirm && props.onConfirm()
  nextTick(() => {
    if(!loading.value){
      isShow.value = false;
      emits('update:modelValue', isShow.value)
    }
  })
}

挂载组件

这个组件可以是单一的,也可以设计成不同状态的组件,所以挂载的时候可以参考上一章的Message消息组件,供调用不同状态的组件。

// index.js

import Modal from './modal.vue';

export function createComponent(component, props){
  const vnode = h(component, props)
  render(vnode, document.createElement('div'))
  return vnode.component
}

Modal.install = function(app){
  app.component('modal', element);
}

let mouseClick;

const getClickPosition = e => {
  mouseClick = {
    x: e.clientX,
    y: e.clientY
  }
  setTimeout(() => (mouseClick = null), 100);
}

document.addEventListener('click', getClickPosition, true);

function ModalsCreate(option, type){
  const props = {
    ...option,
    type,
    mouseCLick,
    teleport:false,
    modalValue: true
  }

  document.body.style.overflow = 'hidden';
  const component = createComponent(Modal, props);
  document.body.appendChild(component.vnode.el);
}

let oneKey = null;

;['info','error','success','warning', 'confirm'].forEach(type => {
  oneKey || (oneKey = type);

  Modal[type] = props => ModalsCreate(props, type)
})

export default Modal;