From 02a96387a40a95d64ee0f54254958f5d27cedf2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=A5=94=E8=B7=91=E7=9A=84=E9=9D=A2=E6=9D=A1?=
 <1262327911@qq.com>
Date: Sat, 8 Jul 2023 21:18:37 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=A2=84=E8=A7=88?=
 =?UTF-8?q?=E6=94=BE=E5=A4=A7=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/hooks/usePreviewScale.hook.ts        | 434 +++++++++++------------
 src/views/preview/hooks/useScale.hook.ts | 166 +++++----
 src/views/preview/suspenseIndex.vue      |   5 +-
 src/views/preview/utils/index.ts         |   5 +-
 src/views/preview/utils/keyboard.ts      |  32 ++
 5 files changed, 355 insertions(+), 287 deletions(-)
 create mode 100644 src/views/preview/utils/keyboard.ts

diff --git a/src/hooks/usePreviewScale.hook.ts b/src/hooks/usePreviewScale.hook.ts
index 066f6b66..9d8bcb78 100644
--- a/src/hooks/usePreviewScale.hook.ts
+++ b/src/hooks/usePreviewScale.hook.ts
@@ -1,218 +1,218 @@
-import throttle from 'lodash/throttle'
-
-// 拆出来是为了更好的分离单独复用
-
-// * 屏幕缩放适配(两边留白)
-export const usePreviewFitScale = (
-  width: number,
-  height: number,
-  scaleDom: HTMLElement | null,
-  callback?: (scale: {
-    width: number;
-    height: number;
-  }) => void
-) => {
-  // * 画布尺寸(px)
-  const baseWidth = width
-  const baseHeight = height
-
-  // * 默认缩放值
-  const scale = {
-    width: 1,
-    height: 1,
-  }
-
-  // * 需保持的比例
-  const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5))
-  const calcRate = () => {
-    // 当前屏幕宽高比
-    const currentRate = parseFloat(
-      (window.innerWidth / window.innerHeight).toFixed(5)
-    )
-    if (scaleDom) {
-      if (currentRate > baseProportion) {
-        // 表示更宽
-        scale.width = parseFloat(((window.innerHeight * baseProportion) / baseWidth).toFixed(5))
-        scale.height = parseFloat((window.innerHeight / baseHeight).toFixed(5))
-        scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
-      } else {
-        // 表示更高
-        scale.height = parseFloat(((window.innerWidth / baseProportion) / baseHeight).toFixed(5))
-        scale.width = parseFloat((window.innerWidth / baseWidth).toFixed(5))
-        scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
-      }
-      if (callback) callback(scale)
-    }
-  }
-
-  const resize = throttle(() => {
-    calcRate()
-  }, 200)
-
-  // * 改变窗口大小重新绘制
-  const windowResize = () => {
-    window.addEventListener('resize', resize)
-  }
-
-  // * 改变窗口大小重新绘制
-  const unWindowResize = () => {
-    window.removeEventListener('resize', resize)
-  }
-
-  return {
-    calcRate,
-    windowResize,
-    unWindowResize,
-  }
-}
-
-// *  X轴撑满,Y轴滚动条
-export const usePreviewScrollYScale = (
-  width: number,
-  height: number,
-  scaleDom: HTMLElement | null,
-  callback?: (scale: {
-    width: number;
-    height: number;
-  }) => void
-) => {
-  // * 画布尺寸(px)
-  const baseWidth = width
-  const baseHeight = height
-
-  // * 默认缩放值
-  const scale = {
-    width: 1,
-    height: 1,
-  }
-
-  // * 需保持的比例
-  const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5))
-  const calcRate = () => {
-    if (scaleDom) {
-      scale.height = parseFloat(((window.innerWidth / baseProportion) / baseHeight).toFixed(5))
-      scale.width = parseFloat((window.innerWidth / baseWidth).toFixed(5))
-      scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
-      if (callback) callback(scale)
-    }
-  }
-
-  const resize = throttle(() => {
-    calcRate()
-  }, 200)
-
-  // * 改变窗口大小重新绘制
-  const windowResize = () => {
-    window.addEventListener('resize', resize)
-  }
-
-  // * 改变窗口大小重新绘制
-  const unWindowResize = () => {
-    window.removeEventListener('resize', resize)
-  }
-
-  return {
-    calcRate,
-    windowResize,
-    unWindowResize,
-  }
-}
-
-// *  Y轴撑满,X轴滚动条
-export const usePreviewScrollXScale = (
-  width: number,
-  height: number,
-  scaleDom: HTMLElement | null,
-  callback?: (scale: {
-    width: number;
-    height: number;
-  }) => void
-) => {
-  // * 画布尺寸(px)
-  const baseWidth = width
-  const baseHeight = height
-
-  // * 默认缩放值
-  const scale = {
-    height: 1,
-    width: 1,
-  }
-
-  // * 需保持的比例
-  const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5))
-  const calcRate = () => {
-    if (scaleDom) {
-      scale.width = parseFloat(((window.innerHeight * baseProportion) / baseWidth).toFixed(5))
-      scale.height = parseFloat((window.innerHeight / baseHeight).toFixed(5))
-      scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
-      if (callback) callback(scale)
-    }
-  }
-
-  const resize = throttle(() => {
-    calcRate()
-  }, 200)
-
-  // * 改变窗口大小重新绘制
-  const windowResize = () => {
-    window.addEventListener('resize', resize)
-  }
-
-  // * 改变窗口大小重新绘制
-  const unWindowResize = () => {
-    window.removeEventListener('resize', resize)
-  }
-
-  return {
-    calcRate,
-    windowResize,
-    unWindowResize,
-  }
-}
-
-// * 变形内容,宽高铺满
-export const usePreviewFullScale = (
-  width: number,
-  height: number,
-  scaleDom: HTMLElement | null,
-  callback?: (scale: {
-    width: number;
-    height: number;
-  }) => void
-) => {
-
-  // * 默认缩放值
-  const scale = {
-    width: 1,
-    height: 1,
-  }
-
-  const calcRate = () => {
-    if (scaleDom) {
-      scale.width = parseFloat((window.innerWidth / width).toFixed(5))
-      scale.height = parseFloat((window.innerHeight / height).toFixed(5))
-      scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
-      if (callback) callback(scale)
-    }
-  }
-
-  const resize = throttle(() => {
-    calcRate()
-  }, 200)
-
-  // * 改变窗口大小重新绘制
-  const windowResize = () => {
-    window.addEventListener('resize', resize)
-  }
-
-  // * 改变窗口大小重新绘制
-  const unWindowResize = () => {
-    window.removeEventListener('resize', resize)
-  }
-
-  return {
-    calcRate,
-    windowResize,
-    unWindowResize,
-  }
+import throttle from 'lodash/throttle'
+
+// 拆出来是为了更好的分离单独复用
+
+// * 屏幕缩放适配(两边留白)
+export const usePreviewFitScale = (
+  width: number,
+  height: number,
+  scaleDom: HTMLElement | null,
+  callback?: (scale: {
+    width: number;
+    height: number;
+  }) => void
+) => {
+  // * 画布尺寸(px)
+  const baseWidth = width
+  const baseHeight = height
+
+  // * 默认缩放值
+  const scale = {
+    width: 1,
+    height: 1,
+  }
+
+  // * 需保持的比例
+  const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5))
+  const calcRate = () => {
+    // 当前屏幕宽高比
+    const currentRate = parseFloat(
+      (window.innerWidth / window.innerHeight).toFixed(5)
+    )
+    if (scaleDom) {
+      if (currentRate > baseProportion) {
+        // 表示更宽
+        scale.width = parseFloat(((window.innerHeight * baseProportion) / baseWidth).toFixed(5))
+        scale.height = parseFloat((window.innerHeight / baseHeight).toFixed(5))
+        scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
+      } else {
+        // 表示更高
+        scale.height = parseFloat(((window.innerWidth / baseProportion) / baseHeight).toFixed(5))
+        scale.width = parseFloat((window.innerWidth / baseWidth).toFixed(5))
+        scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
+      }
+      if (callback) callback(scale)
+    }
+  }
+
+  const resize = throttle(() => {
+    calcRate()
+  }, 200)
+
+  // * 改变窗口大小重新绘制
+  const windowResize = () => {
+    window.addEventListener('resize', resize)
+  }
+
+  // * 卸载监听
+  const unWindowResize = () => {
+    window.removeEventListener('resize', resize)
+  }
+
+  return {
+    calcRate,
+    windowResize,
+    unWindowResize,
+  }
+}
+
+// *  X轴撑满,Y轴滚动条
+export const usePreviewScrollYScale = (
+  width: number,
+  height: number,
+  scaleDom: HTMLElement | null,
+  callback?: (scale: {
+    width: number;
+    height: number;
+  }) => void
+) => {
+  // * 画布尺寸(px)
+  const baseWidth = width
+  const baseHeight = height
+
+  // * 默认缩放值
+  const scale = {
+    width: 1,
+    height: 1,
+  }
+
+  // * 需保持的比例
+  const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5))
+  const calcRate = () => {
+    if (scaleDom) {
+      scale.height = parseFloat(((window.innerWidth / baseProportion) / baseHeight).toFixed(5))
+      scale.width = parseFloat((window.innerWidth / baseWidth).toFixed(5))
+      scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
+      if (callback) callback(scale)
+    }
+  }
+
+  const resize = throttle(() => {
+    calcRate()
+  }, 200)
+
+  // * 改变窗口大小重新绘制
+  const windowResize = () => {
+    window.addEventListener('resize', resize)
+  }
+
+  // * 卸载监听
+  const unWindowResize = () => {
+    window.removeEventListener('resize', resize)
+  }
+
+  return {
+    calcRate,
+    windowResize,
+    unWindowResize,
+  }
+}
+
+// *  Y轴撑满,X轴滚动条
+export const usePreviewScrollXScale = (
+  width: number,
+  height: number,
+  scaleDom: HTMLElement | null,
+  callback?: (scale: {
+    width: number;
+    height: number;
+  }) => void
+) => {
+  // * 画布尺寸(px)
+  const baseWidth = width
+  const baseHeight = height
+
+  // * 默认缩放值
+  const scale = {
+    height: 1,
+    width: 1,
+  }
+
+  // * 需保持的比例
+  const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5))
+  const calcRate = () => {
+    if (scaleDom) {
+      scale.width = parseFloat(((window.innerHeight * baseProportion) / baseWidth).toFixed(5))
+      scale.height = parseFloat((window.innerHeight / baseHeight).toFixed(5))
+      scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
+      if (callback) callback(scale)
+    }
+  }
+
+  const resize = throttle(() => {
+    calcRate()
+  }, 200)
+
+  // * 改变窗口大小重新绘制
+  const windowResize = () => {
+    window.addEventListener('resize', resize)
+  }
+
+  // * 卸载监听
+  const unWindowResize = () => {
+    window.removeEventListener('resize', resize)
+  }
+
+  return {
+    calcRate,
+    windowResize,
+    unWindowResize,
+  }
+}
+
+// * 变形内容,宽高铺满
+export const usePreviewFullScale = (
+  width: number,
+  height: number,
+  scaleDom: HTMLElement | null,
+  callback?: (scale: {
+    width: number;
+    height: number;
+  }) => void
+) => {
+
+  // * 默认缩放值
+  const scale = {
+    width: 1,
+    height: 1,
+  }
+
+  const calcRate = () => {
+    if (scaleDom) {
+      scale.width = parseFloat((window.innerWidth / width).toFixed(5))
+      scale.height = parseFloat((window.innerHeight / height).toFixed(5))
+      scaleDom.style.transform = `scale(${scale.width}, ${scale.height})`
+      if (callback) callback(scale)
+    }
+  }
+
+  const resize = throttle(() => {
+    calcRate()
+  }, 200)
+
+  // * 改变窗口大小重新绘制
+  const windowResize = () => {
+    window.addEventListener('resize', resize)
+  }
+
+  // * 卸载监听
+  const unWindowResize = () => {
+    window.removeEventListener('resize', resize)
+  }
+
+  return {
+    calcRate,
+    windowResize,
+    unWindowResize,
+  }
 }
\ No newline at end of file
diff --git a/src/views/preview/hooks/useScale.hook.ts b/src/views/preview/hooks/useScale.hook.ts
index 3916c187..b0185383 100644
--- a/src/views/preview/hooks/useScale.hook.ts
+++ b/src/views/preview/hooks/useScale.hook.ts
@@ -12,7 +12,31 @@ export const useScale = (localStorageInfo: ChartEditStorageType) => {
   const height = ref(localStorageInfo.editCanvasConfig.height)
   const scaleRef = ref({ width: 1, height: 1 })
 
-  provide(SCALE_KEY, scaleRef);
+  provide(SCALE_KEY, scaleRef)
+
+  // 监听鼠标滚轮 +ctrl 键
+  const useAddWheelHandle = (removeEvent: Function) => {
+    addEventListener(
+      'wheel',
+      (e: any) => {
+        if (window?.$KeyboardActive?.ctrl) {
+          e.preventDefault()
+          e.stopPropagation()
+          removeEvent()
+          const transform = previewRef.value.style.transform
+          // 使用正则解析 scale(1, 1) 中的两个数值
+          const regRes = transform.match(/scale\((\d+\.?\d*)*/) as RegExpMatchArray
+          const width = regRes[1]
+          if (e.wheelDelta > 0) {
+            previewRef.value.style.transform = `scale(${parseFloat(Number(width).toFixed(2)) + 0.1})`
+          } else {
+            previewRef.value.style.transform = `scale(${parseFloat(Number(width).toFixed(2)) - 0.1})`
+          }
+        }
+      },
+      { passive: false }
+    )
+  }
 
   const updateScaleRef = (scale: { width: number; height: number }) => {
     // 这里需要解构,保证赋值给scaleRef的为一个新对象
@@ -23,74 +47,82 @@ export const useScale = (localStorageInfo: ChartEditStorageType) => {
   // 屏幕适配
   onMounted(() => {
     switch (localStorageInfo.editCanvasConfig.previewScaleType) {
-      case PreviewScaleEnum.FIT: (() => {
-        const { calcRate, windowResize, unWindowResize } = usePreviewFitScale(
-          width.value as number,
-          height.value as number,
-          previewRef.value,
-          updateScaleRef
-        )
-        calcRate()
-        windowResize()
-        onUnmounted(() => {
-          unWindowResize()
-        })
-      })()
-        break;
-      case PreviewScaleEnum.SCROLL_Y: (() => {
-        const { calcRate, windowResize, unWindowResize } = usePreviewScrollYScale(
-          width.value as number,
-          height.value as number,
-          previewRef.value,
-          (scale) => {
-            const dom = entityRef.value
-            dom.style.width = `${width.value * scale.width}px`
-            dom.style.height = `${height.value * scale.height}px`
-            updateScaleRef(scale)
-          }
-        )
-        calcRate()
-        windowResize()
-        onUnmounted(() => {
-          unWindowResize()
-        })
-      })()
+      case PreviewScaleEnum.FIT:
+        ;(() => {
+          const { calcRate, windowResize, unWindowResize } = usePreviewFitScale(
+            width.value as number,
+            height.value as number,
+            previewRef.value,
+            updateScaleRef
+          )
+          calcRate()
+          windowResize()
+          useAddWheelHandle(unWindowResize)
+          onUnmounted(() => {
+            unWindowResize()
+          })
+        })()
+        break
+      case PreviewScaleEnum.SCROLL_Y:
+        ;(() => {
+          const { calcRate, windowResize, unWindowResize } = usePreviewScrollYScale(
+            width.value as number,
+            height.value as number,
+            previewRef.value,
+            scale => {
+              const dom = entityRef.value
+              dom.style.width = `${width.value * scale.width}px`
+              dom.style.height = `${height.value * scale.height}px`
+              updateScaleRef(scale)
+            }
+          )
+          calcRate()
+          windowResize()
+          useAddWheelHandle(unWindowResize)
+          onUnmounted(() => {
+            unWindowResize()
+          })
+        })()
 
-        break;
-      case PreviewScaleEnum.SCROLL_X: (() => {
-        const { calcRate, windowResize, unWindowResize } = usePreviewScrollXScale(
-          width.value as number,
-          height.value as number,
-          previewRef.value,
-          (scale) => {
-            const dom = entityRef.value
-            dom.style.width = `${width.value * scale.width}px`
-            dom.style.height = `${height.value * scale.height}px`
-            updateScaleRef(scale)
-          }
-        )
-        calcRate()
-        windowResize()
-        onUnmounted(() => {
-          unWindowResize()
-        })
-      })()
+        break
+      case PreviewScaleEnum.SCROLL_X:
+        ;(() => {
+          const { calcRate, windowResize, unWindowResize } = usePreviewScrollXScale(
+            width.value as number,
+            height.value as number,
+            previewRef.value,
+            scale => {
+              const dom = entityRef.value
+              dom.style.width = `${width.value * scale.width}px`
+              dom.style.height = `${height.value * scale.height}px`
+              updateScaleRef(scale)
+            }
+          )
+          calcRate()
+          windowResize()
+          useAddWheelHandle(unWindowResize)
+          onUnmounted(() => {
+            unWindowResize()
+          })
+        })()
 
-        break;
-      case PreviewScaleEnum.FULL: (() => {
-        const { calcRate, windowResize, unWindowResize } = usePreviewFullScale(
-          width.value as number,
-          height.value as number,
-          previewRef.value,
-          updateScaleRef
-        )
-        calcRate()
-        windowResize()
-        onUnmounted(() => {
-          unWindowResize()
-        })
-      })()
-        break;
+        break
+      case PreviewScaleEnum.FULL:
+        ;(() => {
+          const { calcRate, windowResize, unWindowResize } = usePreviewFullScale(
+            width.value as number,
+            height.value as number,
+            previewRef.value,
+            updateScaleRef
+          )
+          calcRate()
+          windowResize()
+          useAddWheelHandle(unWindowResize)
+          onUnmounted(() => {
+            unWindowResize()
+          })
+        })()
+        break
     }
   })
 
diff --git a/src/views/preview/suspenseIndex.vue b/src/views/preview/suspenseIndex.vue
index fdc3fdf6..1a1932b9 100644
--- a/src/views/preview/suspenseIndex.vue
+++ b/src/views/preview/suspenseIndex.vue
@@ -30,7 +30,7 @@
 import { computed } from 'vue'
 import { PreviewRenderList } from './components/PreviewRenderList'
 import { getFilterStyle, setTitle } from '@/utils'
-import { getEditCanvasConfigStyle, getSessionStorageInfo } from './utils'
+import { getEditCanvasConfigStyle, getSessionStorageInfo, keyRecordHandle } from './utils'
 import { useComInstall } from './hooks/useComInstall.hook'
 import { useScale } from './hooks/useScale.hook'
 import { useStore } from './hooks/useStore.hook'
@@ -60,6 +60,9 @@ const showEntity = computed(() => {
 useStore(chartEditStore)
 const { entityRef, previewRef } = useScale(chartEditStore)
 const { show } = useComInstall(chartEditStore)
+
+// 开启键盘监听
+keyRecordHandle()
 </script>
 
 <style lang="scss" scoped>
diff --git a/src/views/preview/utils/index.ts b/src/views/preview/utils/index.ts
index f0f79cfa..1d0be713 100644
--- a/src/views/preview/utils/index.ts
+++ b/src/views/preview/utils/index.ts
@@ -1,2 +1,3 @@
-export * from './style'
-export * from './storage'
\ No newline at end of file
+export * from './style'
+export * from './storage'
+export * from './keyboard'
\ No newline at end of file
diff --git a/src/views/preview/utils/keyboard.ts b/src/views/preview/utils/keyboard.ts
new file mode 100644
index 00000000..0b895ab4
--- /dev/null
+++ b/src/views/preview/utils/keyboard.ts
@@ -0,0 +1,32 @@
+// 处理键盘记录
+export const keyRecordHandle = () => {
+  // 默认赋值
+  window.$KeyboardActive = {
+    ctrl: false,
+    space: false
+  }
+
+  document.onkeydown = (e: KeyboardEvent) => {
+    const { keyCode } = e
+    if (keyCode == 32 && e.target == document.body) e.preventDefault()
+
+    if ([17, 32].includes(keyCode) && window.$KeyboardActive) {
+      switch (keyCode) {
+        case 17: window.$KeyboardActive.ctrl = true; break
+        case 32: window.$KeyboardActive.space = true; break
+      }
+    }
+  }
+
+  document.onkeyup = (e: KeyboardEvent) => {
+    const { keyCode } = e
+    if (keyCode == 32 && e.target == document.body) e.preventDefault()
+
+    if ([17, 32].includes(keyCode) && window.$KeyboardActive) {
+      switch (keyCode) {
+        case 17: window.$KeyboardActive.ctrl = false; break
+        case 32: window.$KeyboardActive.space = false; break
+      }
+    }
+  }
+}
\ No newline at end of file