@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.
@@ -1,4 +1,4 @@
1
- import { g as getAugmentedNamespace, a as getDefaultExportFromCjs } from "./index-QOLKOKLO.mjs";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koi-br/ocr-web-sdk",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "一个支持多种Office文件格式预览的Vue3组件SDK,包括PDF、Word、Excel、图片、OFD、TIF等格式",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -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
- * 渲染文本图层(使用 blocksData 数据,支持增量渲染)
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
- const pageBlocksData = getPageBlocksData(targetPage);
995
- console.log("renderTextLayer", targetPage, pageBlocksData);
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
- const existingBlocks = new Map<string, HTMLElement>();
1010
- textLayer.querySelectorAll(".text-block").forEach((el) => {
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
- // 创建新的文本块 Map,用于跟踪需要保留的块
1018
- const newBlocksMap = new Map<string, boolean>();
1038
+ // 清空文本图层
1039
+ textLayer.innerHTML = "";
1019
1040
 
1020
- // 使用指定页码的 blocksData 创建或更新可交互的块
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 = "text"; // 改为文本选择光标
1046
- // 设置文本内容(使用 textContent 而不是 innerHTML,避免 XSS 风险)
1047
- blockDiv.textContent = content;
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
- // 检查是否已存在相同的文本块(通过 bbox 匹配)
1112
- const bboxKey = JSON.stringify(bbox);
1113
- const existingBlock = existingBlocks.get(bboxKey);
1114
-
1115
- if (existingBlock) {
1116
- // 如果已存在,更新内容(增量更新,避免闪烁)
1117
- existingBlock.textContent = content;
1118
- existingBlock.dataset.text = content;
1119
- // 更新批注状态
1120
- const existingAnnotation = getAnnotationForBlock(bbox);
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
- if (!existingBlock.classList.contains("has-annotation")) {
1123
- existingBlock.classList.add("has-annotation");
1124
- existingBlock.title = `已有批注: ${existingAnnotation.content}`;
1125
- existingBlock.style.backgroundColor = "rgba(255, 243, 205, 0.5)";
1126
- existingBlock.style.border = "1px solid rgba(255, 193, 7, 0.7)";
1127
- existingBlock.style.borderRadius = "3px";
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
- if (existingBlock.classList.contains("has-annotation")) {
1156
- existingBlock.classList.remove("has-annotation");
1157
- existingBlock.title = "";
1158
- existingBlock.style.backgroundColor = "transparent";
1159
- existingBlock.style.border = "none";
1160
- existingBlock.style.padding = "0";
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
- existingBlocks.forEach((block, bboxKey) => {
1178
- if (!newBlocksMap.has(bboxKey)) {
1179
- block.remove();
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 === currentPage.value)
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 (!text || !bboxStr) return;
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
- if (activeBlockDiv.value && !isHighlighted.value) {
1320
- activeBlockDiv.value.style.backgroundColor = "transparent";
1321
- activeBlockDiv.value.style.boxShadow = "none";
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
- selectedText = activeBlockDiv.value.dataset.text || "";
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
- elementRef.style.backgroundColor =
1812
- "var(--s-color-brand-primary-transparent-3, rgba(0, 102, 255, .15))";
1813
- elementRef.style.boxShadow = "0 0 0 2px rgba(30, 144, 255, 0.6)";
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(),