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

查看文件

@ -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 {