@koi-br/ocr-web-sdk 1.0.45 → 1.0.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-oglARFSv.js → index-BI5CSerG.js} +35 -35
- package/dist/{index-Dr29vR47.mjs → index-CSM6_98N.mjs} +1569 -1633
- package/dist/index.cjs.js +2 -2
- package/dist/index.esm.js +2 -2
- package/dist/{tiff.min-FlWYar5H.mjs → tiff.min-CCAmG2PH.mjs} +1 -1
- package/dist/{tiff.min-BZS1xd8w.js → tiff.min-DHdfwQWQ.js} +1 -1
- package/package.json +1 -1
- package/preview/ImagePreview.vue +110 -168
package/preview/ImagePreview.vue
CHANGED
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
<div class="toolbar-group">
|
|
13
13
|
<span class="scale-text"> {{ Math.round(scale * 100) }}% </span>
|
|
14
14
|
<div class="toolbar-divider"></div>
|
|
15
|
-
<ATooltip
|
|
15
|
+
<ATooltip
|
|
16
|
+
v-if="showResetButton"
|
|
17
|
+
mini
|
|
18
|
+
position="bottom"
|
|
19
|
+
content="重置"
|
|
20
|
+
>
|
|
16
21
|
<AButton size="small" type="outline" @click="reset">
|
|
17
22
|
<RefreshCcw :size="16" />
|
|
18
23
|
</AButton>
|
|
@@ -91,7 +96,7 @@
|
|
|
91
96
|
<AButton
|
|
92
97
|
size="small"
|
|
93
98
|
type="outline"
|
|
94
|
-
style="padding-right: 0px"
|
|
99
|
+
style="padding-right: 0px;"
|
|
95
100
|
:disabled="currentPage >= totalPages"
|
|
96
101
|
@click="goToNextPage"
|
|
97
102
|
>
|
|
@@ -116,9 +121,9 @@
|
|
|
116
121
|
<div class="loading-spinner"></div>
|
|
117
122
|
<div class="loading-text">加载中...</div>
|
|
118
123
|
</div>
|
|
119
|
-
|
|
120
|
-
<div
|
|
121
|
-
class="image-wrapper-container"
|
|
124
|
+
|
|
125
|
+
<div
|
|
126
|
+
class="image-wrapper-container"
|
|
122
127
|
:style="containerStyle"
|
|
123
128
|
:class="{ 'image-hidden': !isImageReady && autoFitWidth }"
|
|
124
129
|
>
|
|
@@ -157,10 +162,7 @@
|
|
|
157
162
|
|
|
158
163
|
<!-- 文本图层(用于文本块选择和定位) -->
|
|
159
164
|
<div
|
|
160
|
-
v-if="
|
|
161
|
-
getPageBlocksData(pageIndex + 1) &&
|
|
162
|
-
getPageBlocksData(pageIndex + 1).length > 0
|
|
163
|
-
"
|
|
165
|
+
v-if="getPageBlocksData(pageIndex + 1) && getPageBlocksData(pageIndex + 1).length > 0"
|
|
164
166
|
:ref="(el) => setTextLayerRef(el, pageIndex + 1)"
|
|
165
167
|
class="text-layer"
|
|
166
168
|
></div>
|
|
@@ -358,6 +360,11 @@ const props = defineProps({
|
|
|
358
360
|
type: Boolean,
|
|
359
361
|
default: true, // 默认不显示,保持向后兼容(isDownload 的默认值)
|
|
360
362
|
},
|
|
363
|
+
// 是否启用文本块的 hover 效果(高亮和显示批注按钮)
|
|
364
|
+
enableBlockHover: {
|
|
365
|
+
type: Boolean,
|
|
366
|
+
default: true,
|
|
367
|
+
},
|
|
361
368
|
});
|
|
362
369
|
|
|
363
370
|
const emit = defineEmits<{
|
|
@@ -405,22 +412,22 @@ const isImageReady = ref(false); // 标记图片是否已准备好显示(自
|
|
|
405
412
|
watch(
|
|
406
413
|
() => imageUrls.value,
|
|
407
414
|
(newUrls, oldUrls) => {
|
|
408
|
-
console.log(
|
|
415
|
+
console.log('[ImagePreview] imageUrls changed:', {
|
|
409
416
|
newUrls: newUrls?.length,
|
|
410
417
|
oldUrls: oldUrls?.length,
|
|
411
418
|
autoFitWidth: props.autoFitWidth,
|
|
412
419
|
isImageReady: isImageReady.value,
|
|
413
420
|
isCalculatingAutoFit: isCalculatingAutoFit.value,
|
|
414
421
|
});
|
|
415
|
-
|
|
422
|
+
|
|
416
423
|
// 如果有新的图片URL,且启用自适应宽度,立即隐藏图片
|
|
417
424
|
if (newUrls && newUrls.length > 0 && props.autoFitWidth) {
|
|
418
|
-
console.log(
|
|
425
|
+
console.log('[ImagePreview] 设置图片隐藏,等待自适应宽度计算');
|
|
419
426
|
isImageReady.value = false;
|
|
420
427
|
isCalculatingAutoFit.value = true;
|
|
421
428
|
} else if (!props.autoFitWidth) {
|
|
422
429
|
// 如果没有启用自适应宽度,立即显示
|
|
423
|
-
console.log(
|
|
430
|
+
console.log('[ImagePreview] 未启用自适应宽度,立即显示图片');
|
|
424
431
|
isImageReady.value = true;
|
|
425
432
|
isCalculatingAutoFit.value = false;
|
|
426
433
|
}
|
|
@@ -486,10 +493,10 @@ const imageSize = computed({
|
|
|
486
493
|
// 在自适应宽度模式下,使用第一页的尺寸;否则使用当前页的尺寸
|
|
487
494
|
const scaledImageSize = computed(() => {
|
|
488
495
|
// 如果启用自适应宽度,使用第一页的尺寸作为基准
|
|
489
|
-
const baseSize = props.autoFitWidth
|
|
490
|
-
? imageSizes.get(1) || { width: 0, height: 0 }
|
|
496
|
+
const baseSize = props.autoFitWidth
|
|
497
|
+
? (imageSizes.get(1) || { width: 0, height: 0 })
|
|
491
498
|
: imageSize.value;
|
|
492
|
-
|
|
499
|
+
|
|
493
500
|
if (baseSize.width === 0 || baseSize.height === 0) {
|
|
494
501
|
return { width: 0, height: 0 };
|
|
495
502
|
}
|
|
@@ -535,7 +542,6 @@ const annotationInput = ref(""); // 批注输入内容
|
|
|
535
542
|
const currentAnnotationBlock = ref<{
|
|
536
543
|
bbox: [number, number, number, number];
|
|
537
544
|
content: string;
|
|
538
|
-
annotationId?: string; // 已有批注的ID(如果存在)
|
|
539
545
|
} | null>(null); // 当前正在添加批注的文本块
|
|
540
546
|
const annotationPopupRef = ref<HTMLElement>(); // 批注弹窗引用
|
|
541
547
|
|
|
@@ -585,7 +591,7 @@ const getPageScaledSize = (pageNo: number) => {
|
|
|
585
591
|
const getPageContainerStyle = (pageNo: number) => {
|
|
586
592
|
const scaledSize = getPageScaledSize(pageNo);
|
|
587
593
|
return {
|
|
588
|
-
height: scaledSize.height > 0 ? `${scaledSize.height}px` :
|
|
594
|
+
height: scaledSize.height > 0 ? `${scaledSize.height}px` : 'auto',
|
|
589
595
|
};
|
|
590
596
|
};
|
|
591
597
|
|
|
@@ -883,7 +889,7 @@ const switchToPage = (page: number) => {
|
|
|
883
889
|
) as HTMLElement;
|
|
884
890
|
if (pageElement) {
|
|
885
891
|
// 标记这是翻页滚动,不应该被同步滚动干扰
|
|
886
|
-
containerRef.value.dataset.pageScrolling =
|
|
892
|
+
containerRef.value.dataset.pageScrolling = 'true';
|
|
887
893
|
pageElement.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
888
894
|
// 更新 lastScrollTop,确保滚动方向判断准确
|
|
889
895
|
nextTick(() => {
|
|
@@ -918,7 +924,7 @@ const reset = () => {
|
|
|
918
924
|
annotationInput.value = "";
|
|
919
925
|
activeBlockDiv.value = null;
|
|
920
926
|
isHighlighted.value = false;
|
|
921
|
-
|
|
927
|
+
|
|
922
928
|
// 清除已渲染页面集合
|
|
923
929
|
renderedPages.value.clear();
|
|
924
930
|
};
|
|
@@ -929,7 +935,7 @@ const original = () => {
|
|
|
929
935
|
|
|
930
936
|
// 计算自适应宽度的缩放比例
|
|
931
937
|
const calculateAutoFitScale = () => {
|
|
932
|
-
console.log(
|
|
938
|
+
console.log('[ImagePreview] calculateAutoFitScale 开始:', {
|
|
933
939
|
autoFitWidth: props.autoFitWidth,
|
|
934
940
|
hasContainerRef: !!containerRef.value,
|
|
935
941
|
containerRect: containerRef.value?.getBoundingClientRect(),
|
|
@@ -938,19 +944,16 @@ const calculateAutoFitScale = () => {
|
|
|
938
944
|
minScale: props.minScale,
|
|
939
945
|
maxScale: props.maxScale,
|
|
940
946
|
});
|
|
941
|
-
|
|
947
|
+
|
|
942
948
|
if (!props.autoFitWidth || !containerRef.value) {
|
|
943
|
-
console.log(
|
|
949
|
+
console.log('[ImagePreview] calculateAutoFitScale 返回 1 (条件不满足)');
|
|
944
950
|
return 1;
|
|
945
951
|
}
|
|
946
952
|
|
|
947
953
|
// 使用第一页的图片尺寸作为基准(所有页面使用相同的缩放比例)
|
|
948
954
|
const firstPageSize = imageSizes.get(1);
|
|
949
955
|
if (!firstPageSize || firstPageSize.width === 0) {
|
|
950
|
-
console.log(
|
|
951
|
-
"[ImagePreview] calculateAutoFitScale 返回 1 (第一页尺寸无效)",
|
|
952
|
-
firstPageSize
|
|
953
|
-
);
|
|
956
|
+
console.log('[ImagePreview] calculateAutoFitScale 返回 1 (第一页尺寸无效)', firstPageSize);
|
|
954
957
|
return 1;
|
|
955
958
|
}
|
|
956
959
|
|
|
@@ -960,10 +963,7 @@ const calculateAutoFitScale = () => {
|
|
|
960
963
|
const containerWidth = containerRect.width - 4;
|
|
961
964
|
|
|
962
965
|
if (containerWidth <= 0) {
|
|
963
|
-
console.log(
|
|
964
|
-
"[ImagePreview] calculateAutoFitScale 返回 1 (容器宽度无效)",
|
|
965
|
-
containerWidth
|
|
966
|
-
);
|
|
966
|
+
console.log('[ImagePreview] calculateAutoFitScale 返回 1 (容器宽度无效)', containerWidth);
|
|
967
967
|
return 1;
|
|
968
968
|
}
|
|
969
969
|
|
|
@@ -974,21 +974,15 @@ const calculateAutoFitScale = () => {
|
|
|
974
974
|
const imageWidth = isRotated ? firstPageSize.height : firstPageSize.width;
|
|
975
975
|
|
|
976
976
|
if (imageWidth <= 0) {
|
|
977
|
-
console.log(
|
|
978
|
-
"[ImagePreview] calculateAutoFitScale 返回 1 (图片宽度无效)",
|
|
979
|
-
imageWidth
|
|
980
|
-
);
|
|
977
|
+
console.log('[ImagePreview] calculateAutoFitScale 返回 1 (图片宽度无效)', imageWidth);
|
|
981
978
|
return 1;
|
|
982
979
|
}
|
|
983
980
|
|
|
984
981
|
// 计算缩放比例,使图片宽度完全适应容器宽度
|
|
985
982
|
const calculatedScale = containerWidth / imageWidth;
|
|
986
|
-
const finalScale = Math.max(
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
);
|
|
990
|
-
|
|
991
|
-
console.log("[ImagePreview] calculateAutoFitScale 计算结果:", {
|
|
983
|
+
const finalScale = Math.max(props.minScale, Math.min(props.maxScale, calculatedScale));
|
|
984
|
+
|
|
985
|
+
console.log('[ImagePreview] calculateAutoFitScale 计算结果:', {
|
|
992
986
|
containerWidth,
|
|
993
987
|
imageWidth,
|
|
994
988
|
calculatedScale,
|
|
@@ -1002,8 +996,8 @@ const calculateAutoFitScale = () => {
|
|
|
1002
996
|
// 图片加载完成处理
|
|
1003
997
|
const onImageLoad = (event: Event, pageNum: number) => {
|
|
1004
998
|
const img = event.target as HTMLImageElement;
|
|
1005
|
-
|
|
1006
|
-
console.log(
|
|
999
|
+
|
|
1000
|
+
console.log('[ImagePreview] 图片加载完成:', {
|
|
1007
1001
|
pageNum,
|
|
1008
1002
|
naturalWidth: img.naturalWidth,
|
|
1009
1003
|
naturalHeight: img.naturalHeight,
|
|
@@ -1011,7 +1005,7 @@ const onImageLoad = (event: Event, pageNum: number) => {
|
|
|
1011
1005
|
isImageReady: isImageReady.value,
|
|
1012
1006
|
isCalculatingAutoFit: isCalculatingAutoFit.value,
|
|
1013
1007
|
});
|
|
1014
|
-
|
|
1008
|
+
|
|
1015
1009
|
// 存储该页的图片尺寸
|
|
1016
1010
|
imageSizes.set(pageNum, {
|
|
1017
1011
|
width: img.naturalWidth,
|
|
@@ -1020,10 +1014,10 @@ const onImageLoad = (event: Event, pageNum: number) => {
|
|
|
1020
1014
|
|
|
1021
1015
|
// 如果是第一页且启用自适应宽度,计算并设置初始缩放比例
|
|
1022
1016
|
if (pageNum === 1 && props.autoFitWidth) {
|
|
1023
|
-
console.log(
|
|
1017
|
+
console.log('[ImagePreview] 第一页加载完成,开始计算自适应宽度');
|
|
1024
1018
|
// 重置用户缩放标记
|
|
1025
1019
|
isUserZooming.value = false;
|
|
1026
|
-
|
|
1020
|
+
|
|
1027
1021
|
// 确保图片是隐藏的(watch 已经设置了,这里再次确认)
|
|
1028
1022
|
if (!isImageReady.value) {
|
|
1029
1023
|
isCalculatingAutoFit.value = true;
|
|
@@ -1031,7 +1025,7 @@ const onImageLoad = (event: Event, pageNum: number) => {
|
|
|
1031
1025
|
|
|
1032
1026
|
// 设置超时保护,防止一直显示 loading(最多等待 3 秒)
|
|
1033
1027
|
const timeoutId = setTimeout(() => {
|
|
1034
|
-
console.warn(
|
|
1028
|
+
console.warn('自适应宽度计算超时,强制显示图片');
|
|
1035
1029
|
isCalculatingAutoFit.value = false;
|
|
1036
1030
|
isImageReady.value = true;
|
|
1037
1031
|
}, 3000);
|
|
@@ -1042,51 +1036,38 @@ const onImageLoad = (event: Event, pageNum: number) => {
|
|
|
1042
1036
|
// 添加小延迟确保容器完全渲染
|
|
1043
1037
|
setTimeout(() => {
|
|
1044
1038
|
try {
|
|
1045
|
-
console.log(
|
|
1039
|
+
console.log('[ImagePreview] onImageLoad: 开始计算自适应宽度...');
|
|
1046
1040
|
const autoScale = calculateAutoFitScale();
|
|
1047
|
-
console.log(
|
|
1041
|
+
console.log('[ImagePreview] onImageLoad: 自适应宽度计算结果:', {
|
|
1048
1042
|
autoScale,
|
|
1049
1043
|
containerRef: !!containerRef.value,
|
|
1050
|
-
containerWidth:
|
|
1051
|
-
containerRef.value?.getBoundingClientRect()?.width,
|
|
1044
|
+
containerWidth: containerRef.value?.getBoundingClientRect()?.width,
|
|
1052
1045
|
firstPageSize: imageSizes.get(1),
|
|
1053
1046
|
});
|
|
1054
|
-
|
|
1047
|
+
|
|
1055
1048
|
if (autoScale > 0) {
|
|
1056
1049
|
scale.value = autoScale;
|
|
1057
1050
|
initialAutoFitScale.value = autoScale; // 记录初始自适应缩放比例
|
|
1058
1051
|
// 记录当前容器宽度,用于后续 resize 检查
|
|
1059
1052
|
if (containerRef.value) {
|
|
1060
|
-
lastContainerWidth =
|
|
1061
|
-
containerRef.value.getBoundingClientRect().width;
|
|
1053
|
+
lastContainerWidth = containerRef.value.getBoundingClientRect().width;
|
|
1062
1054
|
}
|
|
1063
|
-
console.log(
|
|
1064
|
-
"[ImagePreview] onImageLoad: 缩放比例已设置:",
|
|
1065
|
-
autoScale
|
|
1066
|
-
);
|
|
1055
|
+
console.log('[ImagePreview] onImageLoad: 缩放比例已设置:', autoScale);
|
|
1067
1056
|
} else {
|
|
1068
|
-
console.warn(
|
|
1069
|
-
"[ImagePreview] onImageLoad: 计算出的缩放比例无效:",
|
|
1070
|
-
autoScale
|
|
1071
|
-
);
|
|
1057
|
+
console.warn('[ImagePreview] onImageLoad: 计算出的缩放比例无效:', autoScale);
|
|
1072
1058
|
}
|
|
1073
1059
|
} catch (error) {
|
|
1074
|
-
console.error(
|
|
1075
|
-
"[ImagePreview] onImageLoad: 计算自适应宽度失败:",
|
|
1076
|
-
error
|
|
1077
|
-
);
|
|
1060
|
+
console.error('[ImagePreview] onImageLoad: 计算自适应宽度失败:', error);
|
|
1078
1061
|
} finally {
|
|
1079
1062
|
// 清除超时保护
|
|
1080
1063
|
clearTimeout(timeoutId);
|
|
1081
|
-
console.log(
|
|
1082
|
-
"[ImagePreview] onImageLoad: 更新状态: isCalculatingAutoFit = false, isImageReady = true"
|
|
1083
|
-
);
|
|
1064
|
+
console.log('[ImagePreview] onImageLoad: 更新状态: isCalculatingAutoFit = false, isImageReady = true');
|
|
1084
1065
|
// 无论计算结果如何,都要更新状态,避免一直显示 loading
|
|
1085
1066
|
isCalculatingAutoFit.value = false;
|
|
1086
1067
|
// 使用 requestAnimationFrame 确保在下一帧显示,避免闪烁
|
|
1087
1068
|
requestAnimationFrame(() => {
|
|
1088
1069
|
isImageReady.value = true;
|
|
1089
|
-
console.log(
|
|
1070
|
+
console.log('[ImagePreview] onImageLoad: 图片已准备好显示');
|
|
1090
1071
|
});
|
|
1091
1072
|
}
|
|
1092
1073
|
}, 100); // 增加延迟,确保所有图片都已加载
|
|
@@ -1097,7 +1078,7 @@ const onImageLoad = (event: Event, pageNum: number) => {
|
|
|
1097
1078
|
isImageReady.value = true;
|
|
1098
1079
|
isCalculatingAutoFit.value = false;
|
|
1099
1080
|
}
|
|
1100
|
-
|
|
1081
|
+
|
|
1101
1082
|
// 如果第一页已经加载完成,且当前页不是第一页,也应用自适应宽度
|
|
1102
1083
|
if (pageNum > 1 && props.autoFitWidth && initialAutoFitScale.value !== null) {
|
|
1103
1084
|
// 确保后续页面也使用相同的缩放比例
|
|
@@ -1301,6 +1282,11 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1301
1282
|
|
|
1302
1283
|
// Hover 和点击事件
|
|
1303
1284
|
blockDiv.addEventListener("mouseenter", (e) => {
|
|
1285
|
+
// 如果禁用了 hover 效果,不执行相关逻辑
|
|
1286
|
+
if (!props.enableBlockHover) {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1304
1290
|
// 取消之前的隐藏定时器
|
|
1305
1291
|
if (hideTimer) {
|
|
1306
1292
|
clearTimeout(hideTimer);
|
|
@@ -1310,7 +1296,7 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1310
1296
|
// 清除当前页面所有文本块的高亮样式(除了当前文本块)
|
|
1311
1297
|
// 这样可以防止多个文本块同时高亮的竞态条件
|
|
1312
1298
|
clearAllHighlights(blockDiv);
|
|
1313
|
-
|
|
1299
|
+
|
|
1314
1300
|
// 清除跳转高亮标志(如果之前是通过跳转高亮的)
|
|
1315
1301
|
if (isHighlighted.value) {
|
|
1316
1302
|
isHighlighted.value = false;
|
|
@@ -1323,23 +1309,11 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1323
1309
|
const existingAnnotation = getAnnotationForBlock(bbox, targetPage);
|
|
1324
1310
|
if (existingAnnotation) {
|
|
1325
1311
|
// 如果有批注,保持批注背景色,但添加蓝色边框表示 hover
|
|
1326
|
-
blockDiv.style.setProperty(
|
|
1327
|
-
|
|
1328
|
-
"rgba(255, 243, 205, 0.5)",
|
|
1329
|
-
"important"
|
|
1330
|
-
);
|
|
1331
|
-
blockDiv.style.setProperty(
|
|
1332
|
-
"border",
|
|
1333
|
-
"2px solid rgba(30, 144, 255, 0.8)",
|
|
1334
|
-
"important"
|
|
1335
|
-
);
|
|
1312
|
+
blockDiv.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1313
|
+
blockDiv.style.setProperty("border", "2px solid rgba(30, 144, 255, 0.8)", "important");
|
|
1336
1314
|
blockDiv.style.setProperty("border-radius", "3px", "important");
|
|
1337
1315
|
blockDiv.style.setProperty("padding", "1px 3px", "important");
|
|
1338
|
-
blockDiv.style.setProperty(
|
|
1339
|
-
"box-shadow",
|
|
1340
|
-
"0 0 0 2px rgba(30, 144, 255, 0.6), 0 1px 2px rgba(255, 193, 7, 0.25)",
|
|
1341
|
-
"important"
|
|
1342
|
-
);
|
|
1316
|
+
blockDiv.style.setProperty("box-shadow", "0 0 0 2px rgba(30, 144, 255, 0.6), 0 1px 2px rgba(255, 193, 7, 0.25)", "important");
|
|
1343
1317
|
} else {
|
|
1344
1318
|
// 如果没有批注,先清除可能残留的样式,再设置 hover 样式(与 PdfPreview 保持一致)
|
|
1345
1319
|
blockDiv.style.removeProperty("background-color");
|
|
@@ -1358,6 +1332,10 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1358
1332
|
});
|
|
1359
1333
|
|
|
1360
1334
|
blockDiv.addEventListener("mouseleave", () => {
|
|
1335
|
+
// 如果禁用了 hover 效果,不执行隐藏逻辑
|
|
1336
|
+
if (!props.enableBlockHover) {
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1361
1339
|
// 延迟隐藏,给用户时间移动到批注按钮
|
|
1362
1340
|
hideAnnotationButton();
|
|
1363
1341
|
});
|
|
@@ -1412,7 +1390,7 @@ const showAnnotationButtonForBlock = (
|
|
|
1412
1390
|
|
|
1413
1391
|
try {
|
|
1414
1392
|
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1415
|
-
|
|
1393
|
+
|
|
1416
1394
|
// 从 blocksData 中查找对应的 content
|
|
1417
1395
|
const pageBlocksData = getPageBlocksData(currentPage.value);
|
|
1418
1396
|
const blockData = pageBlocksData.find((block) => {
|
|
@@ -1425,7 +1403,7 @@ const showAnnotationButtonForBlock = (
|
|
|
1425
1403
|
Math.abs(y2 - bbox[3]) < tolerance
|
|
1426
1404
|
);
|
|
1427
1405
|
});
|
|
1428
|
-
|
|
1406
|
+
|
|
1429
1407
|
if (!blockData) return;
|
|
1430
1408
|
|
|
1431
1409
|
const rect = blockDiv.getBoundingClientRect();
|
|
@@ -1513,10 +1491,7 @@ const showAnnotationButtonForBlock = (
|
|
|
1513
1491
|
* @param excludeBlockDiv 要排除的文本块(不清除它的样式)
|
|
1514
1492
|
* @param pageNum 要清除的页码,如果不提供则从 excludeBlockDiv 中获取
|
|
1515
1493
|
*/
|
|
1516
|
-
const clearAllHighlights = (
|
|
1517
|
-
excludeBlockDiv?: HTMLElement,
|
|
1518
|
-
pageNum?: number
|
|
1519
|
-
) => {
|
|
1494
|
+
const clearAllHighlights = (excludeBlockDiv?: HTMLElement, pageNum?: number) => {
|
|
1520
1495
|
// 确定要清除的页码
|
|
1521
1496
|
let targetPage = pageNum;
|
|
1522
1497
|
if (!targetPage && excludeBlockDiv) {
|
|
@@ -1550,23 +1525,11 @@ const clearAllHighlights = (
|
|
|
1550
1525
|
|
|
1551
1526
|
if (existingAnnotation) {
|
|
1552
1527
|
// 如果有批注,恢复批注样式(不使用 hover 样式)
|
|
1553
|
-
el.style.setProperty(
|
|
1554
|
-
|
|
1555
|
-
"rgba(255, 243, 205, 0.5)",
|
|
1556
|
-
"important"
|
|
1557
|
-
);
|
|
1558
|
-
el.style.setProperty(
|
|
1559
|
-
"border",
|
|
1560
|
-
"1px solid rgba(255, 193, 7, 0.7)",
|
|
1561
|
-
"important"
|
|
1562
|
-
);
|
|
1528
|
+
el.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1529
|
+
el.style.setProperty("border", "1px solid rgba(255, 193, 7, 0.7)", "important");
|
|
1563
1530
|
el.style.setProperty("border-radius", "3px", "important");
|
|
1564
1531
|
el.style.setProperty("padding", "1px 3px", "important");
|
|
1565
|
-
el.style.setProperty(
|
|
1566
|
-
"box-shadow",
|
|
1567
|
-
"0 1px 2px rgba(255, 193, 7, 0.25)",
|
|
1568
|
-
"important"
|
|
1569
|
-
);
|
|
1532
|
+
el.style.setProperty("box-shadow", "0 1px 2px rgba(255, 193, 7, 0.25)", "important");
|
|
1570
1533
|
} else {
|
|
1571
1534
|
// 如果没有批注,清除所有高亮样式
|
|
1572
1535
|
el.style.backgroundColor = "transparent";
|
|
@@ -1630,29 +1593,17 @@ const restoreBlockStyle = (blockDiv: HTMLElement) => {
|
|
|
1630
1593
|
|
|
1631
1594
|
if (existingAnnotation) {
|
|
1632
1595
|
// 如果有批注,保持批注样式(使用 !important 确保优先级)
|
|
1633
|
-
blockDiv.style.setProperty(
|
|
1634
|
-
|
|
1635
|
-
"rgba(255, 243, 205, 0.5)",
|
|
1636
|
-
"important"
|
|
1637
|
-
);
|
|
1638
|
-
blockDiv.style.setProperty(
|
|
1639
|
-
"border",
|
|
1640
|
-
"1px solid rgba(255, 193, 7, 0.7)",
|
|
1641
|
-
"important"
|
|
1642
|
-
);
|
|
1596
|
+
blockDiv.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1597
|
+
blockDiv.style.setProperty("border", "1px solid rgba(255, 193, 7, 0.7)", "important");
|
|
1643
1598
|
blockDiv.style.setProperty("border-radius", "3px", "important");
|
|
1644
1599
|
blockDiv.style.setProperty("padding", "1px 3px", "important");
|
|
1645
|
-
blockDiv.style.setProperty(
|
|
1646
|
-
|
|
1647
|
-
"0 1px 2px rgba(255, 193, 7, 0.25)",
|
|
1648
|
-
"important"
|
|
1649
|
-
);
|
|
1650
|
-
|
|
1600
|
+
blockDiv.style.setProperty("box-shadow", "0 1px 2px rgba(255, 193, 7, 0.25)", "important");
|
|
1601
|
+
|
|
1651
1602
|
// 确保元素可见
|
|
1652
1603
|
blockDiv.style.setProperty("display", "block", "important");
|
|
1653
1604
|
blockDiv.style.setProperty("visibility", "visible", "important");
|
|
1654
1605
|
blockDiv.style.setProperty("opacity", "1", "important");
|
|
1655
|
-
|
|
1606
|
+
|
|
1656
1607
|
console.log("restoreBlockStyle: 已设置批注样式", {
|
|
1657
1608
|
backgroundColor: blockDiv.style.backgroundColor,
|
|
1658
1609
|
border: blockDiv.style.border,
|
|
@@ -1702,7 +1653,12 @@ const hideAnnotationButton = () => {
|
|
|
1702
1653
|
const bboxStr = activeBlockDiv.value.dataset.bbox;
|
|
1703
1654
|
if (bboxStr) {
|
|
1704
1655
|
try {
|
|
1705
|
-
const bbox = JSON.parse(bboxStr) as [
|
|
1656
|
+
const bbox = JSON.parse(bboxStr) as [
|
|
1657
|
+
number,
|
|
1658
|
+
number,
|
|
1659
|
+
number,
|
|
1660
|
+
number
|
|
1661
|
+
];
|
|
1706
1662
|
const existingAnnotation = getAnnotationForBlock(bbox);
|
|
1707
1663
|
if (existingAnnotation) {
|
|
1708
1664
|
// 如果有批注,恢复批注样式
|
|
@@ -1845,7 +1801,6 @@ const openAnnotationInput = (e?: Event) => {
|
|
|
1845
1801
|
currentAnnotationBlock.value = {
|
|
1846
1802
|
bbox,
|
|
1847
1803
|
content: selectedText,
|
|
1848
|
-
annotationId: existingAnnotation?.id, // 保存已有批注的ID
|
|
1849
1804
|
};
|
|
1850
1805
|
|
|
1851
1806
|
// 确保弹窗显示
|
|
@@ -1955,7 +1910,7 @@ const closeAnnotationInput = () => {
|
|
|
1955
1910
|
currentAnnotationBlock.value = null;
|
|
1956
1911
|
annotationInput.value = "";
|
|
1957
1912
|
showAnnotationPopup.value = false;
|
|
1958
|
-
|
|
1913
|
+
|
|
1959
1914
|
// 关闭批注输入弹窗后,恢复文本块的样式
|
|
1960
1915
|
if (activeBlockDiv.value && !isHighlighted.value) {
|
|
1961
1916
|
restoreBlockStyle(activeBlockDiv.value);
|
|
@@ -1987,22 +1942,15 @@ const saveAnnotation = () => {
|
|
|
1987
1942
|
return;
|
|
1988
1943
|
}
|
|
1989
1944
|
|
|
1990
|
-
const { bbox, content
|
|
1945
|
+
const { bbox, content } = currentAnnotationBlock.value;
|
|
1991
1946
|
const annotationContent = annotationInput.value.trim();
|
|
1992
1947
|
|
|
1993
|
-
//
|
|
1994
|
-
|
|
1995
|
-
if (annotationId && props.annotations) {
|
|
1996
|
-
existingAnnotation =
|
|
1997
|
-
props.annotations.find((ann) => ann.id === annotationId) || null;
|
|
1998
|
-
}
|
|
1999
|
-
// 如果没有通过ID找到,则使用bbox查找(向后兼容)
|
|
2000
|
-
if (!existingAnnotation) {
|
|
2001
|
-
existingAnnotation = getAnnotationForBlock(bbox);
|
|
2002
|
-
}
|
|
1948
|
+
// 检查是否已有批注
|
|
1949
|
+
const existingAnnotation = getAnnotationForBlock(bbox);
|
|
2003
1950
|
|
|
2004
1951
|
const annotation: AnnotationInfo = {
|
|
2005
|
-
id:
|
|
1952
|
+
id:
|
|
1953
|
+
existingAnnotation?.id || "",
|
|
2006
1954
|
blockBbox: bbox,
|
|
2007
1955
|
blockContent: content,
|
|
2008
1956
|
blockPage: currentPage.value,
|
|
@@ -2017,7 +1965,7 @@ const saveAnnotation = () => {
|
|
|
2017
1965
|
} else {
|
|
2018
1966
|
// 添加新批注
|
|
2019
1967
|
emit("annotation-add", annotation);
|
|
2020
|
-
Message.success("批注已添加");
|
|
1968
|
+
// Message.success("批注已添加");
|
|
2021
1969
|
}
|
|
2022
1970
|
|
|
2023
1971
|
// 关闭输入框
|
|
@@ -2034,9 +1982,9 @@ const saveAnnotation = () => {
|
|
|
2034
1982
|
*/
|
|
2035
1983
|
const handleScroll = (e: Event) => {
|
|
2036
1984
|
const container = e.target as HTMLElement;
|
|
2037
|
-
|
|
1985
|
+
|
|
2038
1986
|
// 检查是否是同步滚动触发的
|
|
2039
|
-
const isSyncing = container?.dataset?.syncingScroll ===
|
|
1987
|
+
const isSyncing = container?.dataset?.syncingScroll === 'true';
|
|
2040
1988
|
if (isSyncing) {
|
|
2041
1989
|
// 即使是被同步滚动触发的,也应该立即更新页码(但不触发翻页动画)
|
|
2042
1990
|
// 使用 requestAnimationFrame 确保在浏览器渲染后立即更新
|
|
@@ -2055,7 +2003,7 @@ const handleScroll = (e: Event) => {
|
|
|
2055
2003
|
// 如果没有启用滚动翻页,立即清除标记
|
|
2056
2004
|
delete container.dataset.syncingScroll;
|
|
2057
2005
|
}
|
|
2058
|
-
|
|
2006
|
+
|
|
2059
2007
|
// 同步滚动时,不执行其他逻辑(如隐藏批注按钮等)
|
|
2060
2008
|
return;
|
|
2061
2009
|
}
|
|
@@ -2096,6 +2044,7 @@ const handleScroll = (e: Event) => {
|
|
|
2096
2044
|
}
|
|
2097
2045
|
};
|
|
2098
2046
|
|
|
2047
|
+
|
|
2099
2048
|
// 记录上次滚动位置,用于判断滚动方向
|
|
2100
2049
|
let lastScrollTop = 0;
|
|
2101
2050
|
|
|
@@ -2302,7 +2251,7 @@ const highlightPosition = (
|
|
|
2302
2251
|
const isVisible = isElementVisible(elementRef, containerRef.value);
|
|
2303
2252
|
if (!isVisible) {
|
|
2304
2253
|
// 标记这是定位滚动,不应该被同步滚动干扰
|
|
2305
|
-
containerRef.value.dataset.pageScrolling =
|
|
2254
|
+
containerRef.value.dataset.pageScrolling = 'true';
|
|
2306
2255
|
elementRef.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
2307
2256
|
// 延迟清除标记,确保滚动完成
|
|
2308
2257
|
setTimeout(() => {
|
|
@@ -2367,9 +2316,7 @@ const jumpToPosition = (
|
|
|
2367
2316
|
retryCount++;
|
|
2368
2317
|
setTimeout(tryHighlight, retryDelay);
|
|
2369
2318
|
} else {
|
|
2370
|
-
console.warn(
|
|
2371
|
-
`无法找到并高亮指定位置: 页码 ${pageNum}, bbox: [${bbox.join(", ")}]`
|
|
2372
|
-
);
|
|
2319
|
+
console.warn(`无法找到并高亮指定位置: 页码 ${pageNum}, bbox: [${bbox.join(", ")}]`);
|
|
2373
2320
|
}
|
|
2374
2321
|
};
|
|
2375
2322
|
|
|
@@ -2479,7 +2426,7 @@ const handleContainerResize = () => {
|
|
|
2479
2426
|
lastContainerWidth = currentWidth;
|
|
2480
2427
|
}
|
|
2481
2428
|
|
|
2482
|
-
console.log(
|
|
2429
|
+
console.log('[ImagePreview] handleContainerResize 被调用:', {
|
|
2483
2430
|
autoFitWidth: props.autoFitWidth,
|
|
2484
2431
|
isUserZooming: isUserZooming.value,
|
|
2485
2432
|
isImageReady: isImageReady.value,
|
|
@@ -2498,25 +2445,23 @@ const handleContainerResize = () => {
|
|
|
2498
2445
|
}
|
|
2499
2446
|
|
|
2500
2447
|
// 宽度变化时不显示 loading,只更新缩放比例(避免看起来像重新加载)
|
|
2501
|
-
console.log(
|
|
2448
|
+
console.log('[ImagePreview] handleContainerResize: 开始重新计算');
|
|
2502
2449
|
|
|
2503
2450
|
// 立即计算并应用新的缩放比例,避免过渡期间露出底色
|
|
2504
2451
|
// 使用 requestAnimationFrame 确保在浏览器重绘前更新
|
|
2505
2452
|
requestAnimationFrame(() => {
|
|
2506
2453
|
try {
|
|
2507
|
-
console.log(
|
|
2508
|
-
"[ImagePreview] handleContainerResize: 开始计算自适应宽度..."
|
|
2509
|
-
);
|
|
2454
|
+
console.log('[ImagePreview] handleContainerResize: 开始计算自适应宽度...');
|
|
2510
2455
|
const newScale = calculateAutoFitScale();
|
|
2511
|
-
console.log(
|
|
2512
|
-
|
|
2456
|
+
console.log('[ImagePreview] handleContainerResize: 计算结果:', newScale);
|
|
2457
|
+
|
|
2513
2458
|
if (newScale > 0) {
|
|
2514
2459
|
// 即使变化很小也立即更新,确保过渡期间图片始终填满容器
|
|
2515
2460
|
scale.value = newScale;
|
|
2516
2461
|
initialAutoFitScale.value = newScale;
|
|
2517
2462
|
}
|
|
2518
2463
|
} catch (error) {
|
|
2519
|
-
console.error(
|
|
2464
|
+
console.error('[ImagePreview] handleContainerResize: 计算失败:', error);
|
|
2520
2465
|
}
|
|
2521
2466
|
|
|
2522
2467
|
// 在过渡动画完成后再次检查,确保最终状态正确(处理过渡动画期间的连续变化)
|
|
@@ -2528,13 +2473,10 @@ const handleContainerResize = () => {
|
|
|
2528
2473
|
initialAutoFitScale.value = finalScale;
|
|
2529
2474
|
}
|
|
2530
2475
|
} catch (error) {
|
|
2531
|
-
console.error(
|
|
2532
|
-
"[ImagePreview] handleContainerResize: 最终计算失败:",
|
|
2533
|
-
error
|
|
2534
|
-
);
|
|
2476
|
+
console.error('[ImagePreview] handleContainerResize: 最终计算失败:', error);
|
|
2535
2477
|
} finally {
|
|
2536
2478
|
// 计算完成,重置标记(不改变图片显示状态,因为宽度变化时不应该显示loading)
|
|
2537
|
-
console.log(
|
|
2479
|
+
console.log('[ImagePreview] handleContainerResize: 更新状态完成');
|
|
2538
2480
|
isResizing = false; // 重置标记
|
|
2539
2481
|
}
|
|
2540
2482
|
}, 350); // 350ms 延迟,略大于过渡动画时间(300ms),确保过渡完成后稳定
|
|
@@ -2553,14 +2495,14 @@ onMounted(() => {
|
|
|
2553
2495
|
// 隐藏图片,显示 loading
|
|
2554
2496
|
isImageReady.value = false;
|
|
2555
2497
|
isCalculatingAutoFit.value = true;
|
|
2556
|
-
|
|
2498
|
+
|
|
2557
2499
|
// 设置超时保护,防止一直显示 loading(最多等待 3 秒)
|
|
2558
2500
|
const timeoutId = setTimeout(() => {
|
|
2559
|
-
console.warn(
|
|
2501
|
+
console.warn('自适应宽度计算超时,强制显示图片');
|
|
2560
2502
|
isCalculatingAutoFit.value = false;
|
|
2561
2503
|
isImageReady.value = true;
|
|
2562
2504
|
}, 3000);
|
|
2563
|
-
|
|
2505
|
+
|
|
2564
2506
|
nextTick(() => {
|
|
2565
2507
|
nextTick(() => {
|
|
2566
2508
|
setTimeout(() => {
|
|
@@ -2571,7 +2513,7 @@ onMounted(() => {
|
|
|
2571
2513
|
initialAutoFitScale.value = autoScale;
|
|
2572
2514
|
}
|
|
2573
2515
|
} catch (error) {
|
|
2574
|
-
console.warn(
|
|
2516
|
+
console.warn('计算自适应宽度失败:', error);
|
|
2575
2517
|
} finally {
|
|
2576
2518
|
// 清除超时保护
|
|
2577
2519
|
clearTimeout(timeoutId);
|
|
@@ -2592,7 +2534,7 @@ onMounted(() => {
|
|
|
2592
2534
|
|
|
2593
2535
|
// 监听容器尺寸变化(用于响应外部收起/展开操作)
|
|
2594
2536
|
nextTick(() => {
|
|
2595
|
-
if (containerRef.value && typeof ResizeObserver !==
|
|
2537
|
+
if (containerRef.value && typeof ResizeObserver !== 'undefined') {
|
|
2596
2538
|
resizeObserver = new ResizeObserver((entries) => {
|
|
2597
2539
|
// 使用防抖,避免频繁触发
|
|
2598
2540
|
if (resizeDebounceTimer) {
|
|
@@ -2609,7 +2551,7 @@ onMounted(() => {
|
|
|
2609
2551
|
resizeObserver.observe(containerRef.value);
|
|
2610
2552
|
} else {
|
|
2611
2553
|
// 降级方案:监听窗口大小变化
|
|
2612
|
-
window.addEventListener(
|
|
2554
|
+
window.addEventListener('resize', handleContainerResize);
|
|
2613
2555
|
}
|
|
2614
2556
|
});
|
|
2615
2557
|
});
|
|
@@ -2639,7 +2581,7 @@ onBeforeUnmount(() => {
|
|
|
2639
2581
|
resizeObserver = null;
|
|
2640
2582
|
}
|
|
2641
2583
|
// 移除窗口 resize 监听器(降级方案)
|
|
2642
|
-
window.removeEventListener(
|
|
2584
|
+
window.removeEventListener('resize', handleContainerResize);
|
|
2643
2585
|
});
|
|
2644
2586
|
|
|
2645
2587
|
defineExpose({
|