我们在开发的时候很多组件都带有弹窗,但是有时候,我们不想用组件,但是需要用到弹窗,是否可以自己封装一个呢?
那么我们就自己封装一个
效果图
弹窗是支持拖拽的
思路
写这么一个弹窗不复杂,复杂的是拖拽的逻辑,在点击标题栏的时候,我们通过鼠标按下事件记录鼠标已经按下,同时记录 鼠标点在 弹窗的位置,在鼠标移动的时候,判断鼠标是否已经点在标题上,鼠标移动的过程中用鼠标的位置减去初始鼠标点在弹窗的位置,就可以实现拖动。
主要代码
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>