关于vue3如何封装命令式modal(免写组件标签)
由于之前搜索其他modal框封装的文章时,发现大多数在使用时需要引入并注册组件,传入参数再写方法。每次引用时都要经过上述操作,觉得太麻烦了。后面通过一些资料摸索出vue3如何利用命令方式(即避免写组件标签)来弹出弹框,写下这篇文章,方便日后自己复习
调用及效果
先上使用方法及效果
代码
<template>
<div class="container">
<div class="title_box flex_box">
<div class="out" @click="out">出来啦</div>
</div>
</div>
</template>
<script setup>
// 获取js文件
import globalCom from '@/components/globalCom/index'
let i = 0
function out(){
globalCom.show({title:'55'+i++,leftText:'取消了'}).then(res=>{
// 点击左侧res为left,右侧为right
console.log(res);
})
}
</script>
通过上图可以看到,该调用方式是第一次调用时将弹框挂载到页面上,然后是通过display属性及动态变更内容的方式来修改弹框内容
下面是该方法的封装以及实现
大致思路
组件设计
接下来看看组件
先像常规写弹框组件一样先将其写好,此处左右按钮分别设为left和right,
比较特殊的是需要准备leftClick和rightClick,这是外部需要能够控制的回调,点击左右按钮会分别调用
第二个比较特殊的是getShowModal,这是外部用于获取当前组件的showModal方法
<template>
<div class="container" v-show="visible">
<div class="pup_bg" @click="closeModal"></div>
<div class="main">
<div class="text">{{ title }}</div>
<div class="btns">
<div class="btn_left" @click="lClick">{{ leftText }}</div>
<div class="btn_right" @click="rClick">{{ rightText }}</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { onMounted, ref } from "vue";
export default {
props: {
leftText: {
type: String,
default: '取消'
},
rightText:{
type:String,
default:'确认'
},
title: {
type: String,
default: '',
require:true
},
getShowModal: {
type: Function,
default: () => { }
},
leftClick: {
type: Function,
default: () => { }
},
rightClick: {
type: Function,
default: () => { }
}
},
setup(props) {
let visible = ref(true)
onMounted(() => {
console.log('props');
})
// 点击左侧按钮 先调用传入的leftClick在关闭model
function lClick() {
props.leftClick()
closeModal()
}
// 右侧同理
function rClick() {
props.rightClick()
closeModal()
}
function showModal() {
// 此处避免弹框展示时滚动
document.body.style.overflow = 'hidden'
visible.value = true
}
function closeModal(){
document.body.style.overflow = 'visible'
visible.value = false
}
// 使得外界可以获得并调用showModal
props.getShowModal && props.getShowModal(showModal)
return {
visible, lClick, rClick,closeModal
}
}
}
</script>
<style lang="scss" scoped>
.btns {
width: 100%;
height: 1rem;
font-size: 0.36rem;
font-family: PingFangSC-Regular, PingFang SC;
color: #999999;
line-height: 1rem;
display: flex;
div {
border-top: 1px solid #DDDDDD;
flex: 1;
text-align: center;
}
.btn_left{
border-right: 1px solid #DDDDDD;
}
.btn_right{
color: #00C8BE;
}
}
.container {
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
z-index: 999;
}
.main {
position: absolute;
display: flex;
width: 5.4rem;
left: 50%;
height: 2.57rem;
top: 50%;
transform: translate(-50%, -50%);
background: #FFFFFF;
border-radius: 0.14rem;
flex-direction: column;
justify-content: space-between;
align-items: center;
z-index: 999;
}
.text {
padding-top: 0.5rem;
text-align: center;
font-size: 0.32rem;
color: #333333;
font-weight: 500;
}
</style>
封装以便调用
接下来设置一个ts文件将其封装
目的:调用子组件的show方法,传入参数并做展示,
返回一个promise对象,用户点击之后再调用resolve并传入相关参数(例如让用户明确点了左边还是右边)
import { createVNode, render } from 'vue'
import ConfirmComponent from "./GlobalCom.vue";
import type {typeConfirm} from './index.d'
let showFn: () => void
// 获取到子组件的showFn,能够在外部控制组件的showModal功能
function getShowFn(showF: () => void) {
showFn = showF
}
//也可以在此设置默认值
const config = {
leftText:'这是左侧'
}
enum EPosition{
left="left",
right="right"
}
export default {
/**
*
* @param options
* @returns
* .then参数为 left 或right
*/
show(options:typeConfirm) {
return new Promise((resolve) => {
options = Object.assign(config,options)
function leftClick(){
resolve(EPosition.left)
}
function rightClick(){
resolve(EPosition.right)
}
showFn && showFn()
const vnode = createVNode(ConfirmComponent, { ...options, getShowModal: getShowFn,leftClick,rightClick })
// vue底层会利用diff算法来处理,避免重新渲染
render(vnode, document.body)
})
}
}
typeConfirm如下
export interface typeConfirm {
title: string,
leftText?: string,
rightText?: string,
}