init
这个提交包含在:
父节点
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 {
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户