vue3编程 -动态多开模态框实现方案

页面按需弹出多个模态框:

一、v-for方案:

采用v-for提前生成多个模态框实例,采用这种方案的案例社区已经很多

二、采用h函数方案:

代码如下,代码可运行:

基本思路:

  1. 封装模态框组件DialogModal

2.通过h()函数创建DialogModal的虚拟dom

  1. 通过创建容器元素+vue的mount()函数进行挂载

  2. 模态框关闭后需要解除挂载,通过vue的unmoun()函数

如果模态框是自行开发的,到此应该就可以结束了。但是,基于element plus的模态框,会存在当前模态框阻挡后面模态框点击的问题,解决方案如下:

css 复制代码
.modal-class {
  pointer-events: none;
}
.el-overlay-dialog {
  pointer-events: none;
}
.el-dialog {
  pointer-events: auto;
}

完整代码如下:

javascript 复制代码
<script setup lang="ts">
import DialogModal from '@/components/DialogModal.vue'
import { ref, onUnmounted, h, createApp } from 'vue'
import { ElMessage } from 'element-plus'

const modals = ref([])

const showModal = () => {
  let num = Math.random()
  createModal({ title: 'test' + num, showMsg: 'hello' + num })
}

const createModal = config => {
  const modalRef = ref()
  const modalInstance = h(DialogModal, {
    ref: modalRef,
    title: config.title,
    content: config.showMsg,
    onClosed: () => {
      removeModal(modalRef.value)
    },
    onConfirmed: () => {
      removeModal(modalRef.value)
      ElMessage.success('模态框已关闭')
    }
  })
  console.log('modalInstance', modalInstance)
  modals.value.push(modalRef)

  // 创建虚拟 DOM 并挂载到文档
  const container = document.createElement('div')
  container.id = 'modal-container' + Math.random()
  document.body.appendChild(container)

  const app = createApp(modalInstance)
  app.mount(container)

  return {
    unmount: () => {
      app.unmount()
      document.body.removeChild(container)
    }
  }
}

const removeModal = modal => {
  if (modal) {
    modal.unmount()
    const index = modals.value.findIndex(m => m.component === modal)
    if (index !== -1) {
      modals.value.splice(index, 1)
    }
  }
}

onUnmounted(() => {
  modals.value.forEach(modal => {
    modal.component.unmount()
  })
})
</script>

<template>
  <div>
    <el-button @click="showModal">显示模态框</el-button>
  </div>
  <router-view></router-view>
</template>
javascript 复制代码
<template>
  <div :class="{ scrollDialog: scrollVisible }">
    <el-dialog
      v-model="dialogVisible"
      :width="props.width"
      :destroy-on-close="true"
      :close-on-click-modal="false"
      :modal="false"
      draggable
      :z-index="-1"
      :fullscreen="false"
      @close="closeDialog"
      :modal-class="'modal-class'"
    >
      <template v-slot:header>
        <span class="cus-title">{{ props.title }}</span>
      </template>
      <slot>{{ props.showMsg }}</slot>
      <template v-slot:footer>
        <span v-if="props.dialogFoot || props.dialogFix" class="dialog-footer">
          <el-button v-if="props.dialogFoot" @click="closeDialog">{{ cancleName }}</el-button>
          <el-button v-if="props.dialogFix" type="primary" @click="determine">{{ determineName }}</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import { defineProps, ref, defineExpose, defineEmits } from 'vue'

const emits = defineEmits(['determine', 'closeDialog'])

const props = defineProps({
  width: {
    // 弹框宽度
    type: String,
    default: '480px'
  },
  title: {
    // 弹框模块名称
    type: String,
    default: () => {
      return ''
    }
  },
  showMsg: {
    // 展示的信息
    type: String,
    default: ''
  },
  dialogFoot: {
    // 显示取消代码
    type: Boolean,
    default: true
  },
  dialogFix: {
    // 显示确定按钮
    type: Boolean,
    default: true
  },
  determineName: {
    // 确定键名称
    type: String,
    default: '确认'
  },
  cancleName: {
    // 取消键名称
    type: String,
    default: '取消'
  }
})
// 弹框显示状态
let dialogVisible = ref(true)
// 控制滚动条样式,超过10条显示滚动条
let scrollVisible = ref(false)
/**
 * 打开模态框
 */
function openDialog() {
  if (dialogVisible.value) {
    return
  }
  dialogVisible.value = true
}

/**
 * 关闭模态框
 */
function closeDialog() {
  if (!dialogVisible.value) {
    return
  }
  dialogVisible.value = false
  emits('closeDialog') // 用户自定义取消后函数,例如:清空表单数据
}

/**
 * @description: 确定事件
 * @param null
 * @return: null
 */
function determine() {
  emits('determine') // 触发组件上的determine事件,用户自定义提交
  dialogVisible.value = false
}
/**
 * 对外提供打开和关闭模态框,控制模态框和滚动条的显隐
 */
defineExpose({ closeDialog, openDialog, dialogVisible, scrollVisible })
</script>
<style scoped lang="scss">
// @import '@/assets/scss/auditPublic.scss';
.el-dialog__body {
  padding: 20px;
}
.el-dialog__footer {
  border-top: 1px solid #e9ebef;
}
.cus-title {
  width: 100%;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
  font-size: 16px;
  color: #031129;
  display: inline-block;
}
:deep(.el-transfer .el-transfer-panel__item.el-checkbox) {
  height: auto !important;
}

:deep(.el-dialog__body) {
  max-height: 400px !important;
}

.scrollDialog {
  :deep(.el-dialog__body) {
    overflow-y: auto !important;
    height: 400px !important;
  }
}
</style>
<style>
.modal-class {
  pointer-events: none;
}
.el-overlay-dialog {
  pointer-events: none;
}
.el-dialog {
  pointer-events: auto;
}
</style>
相关推荐
小堃学编程5 分钟前
前端学习(1)—— 使用HTML编写一个简单的个人简历展示页面
前端·javascript·html
hnlucky1 小时前
通俗易懂版知识点:Keepalived + LVS + Web + NFS 高可用集群到底是干什么的?
linux·前端·学习·github·web·可用性测试·lvs
懒羊羊我小弟1 小时前
使用 ECharts GL 实现交互式 3D 饼图:技术解析与实践
前端·vue.js·3d·前端框架·echarts
前端小巷子1 小时前
CSS3 遮罩
前端·css·面试·css3
运维@小兵1 小时前
vue访问后端接口,实现用户注册
前端·javascript·vue.js
雨汨1 小时前
web:InfiniteScroll 无限滚动
前端·javascript·vue.js
小盐巴小严2 小时前
正则表达式
javascript·正则表达式
Samuel-Gyx2 小时前
前端 CSS 样式书写与选择器 基础知识
前端·css
天天打码3 小时前
Rspack:字节跳动自研 Web 构建工具-基于 Rust打造高性能前端工具链
开发语言·前端·javascript·rust·开源
AA-代码批发V哥3 小时前
正则表达式: 从基础到进阶的语法指南
java·开发语言·javascript·python·正则表达式