浏览代码

feat: The dialog supports custom-defined window size. #527

lt5227 8 月之前
父节点
当前提交
e6affad67f

+ 178 - 0
src/components/Dialog/hooks/useResize.ts

@@ -0,0 +1,178 @@
+import { ref } from 'vue'
+
+export const useResize = (props?: {
+  minHeightPx?: number
+  minWidthPx?: number
+  initHeight?: number
+  initWidth?: number
+}) => {
+  const {
+    minHeightPx = 400,
+    minWidthPx = window.innerWidth / 2,
+    initHeight = 400,
+    initWidth = window.innerWidth / 2
+  } = props || {}
+  // 屏幕宽度的 50% 作为最小宽度
+  //   const minWidthPx = window.innerWidth / 2
+  // 固定的最小高度 400px
+  //   const minHeightPx = 400
+  // 初始高度限制为 400px
+  const maxHeight = ref(initHeight + 'px')
+  // 初始宽度限制为 50%
+  const minWidth = ref(initWidth + 'px')
+  const setupDrag = (elDialog: any, el: any) => {
+    // 获取对话框元素
+    // 是否正在调整大小的标志
+    let isResizing = false
+    // 当前调整的方向
+    let currentResizeDirection = ''
+
+    // 鼠标移动时的事件处理器,用于检测鼠标位置并设置相应的光标样式
+    const handleMouseMove = (e: any) => {
+      const rect = elDialog.getBoundingClientRect()
+      // 鼠标相对于对话框左侧的偏移量
+      const offsetX = e.clientX - rect.left
+      // 鼠标相对于对话框顶部的偏移量
+      const offsetY = e.clientY - rect.top
+      const width = elDialog.clientWidth
+      const height = elDialog.clientHeight
+
+      // 获取对话框的内边距
+      const computedStyle = window.getComputedStyle(elDialog)
+      const paddingLeft = parseFloat(computedStyle.paddingLeft)
+      const paddingRight = parseFloat(computedStyle.paddingRight)
+      const paddingBottom = parseFloat(computedStyle.paddingBottom)
+      const paddingTop = parseFloat(computedStyle.paddingTop)
+
+      // 根据鼠标位置设置相应的光标样式和调整方向
+      if (!isResizing) {
+        if (offsetX < paddingLeft && offsetY > paddingTop && offsetY < height - paddingBottom) {
+          elDialog.style.cursor = 'ew-resize' // 左右箭头
+          currentResizeDirection = 'left'
+        } else if (
+          offsetX > width - paddingRight &&
+          offsetY > paddingTop &&
+          offsetY < height - paddingBottom
+        ) {
+          elDialog.style.cursor = 'ew-resize' // 左右箭头
+          currentResizeDirection = 'right'
+        } else if (
+          offsetY < paddingTop &&
+          offsetX > paddingLeft &&
+          offsetX < width - paddingRight
+        ) {
+          elDialog.style.cursor = 'ns-resize' // 上下箭头
+          currentResizeDirection = 'top'
+        } else if (
+          offsetY > height - paddingBottom &&
+          offsetX > paddingLeft &&
+          offsetX < width - paddingRight
+        ) {
+          elDialog.style.cursor = 'ns-resize' // 上下箭头
+          currentResizeDirection = 'bottom'
+        } else if (offsetX < paddingLeft && offsetY < paddingTop) {
+          elDialog.style.cursor = 'nwse-resize' // 左上右下箭头
+          currentResizeDirection = 'top-left'
+        } else if (offsetX > width - paddingRight && offsetY < paddingTop) {
+          elDialog.style.cursor = 'nesw-resize' // 右上左下箭头
+          currentResizeDirection = 'top-right'
+        } else if (offsetX < paddingLeft && offsetY > height - paddingBottom) {
+          elDialog.style.cursor = 'nesw-resize' // 右上左下箭头
+          currentResizeDirection = 'bottom-left'
+        } else if (offsetX > width - paddingRight && offsetY > height - paddingBottom) {
+          elDialog.style.cursor = 'nwse-resize' // 左上右下箭头
+          currentResizeDirection = 'bottom-right'
+        } else {
+          elDialog.style.cursor = 'default'
+          currentResizeDirection = ''
+        }
+      }
+    }
+
+    // 鼠标按下时的事件处理器,开始调整对话框大小
+    const handleMouseDown = (e) => {
+      if (currentResizeDirection) {
+        isResizing = true
+
+        const initialX = e.clientX
+        const initialY = e.clientY
+        const initialWidth = elDialog.clientWidth
+        const initialHeight = el.querySelector('.el-dialog__body').clientHeight
+
+        // 调整大小的事件处理器
+        const handleResizing = (e: any) => {
+          if (!isResizing) return
+
+          let newWidth = initialWidth
+          let newHeight = initialHeight
+
+          // 根据当前调整方向计算新的宽度和高度
+          if (currentResizeDirection.includes('right')) {
+            newWidth = Math.max(minWidthPx, initialWidth + (e.clientX - initialX) * 2)
+            minWidth.value = `${newWidth}px`
+          }
+
+          if (currentResizeDirection.includes('left')) {
+            newWidth = Math.max(minWidthPx, initialWidth - (e.clientX - initialX) * 2)
+            minWidth.value = `${newWidth}px`
+          }
+
+          if (currentResizeDirection.includes('bottom')) {
+            newHeight = Math.max(minHeightPx, initialHeight + (e.clientY - initialY) * 2 - 20)
+            maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
+          }
+
+          if (currentResizeDirection.includes('top')) {
+            newHeight = Math.max(minHeightPx, initialHeight - (e.clientY - initialY) * 2 - 20)
+            maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
+          }
+
+          if (currentResizeDirection === 'top-left') {
+            newWidth = Math.max(minWidthPx, initialWidth - (e.clientX - initialX) * 2)
+            minWidth.value = `${newWidth}px`
+            newHeight = Math.max(minHeightPx, initialHeight - (e.clientY - initialY) * 2 - 20)
+            maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
+          }
+
+          if (currentResizeDirection === 'top-right') {
+            newWidth = Math.max(minWidthPx, initialWidth + (e.clientX - initialX) * 2)
+            minWidth.value = `${newWidth}px`
+            newHeight = Math.max(minHeightPx, initialHeight - (e.clientY - initialY) * 2 - 20)
+            maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
+          }
+
+          if (currentResizeDirection === 'bottom-left') {
+            newWidth = Math.max(minWidthPx, initialWidth - (e.clientX - initialX) * 2)
+            minWidth.value = `${newWidth}px`
+            newHeight = Math.max(minHeightPx, initialHeight + (e.clientY - initialY) * 2 - 20)
+            maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
+          }
+
+          if (currentResizeDirection === 'bottom-right') {
+            newWidth = Math.max(minWidthPx, initialWidth + (e.clientX - initialX) * 2)
+            minWidth.value = `${newWidth}px`
+            newHeight = Math.max(minHeightPx, initialHeight + (e.clientY - initialY) * 2 - 20)
+            maxHeight.value = `${Math.min(newHeight, window.innerHeight - 165)}px`
+          }
+        }
+        // 停止调整大小的事件处理器
+        const stopResizing = () => {
+          isResizing = false
+          document.removeEventListener('mousemove', handleResizing)
+          document.removeEventListener('mouseup', stopResizing)
+        }
+
+        document.addEventListener('mousemove', handleResizing)
+        document.addEventListener('mouseup', stopResizing)
+      }
+    }
+    elDialog.addEventListener('mousemove', handleMouseMove)
+    elDialog.addEventListener('mousedown', handleMouseDown)
+  }
+
+  return {
+    setupDrag,
+    maxHeight,
+    minWidth
+  }
+}

+ 7 - 0
src/components/Dialog/src/Dialog.vue

@@ -49,6 +49,13 @@ watch(
   }
 )
 
+watch(
+  () => props.maxHeight,
+  (val) => {
+    dialogHeight.value = isNumber(val) ? `${val}px` : val
+  }
+)
+
 const dialogStyle = computed(() => {
   return {
     height: unref(dialogHeight)

+ 73 - 0
src/components/Dialog/src/ResizeDialog.vue

@@ -0,0 +1,73 @@
+<script lang="tsx" setup>
+import { propTypes } from '@/utils/propTypes'
+import { computed, getCurrentInstance, onMounted, unref, useAttrs, useSlots } from 'vue'
+import Dialog from './Dialog.vue'
+import { useResize } from '../hooks/useResize'
+
+const props = defineProps({
+  modelValue: propTypes.bool.def(false),
+  title: propTypes.string.def('Dialog'),
+  fullscreen: propTypes.bool.def(true),
+  initWidth: propTypes.number.def(window.innerWidth / 2),
+  initHeight: propTypes.number.def(200),
+  minResizeWidth: propTypes.number.def(window.innerWidth / 2),
+  minResizeHeight: propTypes.number.def(200)
+})
+const { maxHeight, minWidth, setupDrag } = useResize({
+  minHeightPx: props.minResizeHeight,
+  minWidthPx: props.minResizeWidth,
+  initHeight: props.initHeight,
+  initWidth: props.initWidth
+})
+
+const vResize = {
+  mounted(el) {
+    const observer = new MutationObserver(() => {
+      const elDialog = el.querySelector('.el-dialog')
+
+      if (elDialog) {
+        // 在确认 `elDialog` 已渲染后进行处理
+        setupDrag(elDialog, el)
+        // observer.disconnect() // 一旦获取到元素,停止观察
+      }
+    })
+    // 开始观察子节点的变化
+    observer.observe(el, { childList: true, subtree: true })
+  }
+}
+
+const attrs = useAttrs()
+const slots = useSlots()
+const getBindValue = computed(() => {
+  const delArr: string[] = ['maxHeight', 'width']
+  const obj = Object.assign({}, { ...unref(attrs), ...props })
+  for (const key in obj) {
+    if (delArr.indexOf(key) !== -1) {
+      delete obj[key]
+    }
+  }
+  return obj
+})
+const instance = getCurrentInstance()
+const initDirective = () => {
+  const directives = instance?.appContext?.app._context?.directives
+
+  // 检查指令是否已经注册
+  if (!directives || !directives['resize']) {
+    instance?.appContext?.app.directive('resize', vResize)
+  }
+}
+onMounted(() => {
+  initDirective()
+})
+</script>
+<template>
+  <div v-resize>
+    <Dialog v-bind="getBindValue" :maxHeight="maxHeight" :width="minWidth">
+      <slot></slot>
+      <template v-if="slots.footer" #footer>
+        <slot name="footer"></slot>
+      </template>
+    </Dialog>
+  </div>
+</template>

+ 1 - 0
src/locales/en.ts

@@ -480,6 +480,7 @@ export default {
   },
   dialogDemo: {
     dialog: 'Dialog',
+    resizeDialog: 'Resize dialog',
     dialogDes: 'Secondary packaging of Dialog components based on ElementPlus',
     open: 'Open',
     close: 'Close',

+ 1 - 0
src/locales/zh-CN.ts

@@ -471,6 +471,7 @@ export default {
   },
   dialogDemo: {
     dialog: '弹窗',
+    resizeDialog: '可自定义调节弹窗大小的弹窗',
     dialogDes: '基于 ElementPlus 的 Dialog 组件二次封装',
     open: '打开',
     close: '关闭',

+ 34 - 0
src/views/Components/Dialog.vue

@@ -8,6 +8,7 @@ import { useValidator } from '@/hooks/web/useValidator'
 import { getDictOneApi } from '@/api/common'
 import { useForm } from '@/hooks/web/useForm'
 import Echart from './Echart.vue'
+import ResizeDialog from '@/components/Dialog/src/ResizeDialog.vue'
 
 const { required } = useValidator()
 
@@ -17,6 +18,10 @@ const dialogVisible = ref(false)
 
 const dialogVisible2 = ref(false)
 
+const dialogVisible3 = ref(false)
+
+const dialogVisible4 = ref(false)
+
 const { formRegister, formMethods } = useForm()
 const { getElFormExpose } = formMethods
 
@@ -128,4 +133,33 @@ const formSubmit = async () => {
       </template>
     </Dialog>
   </ContentWrap>
+
+  <ContentWrap
+    class="mt-10px"
+    :title="t('dialogDemo.resizeDialog')"
+    :message="t('dialogDemo.dialogDes')"
+  >
+    <BaseButton type="primary" @click="dialogVisible3 = !dialogVisible3">
+      {{ t('dialogDemo.open') }}
+    </BaseButton>
+
+    <BaseButton type="primary" @click="dialogVisible4 = !dialogVisible4">
+      {{ t('dialogDemo.combineWithForm') }}
+    </BaseButton>
+
+    <ResizeDialog v-model="dialogVisible3" :title="t('dialogDemo.dialog')">
+      <Echart />
+      <template #footer>
+        <BaseButton @click="dialogVisible3 = false">{{ t('dialogDemo.close') }}</BaseButton>
+      </template>
+    </ResizeDialog>
+
+    <ResizeDialog v-model="dialogVisible4" :title="t('dialogDemo.dialog')">
+      <Form :schema="schema" @register="formRegister" />
+      <template #footer>
+        <BaseButton type="primary" @click="formSubmit">{{ t('dialogDemo.submit') }}</BaseButton>
+        <BaseButton @click="dialogVisible4 = false">{{ t('dialogDemo.close') }}</BaseButton>
+      </template>
+    </ResizeDialog>
+  </ContentWrap>
 </template>