使用vue封装一个可以弹出多个弹出框的组件
需求的背景是客户想要同时可以弹出多个弹出框,而不是dialog那种,每次弹出一个,然后下次再点击就只能响应式的在同一个弹出框里变化信息。
总体来说是使用了vue的 h函数,每次都动态创建一个弹出框,把弹出框内部dom内容copy一份。
1. 弹出框组件
<template> <div class="popup-panel" v-if="showPanel" ref="popupPanel" :style="{left:x,top:y}" > <div class="header" v-if="showHeader" @mousedown="startDrag"> <div class="title">{{title}}</div> <div class="operate-button"> <div @click="minimizePopup" v-if="!isMin" > <i class="iconfont iconfont icon-minus"></i> </div> <div @click="resizePopup" v-else > <span class="icon iconfont icon-max"></span> </div> <div @click="closePopup"> <span class="icon iconfont icon-close"></span> </div> </div> </div> <div class="body" v-show="!isMin"> <slot></slot> </div> <div class="footer" v-if="showFooter">aa</div> </div> </template> <script setup> import { ref, computed, onMounted, defineProps } from 'vue'; // import VueDraggableResizable from 'vue-draggable-resizable'; // import 'vue-draggable-resizable/dist/VueDraggableResizable.css'; const props = defineProps({ showHeader: { type: Boolean, default: true, }, showFooter: { type: Boolean, default: false, }, draggable: { type: Boolean, default: true }, title: { type: String, default: '标题' }, showPanel: { type: Boolean, default: false }, xPos: { type: String, default: '' }, yPos: { type: String, default: '' }, }); const emit = defineEmits(['close','minimize']); const isMin = ref(false); const x = ref('50%'); const y = ref('150px'); const popupPanel = ref(null); let isDragging = false; let dragOffset = ref({ x: 0, y: 0 }); onMounted(() => { debugger if (props.xPos) { x.value = props.xPos; } if (props.yPos) { y.value = props.yPos; } }); function startDrag(event) { if(props.draggable) { isDragging = true; dragOffset.value.x = event.clientX - popupPanel.value.offsetLeft; dragOffset.value.y = event.clientY - popupPanel.value.offsetTop; document.addEventListener('mousemove', onDrag); document.addEventListener('mouseup', stopDrag); } } function onDrag(event) { if(isDragging) { x.value = event.clientX - dragOffset.value.x + 'px'; y.value = event.clientY - dragOffset.value.y + 'px'; } } function stopDrag() { isDragging = false; document.removeEventListener('mousemove', onDrag); document.removeEventListener('mouseup', stopDrag); } const closePopup = () => { emit('close',props.showPanel); }; const minimizePopup = () => { emit('minimize'); isMin.value = true; }; function resizePopup() { isMin.value = false; } </script> <style scoped lang="scss"> .popup-panel { width: 750px; max-height: 750px; overflow: auto; position: fixed; background: white; z-index: 999; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); top:15%; left:50%; Transform:translate(-50%,0%); .header { cursor: move; width: 100%; height: 45px; padding: 4px 16px 4px 16px; display: flex; color: white; justify-content: space-between; align-items: center; background: var(--primary-color); .title { display: flex; align-items: center; font-weight: bold; } .operate-button { display: flex; align-items: center; div { width: 20px; height: 20px; margin-left: 16px; cursor: pointer; } } } .body { width: 100%; max-height: 650px; overflow: auto; } } </style>
2. popupmanager.js
import { h, render } from "vue"; import CustomPopup from "@/components/common/custom-popup/index"; import { v4 as uuidv4 } from 'uuid'; const popupInstances = []; // 存储所有弹出框实例 const createPopup = (content) => { // 生成随机数作为id const randomId = uuidv4(); const container = document.createElement('div'); document.body.appendChild(container); let zIndex = setZindexForNewContainer(); container.style.zIndex = zIndex; container.style.position = 'relative'; container.className = 'container' +randomId; container.addEventListener('click',function (e) { // 点击到的话就把Zindex提到最上面 setTopZIndex(e.currentTarget); }); const Pos = getNewNodePos(); // 克隆传入的内容节点,确保每次内容独立 const contentNode = content.cloneNode(true); contentNode.style.display='block'; // 显示 // 把content的虚拟元素插入到custom-popup组件中 const vnode = h(CustomPopup, { showPanel: true, title: '预警业务会商记录表预览', xPos: Pos.left? Pos.left + 'px':'', yPos: Pos.top? Pos.top + 'px':'', onClose: () =>{ // 关闭时销毁实例 const index = popupInstances.findIndex(item => item.id === randomId); if (index > -1) { popupInstances.splice(index, 1); } container.remove(); }, onVnodeMounted: () => { // 在组件加载完之后挂载contentNode const div= document.getElementById(randomId) // div的同级插入contentNode div.parentNode.insertBefore(contentNode, i); content.id = randomId; // 删除 div div.remove(); } },{ default: h('div', {id:randomId}) }); // 渲染组件 render(vnode, container); // 保存实例用于后续管理 popupInstances.push({ id:randomId,vnode,container:container,zIndex: zIndex }); }; // 获取当前所有弹出框实例(可用于调试或统一关闭) const getAllPopups = () => popupInstances; // 为新的container设置zIndex function setZindexForNewContainer () { //找到所有的顺序 if (popupInstances.length > 0) { let Order = popupInstances.map(item=>{ return item.zIndex; }).sort((a,b)=> { return b-a; })[0]; console.log('Order',Order); Order+=1; return Order; } else { return 9999; } } // 获取新的节点位置 function getNewNodePos () { if (popupInstances.length > 0) { const TopContainer = popupInstances.sort((a,b)=>{ return b.zIndex - a.zIndex; })[0]; // 找到container 的第一个子元素 const firstChild = TopContainer.container.children[0]; const top = firstChild.offsetTop + 45; const left = firstChild.offsetLeft + 10; return {top,left}; } else { return {top: '', left: ''}; } } // 设置最上面 function setTopZIndex (dom) { // 找到最大的ZIndex const maxZIndex = setZindexForNewContainer(); for(let i = 0; i < popupInstances.length; i++){ if (popupInstances[i].container.className == dom.className) { popupInstances[i].zIndex = maxZIndex; dom.style.zIndex = popupInstances[i].zIndex; } } } // 关闭所有弹出框 function closeAllPopups () { for(let i = 0; i < popupInstances.length; i++){ popupInstances[i].container.remove(); } popupInstances.splice(0, popupInstances.length); } export default { createPopup, getAllPopups, closeAllPopups };
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2024-01-26 github action 自动化部署asp.net core应用到服务器
2024-01-26 git branch -r 出现了远程仓库已经删除的分支