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>
相关推荐
ekskef_sef1 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6411 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻2 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云2 小时前
npm淘宝镜像
前端·npm·node.js
dz88i82 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr2 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
程序员_三木2 小时前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
顾平安3 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网3 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工3 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染