@koi-br/ocr-web-sdk 1.0.28 → 1.0.30
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-DxvQQd3Y.js → index-BQyoWLBi.js} +74 -74
- package/dist/{index-BUW0_Hv2.mjs → index-CPXnfqe3.mjs} +6500 -6443
- package/dist/index.cjs.js +2 -2
- package/dist/index.esm.js +2 -2
- package/dist/{tiff.min-DFX5shSB.mjs → tiff.min-CDFWZFIu.mjs} +1 -1
- package/dist/{tiff.min-BDrWBfNI.js → tiff.min-DXit1zYs.js} +1 -1
- package/package.json +1 -1
- package/preview/ImagePreview.vue +313 -95
package/package.json
CHANGED
package/preview/ImagePreview.vue
CHANGED
|
@@ -395,6 +395,9 @@ const annotationButtonRef = ref<HTMLElement>();
|
|
|
395
395
|
// 图片尺寸(存储每页的尺寸)
|
|
396
396
|
const imageSizes = new Map<number, { width: number; height: number }>();
|
|
397
397
|
|
|
398
|
+
// 已渲染的页面集合(用于跟踪哪些页面已渲染)
|
|
399
|
+
const renderedPages = ref<Set<number>>(new Set());
|
|
400
|
+
|
|
398
401
|
// 设置图片引用
|
|
399
402
|
const setImageRef = (el: any, pageNum: number) => {
|
|
400
403
|
if (el) {
|
|
@@ -426,12 +429,18 @@ const imageSize = computed({
|
|
|
426
429
|
});
|
|
427
430
|
|
|
428
431
|
// 计算图片放大后的实际尺寸(考虑旋转)
|
|
432
|
+
// 在自适应宽度模式下,使用第一页的尺寸;否则使用当前页的尺寸
|
|
429
433
|
const scaledImageSize = computed(() => {
|
|
430
|
-
|
|
434
|
+
// 如果启用自适应宽度,使用第一页的尺寸作为基准
|
|
435
|
+
const baseSize = props.autoFitWidth
|
|
436
|
+
? (imageSizes.get(1) || { width: 0, height: 0 })
|
|
437
|
+
: imageSize.value;
|
|
438
|
+
|
|
439
|
+
if (baseSize.width === 0 || baseSize.height === 0) {
|
|
431
440
|
return { width: 0, height: 0 };
|
|
432
441
|
}
|
|
433
442
|
|
|
434
|
-
const { width, height } =
|
|
443
|
+
const { width, height } = baseSize;
|
|
435
444
|
const scaledWidth = width * scale.value;
|
|
436
445
|
const scaledHeight = height * scale.value;
|
|
437
446
|
|
|
@@ -817,6 +826,9 @@ const reset = () => {
|
|
|
817
826
|
annotationInput.value = "";
|
|
818
827
|
activeBlockDiv.value = null;
|
|
819
828
|
isHighlighted.value = false;
|
|
829
|
+
|
|
830
|
+
// 清除已渲染页面集合
|
|
831
|
+
renderedPages.value.clear();
|
|
820
832
|
};
|
|
821
833
|
|
|
822
834
|
const original = () => {
|
|
@@ -825,11 +837,13 @@ const original = () => {
|
|
|
825
837
|
|
|
826
838
|
// 计算自适应宽度的缩放比例
|
|
827
839
|
const calculateAutoFitScale = () => {
|
|
828
|
-
if (
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
840
|
+
if (!props.autoFitWidth || !containerRef.value) {
|
|
841
|
+
return 1;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// 使用第一页的图片尺寸作为基准(所有页面使用相同的缩放比例)
|
|
845
|
+
const firstPageSize = imageSizes.get(1);
|
|
846
|
+
if (!firstPageSize || firstPageSize.width === 0) {
|
|
833
847
|
return 1;
|
|
834
848
|
}
|
|
835
849
|
|
|
@@ -846,7 +860,7 @@ const calculateAutoFitScale = () => {
|
|
|
846
860
|
const normalizedRotation = ((rotation.value % 360) + 360) % 360;
|
|
847
861
|
const isRotated = normalizedRotation === 90 || normalizedRotation === 270;
|
|
848
862
|
|
|
849
|
-
const imageWidth = isRotated ?
|
|
863
|
+
const imageWidth = isRotated ? firstPageSize.height : firstPageSize.width;
|
|
850
864
|
|
|
851
865
|
if (imageWidth <= 0) {
|
|
852
866
|
return 1;
|
|
@@ -884,10 +898,18 @@ const onImageLoad = (event: Event, pageNum: number) => {
|
|
|
884
898
|
scale.value = autoScale;
|
|
885
899
|
initialAutoFitScale.value = autoScale; // 记录初始自适应缩放比例
|
|
886
900
|
}
|
|
887
|
-
},
|
|
901
|
+
}, 100); // 增加延迟,确保所有图片都已加载
|
|
888
902
|
});
|
|
889
903
|
});
|
|
890
904
|
}
|
|
905
|
+
|
|
906
|
+
// 如果第一页已经加载完成,且当前页不是第一页,也应用自适应宽度
|
|
907
|
+
if (pageNum > 1 && props.autoFitWidth && initialAutoFitScale.value !== null) {
|
|
908
|
+
// 确保后续页面也使用相同的缩放比例
|
|
909
|
+
if (Math.abs(scale.value - initialAutoFitScale.value) > 0.01) {
|
|
910
|
+
scale.value = initialAutoFitScale.value;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
891
913
|
|
|
892
914
|
emit("load", {
|
|
893
915
|
width: img.naturalWidth,
|
|
@@ -959,7 +981,7 @@ const calculateFontSize = (
|
|
|
959
981
|
};
|
|
960
982
|
|
|
961
983
|
/**
|
|
962
|
-
*
|
|
984
|
+
* 渲染文本图层
|
|
963
985
|
*/
|
|
964
986
|
const renderTextLayer = (pageNum?: number) => {
|
|
965
987
|
const targetPage = pageNum || currentPage.value;
|
|
@@ -970,15 +992,35 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
970
992
|
return;
|
|
971
993
|
}
|
|
972
994
|
|
|
995
|
+
// 如果图片还没加载完成,等待加载完成后再渲染
|
|
996
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// 如果没有提供分块数据,跳过渲染(不标记为已渲染)
|
|
1001
|
+
if (!props.blocksData || props.blocksData.length === 0) {
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
973
1005
|
const pageBlocksData = getPageBlocksData(targetPage);
|
|
974
|
-
console.log("renderTextLayer", targetPage, pageBlocksData);
|
|
975
1006
|
|
|
976
|
-
//
|
|
1007
|
+
// 如果当前页面没有数据,跳过渲染(不标记为已渲染)
|
|
977
1008
|
if (!pageBlocksData || pageBlocksData.length === 0) {
|
|
978
|
-
textLayer.innerHTML = "";
|
|
979
1009
|
return;
|
|
980
1010
|
}
|
|
981
1011
|
|
|
1012
|
+
// 如果页面已渲染且用户正在交互,跳过渲染(避免打断用户)
|
|
1013
|
+
if (renderedPages.value.has(targetPage) && showAnnotationPopup.value) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// 如果页面已渲染,但当前有数据,需要检查是否真的渲染过
|
|
1018
|
+
// 如果textLayer是空的,说明之前没有真正渲染过,应该重新渲染
|
|
1019
|
+
if (renderedPages.value.has(targetPage) && textLayer.children.length === 0) {
|
|
1020
|
+
// 从已渲染集合中移除,允许重新渲染
|
|
1021
|
+
renderedPages.value.delete(targetPage);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
982
1024
|
try {
|
|
983
1025
|
// 设置文本图层的尺寸与图片尺寸一致
|
|
984
1026
|
textLayer.style.width = `${image.naturalWidth}px`;
|
|
@@ -987,7 +1029,19 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
987
1029
|
// 清空文本图层
|
|
988
1030
|
textLayer.innerHTML = "";
|
|
989
1031
|
|
|
990
|
-
//
|
|
1032
|
+
// 如果清空前有激活的文本块,且该文本块属于当前页面,则清除引用
|
|
1033
|
+
if (activeBlockDiv.value) {
|
|
1034
|
+
const activePageElement = activeBlockDiv.value.closest(
|
|
1035
|
+
".image-page-container"
|
|
1036
|
+
);
|
|
1037
|
+
const currentPageElement = textLayer.closest(".image-page-container");
|
|
1038
|
+
if (activePageElement === currentPageElement) {
|
|
1039
|
+
activeBlockDiv.value = null;
|
|
1040
|
+
showAnnotationPopup.value = false;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// 使用 blocksData 数据创建可交互的块
|
|
991
1045
|
pageBlocksData.forEach((block, index) => {
|
|
992
1046
|
const { content, bbox } = block;
|
|
993
1047
|
|
|
@@ -996,14 +1050,11 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
996
1050
|
const width = x2 - x1;
|
|
997
1051
|
const height = y2 - y1;
|
|
998
1052
|
|
|
999
|
-
//
|
|
1000
|
-
const calculatedFontSize = calculateFontSize(content, width, height);
|
|
1001
|
-
|
|
1002
|
-
// 创建文本块
|
|
1053
|
+
// 创建文本块(不显示内容,只用于 hover 交互和批注功能)
|
|
1003
1054
|
const blockDiv = document.createElement("div");
|
|
1004
1055
|
blockDiv.className = "text-block";
|
|
1005
|
-
blockDiv.dataset.text = content;
|
|
1006
1056
|
blockDiv.dataset.bbox = JSON.stringify(bbox);
|
|
1057
|
+
blockDiv.dataset.page = String(targetPage);
|
|
1007
1058
|
|
|
1008
1059
|
// 设置基础样式
|
|
1009
1060
|
blockDiv.style.position = "absolute";
|
|
@@ -1011,38 +1062,16 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1011
1062
|
blockDiv.style.top = `${y1}px`;
|
|
1012
1063
|
blockDiv.style.width = `${width}px`;
|
|
1013
1064
|
blockDiv.style.height = `${height}px`;
|
|
1014
|
-
blockDiv.style.zIndex = "20";
|
|
1015
|
-
blockDiv.style.cursor = "
|
|
1016
|
-
|
|
1017
|
-
blockDiv.
|
|
1018
|
-
|
|
1019
|
-
blockDiv.style.color = "red"; // 红色文字
|
|
1020
|
-
blockDiv.style.fontSize = `${calculatedFontSize}px`; // 使用计算出的字体大小
|
|
1021
|
-
blockDiv.style.fontFamily = "Arial, sans-serif"; // 设置明确的字体
|
|
1022
|
-
blockDiv.style.lineHeight = "1.2"; // 设置合适的行高
|
|
1023
|
-
blockDiv.style.whiteSpace = "pre-wrap"; // 保留换行和空格
|
|
1024
|
-
blockDiv.style.overflow = "visible"; // 确保文字不被裁剪
|
|
1025
|
-
blockDiv.style.display = "block"; // 确保是块级元素
|
|
1026
|
-
blockDiv.style.visibility = "visible"; // 确保可见
|
|
1027
|
-
// 允许文本选择
|
|
1028
|
-
blockDiv.style.userSelect = "text";
|
|
1029
|
-
blockDiv.style.webkitUserSelect = "text";
|
|
1030
|
-
blockDiv.style.mozUserSelect = "text";
|
|
1031
|
-
blockDiv.style.msUserSelect = "text";
|
|
1032
|
-
// 允许指针事件,但不阻止文本选择
|
|
1065
|
+
blockDiv.style.zIndex = "20";
|
|
1066
|
+
blockDiv.style.cursor = "pointer";
|
|
1067
|
+
blockDiv.style.borderRadius = "2px";
|
|
1068
|
+
blockDiv.style.transition = "all 0.2s ease";
|
|
1069
|
+
blockDiv.style.backgroundColor = "transparent";
|
|
1033
1070
|
blockDiv.style.pointerEvents = "auto";
|
|
1034
|
-
|
|
1035
|
-
// 右键菜单:显示批注按钮
|
|
1036
|
-
blockDiv.addEventListener("contextmenu", (e) => {
|
|
1037
|
-
e.preventDefault();
|
|
1038
|
-
// 设置当前文本块为激活状态
|
|
1039
|
-
activeBlockDiv.value = blockDiv;
|
|
1040
|
-
// 显示批注按钮
|
|
1041
|
-
showAnnotationButtonForBlock(e, blockDiv);
|
|
1042
|
-
});
|
|
1071
|
+
blockDiv.style.userSelect = "none";
|
|
1043
1072
|
|
|
1044
1073
|
// 检查是否有已有批注,如果有则显示批注标记
|
|
1045
|
-
const existingAnnotation = getAnnotationForBlock(bbox);
|
|
1074
|
+
const existingAnnotation = getAnnotationForBlock(bbox, targetPage);
|
|
1046
1075
|
if (existingAnnotation) {
|
|
1047
1076
|
// 添加批注标记样式类
|
|
1048
1077
|
blockDiv.classList.add("has-annotation");
|
|
@@ -1073,13 +1102,57 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1073
1102
|
annotationMarker.style.boxShadow = "0 1px 3px rgba(0, 0, 0, 0.25)";
|
|
1074
1103
|
annotationMarker.style.lineHeight = "1";
|
|
1075
1104
|
blockDiv.appendChild(annotationMarker);
|
|
1076
|
-
} else {
|
|
1077
|
-
// 没有批注时设置为透明背景
|
|
1078
|
-
blockDiv.style.backgroundColor = "transparent";
|
|
1079
1105
|
}
|
|
1080
1106
|
|
|
1107
|
+
// Hover 和点击事件
|
|
1108
|
+
blockDiv.addEventListener("mouseenter", (e) => {
|
|
1109
|
+
// 取消之前的隐藏定时器
|
|
1110
|
+
if (hideTimer) {
|
|
1111
|
+
clearTimeout(hideTimer);
|
|
1112
|
+
hideTimer = null;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// 如果有之前激活的文本块,先恢复其样式
|
|
1116
|
+
if (activeBlockDiv.value && activeBlockDiv.value !== blockDiv) {
|
|
1117
|
+
restoreBlockStyle(activeBlockDiv.value);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// 设置当前文本块为激活状态
|
|
1121
|
+
activeBlockDiv.value = blockDiv;
|
|
1122
|
+
|
|
1123
|
+
// 检查是否有批注,如果有批注,hover 时在批注样式基础上添加蓝色边框
|
|
1124
|
+
const existingAnnotation = getAnnotationForBlock(bbox, targetPage);
|
|
1125
|
+
if (existingAnnotation) {
|
|
1126
|
+
// 如果有批注,保持批注背景色,但添加蓝色边框表示 hover
|
|
1127
|
+
blockDiv.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1128
|
+
blockDiv.style.setProperty("border", "2px solid rgba(30, 144, 255, 0.8)", "important");
|
|
1129
|
+
blockDiv.style.setProperty("border-radius", "3px", "important");
|
|
1130
|
+
blockDiv.style.setProperty("padding", "1px 3px", "important");
|
|
1131
|
+
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");
|
|
1132
|
+
} else {
|
|
1133
|
+
// 如果没有批注,使用 hover 样式(与 PdfPreview 保持一致)
|
|
1134
|
+
blockDiv.style.backgroundColor =
|
|
1135
|
+
"var(--s-color-brand-primary-transparent-3, rgba(0, 102, 255, .15))";
|
|
1136
|
+
blockDiv.style.boxShadow = "0 0 0 2px rgba(30, 144, 255, 0.6)";
|
|
1137
|
+
blockDiv.style.borderRadius = "2px";
|
|
1138
|
+
blockDiv.style.padding = "1px 3px";
|
|
1139
|
+
blockDiv.style.border = "none";
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// 显示批注按钮浮层
|
|
1143
|
+
showAnnotationButtonForBlock(e, blockDiv);
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
blockDiv.addEventListener("mouseleave", () => {
|
|
1147
|
+
// 延迟隐藏,给用户时间移动到批注按钮
|
|
1148
|
+
hideAnnotationButton();
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1081
1151
|
textLayer.appendChild(blockDiv);
|
|
1082
1152
|
});
|
|
1153
|
+
|
|
1154
|
+
// 标记该页面已渲染
|
|
1155
|
+
renderedPages.value.add(targetPage);
|
|
1083
1156
|
} catch (error) {
|
|
1084
1157
|
console.error("❌ 文本图层渲染失败:", error);
|
|
1085
1158
|
}
|
|
@@ -1089,12 +1162,14 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1089
1162
|
* 获取文本块对应的批注
|
|
1090
1163
|
*/
|
|
1091
1164
|
const getAnnotationForBlock = (
|
|
1092
|
-
bbox: [number, number, number, number]
|
|
1165
|
+
bbox: [number, number, number, number],
|
|
1166
|
+
pageNum?: number
|
|
1093
1167
|
): AnnotationInfo | null => {
|
|
1094
1168
|
if (!props.annotations || props.annotations.length === 0) {
|
|
1095
1169
|
return null;
|
|
1096
1170
|
}
|
|
1097
1171
|
|
|
1172
|
+
const targetPage = pageNum !== undefined ? pageNum : currentPage.value;
|
|
1098
1173
|
const tolerance = 2; // 容差
|
|
1099
1174
|
return (
|
|
1100
1175
|
props.annotations.find((annotation) => {
|
|
@@ -1105,7 +1180,7 @@ const getAnnotationForBlock = (
|
|
|
1105
1180
|
Math.abs(x2 - bbox[2]) < tolerance &&
|
|
1106
1181
|
Math.abs(y2 - bbox[3]) < tolerance &&
|
|
1107
1182
|
(annotation.blockPage === undefined ||
|
|
1108
|
-
annotation.blockPage ===
|
|
1183
|
+
annotation.blockPage === targetPage)
|
|
1109
1184
|
);
|
|
1110
1185
|
}) || null
|
|
1111
1186
|
);
|
|
@@ -1118,12 +1193,26 @@ const showAnnotationButtonForBlock = (
|
|
|
1118
1193
|
event: MouseEvent,
|
|
1119
1194
|
blockDiv: HTMLElement
|
|
1120
1195
|
) => {
|
|
1121
|
-
const text = blockDiv.dataset.text || "";
|
|
1122
1196
|
const bboxStr = blockDiv.dataset.bbox || "";
|
|
1123
|
-
if (!
|
|
1197
|
+
if (!bboxStr) return;
|
|
1124
1198
|
|
|
1125
1199
|
try {
|
|
1126
1200
|
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1201
|
+
|
|
1202
|
+
// 从 blocksData 中查找对应的 content
|
|
1203
|
+
const pageBlocksData = getPageBlocksData(currentPage.value);
|
|
1204
|
+
const blockData = pageBlocksData.find((block) => {
|
|
1205
|
+
const [x1, y1, x2, y2] = block.bbox;
|
|
1206
|
+
const tolerance = 2;
|
|
1207
|
+
return (
|
|
1208
|
+
Math.abs(x1 - bbox[0]) < tolerance &&
|
|
1209
|
+
Math.abs(y1 - bbox[1]) < tolerance &&
|
|
1210
|
+
Math.abs(x2 - bbox[2]) < tolerance &&
|
|
1211
|
+
Math.abs(y2 - bbox[3]) < tolerance
|
|
1212
|
+
);
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
if (!blockData) return;
|
|
1127
1216
|
|
|
1128
1217
|
const rect = blockDiv.getBoundingClientRect();
|
|
1129
1218
|
const containerRect = containerRef.value?.getBoundingClientRect();
|
|
@@ -1206,7 +1295,78 @@ const showAnnotationButtonForBlock = (
|
|
|
1206
1295
|
};
|
|
1207
1296
|
|
|
1208
1297
|
/**
|
|
1209
|
-
*
|
|
1298
|
+
* 恢复文本块样式(根据是否有批注)
|
|
1299
|
+
*/
|
|
1300
|
+
const restoreBlockStyle = (blockDiv: HTMLElement) => {
|
|
1301
|
+
const bboxStr = blockDiv.dataset.bbox;
|
|
1302
|
+
if (!bboxStr) {
|
|
1303
|
+
console.log("restoreBlockStyle: 没有 bbox");
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
try {
|
|
1308
|
+
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1309
|
+
const pageStr = blockDiv.dataset.page;
|
|
1310
|
+
const pageNum = pageStr ? parseInt(pageStr, 10) : undefined;
|
|
1311
|
+
const existingAnnotation = getAnnotationForBlock(bbox, pageNum);
|
|
1312
|
+
|
|
1313
|
+
console.log("restoreBlockStyle:", {
|
|
1314
|
+
bbox,
|
|
1315
|
+
pageNum,
|
|
1316
|
+
existingAnnotation,
|
|
1317
|
+
blockDiv: blockDiv,
|
|
1318
|
+
width: blockDiv.style.width,
|
|
1319
|
+
height: blockDiv.style.height,
|
|
1320
|
+
computedStyle: {
|
|
1321
|
+
backgroundColor: window.getComputedStyle(blockDiv).backgroundColor,
|
|
1322
|
+
width: window.getComputedStyle(blockDiv).width,
|
|
1323
|
+
height: window.getComputedStyle(blockDiv).height,
|
|
1324
|
+
display: window.getComputedStyle(blockDiv).display,
|
|
1325
|
+
visibility: window.getComputedStyle(blockDiv).visibility,
|
|
1326
|
+
},
|
|
1327
|
+
inlineStyle: {
|
|
1328
|
+
backgroundColor: blockDiv.style.backgroundColor,
|
|
1329
|
+
border: blockDiv.style.border,
|
|
1330
|
+
},
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
if (existingAnnotation) {
|
|
1334
|
+
// 如果有批注,保持批注样式(使用 !important 确保优先级)
|
|
1335
|
+
blockDiv.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1336
|
+
blockDiv.style.setProperty("border", "1px solid rgba(255, 193, 7, 0.7)", "important");
|
|
1337
|
+
blockDiv.style.setProperty("border-radius", "3px", "important");
|
|
1338
|
+
blockDiv.style.setProperty("padding", "1px 3px", "important");
|
|
1339
|
+
blockDiv.style.setProperty("box-shadow", "0 1px 2px rgba(255, 193, 7, 0.25)", "important");
|
|
1340
|
+
|
|
1341
|
+
// 确保元素可见
|
|
1342
|
+
blockDiv.style.setProperty("display", "block", "important");
|
|
1343
|
+
blockDiv.style.setProperty("visibility", "visible", "important");
|
|
1344
|
+
blockDiv.style.setProperty("opacity", "1", "important");
|
|
1345
|
+
|
|
1346
|
+
console.log("restoreBlockStyle: 已设置批注样式", {
|
|
1347
|
+
backgroundColor: blockDiv.style.backgroundColor,
|
|
1348
|
+
border: blockDiv.style.border,
|
|
1349
|
+
computedAfter: window.getComputedStyle(blockDiv).backgroundColor,
|
|
1350
|
+
});
|
|
1351
|
+
} else {
|
|
1352
|
+
// 如果没有批注,恢复透明背景
|
|
1353
|
+
blockDiv.style.backgroundColor = "transparent";
|
|
1354
|
+
blockDiv.style.border = "none";
|
|
1355
|
+
blockDiv.style.padding = "0";
|
|
1356
|
+
blockDiv.style.boxShadow = "none";
|
|
1357
|
+
}
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
console.error("restoreBlockStyle 错误:", error);
|
|
1360
|
+
// 如果解析失败,恢复透明背景
|
|
1361
|
+
blockDiv.style.backgroundColor = "transparent";
|
|
1362
|
+
blockDiv.style.border = "none";
|
|
1363
|
+
blockDiv.style.padding = "0";
|
|
1364
|
+
blockDiv.style.boxShadow = "none";
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* 隐藏批注按钮和高亮
|
|
1210
1370
|
*/
|
|
1211
1371
|
const hideAnnotationButton = () => {
|
|
1212
1372
|
// 如果正在输入批注,不自动隐藏
|
|
@@ -1217,9 +1377,41 @@ const hideAnnotationButton = () => {
|
|
|
1217
1377
|
hideTimer = setTimeout(() => {
|
|
1218
1378
|
showAnnotationPopup.value = false;
|
|
1219
1379
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
activeBlockDiv.value.
|
|
1380
|
+
// 恢复文本块的样式(如果有批注则保持批注样式)
|
|
1381
|
+
if (activeBlockDiv.value) {
|
|
1382
|
+
const bboxStr = activeBlockDiv.value.dataset.bbox;
|
|
1383
|
+
if (bboxStr) {
|
|
1384
|
+
try {
|
|
1385
|
+
const bbox = JSON.parse(bboxStr) as [
|
|
1386
|
+
number,
|
|
1387
|
+
number,
|
|
1388
|
+
number,
|
|
1389
|
+
number
|
|
1390
|
+
];
|
|
1391
|
+
const existingAnnotation = getAnnotationForBlock(bbox);
|
|
1392
|
+
if (existingAnnotation) {
|
|
1393
|
+
// 如果有批注,恢复批注样式
|
|
1394
|
+
activeBlockDiv.value.style.backgroundColor =
|
|
1395
|
+
"rgba(255, 243, 205, 0.5)";
|
|
1396
|
+
activeBlockDiv.value.style.border =
|
|
1397
|
+
"1px solid rgba(255, 193, 7, 0.7)";
|
|
1398
|
+
activeBlockDiv.value.style.padding = "1px 3px";
|
|
1399
|
+
activeBlockDiv.value.style.boxShadow =
|
|
1400
|
+
"0 1px 2px rgba(255, 193, 7, 0.25)";
|
|
1401
|
+
} else {
|
|
1402
|
+
// 如果没有批注,恢复透明背景
|
|
1403
|
+
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1404
|
+
activeBlockDiv.value.style.border = "none";
|
|
1405
|
+
activeBlockDiv.value.style.padding = "0";
|
|
1406
|
+
activeBlockDiv.value.style.boxShadow = "none";
|
|
1407
|
+
}
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1410
|
+
activeBlockDiv.value.style.border = "none";
|
|
1411
|
+
activeBlockDiv.value.style.padding = "0";
|
|
1412
|
+
activeBlockDiv.value.style.boxShadow = "none";
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1223
1415
|
activeBlockDiv.value = null;
|
|
1224
1416
|
}
|
|
1225
1417
|
}, 300);
|
|
@@ -1244,14 +1436,36 @@ const openAnnotationInput = (e?: Event) => {
|
|
|
1244
1436
|
e.stopPropagation();
|
|
1245
1437
|
}
|
|
1246
1438
|
|
|
1247
|
-
//
|
|
1439
|
+
// 获取选中的文本或从文本块中获取
|
|
1248
1440
|
const selection = window.getSelection();
|
|
1249
1441
|
let selectedText = "";
|
|
1250
1442
|
|
|
1251
1443
|
if (selection && selection.toString().trim().length > 0) {
|
|
1252
1444
|
selectedText = selection.toString().trim();
|
|
1253
1445
|
} else if (activeBlockDiv.value) {
|
|
1254
|
-
|
|
1446
|
+
// 从 blocksData 中查找对应的 content
|
|
1447
|
+
const bboxStr = activeBlockDiv.value.dataset.bbox || "";
|
|
1448
|
+
if (bboxStr) {
|
|
1449
|
+
try {
|
|
1450
|
+
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1451
|
+
const pageBlocksData = getPageBlocksData(currentPage.value);
|
|
1452
|
+
const blockData = pageBlocksData.find((block) => {
|
|
1453
|
+
const [x1, y1, x2, y2] = block.bbox;
|
|
1454
|
+
const tolerance = 2;
|
|
1455
|
+
return (
|
|
1456
|
+
Math.abs(x1 - bbox[0]) < tolerance &&
|
|
1457
|
+
Math.abs(y1 - bbox[1]) < tolerance &&
|
|
1458
|
+
Math.abs(x2 - bbox[2]) < tolerance &&
|
|
1459
|
+
Math.abs(y2 - bbox[3]) < tolerance
|
|
1460
|
+
);
|
|
1461
|
+
});
|
|
1462
|
+
if (blockData) {
|
|
1463
|
+
selectedText = blockData.content || "";
|
|
1464
|
+
}
|
|
1465
|
+
} catch (error) {
|
|
1466
|
+
console.error("解析 bbox 失败:", error);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1255
1469
|
}
|
|
1256
1470
|
|
|
1257
1471
|
if (!selectedText && !activeBlockDiv.value) return;
|
|
@@ -1412,6 +1626,12 @@ const closeAnnotationInput = () => {
|
|
|
1412
1626
|
currentAnnotationBlock.value = null;
|
|
1413
1627
|
annotationInput.value = "";
|
|
1414
1628
|
showAnnotationPopup.value = false;
|
|
1629
|
+
|
|
1630
|
+
// 关闭批注输入弹窗后,恢复文本块的样式
|
|
1631
|
+
if (activeBlockDiv.value && !isHighlighted.value) {
|
|
1632
|
+
restoreBlockStyle(activeBlockDiv.value);
|
|
1633
|
+
activeBlockDiv.value = null;
|
|
1634
|
+
}
|
|
1415
1635
|
};
|
|
1416
1636
|
|
|
1417
1637
|
/**
|
|
@@ -1777,65 +1997,58 @@ const jumpToPosition = (
|
|
|
1777
1997
|
};
|
|
1778
1998
|
|
|
1779
1999
|
/**
|
|
1780
|
-
* 监听 blocksData
|
|
2000
|
+
* 监听 blocksData 变化,增量渲染所有页面的文本图层(避免闪烁)
|
|
1781
2001
|
*/
|
|
1782
2002
|
watch(
|
|
1783
2003
|
() => props.blocksData,
|
|
1784
2004
|
() => {
|
|
1785
2005
|
nextTick(() => {
|
|
1786
|
-
|
|
2006
|
+
// 渲染所有页面的文本图层
|
|
2007
|
+
for (let pageNum = 1; pageNum <= totalPages.value; pageNum++) {
|
|
2008
|
+
renderTextLayer(pageNum);
|
|
2009
|
+
}
|
|
1787
2010
|
});
|
|
1788
2011
|
},
|
|
1789
|
-
{ deep: true, immediate:
|
|
2012
|
+
{ deep: true, immediate: true }
|
|
1790
2013
|
);
|
|
1791
2014
|
|
|
1792
2015
|
/**
|
|
1793
|
-
* 监听 annotations
|
|
2016
|
+
* 监听 annotations 变化,增量更新所有页面的文本图层以显示批注标记
|
|
1794
2017
|
*/
|
|
1795
2018
|
watch(
|
|
1796
2019
|
() => props.annotations,
|
|
1797
2020
|
() => {
|
|
1798
2021
|
nextTick(() => {
|
|
1799
|
-
|
|
2022
|
+
// 更新所有页面的文本图层
|
|
2023
|
+
for (let pageNum = 1; pageNum <= totalPages.value; pageNum++) {
|
|
2024
|
+
renderTextLayer(pageNum);
|
|
2025
|
+
}
|
|
1800
2026
|
});
|
|
1801
2027
|
},
|
|
1802
2028
|
{ deep: true }
|
|
1803
2029
|
);
|
|
1804
2030
|
|
|
1805
2031
|
/**
|
|
1806
|
-
*
|
|
2032
|
+
* 监听当前页码变化,确保当前页的文本图层已渲染
|
|
1807
2033
|
*/
|
|
1808
2034
|
watch(
|
|
1809
2035
|
() => currentPage.value,
|
|
1810
2036
|
() => {
|
|
1811
2037
|
nextTick(() => {
|
|
1812
|
-
renderTextLayer();
|
|
2038
|
+
renderTextLayer(currentPage.value);
|
|
1813
2039
|
});
|
|
1814
2040
|
}
|
|
1815
2041
|
);
|
|
1816
2042
|
|
|
1817
2043
|
/**
|
|
1818
|
-
*
|
|
1819
|
-
*/
|
|
1820
|
-
watch(
|
|
1821
|
-
() => currentPageBlocksData.value,
|
|
1822
|
-
() => {
|
|
1823
|
-
nextTick(() => {
|
|
1824
|
-
renderTextLayer();
|
|
1825
|
-
});
|
|
1826
|
-
},
|
|
1827
|
-
{ deep: true }
|
|
1828
|
-
);
|
|
1829
|
-
|
|
1830
|
-
/**
|
|
1831
|
-
* 监听图片尺寸变化,重新渲染文本图层
|
|
2044
|
+
* 监听图片尺寸变化,重新渲染当前页的文本图层
|
|
1832
2045
|
*/
|
|
1833
2046
|
watch(
|
|
1834
2047
|
() => imageSize.value,
|
|
1835
2048
|
() => {
|
|
1836
2049
|
if (imageSize.value.width > 0 && imageSize.value.height > 0) {
|
|
1837
2050
|
nextTick(() => {
|
|
1838
|
-
renderTextLayer();
|
|
2051
|
+
renderTextLayer(currentPage.value);
|
|
1839
2052
|
});
|
|
1840
2053
|
}
|
|
1841
2054
|
},
|
|
@@ -1858,18 +2071,23 @@ watch(
|
|
|
1858
2071
|
* 组件挂载时的初始化
|
|
1859
2072
|
*/
|
|
1860
2073
|
onMounted(() => {
|
|
1861
|
-
//
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2074
|
+
// 如果第一页图片已经加载完成,立即计算自适应宽度
|
|
2075
|
+
const firstPageImage = imageRefs.get(1);
|
|
2076
|
+
if (firstPageImage && firstPageImage.complete && props.autoFitWidth) {
|
|
2077
|
+
const firstPageSize = imageSizes.get(1);
|
|
2078
|
+
if (firstPageSize && firstPageSize.width > 0) {
|
|
2079
|
+
nextTick(() => {
|
|
2080
|
+
nextTick(() => {
|
|
2081
|
+
setTimeout(() => {
|
|
2082
|
+
const autoScale = calculateAutoFitScale();
|
|
2083
|
+
if (autoScale !== 1 && autoScale > 0) {
|
|
2084
|
+
scale.value = autoScale;
|
|
2085
|
+
initialAutoFitScale.value = autoScale;
|
|
2086
|
+
}
|
|
2087
|
+
}, 100);
|
|
2088
|
+
});
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
1873
2091
|
}
|
|
1874
2092
|
});
|
|
1875
2093
|
|