【升级打怪实录】uniapp 全局弹窗实现方案

490 阅读2分钟

需求描述: 我希望用户不论在哪个页面,在收到某个消息时都会弹出一个弹窗。

技术原理: vue 中可以在 app.vue 中定义一个弹窗,这个弹窗会在所有组件中上层弹出。但是 uniapp 中不可以,uniapp 自身无法实现在所有页面中弹出自定义组件。

实现方案概述

  1. 实现一个弹窗组件(vue 文件)

    定义一个必要的 props 用来控制弹窗显示隐藏;**

    定义一个必要的 emit 用来抛出关闭时要触发的方法。

  2. 定义管理工具(xxx.ts)

管理工具中暴露打开弹窗方法,后续需要显示弹窗时就调用该方法,方法内部主要做了以下几个内容:

  1. 创建一个 DOM
  2. 创建 vue 实例
  3. vue 实例内部以渲染函数的形式加载前边定义的弹窗组件
  4. 将 vue 实例挂载到 DOM 上
  5. vue 实例内部定义 close 方法,通过渲染函数将 close 函数传递给弹窗组件

代码实现

  1. 定义一个你自己的弹窗组件

    // testDialog.vue
    <template>
      <van-dialog v-model:show="showDialog">
        测试内容
        <view class="close-btn" @click="close"> 关闭 </view>
      </van-dialog>
    </template><script setup lang="ts">
    import { ref } from "vue";
    ​
    const props = defineProps({
      // 控制显示隐藏
      visible: Boolean,
      config: {
        type: Object,
        default: () => ({}),
      },
      data: {
        type: Object,
        default: () => ({}),
      },
    });
    ​
    // 控制关闭时触发
    const emit = defineEmits(["close"]);
    ​
    const showDialog = ref(props.visible);
    ​
    // 关闭弹窗时通知父组件
    const close = () => {
      emit("close");
    };
    </script><style lang="scss" scoped></style>
    
  2. 创建管理工具

    // dialogManager.ts
    import { createApp, h, ref } from 'vue'
    import TestDialog from './testDialog.vue'// 创建并显示新弹窗
    export const showDialog = (options?: { config?: any, data?: any }) => {
    ​
      const config = options?.config || {}
      const data = options?.data || {}
    ​
      // 创建唯一ID
      const dialogId = `dialog_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`
      
      // 创建容器元素
      const container = document.createElement('div')
      container.id = dialogId
      document.body.appendChild(container)
      
      // 创建应用实例
      const app = createApp({
        setup() {
          const visible = ref(true)
          
          // 关闭弹窗时卸载
          const closeHandler = () => {
            visible.value = false
            setTimeout(() => {
              // 延迟卸载确保动画完成
              app.unmount()
              document.body.removeChild(container)
              
            }, 300)
          }
          
          // 渲染弹窗组件
          return () => h(TestDialog, {
            visible: visible.value,
            onClose: closeHandler
          })
        }
      })
      
      // 挂载应用
      app.mount(container)
      
      // 存储实例引用
      dialogInstances.value.push({
        id: dialogId,
        container,
        app,
        close: () => app.unmount()
      })
      
      return dialogId
    }
    ​
    

源码地址