一、最终效果
mydialog
二、组件代码
<template>
<teleport to="body">
<div v-if="isOpen" class="modal">
<div ref="myContentDIV" class="modal-content" :style="contentstyle">
<div class="modal-header">
<!-- 头部 -->
<slot name="header"></slot>
</div>
<div class="modal-body">
<!-- 内容 -->
<slot></slot>
</div>
<div class="modal-footer">
<!-- 底部 -->
<slot name="footer"></slot>
</div>
<!-- 缩放bar -->
<div ref="rightBar" class="right-bar"></div>
<div ref="lowerRightBar" class="lower-right-corner-bar"></div>
<div ref="bottomBar" class="bottom-bar"></div>
</div>
</div>
</teleport>
</template>
<script setup>
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
// 组件属性
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
},
});
// 事件发射
const emit = defineEmits(["update:modelValue"]);
// 模态框开关
const isOpen = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
});
const contentstyle = ref({ width: '800px', height: '700px' });
const myContentDIV = ref(null);
const rightBar = ref(null);
const lowerRightBar = ref(null);
const bottomBar = ref(null);
const scale = ref({
xscale: 1,
yscale: 1
});
// 双轴缩放
const handleResizeXY = (event) => {
const newRation = {
xscale: 0,
yscale: 0,
}
document.onmousemove = function (e) {
e.preventDefault();
const moveScale = {
xscale: (e.clientX - event.clientX) / (document.body.clientWidth),
yscale: (e.clientY - event.clientY) / (document.body.clientHeight)
}
newRation.xscale = scale.value.xscale + moveScale.xscale * 2;
newRation.yscale = scale.value.yscale + moveScale.yscale * 2;
if (newRation.xscale > 0.3 && newRation.xscale < 0.95) {
contentstyle.value.width = (document.body.clientWidth * newRation.xscale).toString() + 'px';
}
if (newRation.yscale > 0.3 && newRation.yscale < 0.95) {
contentstyle.value.height = (document.body.clientHeight * newRation.yscale).toString() + 'px';
}
};
document.onmouseup = function (e) {
scale.value.xscale = newRation.xscale > 0.95 ? 0.95 : (newRation.xscale < 0.3 ? 0.3 : newRation.xscale);
scale.value.yscale = newRation.yscale > 0.95 ? 0.95 : (newRation.yscale < 0.3 ? 0.3 : newRation.yscale);
document.onmousemove = null;
document.onmouseup = null;
};
};
// x轴缩放
const handleResizeX = (event) => {
const newRation = {
xscale: 0,
}
document.onmousemove = function (e) {
e.preventDefault();
const moveScale = {
xscale: (e.clientX - event.clientX) / (document.body.clientWidth),
}
newRation.xscale = scale.value.xscale + moveScale.xscale * 2;
if (newRation.xscale > 0.3 && newRation.xscale < 0.95) {
contentstyle.value.width = (document.body.clientWidth * newRation.xscale).toString() + 'px';
}
};
document.onmouseup = function (e) {
scale.value.xscale = newRation.xscale > 0.95 ? 0.95 : (newRation.xscale < 0.3 ? 0.3 : newRation.xscale);
document.onmousemove = null;
document.onmouseup = null;
};
};
// y轴缩放
const handleResizeY = (event) => {
const newRation = {
yscale: 0,
}
document.onmousemove = function (e) {
e.preventDefault();
const moveScale = {
yscale: (e.clientY - event.clientY) / (document.body.clientHeight)
}
newRation.yscale = scale.value.yscale + moveScale.yscale * 2;
if (newRation.yscale > 0.3 && newRation.yscale < 0.95) {
contentstyle.value.height = (document.body.clientHeight * newRation.yscale).toString() + 'px';
}
};
document.onmouseup = function (e) {
scale.value.yscale = newRation.yscale > 0.95 ? 0.95 : (newRation.yscale < 0.3 ? 0.3 : newRation.yscale);
document.onmousemove = null;
document.onmouseup = null;
};
};
onMounted(() => {
// 初始化比例
if (myContentDIV.value) {
scale.value.xscale = myContentDIV.value.getBoundingClientRect().width / (document.body.clientWidth);
scale.value.yscale = myContentDIV.value.getBoundingClientRect().height / (document.body.clientHeight);
rightBar.value.addEventListener('mousedown', handleResizeX, false);
lowerRightBar.value.addEventListener('mousedown', handleResizeXY, false);
bottomBar.value.addEventListener('mousedown', handleResizeY, false);
}
})
// 监听 isOpen 状态变化
watch(isOpen, (newValue) => {
if (newValue) {
// 初始化比例
nextTick(() => {
scale.value.xscale = myContentDIV.value.getBoundingClientRect().width / document.body.clientWidth;
scale.value.yscale = myContentDIV.value.getBoundingClientRect().height / document.body.clientHeight;
rightBar.value.addEventListener('mousedown', handleResizeX, false);
lowerRightBar.value.addEventListener('mousedown', handleResizeXY, false);
bottomBar.value.addEventListener('mousedown', handleResizeY, false);
})
} else {
rightBar.value.removeEventListener('mousedown', handleResizeX);
lowerRightBar.value.removeEventListener('mousedown', handleResizeXY);
bottomBar.value.removeEventListener('mousedown', handleResizeY);
document.onmousemove = null;
document.onmouseup = null;
}
});
// 在组件卸载前移除事件监听
onUnmounted(() => {
rightBar.value.removeEventListener('mousedown', handleResizeX);
lowerRightBar.value.removeEventListener('mousedown', handleResizeXY);
bottomBar.value.removeEventListener('mousedown', handleResizeY);
document.onmousemove = null;
document.onmouseup = null;
});
</script>
<style scoped>
.modal {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999999;
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
width: 100%;
position: relative;
}
.modal-header {
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.modal-body {
padding: 20px 0;
height: calc(100% - 100px);
}
.modal-footer {
padding: 0 0 10px 0;
height: 60px;
}
.right-bar {
position: absolute;
top: 0;
right: 0;
height: calc(100% - 20px);
width: 10px;
cursor: col-resize;
}
.right-bar::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
background-color: rgba(0, 0, 0, .15);
width: 1.5px;
height: 30px;
margin-left: -2px;
}
.right-bar::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
background-color: rgba(0, 0, 0, .15);
width: 1.5px;
height: 30px;
margin-left: 1px;
}
.lower-right-corner-bar {
position: absolute;
bottom: 0;
right: 0;
height: 20px;
width: 20px;
cursor: crosshair;
}
.lower-right-corner-bar::after {
content: "";
position: absolute;
top: 1px;
right: 3px;
border-width: 8px;
border-style: solid;
border-color: transparent rgba(0, 0, 0, .15) rgba(0, 0, 0, .15) transparent;
}
.bottom-bar {
position: absolute;
bottom: 0;
left: 0;
height: 10px;
width: calc(100% - 20px);
cursor: row-resize;
}
.bottom-bar::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
background-color: rgba(0, 0, 0, .15);
width: 30px;
height: 1.5px;
margin-top: -2px;
}
.bottom-bar::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
background-color: rgba(0, 0, 0, .15);
width: 30px;
height: 1.5px;
margin-top: 1px;
}
</style>
三、使用实例
<template>
<div class="control">
<button class="pretty-button" @click="open">打开弹窗</button>
<MyDialog v-model="dialogVisible">
<template #header>
<div style="color:#000000">
<h4 style="margin-right: 40px;">头部内容</h4>
<button style="position: absolute;top: 10px;right: 20px;background-color: #ffffff;"
@click="colse">X</button>
</div>
</template>
<div style="color:#000000">主体内容</div>
<template #footer>
<div style="display: flex;justify-content:flex-end;width: 100%;user-select: none;">
<button class="pretty-button" @click="colse">确认</button>
</div>
</template>
</MyDialog>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MyDialog from '@/components/myDialog/index.vue';
const dialogVisible = ref(false);
function open() {
dialogVisible.value = true;
}
function colse() {
dialogVisible.value = false;
}
</script>
<style scoped>
.pretty-button {
background-color: #67c23a;
border: none;
color: white;
padding: 5px 12px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 5px;
transition: all 0.3s;
min-width: 62px;
}
.pretty-button:hover {
background-color: #529b2e;
}
</style>