vue实现全局调用 自定义弹窗组件

在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: ()=>{}"来绑定。

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue3封装全局弹窗组件的步骤如下: 1. 创建一个Vue实例,作为全局弹窗组件的容器。可以使用`createApp`方法创建Vue实例,并将其挂载到一个DOM元素上。 2. 在全局弹窗组件上定义必要的属性和方法。比如,可以定义一个`visible`属性控制弹窗的显示与隐藏,一个`title`属性用于显示弹窗的标题,一个`content`属性用于显示弹窗的内容等。 3. 在全局弹窗组件内部实现弹窗的样式和交互逻辑。可以使用Vue的模板语法和样式定义实现弹窗的外观和样式效果,并通过Vue的响应式特性,实现弹窗的交互逻辑,比如点击关闭按钮时隐藏弹窗。 4. 添加全局方法,在Vue实例的原型上添加一个方法,用于在任意组件调用弹窗组件。可以使用`app.config.globalProperties`来添加全局方法,以便在任何地方都可以访问到该方法。 5. 在组件中使用全局弹窗组件。在需要显示弹窗组件中,通过调用全局方法来调用弹窗组件。可以通过传递参数的方式,动态设置弹窗的内容和样式。 6. 在全局弹窗组件的内部实现弹窗的生命周期钩子函数,比如`mounted`函数用于在弹窗组件被挂载到DOM后执行相应的逻辑。 通过以上步骤,就可以封装一个可在任何组件中使用的全局弹窗组件。在使用过程中,只需要调用全局方法,传入相应的参数,即可显示自定义弹窗内容和样式,提供更好的用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值