在components/modal文件夹下中新增两个文件,index.vue 和 index.ts(vue2为js文件)
index.vue 为弹窗组件
index.ts 中实现 将model.vue组件暴露出去,可在其他页面使用js方式调用该弹窗组件
vue3方式
index.vue
<template>
<div class="modal-container">
<div class="modal-mask" @click="close" v-if="visible"></div>
<transition name="bounce">
<div class="custom-modal"
ref="modalRef"
:class="[customClass, {FullScreenClass: selfProps?.fullScreen && isFullScreen}]" v-if="visible">
<div class="back-img">
<img v-if="selfProps?.background" :src="selfProps?.fullScreen && isFullScreen ? fullScreenImg : selfProps?.background" />
<div v-else class="back_bg"></div>
</div>
<div class="modal-header">
<slot name="header">
<div class="header-title">
{{ selfProps?.title }}
</div>
</slot>
<div class="header-btns">
<div class="close" v-if="selfProps?.fullScreen" :title="tip()" @click="handleFullScreen">
<img v-if="isFullScreen" class="closeImg1" src="@/assets/images/fullScreenOut.png" alt="" />
<img v-else class="closeImg1" src="@/assets/images/fullScreen.png" alt="" />
</div>
<div class="close" v-if="selfProps?.showClose" title="关闭" @click="close">
<img class="closeImg" src="@/assets/images/close.png" alt="" />
</div>
</div>
</div>
<div class="modal-body">
<component :is="selfProps?.com" v-bind="{ ...selfProps.params }" />
</div>
</div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef } from "vue";
import { TModalProps } from "./";
import defaultImg from "@/assets/images/modal_bg.png";
import fullScreenImg from "@/assets/images/modal_bg_16_9.png";
const props = defineProps<{ id: string; mInsMapping: Record<string, any> }>();
const selfProps = shallowRef<TModalProps>({
width: 1400,
// height: 800,
height: 787.5,
headHeight: 76,
fullScreen: true,
showClose: true,
background: defaultImg,
customClass: undefined,
com: () => { },
params: {},
});
const visible = ref<boolean>(false);
const modalWidth = ref<number | undefined>(0);
const modalHeight = ref<number | undefined>(0);
const headHeight = ref<number | undefined>(0);
const customClass = ref<string | undefined>();
const open = (params: TModalProps) => {
console.log(params);
Object.assign(selfProps.value, params);
modalWidth.value = params.width || selfProps.value.width;
modalHeight.value = params.height || selfProps.value.height;
if (params.title) {
headHeight.value = params.headHeight || selfProps.value.headHeight;
}
customClass.value = params.customClass || "";
visible.value = true;
};
const close = () => {
visible.value = false;
isFullScreen.value = false;
let dom = document.querySelector(`#${props.id}`);
dom?.parentElement?.removeChild(dom);
delete props.mInsMapping[props.id];
};
defineExpose({
close,
open,
});
const modalRef = ref()
const isFullScreen = ref(true)
function tip() {
return isFullScreen.value ? "退出全屏" : "全屏";
}
function handleFullScreen() {
isFullScreen.value = !isFullScreen.value;
}
</script>
<style lang="scss" scoped>
* {
box-sizing: border-box;
}
.modal-container {
.modal-mask {
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
z-index: 999;
width: 100%;
height: 100%;
background-color: rgba($color: #000, $alpha: 0.55);
}
}
.custom-modal {
position: fixed;
margin: auto;
z-index: 1000;
overflow: hidden;
box-sizing: border-box;
// border-radius: width(10);
background-size: cover;
width: width(v-bind(modalWidth));
height: height(v-bind(modalHeight));
left: calc(calc(100% - width(v-bind(modalWidth))) / 2);
top: calc(calc(100% - height(v-bind(modalHeight))) / 2);
&.FullScreenClass {
width: 100vw;
height: 100vh;
left: 0;
top: 0;
}
.back-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
>img {
object-fit: fill;
width: 100%;
height: 100%;
}
.back_bg {
width: 100%;
height: 100%;
background-color: #011a46;
}
}
.modal-header {
width: auto;
height: height(v-bind(headHeight));
line-height: height(v-bind(headHeight));
display: flex;
justify-content: center;
align-items: center;
.header-title {
font-size: 32px;
font-family: alihei;
font-weight: bold;
color: #fff;
text-align: center;
letter-spacing: 1px;
text-shadow: 0px 2px 4px rgba(8, 52, 133, 0.5);
background-clip: text;
-webkit-background-clip: text;
position: relative;
padding: 0 width(80);
&::before {
content: "";
position: absolute;
width: width(40);
height: width(28);
@include bgImg("@/assets/images/modal_title.png");
background-size: cover;
background-position: left top;
left: width(-45);
top: calc(50% - width(14));
}
&::after {
content: "";
position: absolute;
width: width(40);
height: width(28);
@include bgImg("@/assets/images/modal_title.png");
background-size: cover;
background-position: left top;
right: width(-45);
// top:height((v-bind(headHeight) / 2) + 14);
top: calc(50% - width(14));
}
}
.header-btns {
z-index: 333;
line-height: 0;
position: absolute;
right: width(20);
top: height(20);
display: flex;
justify-content: center;
align-items: center;
color: #fff;
text-indent: 0px;
line-height: 0;
.close {
cursor: pointer;
font-size: 18px;
width: width(42);
height: width(42);
display: flex;
align-items: center;
justify-content: center;
.closeImg {
width: width(20);
height: width(20);
}
.closeImg1 {
width: width(30);
height: width(30);
}
}
}
}
.modal-body {
width: 100%;
height: calc(100% - height(20) - height(v-bind(headHeight)));
position: relative;
overflow-y: auto;
padding: 0 width(20);
// background-image: linear-gradient(#144e9c, #0b3d80);
&::-webkit-scrollbar {
/*滚动条整体样式*/
width: width(5);
/*高宽分别对应横竖滚动条的尺寸*/
height: height(1);
}
&::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 0.2rem;
background-color: #cbcbcb;
}
&::-webkit-scrollbar-track {
/*滚动条里面轨道*/
box-shadow: inset 0 0 0.5rem rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, 0.2);
border-radius: 1rem;
}
}
}
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.15);
}
100% {
transform: scale(1);
}
}
</style>
index.ts文件
import FnModal from "./index.vue"; // 自定义弹窗组件
import { h, render, VNode } from "vue";
import {getUuid} from "@/utils";
const mInsMapping: Record<string, any> = {};
export type TModalProps = {
mountEl?: HTMLElement,
title?: string,
width?: number,
height?: number,
headHeight?: number,
fullScreen?: boolean,
showClose?: boolean,
background?: string,
com?: any,
params?: Record<string, any>,
customClass?:string
}
const renderModal = (id: string, mountEl?: HTMLElement): VNode => {
const container = document.createElement("div");
container.id = id;
Object.assign(container.style, {
position: "fixed",
top: 0,
left: 0,
zIndex: 2000, // element-plus 的el-popper初始为2001开始
PointerEvent: "none",
});
const modalVNode = h(FnModal, {id, mInsMapping});
render(modalVNode, container);
if (!mountEl) {
mountEl = document.body;
}
mountEl?.appendChild(container);
return modalVNode;
}
const _EgModal = (params: TModalProps) => {
let MODAL_ID ="MODAL_ID_"+getUuid();
mInsMapping[MODAL_ID] = renderModal(MODAL_ID, params.mountEl);
const modalVue = mInsMapping[MODAL_ID].component;
modalVue?.exposed?.open?.(params);
return modalVue;
}
const EgModal = _EgModal;
export default EgModal;
调用方式:
let modal = useModal({
title,
width: 1400,
height: 850,
fullScreen: false,
com: SocialCenterDetail, // SocialCenterDetail 弹窗内容组件
params: {
...params,
onClose: () => {
modal?:exposed?.close() // 调用弹窗组件的关闭弹窗事件
}
},
});
vue2方式
import Vue from "vue"
import store from "@/store";
import FnModal from "./index.vue";
const ModalConstructor = Vue.extend(FnModal)
export const MODAL_ID = "MODAL_IDS";
export const Props = {
selfProps: {
width: 842,
height: 787,
headHeight: 60,
background: require("@/static/images/com-bg.png"),
com: () => { },
params: {},
},
visible: false,
modalWidth: 0,
modalHeight: 0,
headHeight: 0,
};
const container = document.createElement("div");
container.id = MODAL_ID;
document.body.appendChild(container);
Object.assign(container.style, {
position: "fixed",
top: 0,
left: 0,
zIndex: 2000,
PointerEvent: "none",
});
const createModal = () => {
const instance = new ModalConstructor({ store }).$mount();
container.appendChild(instance.$el);
return instance
}
const useModal = (params = {
title: "title",
width: null,
height: null,
headHeight: null,
background: null,
com: () => { },
params: {}
}) => {
let modalInstance = null;
modalInstance = createModal();
modalInstance.open(params);
return modalInstance;
}
export default useModal;
调用方式:
let modal = useGasModal({
title: item.dialogTitle || item.title,
headHeight: 84,
width: 1280,
height: 768,
com: item.com,
params: Object.assign({ areaCode: state.areaCode }, item.params, {
events: {
'close': () => {
modal.close()
},
...item.events
}
})
});
参数和事件绑定时需要注意,在vue2和vue3中动态组件绑定事件有所区别,在vue3中可以使用 v-bind=“{ onClose: ()=>{}}”,
在vue2中需要用 v-on="close: ()=>{}"来绑定。