使用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
};

浙公网安备 33010602011771号