@koi-br/ocr-web-sdk 1.0.29 → 1.0.31
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-DG14_3TA.js → index-BZ8ndzte.js} +84 -84
- package/dist/{index-QOLKOKLO.mjs → index-IRUtAb6l.mjs} +8649 -8619
- package/dist/index.cjs.js +2 -2
- package/dist/index.esm.js +2 -2
- package/dist/{tiff.min-Com-G-pb.js → tiff.min-BJf8TYMB.js} +1 -1
- package/dist/{tiff.min-D0JMbw7k.mjs → tiff.min-BaSKqyGR.mjs} +1 -1
- package/package.json +1 -1
- package/preview/ImagePreview.vue +288 -140
- package/preview/index.vue +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as getAugmentedNamespace, a as getDefaultExportFromCjs } from "./index-
|
|
1
|
+
import { g as getAugmentedNamespace, a as getDefaultExportFromCjs } from "./index-IRUtAb6l.mjs";
|
|
2
2
|
function _mergeNamespaces(U, W) {
|
|
3
3
|
for (var Z = 0; Z < W.length; Z++) {
|
|
4
4
|
const s0 = W[Z];
|
package/package.json
CHANGED
package/preview/ImagePreview.vue
CHANGED
|
@@ -272,6 +272,15 @@ interface AnnotationInfo {
|
|
|
272
272
|
createTime: number; // 创建时间戳
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/**
|
|
276
|
+
* 高亮样式配置
|
|
277
|
+
*/
|
|
278
|
+
interface HighlightStyle {
|
|
279
|
+
backgroundColor?: string; // 背景色
|
|
280
|
+
border?: string; // 边框样式(如 "2px solid rgba(30, 144, 255, 0.8)")
|
|
281
|
+
boxShadow?: string; // 阴影样式
|
|
282
|
+
}
|
|
283
|
+
|
|
275
284
|
const props = defineProps({
|
|
276
285
|
// 支持单个URL(向后兼容)或URL数组
|
|
277
286
|
url: {
|
|
@@ -395,6 +404,9 @@ const annotationButtonRef = ref<HTMLElement>();
|
|
|
395
404
|
// 图片尺寸(存储每页的尺寸)
|
|
396
405
|
const imageSizes = new Map<number, { width: number; height: number }>();
|
|
397
406
|
|
|
407
|
+
// 已渲染的页面集合(用于跟踪哪些页面已渲染)
|
|
408
|
+
const renderedPages = ref<Set<number>>(new Set());
|
|
409
|
+
|
|
398
410
|
// 设置图片引用
|
|
399
411
|
const setImageRef = (el: any, pageNum: number) => {
|
|
400
412
|
if (el) {
|
|
@@ -823,6 +835,9 @@ const reset = () => {
|
|
|
823
835
|
annotationInput.value = "";
|
|
824
836
|
activeBlockDiv.value = null;
|
|
825
837
|
isHighlighted.value = false;
|
|
838
|
+
|
|
839
|
+
// 清除已渲染页面集合
|
|
840
|
+
renderedPages.value.clear();
|
|
826
841
|
};
|
|
827
842
|
|
|
828
843
|
const original = () => {
|
|
@@ -975,7 +990,7 @@ const calculateFontSize = (
|
|
|
975
990
|
};
|
|
976
991
|
|
|
977
992
|
/**
|
|
978
|
-
*
|
|
993
|
+
* 渲染文本图层
|
|
979
994
|
*/
|
|
980
995
|
const renderTextLayer = (pageNum?: number) => {
|
|
981
996
|
const targetPage = pageNum || currentPage.value;
|
|
@@ -991,33 +1006,51 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
991
1006
|
return;
|
|
992
1007
|
}
|
|
993
1008
|
|
|
994
|
-
|
|
995
|
-
|
|
1009
|
+
// 如果没有提供分块数据,跳过渲染(不标记为已渲染)
|
|
1010
|
+
if (!props.blocksData || props.blocksData.length === 0) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
996
1013
|
|
|
997
|
-
|
|
998
|
-
textLayer.style.width = `${image.naturalWidth}px`;
|
|
999
|
-
textLayer.style.height = `${image.naturalHeight}px`;
|
|
1014
|
+
const pageBlocksData = getPageBlocksData(targetPage);
|
|
1000
1015
|
|
|
1001
|
-
//
|
|
1016
|
+
// 如果当前页面没有数据,跳过渲染(不标记为已渲染)
|
|
1002
1017
|
if (!pageBlocksData || pageBlocksData.length === 0) {
|
|
1003
|
-
textLayer.innerHTML = "";
|
|
1004
1018
|
return;
|
|
1005
1019
|
}
|
|
1006
1020
|
|
|
1021
|
+
// 如果页面已渲染且用户正在交互,跳过渲染(避免打断用户)
|
|
1022
|
+
if (renderedPages.value.has(targetPage) && showAnnotationPopup.value) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// 如果页面已渲染,但当前有数据,需要检查是否真的渲染过
|
|
1027
|
+
// 如果textLayer是空的,说明之前没有真正渲染过,应该重新渲染
|
|
1028
|
+
if (renderedPages.value.has(targetPage) && textLayer.children.length === 0) {
|
|
1029
|
+
// 从已渲染集合中移除,允许重新渲染
|
|
1030
|
+
renderedPages.value.delete(targetPage);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1007
1033
|
try {
|
|
1008
|
-
//
|
|
1009
|
-
|
|
1010
|
-
textLayer.
|
|
1011
|
-
const bboxStr = (el as HTMLElement).dataset.bbox;
|
|
1012
|
-
if (bboxStr) {
|
|
1013
|
-
existingBlocks.set(bboxStr, el as HTMLElement);
|
|
1014
|
-
}
|
|
1015
|
-
});
|
|
1034
|
+
// 设置文本图层的尺寸与图片尺寸一致
|
|
1035
|
+
textLayer.style.width = `${image.naturalWidth}px`;
|
|
1036
|
+
textLayer.style.height = `${image.naturalHeight}px`;
|
|
1016
1037
|
|
|
1017
|
-
//
|
|
1018
|
-
|
|
1038
|
+
// 清空文本图层
|
|
1039
|
+
textLayer.innerHTML = "";
|
|
1019
1040
|
|
|
1020
|
-
//
|
|
1041
|
+
// 如果清空前有激活的文本块,且该文本块属于当前页面,则清除引用
|
|
1042
|
+
if (activeBlockDiv.value) {
|
|
1043
|
+
const activePageElement = activeBlockDiv.value.closest(
|
|
1044
|
+
".image-page-container"
|
|
1045
|
+
);
|
|
1046
|
+
const currentPageElement = textLayer.closest(".image-page-container");
|
|
1047
|
+
if (activePageElement === currentPageElement) {
|
|
1048
|
+
activeBlockDiv.value = null;
|
|
1049
|
+
showAnnotationPopup.value = false;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// 使用 blocksData 数据创建可交互的块
|
|
1021
1054
|
pageBlocksData.forEach((block, index) => {
|
|
1022
1055
|
const { content, bbox } = block;
|
|
1023
1056
|
|
|
@@ -1026,14 +1059,11 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1026
1059
|
const width = x2 - x1;
|
|
1027
1060
|
const height = y2 - y1;
|
|
1028
1061
|
|
|
1029
|
-
//
|
|
1030
|
-
const calculatedFontSize = calculateFontSize(content, width, height);
|
|
1031
|
-
|
|
1032
|
-
// 创建文本块
|
|
1062
|
+
// 创建文本块(不显示内容,只用于 hover 交互和批注功能)
|
|
1033
1063
|
const blockDiv = document.createElement("div");
|
|
1034
1064
|
blockDiv.className = "text-block";
|
|
1035
|
-
blockDiv.dataset.text = content;
|
|
1036
1065
|
blockDiv.dataset.bbox = JSON.stringify(bbox);
|
|
1066
|
+
blockDiv.dataset.page = String(targetPage);
|
|
1037
1067
|
|
|
1038
1068
|
// 设置基础样式
|
|
1039
1069
|
blockDiv.style.position = "absolute";
|
|
@@ -1041,38 +1071,16 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1041
1071
|
blockDiv.style.top = `${y1}px`;
|
|
1042
1072
|
blockDiv.style.width = `${width}px`;
|
|
1043
1073
|
blockDiv.style.height = `${height}px`;
|
|
1044
|
-
blockDiv.style.zIndex = "20";
|
|
1045
|
-
blockDiv.style.cursor = "
|
|
1046
|
-
|
|
1047
|
-
blockDiv.
|
|
1048
|
-
|
|
1049
|
-
blockDiv.style.color = "red"; // 红色文字
|
|
1050
|
-
blockDiv.style.fontSize = `${calculatedFontSize}px`; // 使用计算出的字体大小
|
|
1051
|
-
blockDiv.style.fontFamily = "Arial, sans-serif"; // 设置明确的字体
|
|
1052
|
-
blockDiv.style.lineHeight = "1.2"; // 设置合适的行高
|
|
1053
|
-
blockDiv.style.whiteSpace = "pre-wrap"; // 保留换行和空格
|
|
1054
|
-
blockDiv.style.overflow = "visible"; // 确保文字不被裁剪
|
|
1055
|
-
blockDiv.style.display = "block"; // 确保是块级元素
|
|
1056
|
-
blockDiv.style.visibility = "visible"; // 确保可见
|
|
1057
|
-
// 允许文本选择
|
|
1058
|
-
blockDiv.style.userSelect = "text";
|
|
1059
|
-
blockDiv.style.webkitUserSelect = "text";
|
|
1060
|
-
blockDiv.style.mozUserSelect = "text";
|
|
1061
|
-
blockDiv.style.msUserSelect = "text";
|
|
1062
|
-
// 允许指针事件,但不阻止文本选择
|
|
1074
|
+
blockDiv.style.zIndex = "20";
|
|
1075
|
+
blockDiv.style.cursor = "pointer";
|
|
1076
|
+
blockDiv.style.borderRadius = "2px";
|
|
1077
|
+
blockDiv.style.transition = "all 0.2s ease";
|
|
1078
|
+
blockDiv.style.backgroundColor = "transparent";
|
|
1063
1079
|
blockDiv.style.pointerEvents = "auto";
|
|
1064
|
-
|
|
1065
|
-
// 右键菜单:显示批注按钮
|
|
1066
|
-
blockDiv.addEventListener("contextmenu", (e) => {
|
|
1067
|
-
e.preventDefault();
|
|
1068
|
-
// 设置当前文本块为激活状态
|
|
1069
|
-
activeBlockDiv.value = blockDiv;
|
|
1070
|
-
// 显示批注按钮
|
|
1071
|
-
showAnnotationButtonForBlock(e, blockDiv);
|
|
1072
|
-
});
|
|
1080
|
+
blockDiv.style.userSelect = "none";
|
|
1073
1081
|
|
|
1074
1082
|
// 检查是否有已有批注,如果有则显示批注标记
|
|
1075
|
-
const existingAnnotation = getAnnotationForBlock(bbox);
|
|
1083
|
+
const existingAnnotation = getAnnotationForBlock(bbox, targetPage);
|
|
1076
1084
|
if (existingAnnotation) {
|
|
1077
1085
|
// 添加批注标记样式类
|
|
1078
1086
|
blockDiv.classList.add("has-annotation");
|
|
@@ -1103,82 +1111,57 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1103
1111
|
annotationMarker.style.boxShadow = "0 1px 3px rgba(0, 0, 0, 0.25)";
|
|
1104
1112
|
annotationMarker.style.lineHeight = "1";
|
|
1105
1113
|
blockDiv.appendChild(annotationMarker);
|
|
1106
|
-
} else {
|
|
1107
|
-
// 没有批注时设置为透明背景
|
|
1108
|
-
blockDiv.style.backgroundColor = "transparent";
|
|
1109
1114
|
}
|
|
1110
1115
|
|
|
1111
|
-
//
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
//
|
|
1120
|
-
|
|
1116
|
+
// Hover 和点击事件
|
|
1117
|
+
blockDiv.addEventListener("mouseenter", (e) => {
|
|
1118
|
+
// 取消之前的隐藏定时器
|
|
1119
|
+
if (hideTimer) {
|
|
1120
|
+
clearTimeout(hideTimer);
|
|
1121
|
+
hideTimer = null;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// 如果有之前激活的文本块,先恢复其样式
|
|
1125
|
+
if (activeBlockDiv.value && activeBlockDiv.value !== blockDiv) {
|
|
1126
|
+
restoreBlockStyle(activeBlockDiv.value);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// 设置当前文本块为激活状态
|
|
1130
|
+
activeBlockDiv.value = blockDiv;
|
|
1131
|
+
|
|
1132
|
+
// 检查是否有批注,如果有批注,hover 时在批注样式基础上添加蓝色边框
|
|
1133
|
+
const existingAnnotation = getAnnotationForBlock(bbox, targetPage);
|
|
1121
1134
|
if (existingAnnotation) {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
existingBlock.style.padding = "1px 3px";
|
|
1129
|
-
existingBlock.style.boxShadow = "0 1px 2px rgba(255, 193, 7, 0.25)";
|
|
1130
|
-
|
|
1131
|
-
// 添加批注标记(如果还没有)
|
|
1132
|
-
if (!existingBlock.querySelector(".annotation-marker")) {
|
|
1133
|
-
const annotationMarker = document.createElement("span");
|
|
1134
|
-
annotationMarker.className = "annotation-marker";
|
|
1135
|
-
annotationMarker.textContent = "📝";
|
|
1136
|
-
annotationMarker.style.position = "absolute";
|
|
1137
|
-
annotationMarker.style.top = "-6px";
|
|
1138
|
-
annotationMarker.style.right = "-6px";
|
|
1139
|
-
annotationMarker.style.fontSize = "11px";
|
|
1140
|
-
annotationMarker.style.backgroundColor = "rgba(255, 193, 7, 0.95)";
|
|
1141
|
-
annotationMarker.style.borderRadius = "50%";
|
|
1142
|
-
annotationMarker.style.width = "16px";
|
|
1143
|
-
annotationMarker.style.height = "16px";
|
|
1144
|
-
annotationMarker.style.display = "flex";
|
|
1145
|
-
annotationMarker.style.alignItems = "center";
|
|
1146
|
-
annotationMarker.style.justifyContent = "center";
|
|
1147
|
-
annotationMarker.style.zIndex = "30";
|
|
1148
|
-
annotationMarker.style.boxShadow = "0 1px 3px rgba(0, 0, 0, 0.25)";
|
|
1149
|
-
annotationMarker.style.lineHeight = "1";
|
|
1150
|
-
existingBlock.appendChild(annotationMarker);
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1135
|
+
// 如果有批注,保持批注背景色,但添加蓝色边框表示 hover
|
|
1136
|
+
blockDiv.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1137
|
+
blockDiv.style.setProperty("border", "2px solid rgba(30, 144, 255, 0.8)", "important");
|
|
1138
|
+
blockDiv.style.setProperty("border-radius", "3px", "important");
|
|
1139
|
+
blockDiv.style.setProperty("padding", "1px 3px", "important");
|
|
1140
|
+
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");
|
|
1153
1141
|
} else {
|
|
1154
|
-
//
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
existingBlock.style.boxShadow = "none";
|
|
1162
|
-
const marker = existingBlock.querySelector(".annotation-marker");
|
|
1163
|
-
if (marker) {
|
|
1164
|
-
marker.remove();
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1142
|
+
// 如果没有批注,使用 hover 样式(与 PdfPreview 保持一致)
|
|
1143
|
+
blockDiv.style.backgroundColor =
|
|
1144
|
+
"var(--s-color-brand-primary-transparent-3, rgba(0, 102, 255, .15))";
|
|
1145
|
+
blockDiv.style.boxShadow = "0 0 0 2px rgba(30, 144, 255, 0.6)";
|
|
1146
|
+
blockDiv.style.borderRadius = "2px";
|
|
1147
|
+
blockDiv.style.padding = "1px 3px";
|
|
1148
|
+
blockDiv.style.border = "none";
|
|
1167
1149
|
}
|
|
1168
|
-
newBlocksMap.set(bboxKey, true);
|
|
1169
|
-
} else {
|
|
1170
|
-
// 如果不存在,创建新的文本块
|
|
1171
|
-
textLayer.appendChild(blockDiv);
|
|
1172
|
-
newBlocksMap.set(bboxKey, true);
|
|
1173
|
-
}
|
|
1174
|
-
});
|
|
1175
1150
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1151
|
+
// 显示批注按钮浮层
|
|
1152
|
+
showAnnotationButtonForBlock(e, blockDiv);
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
blockDiv.addEventListener("mouseleave", () => {
|
|
1156
|
+
// 延迟隐藏,给用户时间移动到批注按钮
|
|
1157
|
+
hideAnnotationButton();
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
textLayer.appendChild(blockDiv);
|
|
1181
1161
|
});
|
|
1162
|
+
|
|
1163
|
+
// 标记该页面已渲染
|
|
1164
|
+
renderedPages.value.add(targetPage);
|
|
1182
1165
|
} catch (error) {
|
|
1183
1166
|
console.error("❌ 文本图层渲染失败:", error);
|
|
1184
1167
|
}
|
|
@@ -1188,12 +1171,14 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1188
1171
|
* 获取文本块对应的批注
|
|
1189
1172
|
*/
|
|
1190
1173
|
const getAnnotationForBlock = (
|
|
1191
|
-
bbox: [number, number, number, number]
|
|
1174
|
+
bbox: [number, number, number, number],
|
|
1175
|
+
pageNum?: number
|
|
1192
1176
|
): AnnotationInfo | null => {
|
|
1193
1177
|
if (!props.annotations || props.annotations.length === 0) {
|
|
1194
1178
|
return null;
|
|
1195
1179
|
}
|
|
1196
1180
|
|
|
1181
|
+
const targetPage = pageNum !== undefined ? pageNum : currentPage.value;
|
|
1197
1182
|
const tolerance = 2; // 容差
|
|
1198
1183
|
return (
|
|
1199
1184
|
props.annotations.find((annotation) => {
|
|
@@ -1204,7 +1189,7 @@ const getAnnotationForBlock = (
|
|
|
1204
1189
|
Math.abs(x2 - bbox[2]) < tolerance &&
|
|
1205
1190
|
Math.abs(y2 - bbox[3]) < tolerance &&
|
|
1206
1191
|
(annotation.blockPage === undefined ||
|
|
1207
|
-
annotation.blockPage ===
|
|
1192
|
+
annotation.blockPage === targetPage)
|
|
1208
1193
|
);
|
|
1209
1194
|
}) || null
|
|
1210
1195
|
);
|
|
@@ -1217,12 +1202,26 @@ const showAnnotationButtonForBlock = (
|
|
|
1217
1202
|
event: MouseEvent,
|
|
1218
1203
|
blockDiv: HTMLElement
|
|
1219
1204
|
) => {
|
|
1220
|
-
const text = blockDiv.dataset.text || "";
|
|
1221
1205
|
const bboxStr = blockDiv.dataset.bbox || "";
|
|
1222
|
-
if (!
|
|
1206
|
+
if (!bboxStr) return;
|
|
1223
1207
|
|
|
1224
1208
|
try {
|
|
1225
1209
|
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1210
|
+
|
|
1211
|
+
// 从 blocksData 中查找对应的 content
|
|
1212
|
+
const pageBlocksData = getPageBlocksData(currentPage.value);
|
|
1213
|
+
const blockData = pageBlocksData.find((block) => {
|
|
1214
|
+
const [x1, y1, x2, y2] = block.bbox;
|
|
1215
|
+
const tolerance = 2;
|
|
1216
|
+
return (
|
|
1217
|
+
Math.abs(x1 - bbox[0]) < tolerance &&
|
|
1218
|
+
Math.abs(y1 - bbox[1]) < tolerance &&
|
|
1219
|
+
Math.abs(x2 - bbox[2]) < tolerance &&
|
|
1220
|
+
Math.abs(y2 - bbox[3]) < tolerance
|
|
1221
|
+
);
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
if (!blockData) return;
|
|
1226
1225
|
|
|
1227
1226
|
const rect = blockDiv.getBoundingClientRect();
|
|
1228
1227
|
const containerRect = containerRef.value?.getBoundingClientRect();
|
|
@@ -1305,7 +1304,78 @@ const showAnnotationButtonForBlock = (
|
|
|
1305
1304
|
};
|
|
1306
1305
|
|
|
1307
1306
|
/**
|
|
1308
|
-
*
|
|
1307
|
+
* 恢复文本块样式(根据是否有批注)
|
|
1308
|
+
*/
|
|
1309
|
+
const restoreBlockStyle = (blockDiv: HTMLElement) => {
|
|
1310
|
+
const bboxStr = blockDiv.dataset.bbox;
|
|
1311
|
+
if (!bboxStr) {
|
|
1312
|
+
console.log("restoreBlockStyle: 没有 bbox");
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
try {
|
|
1317
|
+
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1318
|
+
const pageStr = blockDiv.dataset.page;
|
|
1319
|
+
const pageNum = pageStr ? parseInt(pageStr, 10) : undefined;
|
|
1320
|
+
const existingAnnotation = getAnnotationForBlock(bbox, pageNum);
|
|
1321
|
+
|
|
1322
|
+
console.log("restoreBlockStyle:", {
|
|
1323
|
+
bbox,
|
|
1324
|
+
pageNum,
|
|
1325
|
+
existingAnnotation,
|
|
1326
|
+
blockDiv: blockDiv,
|
|
1327
|
+
width: blockDiv.style.width,
|
|
1328
|
+
height: blockDiv.style.height,
|
|
1329
|
+
computedStyle: {
|
|
1330
|
+
backgroundColor: window.getComputedStyle(blockDiv).backgroundColor,
|
|
1331
|
+
width: window.getComputedStyle(blockDiv).width,
|
|
1332
|
+
height: window.getComputedStyle(blockDiv).height,
|
|
1333
|
+
display: window.getComputedStyle(blockDiv).display,
|
|
1334
|
+
visibility: window.getComputedStyle(blockDiv).visibility,
|
|
1335
|
+
},
|
|
1336
|
+
inlineStyle: {
|
|
1337
|
+
backgroundColor: blockDiv.style.backgroundColor,
|
|
1338
|
+
border: blockDiv.style.border,
|
|
1339
|
+
},
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
if (existingAnnotation) {
|
|
1343
|
+
// 如果有批注,保持批注样式(使用 !important 确保优先级)
|
|
1344
|
+
blockDiv.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1345
|
+
blockDiv.style.setProperty("border", "1px solid rgba(255, 193, 7, 0.7)", "important");
|
|
1346
|
+
blockDiv.style.setProperty("border-radius", "3px", "important");
|
|
1347
|
+
blockDiv.style.setProperty("padding", "1px 3px", "important");
|
|
1348
|
+
blockDiv.style.setProperty("box-shadow", "0 1px 2px rgba(255, 193, 7, 0.25)", "important");
|
|
1349
|
+
|
|
1350
|
+
// 确保元素可见
|
|
1351
|
+
blockDiv.style.setProperty("display", "block", "important");
|
|
1352
|
+
blockDiv.style.setProperty("visibility", "visible", "important");
|
|
1353
|
+
blockDiv.style.setProperty("opacity", "1", "important");
|
|
1354
|
+
|
|
1355
|
+
console.log("restoreBlockStyle: 已设置批注样式", {
|
|
1356
|
+
backgroundColor: blockDiv.style.backgroundColor,
|
|
1357
|
+
border: blockDiv.style.border,
|
|
1358
|
+
computedAfter: window.getComputedStyle(blockDiv).backgroundColor,
|
|
1359
|
+
});
|
|
1360
|
+
} else {
|
|
1361
|
+
// 如果没有批注,恢复透明背景
|
|
1362
|
+
blockDiv.style.backgroundColor = "transparent";
|
|
1363
|
+
blockDiv.style.border = "none";
|
|
1364
|
+
blockDiv.style.padding = "0";
|
|
1365
|
+
blockDiv.style.boxShadow = "none";
|
|
1366
|
+
}
|
|
1367
|
+
} catch (error) {
|
|
1368
|
+
console.error("restoreBlockStyle 错误:", error);
|
|
1369
|
+
// 如果解析失败,恢复透明背景
|
|
1370
|
+
blockDiv.style.backgroundColor = "transparent";
|
|
1371
|
+
blockDiv.style.border = "none";
|
|
1372
|
+
blockDiv.style.padding = "0";
|
|
1373
|
+
blockDiv.style.boxShadow = "none";
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* 隐藏批注按钮和高亮
|
|
1309
1379
|
*/
|
|
1310
1380
|
const hideAnnotationButton = () => {
|
|
1311
1381
|
// 如果正在输入批注,不自动隐藏
|
|
@@ -1316,9 +1386,41 @@ const hideAnnotationButton = () => {
|
|
|
1316
1386
|
hideTimer = setTimeout(() => {
|
|
1317
1387
|
showAnnotationPopup.value = false;
|
|
1318
1388
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
activeBlockDiv.value.
|
|
1389
|
+
// 恢复文本块的样式(如果有批注则保持批注样式)
|
|
1390
|
+
if (activeBlockDiv.value) {
|
|
1391
|
+
const bboxStr = activeBlockDiv.value.dataset.bbox;
|
|
1392
|
+
if (bboxStr) {
|
|
1393
|
+
try {
|
|
1394
|
+
const bbox = JSON.parse(bboxStr) as [
|
|
1395
|
+
number,
|
|
1396
|
+
number,
|
|
1397
|
+
number,
|
|
1398
|
+
number
|
|
1399
|
+
];
|
|
1400
|
+
const existingAnnotation = getAnnotationForBlock(bbox);
|
|
1401
|
+
if (existingAnnotation) {
|
|
1402
|
+
// 如果有批注,恢复批注样式
|
|
1403
|
+
activeBlockDiv.value.style.backgroundColor =
|
|
1404
|
+
"rgba(255, 243, 205, 0.5)";
|
|
1405
|
+
activeBlockDiv.value.style.border =
|
|
1406
|
+
"1px solid rgba(255, 193, 7, 0.7)";
|
|
1407
|
+
activeBlockDiv.value.style.padding = "1px 3px";
|
|
1408
|
+
activeBlockDiv.value.style.boxShadow =
|
|
1409
|
+
"0 1px 2px rgba(255, 193, 7, 0.25)";
|
|
1410
|
+
} else {
|
|
1411
|
+
// 如果没有批注,恢复透明背景
|
|
1412
|
+
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1413
|
+
activeBlockDiv.value.style.border = "none";
|
|
1414
|
+
activeBlockDiv.value.style.padding = "0";
|
|
1415
|
+
activeBlockDiv.value.style.boxShadow = "none";
|
|
1416
|
+
}
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1419
|
+
activeBlockDiv.value.style.border = "none";
|
|
1420
|
+
activeBlockDiv.value.style.padding = "0";
|
|
1421
|
+
activeBlockDiv.value.style.boxShadow = "none";
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1322
1424
|
activeBlockDiv.value = null;
|
|
1323
1425
|
}
|
|
1324
1426
|
}, 300);
|
|
@@ -1343,14 +1445,36 @@ const openAnnotationInput = (e?: Event) => {
|
|
|
1343
1445
|
e.stopPropagation();
|
|
1344
1446
|
}
|
|
1345
1447
|
|
|
1346
|
-
//
|
|
1448
|
+
// 获取选中的文本或从文本块中获取
|
|
1347
1449
|
const selection = window.getSelection();
|
|
1348
1450
|
let selectedText = "";
|
|
1349
1451
|
|
|
1350
1452
|
if (selection && selection.toString().trim().length > 0) {
|
|
1351
1453
|
selectedText = selection.toString().trim();
|
|
1352
1454
|
} else if (activeBlockDiv.value) {
|
|
1353
|
-
|
|
1455
|
+
// 从 blocksData 中查找对应的 content
|
|
1456
|
+
const bboxStr = activeBlockDiv.value.dataset.bbox || "";
|
|
1457
|
+
if (bboxStr) {
|
|
1458
|
+
try {
|
|
1459
|
+
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1460
|
+
const pageBlocksData = getPageBlocksData(currentPage.value);
|
|
1461
|
+
const blockData = pageBlocksData.find((block) => {
|
|
1462
|
+
const [x1, y1, x2, y2] = block.bbox;
|
|
1463
|
+
const tolerance = 2;
|
|
1464
|
+
return (
|
|
1465
|
+
Math.abs(x1 - bbox[0]) < tolerance &&
|
|
1466
|
+
Math.abs(y1 - bbox[1]) < tolerance &&
|
|
1467
|
+
Math.abs(x2 - bbox[2]) < tolerance &&
|
|
1468
|
+
Math.abs(y2 - bbox[3]) < tolerance
|
|
1469
|
+
);
|
|
1470
|
+
});
|
|
1471
|
+
if (blockData) {
|
|
1472
|
+
selectedText = blockData.content || "";
|
|
1473
|
+
}
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
console.error("解析 bbox 失败:", error);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1354
1478
|
}
|
|
1355
1479
|
|
|
1356
1480
|
if (!selectedText && !activeBlockDiv.value) return;
|
|
@@ -1511,6 +1635,12 @@ const closeAnnotationInput = () => {
|
|
|
1511
1635
|
currentAnnotationBlock.value = null;
|
|
1512
1636
|
annotationInput.value = "";
|
|
1513
1637
|
showAnnotationPopup.value = false;
|
|
1638
|
+
|
|
1639
|
+
// 关闭批注输入弹窗后,恢复文本块的样式
|
|
1640
|
+
if (activeBlockDiv.value && !isHighlighted.value) {
|
|
1641
|
+
restoreBlockStyle(activeBlockDiv.value);
|
|
1642
|
+
activeBlockDiv.value = null;
|
|
1643
|
+
}
|
|
1514
1644
|
};
|
|
1515
1645
|
|
|
1516
1646
|
/**
|
|
@@ -1735,12 +1865,14 @@ const isElementVisible = (
|
|
|
1735
1865
|
const highlightPosition = (
|
|
1736
1866
|
pageNum: number,
|
|
1737
1867
|
bbox: [number, number, number, number],
|
|
1738
|
-
shouldScroll: boolean = true
|
|
1868
|
+
shouldScroll: boolean = true,
|
|
1869
|
+
highlightStyle?: HighlightStyle
|
|
1739
1870
|
): boolean => {
|
|
1740
1871
|
// 清除之前的高亮
|
|
1741
1872
|
if (activeBlockDiv.value) {
|
|
1742
1873
|
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1743
1874
|
activeBlockDiv.value.style.boxShadow = "none";
|
|
1875
|
+
activeBlockDiv.value.style.border = "none";
|
|
1744
1876
|
activeBlockDiv.value = null;
|
|
1745
1877
|
}
|
|
1746
1878
|
isHighlighted.value = false;
|
|
@@ -1795,7 +1927,7 @@ const highlightPosition = (
|
|
|
1795
1927
|
// 等待页面切换完成后再高亮
|
|
1796
1928
|
nextTick(() => {
|
|
1797
1929
|
setTimeout(() => {
|
|
1798
|
-
highlightPosition(pageNum, bbox, shouldScroll);
|
|
1930
|
+
highlightPosition(pageNum, bbox, shouldScroll, highlightStyle);
|
|
1799
1931
|
}, 300);
|
|
1800
1932
|
});
|
|
1801
1933
|
return true;
|
|
@@ -1807,10 +1939,24 @@ const highlightPosition = (
|
|
|
1807
1939
|
activeBlockDiv.value = elementRef;
|
|
1808
1940
|
isHighlighted.value = true;
|
|
1809
1941
|
|
|
1810
|
-
//
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1942
|
+
// 使用传入的高亮样式,如果没有传入则使用默认样式
|
|
1943
|
+
if (highlightStyle) {
|
|
1944
|
+
if (highlightStyle.backgroundColor) {
|
|
1945
|
+
elementRef.style.backgroundColor = highlightStyle.backgroundColor;
|
|
1946
|
+
}
|
|
1947
|
+
if (highlightStyle.border) {
|
|
1948
|
+
elementRef.style.border = highlightStyle.border;
|
|
1949
|
+
}
|
|
1950
|
+
if (highlightStyle.boxShadow) {
|
|
1951
|
+
elementRef.style.boxShadow = highlightStyle.boxShadow;
|
|
1952
|
+
}
|
|
1953
|
+
} else {
|
|
1954
|
+
// 默认高亮样式
|
|
1955
|
+
elementRef.style.backgroundColor =
|
|
1956
|
+
"var(--s-color-brand-primary-transparent-3, rgba(0, 102, 255, .15))";
|
|
1957
|
+
elementRef.style.boxShadow = "0 0 0 2px rgba(30, 144, 255, 0.6)";
|
|
1958
|
+
elementRef.style.border = "none";
|
|
1959
|
+
}
|
|
1814
1960
|
|
|
1815
1961
|
// 只有在需要滚动且元素不在视口内时才滚动
|
|
1816
1962
|
if (shouldScroll && containerRef.value) {
|
|
@@ -1825,6 +1971,7 @@ const highlightPosition = (
|
|
|
1825
1971
|
if (activeBlockDiv.value === elementRef && isHighlighted.value) {
|
|
1826
1972
|
elementRef.style.backgroundColor = "transparent";
|
|
1827
1973
|
elementRef.style.boxShadow = "none";
|
|
1974
|
+
elementRef.style.border = "none";
|
|
1828
1975
|
activeBlockDiv.value = null;
|
|
1829
1976
|
isHighlighted.value = false;
|
|
1830
1977
|
}
|
|
@@ -1842,7 +1989,8 @@ const highlightPosition = (
|
|
|
1842
1989
|
const jumpToPosition = (
|
|
1843
1990
|
pageNum: number,
|
|
1844
1991
|
bbox: [number, number, number, number],
|
|
1845
|
-
emitEvent: boolean = true
|
|
1992
|
+
emitEvent: boolean = true,
|
|
1993
|
+
highlightStyle?: HighlightStyle
|
|
1846
1994
|
) => {
|
|
1847
1995
|
// 如果页码不在有效范围内,直接返回
|
|
1848
1996
|
if (pageNum < 1 || pageNum > totalPages.value) {
|
|
@@ -1857,7 +2005,7 @@ const jumpToPosition = (
|
|
|
1857
2005
|
const retryDelay = 200;
|
|
1858
2006
|
|
|
1859
2007
|
const tryHighlight = () => {
|
|
1860
|
-
const success = highlightPosition(pageNum, bbox, true);
|
|
2008
|
+
const success = highlightPosition(pageNum, bbox, true, highlightStyle);
|
|
1861
2009
|
if (success) {
|
|
1862
2010
|
// 高亮成功,触发事件
|
|
1863
2011
|
if (emitEvent) {
|
package/preview/index.vue
CHANGED
|
@@ -483,14 +483,14 @@ defineExpose({
|
|
|
483
483
|
getCurrentPreview: getCurrentPreviewRef,
|
|
484
484
|
// PDF 预览的代理方法(方便使用)
|
|
485
485
|
goToPage: (pageNum) => pdfPreviewRef.value?.goToPage(pageNum),
|
|
486
|
-
jumpToPosition: (pageNum, bbox, emitEvent) => {
|
|
486
|
+
jumpToPosition: (pageNum, bbox, emitEvent, highlightStyle) => {
|
|
487
487
|
// PDF 预览的定位方法
|
|
488
488
|
if (fileType.value === 'pdf' && pdfPreviewRef.value) {
|
|
489
|
-
return pdfPreviewRef.value.jumpToPosition(pageNum, bbox, emitEvent);
|
|
489
|
+
return pdfPreviewRef.value.jumpToPosition(pageNum, bbox, emitEvent, highlightStyle);
|
|
490
490
|
}
|
|
491
|
-
// 图片预览的定位方法(现在也支持 pageNum)
|
|
491
|
+
// 图片预览的定位方法(现在也支持 pageNum 和 highlightStyle)
|
|
492
492
|
if (fileType.value === 'image' && imagePreviewRef.value) {
|
|
493
|
-
return imagePreviewRef.value.jumpToPosition(pageNum, bbox, emitEvent);
|
|
493
|
+
return imagePreviewRef.value.jumpToPosition(pageNum, bbox, emitEvent, highlightStyle);
|
|
494
494
|
}
|
|
495
495
|
},
|
|
496
496
|
getCurrentPage: () => pdfPreviewRef.value?.getCurrentPage(),
|