vue3中自定义可缩放弹窗dialog组件

一、最终效果

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>
Vue 3 ,你可以使用 `Teleport` 组件来实现弹窗。以下是一个简单的自定义弹窗的示例: 1. 创建一个 `MyDialog.vue` 组件,它包含你想要显示在弹窗的内容: ```vue <template> <div class="dialog"> <h2>{{ title }}</h2> <p>{{ message }}</p> <button @click="$emit('close')">Close</button> </div> </template> <script> export default { props: { title: String, message: String, }, }; </script> <style scoped> .dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: white; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); } </style> ``` 2. 在你的父组件,使用 `Teleport` 组件来渲染弹窗。当需要显示弹窗时,将 `showDialog` 设置为 `true`: ```vue <template> <div> <button @click="showDialog = true">Show Dialog</button> <teleport to="body"> <my-dialog v-if="showDialog" @close="showDialog = false" title="Title" message="Message" /> </teleport> </div> </template> <script> import MyDialog from './MyDialog.vue'; export default { components: { MyDialog, }, data() { return { showDialog: false, }; }, }; </script> ``` 这里我们使用了 `Teleport` 组件将 `MyDialog` 组件渲染到 `body` 元素,以确保它能够在其他元素上方弹出。 当用户点击弹窗的 "Close" 按钮时,我们触发 `close` 事件并将 `showDialog` 设置为 `false`,以关闭弹窗。 这只是一个简单的示例,你可以根据自己的需要对弹窗进行样式和功能的定制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值