这个提交包含在:
徐勤民 2025-11-11 16:44:22 +08:00
父节点 e2e7bb066b
当前提交 3b5a1eea26

查看文件

@ -80,8 +80,8 @@
@wheel="handleWheel"> @wheel="handleWheel">
<div v-if="currentFile" class="preview-content"> <div v-if="currentFile" class="preview-content">
<!-- 图片预览 - 添加缩放拖拽 --> <!-- 图片预览 - 添加缩放拖拽 -->
<div class="image-wrapper" ref="imageWrapper" v-if="isImage(currentFile)">
<img <img
v-if="isImage(currentFile)"
:src="getCurrentPageImage(currentFile)" :src="getCurrentPageImage(currentFile)"
:alt="currentFile.originalName" :alt="currentFile.originalName"
class="preview-image" class="preview-image"
@ -91,9 +91,9 @@
}" }"
@load="handleImageLoad" @load="handleImageLoad"
@error="handlePreviewError" @error="handlePreviewError"
ref="previewImage"
/> />
</div>
<!-- 不支持预览的文件类型 -->
<div v-else class="unsupported-preview"> <div v-else class="unsupported-preview">
<div class="file-type-large" :class="getFileTypeClass(currentFile)"> <div class="file-type-large" :class="getFileTypeClass(currentFile)">
{{ getFileTypeIcon(currentFile) }} {{ getFileTypeIcon(currentFile) }}
@ -279,6 +279,8 @@ const isDragging = ref(false)
const lastDragPos = ref({ x: 0, y: 0 }) const lastDragPos = ref({ x: 0, y: 0 })
const resultContainer = ref<HTMLElement | null>(null) const resultContainer = ref<HTMLElement | null>(null)
const previewContainer = 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[]>([]) const textareas = ref<HTMLTextAreaElement[]>([])
// //
@ -340,16 +342,38 @@ const selectFile = async (file: FileRecord): Promise<void> => {
// //
const handleImageLoad = (): void => { const handleImageLoad = (): void => {
// //
nextTick(() => {
resetZoom() 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 => { const zoomIn = (): void => {
zoomLevel.value = Math.min(zoomLevel.value + 0.1, 3) zoomLevel.value = Math.min(zoomLevel.value + 0.1, 3)
constrainDragOffset() //
} }
const zoomOut = (): void => { const zoomOut = (): void => {
zoomLevel.value = Math.max(zoomLevel.value - 0.1, 0.5) zoomLevel.value = Math.max(zoomLevel.value - 0.1, 0.5)
constrainDragOffset() //
} }
const resetZoom = (): void => { const resetZoom = (): void => {
@ -357,6 +381,28 @@ const resetZoom = (): void => {
dragOffset.value = { x: 0, y: 0 } 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 => { const startDrag = (event: MouseEvent): void => {
if (!currentFile.value || !isImage(currentFile.value)) return if (!currentFile.value || !isImage(currentFile.value)) return
@ -375,6 +421,9 @@ const doDrag = (event: MouseEvent): void => {
dragOffset.value.x += deltaX dragOffset.value.x += deltaX
dragOffset.value.y += deltaY dragOffset.value.y += deltaY
//
constrainDragOffset()
lastDragPos.value = { x: event.clientX, y: event.clientY } 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 const newHeight = Math.min(textarea.scrollHeight, 300) // 300px
textarea.style.height = `${newHeight}px` textarea.style.height = `${newHeight}px`
//
if (resultContainer.value) {
resultContainer.value.style.overflowY = 'auto'
}
} }
const saveTextEdit = async (): Promise<void> => { const saveTextEdit = async (): Promise<void> => {
@ -526,6 +570,9 @@ const startOcr = async (): Promise<void> => {
// //
resetZoom() resetZoom()
nextTick(() => {
centerImage()
})
} else { } else {
throw new Error('OCR识别失败') throw new Error('OCR识别失败')
} }
@ -794,6 +841,7 @@ onUnmounted(() => {
gap: 1px; gap: 1px;
background: #e9ecef; background: #e9ecef;
overflow: hidden; overflow: hidden;
min-height: 0; /* 重要:允许内容缩小 */
} }
.file-panel, .file-panel,
@ -802,6 +850,7 @@ onUnmounted(() => {
background: white; background: white;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; /* 重要:允许内容缩小 */
} }
.panel-header { .panel-header {
@ -860,6 +909,7 @@ onUnmounted(() => {
overflow: hidden; /* 防止内部溢出 */ overflow: hidden; /* 防止内部溢出 */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; /* 重要:允许内容缩小 */
} }
.file-preview { .file-preview {
@ -879,6 +929,25 @@ onUnmounted(() => {
cursor: grabbing; 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 { .preview-image {
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
@ -887,6 +956,7 @@ onUnmounted(() => {
border-radius: 4px; border-radius: 4px;
transition: transform 0.1s ease; transition: transform 0.1s ease;
transform-origin: center center; transform-origin: center center;
position: relative;
} }
.file-list { .file-list {
@ -986,10 +1056,19 @@ onUnmounted(() => {
color: #6c757d; color: #6c757d;
} }
/* 修复识别结果滚动问题 */
.ocr-result { .ocr-result {
flex: 1; flex: 1;
overflow-y: auto; display: flex;
padding: 1rem; flex-direction: column;
min-height: 0; /* 重要:允许内容缩小 */
overflow: hidden; /* 外层隐藏滚动 */
}
.result-content {
flex: 1;
overflow-y: auto; /* 内容区域滚动 */
padding: 0 1rem 1rem 1rem;
min-height: 0; /* 重要:允许内容缩小 */ min-height: 0; /* 重要:允许内容缩小 */
} }
@ -997,6 +1076,11 @@ onUnmounted(() => {
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
color: #6c757d; color: #6c757d;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
} }
.spinner { .spinner {
@ -1019,10 +1103,6 @@ onUnmounted(() => {
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.result-content {
line-height: 1.6;
}
.text-block { .text-block {
margin-bottom: 1rem; margin-bottom: 1rem;
padding: 0.5rem; padding: 0.5rem;
@ -1181,6 +1261,11 @@ onUnmounted(() => {
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
color: #6c757d; color: #6c757d;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
} }
.result-summary { .result-summary {