使用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
};
复制代码

 

posted @   拎着红杯子的黄鸭子  Views(63)  Comments(0Edit  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2024-01-26 github action 自动化部署asp.net core应用到服务器
2024-01-26 git branch -r 出现了远程仓库已经删除的分支
点击右上角即可分享
微信分享提示