vue3.0自制函数式弹窗组件

1,166 阅读1分钟

最近公司需要用到弹窗,但是element-puls的弹窗组件达不到想要的效果,于是自己动手写了个,顺便学习一下vue3.0如何使用方法调用组件显示,写的比较简陋,主要是表达实现方式,学习为主。

组件显示效果:

image.png

使用方法如下:(支持promise)

// 自制弹窗组件
        showMessageBox({
            title: '配置条件已超过3条,将无法继续新增',
            desc: '现场景配置仅支持至多3条公式内的计算',
            cancelButtonText:'我知道了',
            type:'confirm',
            closeOnClickModal: false
        })

主要实现方法如下:

import { h, render } from 'vue';
//需要引入弹窗组件
import MessageBox from './index.vue';
// 单例模式
let vnode = null;
// 显示弹窗组件
let showMessageBox = option => {
   if (vnode) {
       return;
   }
   // vue的props
   let options = {
       ...option,
       onVanish: () => {
           vnode=null
           render(null, container);
          // document.body.removeChild(container);
       },
   };
   // 组件挂载的容器
   let container =document.createElement('div');
   // 生成vnode
   vnode = h(MessageBox, options);
   // 渲染到页面
   render(vnode, container);
   //document.body.appendChild(container);
   document.body.appendChild(container.firstElementChild);
   // vnode对应的组件
   let component = vnode.component;
   // vue实例
   let vm = component.proxy;
   vm.visible = true;
   let vmProps = Object.keys(option);
   // props列表不存在则移至到vm自身属性
   vmProps.forEach(key => {
       if (!propsList.includes(key)) {
           vm[key]=option[key]
       }
   });
};

// 配置的props数组
export const propsList = [
   'closeOnClickModal',
   'update:visible',
   'desc',
   'title',
   'cancelButtonText',
   'type',
   'saveButtonText',
   'onSave',
   'onCancel',
];

showMessageBox.confirm = (title, desc, option) => {
   return new Promise((reslove, reject) => {
       const vm = showMessageBox({
           title,
           desc,
           type: 'confirm',
           ...option,
           onSave: () => {
               reslove(vm);
           },
           onCancel: () => {
               reject();
           },
       });
   });
};

showMessageBox.alert = (title, desc, option) => {
   return new Promise((reslove, reject) => {
       const vm = showMessageBox({
           title,
           desc,
           type: 'alert',
           ...option,
           onSave: () => {
               reslove(vm);
           },
           onCancel: () => {
               reject();
           },
       });
   });
};
export default showMessageBox;

vue组件部分

<template>
   <div class="el-overlay is-message-box message-main" @click="closeOnClickModal ? emits('vanish') : null" v-show="visible">
       <div class="el-message-box message-box" @click.stop>
           <div class="message-title"><img src="../image/warn.png" class="message-info" />{{ title }}</div>
           <div class="message-desc">{{ desc }}</div>
           <div class="meesage-btns" v-if="type == 'alert'">
               <el-button type="primary" class="btn" @click="onVanish">{{ cancelButtonText || '我知道了' }}</el-button>
           </div>
           <div class="meesage-btns" v-else-if="type == 'confirm'">
               <el-button class="btn" @click="cancel">{{ cancelButtonText || '取消' }}</el-button>
               <el-button type="primary" class="btn" @click="save">{{ saveButtonText || '保存' }}</el-button>
           </div>
       </div>
   </div>
</template>

<script lang="ts" setup>
import { ref, defineProps,defineEmits } from 'vue';
import { ElButton } from 'element-plus';
// 配置外部传来的事件
const emits = defineEmits(['vanish'])
// 可见状态
const visible = ref(false);
// props
const props = defineProps([
   //'onVanish',
   'closeOnClickModal',
   'update:visible',
   'desc',
   'title',
   'cancelButtonText',
   'type',
   'saveButtonText',
   'onSave',
   'onCancel'
]);
const cancel = () => {
   emits('vanish')
   props.onCancel?.()
};
const save = () => {
   emits('vanish')
   props.onSave?.()
};
</script>
<style lang="scss" scoped>
//@import url(); 引入公共css类
.message-box {
   background: #ffffff;
   box-shadow: 0px 2px 16px rgba(0, 0, 0, 0.2);
   border-radius: 8px;
   padding: 32px;
   width: 560px;
   height: 218px;
   box-sizing: border-box;
   z-index: 9999;
}
.message-main {
   z-index: 888;
}
.message-title {
   font-weight: 500;
   font-size: 20px;
   line-height: 29px;
   display: flex;
   align-items: center;
   color: #000000;
   margin-bottom: 12px;
}
.message-info {
   width: 24px;
   height: 24px;
   margin-right: 12px;
}
.message-desc {
   padding-left: 36px;
   font-weight: 400;
   font-size: 16px;
   line-height: 23px;
   display: flex;
   align-items: center;
   color: #a5a7ae;
   padding-bottom: 32px;
   border-bottom: 1px solid rgba(0, 0, 0, 0.08);
   margin-bottom: 16px;
}
.meesage-btns {
   text-align: right;
   .btn {
       display: inline-block;
       border-radius: 4px;
       width: 96px;
       height: 40px;
       margin-left: 16px;
       font-weight: 400;
       font-size: 14px;
   }
   .btn[type='primary'] {
       background: #426fe3;
       color: #fff;
       border: 1px solid #426fe3;
   }
}
</style>