博客 分类专栏 专题 成员
自己封装一个Vue 弹窗
2022-11-01 03:16:24
分类专栏: Vue3

我们在开发的时候很多组件都带有弹窗,但是有时候,我们不想用组件,但是需要用到弹窗,是否可以自己封装一个呢?
那么我们就自己封装一个

效果图

图片
弹窗是支持拖拽的

思路

写这么一个弹窗不复杂,复杂的是拖拽的逻辑,在点击标题栏的时候,我们通过鼠标按下事件记录鼠标已经按下,同时记录 鼠标点在 弹窗的位置,在鼠标移动的时候,判断鼠标是否已经点在标题上,鼠标移动的过程中用鼠标的位置减去初始鼠标点在弹窗的位置,就可以实现拖动。

主要代码

template

<template>
  <div v-if="show">
    <div
      class="easy-mask"
      :style="{ 'z-index': zIndex - 1 }"
      v-if="showMask"
    ></div>
    <div
      class="easy-dialog"
      :style="{
        'z-index': zIndex,
        width: width + 'px',
        left: dialogPosition.left + 'px',
        top: dialogPosition.top + 'px',
      }"
    >
      <div class="header" @mousedown="mounseDownHandler">
        <span class="title">{{ title }}</span>
        <span class="close" @click="close">x</span>
      </div>
      <div class="body" :style="{ 'max-height': bodyMaxHeight + 'px' }">
        <slot></slot>
      </div>
      <div class="footer" v-if="buttons.length > 0 || showCancel">
        <a href="javascript:void(0)" class="btn cancel" @click="close">取消</a>
        <a
          href="javascript:void(0)"
          v-for="btn in buttons"
          class="btn"
          @click="btn.click"
        >
          {{ btn.text }}
        </a>
      </div>
    </div>
  </div>
</template>

js

<script setup>
import { reactive, ref, onMounted, watch, nextTick } from "vue";

const props = defineProps({
  show: {
    type: Boolean,
    default: false,
  },
  showMask: {
    type: Boolean,
    default: true,
  },
  title: {
    type: String,
    default: "",
  },
  showClose: {
    type: Boolean,
    default: true,
  },
  buttons: {
    type: Array,
    default: [],
  },
  showCancel: {
    type: Boolean,
    default: true,
  },
  width: {
    type: Number,
    default: 500,
  },
  top: {
    type: Number,
    default: 50,
  },
});

const initZIndex = 100;
const zIndex = ref(initZIndex);

//body最大高度   标题栏 高度40  底部按钮40 距离底部距离10
const bodyMaxHeight = window.innerHeight - props.top - 40 - 40 - 10;
//定义拖动效果
const emit = defineEmits(["close"]);
const close = () => {
  emit("close");
};

const dialogPosition = reactive({
  top: props.top,
  left: (window.innerWidth - props.width) / 2,
});
const monseDown = ref(false);
//鼠标点击弹窗初始位置
const initX = ref(0);
const initY = ref(0);
//最大left
const maxLeft = window.innerWidth - props.width - 2;
//鼠标点下
const mounseDownHandler = (e) => {
  monseDown.value = true;
  //记录初始 鼠标点击弹窗的位置
  initX.value = e.clientX - dialogPosition.left;
  initY.value = e.clientY - dialogPosition.top;
};

const init = () => {
  document.addEventListener("mouseup", (e) => {
    monseDown.value = false;
  });

  //解决拖拽过程中 无法监听到 mouseUp事件
  document.addEventListener("dragstart", (e) => {
    e.preventDefault();
  });

  document.addEventListener("dragend", (e) => {
    e.preventDefault();
  });

  document.addEventListener("mousemove", (e) => {
    if (!monseDown.value) {
      return;
    }
    let currentTop = e.clientY - initY.value;
    let currentLeft = e.clientX - initX.value;
    if (currentTop <= 0) {
      currentTop = 0;
    }
    if (currentLeft <= 0) {
      currentLeft = 0;
    }
    if (currentLeft >= maxLeft) {
      currentLeft = maxLeft;
    }
    dialogPosition.top = currentTop;
    dialogPosition.left = currentLeft;
  });
};

onMounted(() => {
  init();
});

//监听弹窗数量,修改zindex
watch(
  () => props.show,
  (oldVal, newVal) => {
    if (oldVal) {
      nextTick(() => {
        let easyDialogCount = document.querySelectorAll(".easy-dialog").length;
        zIndex.value = initZIndex + easyDialogCount;
        console.log(zIndex.value);
      });
    }
  },
  { immediate: true, deep: true }
);
</script>

css

<style lang="scss" scoped>
.easy-mask {
  position: fixed;
  left: 0px;
  top: 0px;
  width: 100%;
  height: calc(100vh);
  background: rgba(0, 0, 0, 0.5);
}
.easy-dialog {
  background: #fff;
  position: absolute;
  top: 50px;
  left: 30px;
  border: 1px solid #ddd;
  div {
    box-sizing: border-box;
  }
  .header {
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 10px 10px;
    border-bottom: 1px solid #ddd;
    cursor: move;
    .title {
      font-size: 14px;
      flex: 1;
      overflow: auto;
    }
    .close {
      width: 10px;
      margin-left: 5px;
      color: rgb(133, 133, 133);
      cursor: pointer;
    }
    .close:hover {
      color: #060606;
    }
  }
  .body {
    padding: 10px;
    min-height: 50px;
    overflow: auto;
  }
  .footer {
    border-top: 1px solid #ddd;
    text-align: right;
    height: 40px;
    line-height: 40px;
    .btn {
      font-size: 14px;
      text-decoration: none;
      display: inline-block;
      margin-right: 10px;
      background: rgb(5, 172, 244);
      color: #fff;
      border-radius: 5px;
      padding: 5px 15px;
      line-height: normal;
    }
    .btn:hover {
      opacity: 0.8;
    }
    .cancel {
      color: rgb(18, 157, 243);
      background: none;
    }
  }
}
</style>