徐勤民 пре 2 дана
родитељ
комит
3b5a1eea26
1 измењених фајлова са 111 додато и 26 уклоњено
  1. 111 26
      src/renderer/components/OCRPage.vue

+ 111 - 26
src/renderer/components/OCRPage.vue

@@ -80,20 +80,20 @@
                @wheel="handleWheel">
             <div v-if="currentFile" class="preview-content">
               <!-- 图片预览 - 添加缩放拖拽 -->
-              <img
-                  v-if="isImage(currentFile)"
-                  :src="getCurrentPageImage(currentFile)"
-                  :alt="currentFile.originalName"
-                  class="preview-image"
-                  :style="{
-                  transform: `scale(${zoomLevel}) translate(${dragOffset.x}px, ${dragOffset.y}px)`,
-                  cursor: isDragging ? 'grabbing' : 'grab'
-                }"
-                  @load="handleImageLoad"
-                  @error="handlePreviewError"
-              />
-
-              <!-- 不支持预览的文件类型 -->
+              <div class="image-wrapper" ref="imageWrapper" v-if="isImage(currentFile)">
+                <img
+                    :src="getCurrentPageImage(currentFile)"
+                    :alt="currentFile.originalName"
+                    class="preview-image"
+                    :style="{
+                    transform: `scale(${zoomLevel}) translate(${dragOffset.x}px, ${dragOffset.y}px)`,
+                    cursor: isDragging ? 'grabbing' : 'grab'
+                  }"
+                    @load="handleImageLoad"
+                    @error="handlePreviewError"
+                    ref="previewImage"
+                />
+              </div>
               <div v-else class="unsupported-preview">
                 <div class="file-type-large" :class="getFileTypeClass(currentFile)">
                   {{ getFileTypeIcon(currentFile) }}
@@ -279,6 +279,8 @@ const isDragging = ref(false)
 const lastDragPos = ref({ x: 0, y: 0 })
 const resultContainer = ref<HTMLElement | null>(null)
 const previewContainer = ref<HTMLElement | null>(null)
+const previewImage = ref<HTMLImageElement | null>(null)
+const imageWrapper = ref<HTMLElement | null>(null)
 const textareas = ref<HTMLTextAreaElement[]>([])
 
 // 计算属性
@@ -340,16 +342,38 @@ const selectFile = async (file: FileRecord): Promise<void> => {
 // 图片加载完成后的处理
 const handleImageLoad = (): void => {
   // 确保图片在可视区域内
-  resetZoom()
+  nextTick(() => {
+    resetZoom()
+    centerImage()
+  })
+}
+
+// 居中图片
+const centerImage = (): void => {
+  if (!previewImage.value || !imageWrapper.value) return
+
+  const container = imageWrapper.value
+  const img = previewImage.value
+
+  // 计算居中偏移
+  const containerRect = container.getBoundingClientRect()
+  const imgRect = img.getBoundingClientRect()
+
+  // 如果图片比容器小,居中显示
+  if (imgRect.width < containerRect.width && imgRect.height < containerRect.height) {
+    dragOffset.value = { x: 0, y: 0 }
+  }
 }
 
 // 缩放功能
 const zoomIn = (): void => {
   zoomLevel.value = Math.min(zoomLevel.value + 0.1, 3)
+  constrainDragOffset() // 限制拖拽范围
 }
 
 const zoomOut = (): void => {
   zoomLevel.value = Math.max(zoomLevel.value - 0.1, 0.5)
+  constrainDragOffset() // 限制拖拽范围
 }
 
 const resetZoom = (): void => {
@@ -357,6 +381,28 @@ const resetZoom = (): void => {
   dragOffset.value = { x: 0, y: 0 }
 }
 
+// 限制拖拽范围,防止图片被拖出可视区域
+const constrainDragOffset = (): void => {
+  if (!previewImage.value || !imageWrapper.value) return
+
+  const container = imageWrapper.value
+  const img = previewImage.value
+
+  const containerRect = container.getBoundingClientRect()
+  const imgRect = {
+    width: img.naturalWidth * zoomLevel.value,
+    height: img.naturalHeight * zoomLevel.value
+  }
+
+  // 计算最大允许的拖拽偏移
+  const maxX = Math.max(0, (imgRect.width - containerRect.width) / 2)
+  const maxY = Math.max(0, (imgRect.height - containerRect.height) / 2)
+
+  // 限制拖拽范围
+  dragOffset.value.x = Math.max(-maxX, Math.min(maxX, dragOffset.value.x))
+  dragOffset.value.y = Math.max(-maxY, Math.min(maxY, dragOffset.value.y))
+}
+
 // 拖拽功能
 const startDrag = (event: MouseEvent): void => {
   if (!currentFile.value || !isImage(currentFile.value)) return
@@ -375,6 +421,9 @@ const doDrag = (event: MouseEvent): void => {
   dragOffset.value.x += deltaX
   dragOffset.value.y += deltaY
 
+  // 限制拖拽范围
+  constrainDragOffset()
+
   lastDragPos.value = { x: event.clientX, y: event.clientY }
 }
 
@@ -425,11 +474,6 @@ const autoResizeTextarea = (event: { target: EventTarget | null }): void => {
   // 设置新高度,但限制最大高度
   const newHeight = Math.min(textarea.scrollHeight, 300) // 最大300px
   textarea.style.height = `${newHeight}px`
-
-  // 确保结果容器可以滚动
-  if (resultContainer.value) {
-    resultContainer.value.style.overflowY = 'auto'
-  }
 }
 
 const saveTextEdit = async (): Promise<void> => {
@@ -526,6 +570,9 @@ const startOcr = async (): Promise<void> => {
 
       // 识别成功后重置图片位置,确保在可视区域内
       resetZoom()
+      nextTick(() => {
+        centerImage()
+      })
     } else {
       throw new Error('OCR识别失败')
     }
@@ -794,6 +841,7 @@ onUnmounted(() => {
   gap: 1px;
   background: #e9ecef;
   overflow: hidden;
+  min-height: 0; /* 重要:允许内容缩小 */
 }
 
 .file-panel,
@@ -802,6 +850,7 @@ onUnmounted(() => {
   background: white;
   display: flex;
   flex-direction: column;
+  min-height: 0; /* 重要:允许内容缩小 */
 }
 
 .panel-header {
@@ -860,6 +909,7 @@ onUnmounted(() => {
   overflow: hidden; /* 防止内部溢出 */
   display: flex;
   flex-direction: column;
+  min-height: 0; /* 重要:允许内容缩小 */
 }
 
 .file-preview {
@@ -879,6 +929,25 @@ onUnmounted(() => {
   cursor: grabbing;
 }
 
+.preview-content {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+}
+
+.image-wrapper {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+  position: relative;
+}
+
 .preview-image {
   max-width: 100%;
   max-height: 100%;
@@ -887,6 +956,7 @@ onUnmounted(() => {
   border-radius: 4px;
   transition: transform 0.1s ease;
   transform-origin: center center;
+  position: relative;
 }
 
 .file-list {
@@ -986,10 +1056,19 @@ onUnmounted(() => {
   color: #6c757d;
 }
 
+/* 修复识别结果滚动问题 */
 .ocr-result {
   flex: 1;
-  overflow-y: auto;
-  padding: 1rem;
+  display: flex;
+  flex-direction: column;
+  min-height: 0; /* 重要:允许内容缩小 */
+  overflow: hidden; /* 外层隐藏滚动 */
+}
+
+.result-content {
+  flex: 1;
+  overflow-y: auto; /* 内容区域滚动 */
+  padding: 0 1rem 1rem 1rem;
   min-height: 0; /* 重要:允许内容缩小 */
 }
 
@@ -997,6 +1076,11 @@ onUnmounted(() => {
   text-align: center;
   padding: 2rem;
   color: #6c757d;
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
 }
 
 .spinner {
@@ -1019,10 +1103,6 @@ onUnmounted(() => {
   margin-top: 0.5rem;
 }
 
-.result-content {
-  line-height: 1.6;
-}
-
 .text-block {
   margin-bottom: 1rem;
   padding: 0.5rem;
@@ -1181,6 +1261,11 @@ onUnmounted(() => {
   text-align: center;
   padding: 2rem;
   color: #6c757d;
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
 }
 
 .result-summary {