主要文件:
Dialog.vue 弹窗组件,可以直接在vue中以<Dialog></Dialog>组件嵌入的形式使用,使用Teleport将组件挂载至外层body中(Teleport在不使用函数方式调起弹窗时有用,如果只使用Dialog.ts,则可以不要)
Dialog.ts函数式调用的封装,可以直接使用函数调起弹窗。弹窗函数式调起(动态创建)一般有(createApp+monted) 和 (render+h)两种方案。
区别
createApp和mount用于创建和挂载整个Vue应用,而render和h函数用于渲染单个组件。
createApp和mount:
1.创建应用实例:使用createApp创建一个Vue应用实例
2.挂载到dom:使用mount方法将这个应用实例挂载到DOM中的一个元素上。
render+h:
1.创建虚拟节点:使用h函数手动创建一个虚拟节点(VNode),这个虚拟节点代表你想要渲染的组件。
2.渲染到DOM:使用render方法将这个虚拟节点渲染到DOM中的一个元素上。
下文采用createApp+mounted方案
具体代码:
//Dialog.vue
<template>
<Teleport to="body">
<div class="popModel" :style="{ backgroundColor: bg }">
<div class="popContent" ref="el" :style="{ left: x + 'px', top: y + 'px' }">
<div class="head">
<span>{{ title }}</span>
<img src="@/assets/imgs/close.png" alt="关闭" @click="cancel()">
</div>
<div class="content">
<slot></slot>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue';
import { useDraggable } from '@vueuse/core' //使用vueuse的拖拽
const props = defineProps<{
title: string, //弹窗标题
bg?: string, //蒙层背景
x?: number, //弹窗初始left位置,未设置默认为窗口中间
y?: number, //弹窗初top始位置,未设置默认为窗口中间
cancel: Function //关闭弹窗的回调
}>()
const el = ref<HTMLElement | null>(null)
const { x, y, style } = useDraggable(el, {
initialValue: { x: document.body.clientWidth / 2, y: document.body.clientHeight / 2 },
})
onMounted(() => {
if (el.value) {
x.value = props.x ?? x.value - el.value.offsetWidth / 2 //设置弹窗初始居中
y.value = props.y ?? y.value - el.value.offsetHeight / 2 //设置弹窗初始居中
setTimeout(() => {
el.value.style.transition = 'unset' //为了使得弹窗出现时有动画,而拖拽时取消动画
});
}
})
</script>
<style scoped>
.popModel {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100vh;
}
.popContent {
background: linear-gradient(to right, rgba(21, 40, 65, 0.9) 60%, rgba(21, 40, 65, 0.6));
position: fixed;
border-radius: 5px;
padding: 20px;
box-sizing: border-box;
transition: all .2s ease-in;
>.head {
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
>span {
font-weight: bold;
font-size: 18px;
color: #21A6FF;
background: linear-gradient(90deg, #3BE1F6 0%, #21A6FF 100%);
-webkit-text-fill-color: transparent;
background-clip: text;
}
>img {
width: 14px;
height: 14px;
object-fit: cover;
cursor: pointer;
}
}
}
</style>
//Dialog.ts
import { createApp, h, type VNode } from "vue";
import DialogVue from "./Dialog.vue";
//也可以直接创建一个函数,而不是在DialogVue对象上加方法
DialogVue.open = (props: any, content: VNode) => {
const mountDom = document.createElement('div')
document.body.appendChild(mountDom)
return new Promise((resolve, reject) => {
const dialogInstance = createApp(h(DialogVue, {
...props,
cancel,
}, content)) //content作为Dialog.vue中的slot
dialogInstance.mount(mountDom) //挂载
/**
* 关闭
*/
function cancel() {
//卸载实例
dialogInstance.unmount()
//卸载容器
mountDom.remove()
//执行回调
resolve(null)
}
})
}
export default DialogVue
函数式调用示例
import Dialog from '@/components/Dialog.js';
//ChildVue为弹窗中嵌入的slot
Dialog.open({ title: '弹窗标题' }, h(ChildVue, props)).then(()=>{console.log('弹窗关闭了')})