@koi-br/ocr-web-sdk 1.0.44 → 1.0.45

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-DwGxSvlf.mjs";
1
+ import { g as getAugmentedNamespace, a as getDefaultExportFromCjs } from "./index-Dr29vR47.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.44",
3
+ "version": "1.0.45",
4
4
  "description": "一个支持多种Office文件格式预览的Vue3组件SDK,包括PDF、Word、Excel、图片、OFD、TIF等格式",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -12,12 +12,7 @@
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
16
- v-if="showResetButton"
17
- mini
18
- position="bottom"
19
- content="重置"
20
- >
15
+ <ATooltip v-if="showResetButton" mini position="bottom" content="重置">
21
16
  <AButton size="small" type="outline" @click="reset">
22
17
  <RefreshCcw :size="16" />
23
18
  </AButton>
@@ -96,7 +91,7 @@
96
91
  <AButton
97
92
  size="small"
98
93
  type="outline"
99
- style="padding-right: 0px;"
94
+ style="padding-right: 0px"
100
95
  :disabled="currentPage >= totalPages"
101
96
  @click="goToNextPage"
102
97
  >
@@ -121,9 +116,9 @@
121
116
  <div class="loading-spinner"></div>
122
117
  <div class="loading-text">加载中...</div>
123
118
  </div>
124
-
125
- <div
126
- class="image-wrapper-container"
119
+
120
+ <div
121
+ class="image-wrapper-container"
127
122
  :style="containerStyle"
128
123
  :class="{ 'image-hidden': !isImageReady && autoFitWidth }"
129
124
  >
@@ -162,7 +157,10 @@
162
157
 
163
158
  <!-- 文本图层(用于文本块选择和定位) -->
164
159
  <div
165
- v-if="getPageBlocksData(pageIndex + 1) && getPageBlocksData(pageIndex + 1).length > 0"
160
+ v-if="
161
+ getPageBlocksData(pageIndex + 1) &&
162
+ getPageBlocksData(pageIndex + 1).length > 0
163
+ "
166
164
  :ref="(el) => setTextLayerRef(el, pageIndex + 1)"
167
165
  class="text-layer"
168
166
  ></div>
@@ -407,22 +405,22 @@ const isImageReady = ref(false); // 标记图片是否已准备好显示(自
407
405
  watch(
408
406
  () => imageUrls.value,
409
407
  (newUrls, oldUrls) => {
410
- console.log('[ImagePreview] imageUrls changed:', {
408
+ console.log("[ImagePreview] imageUrls changed:", {
411
409
  newUrls: newUrls?.length,
412
410
  oldUrls: oldUrls?.length,
413
411
  autoFitWidth: props.autoFitWidth,
414
412
  isImageReady: isImageReady.value,
415
413
  isCalculatingAutoFit: isCalculatingAutoFit.value,
416
414
  });
417
-
415
+
418
416
  // 如果有新的图片URL,且启用自适应宽度,立即隐藏图片
419
417
  if (newUrls && newUrls.length > 0 && props.autoFitWidth) {
420
- console.log('[ImagePreview] 设置图片隐藏,等待自适应宽度计算');
418
+ console.log("[ImagePreview] 设置图片隐藏,等待自适应宽度计算");
421
419
  isImageReady.value = false;
422
420
  isCalculatingAutoFit.value = true;
423
421
  } else if (!props.autoFitWidth) {
424
422
  // 如果没有启用自适应宽度,立即显示
425
- console.log('[ImagePreview] 未启用自适应宽度,立即显示图片');
423
+ console.log("[ImagePreview] 未启用自适应宽度,立即显示图片");
426
424
  isImageReady.value = true;
427
425
  isCalculatingAutoFit.value = false;
428
426
  }
@@ -488,10 +486,10 @@ const imageSize = computed({
488
486
  // 在自适应宽度模式下,使用第一页的尺寸;否则使用当前页的尺寸
489
487
  const scaledImageSize = computed(() => {
490
488
  // 如果启用自适应宽度,使用第一页的尺寸作为基准
491
- const baseSize = props.autoFitWidth
492
- ? (imageSizes.get(1) || { width: 0, height: 0 })
489
+ const baseSize = props.autoFitWidth
490
+ ? imageSizes.get(1) || { width: 0, height: 0 }
493
491
  : imageSize.value;
494
-
492
+
495
493
  if (baseSize.width === 0 || baseSize.height === 0) {
496
494
  return { width: 0, height: 0 };
497
495
  }
@@ -537,6 +535,7 @@ const annotationInput = ref(""); // 批注输入内容
537
535
  const currentAnnotationBlock = ref<{
538
536
  bbox: [number, number, number, number];
539
537
  content: string;
538
+ annotationId?: string; // 已有批注的ID(如果存在)
540
539
  } | null>(null); // 当前正在添加批注的文本块
541
540
  const annotationPopupRef = ref<HTMLElement>(); // 批注弹窗引用
542
541
 
@@ -586,7 +585,7 @@ const getPageScaledSize = (pageNo: number) => {
586
585
  const getPageContainerStyle = (pageNo: number) => {
587
586
  const scaledSize = getPageScaledSize(pageNo);
588
587
  return {
589
- height: scaledSize.height > 0 ? `${scaledSize.height}px` : 'auto',
588
+ height: scaledSize.height > 0 ? `${scaledSize.height}px` : "auto",
590
589
  };
591
590
  };
592
591
 
@@ -884,7 +883,7 @@ const switchToPage = (page: number) => {
884
883
  ) as HTMLElement;
885
884
  if (pageElement) {
886
885
  // 标记这是翻页滚动,不应该被同步滚动干扰
887
- containerRef.value.dataset.pageScrolling = 'true';
886
+ containerRef.value.dataset.pageScrolling = "true";
888
887
  pageElement.scrollIntoView({ behavior: "smooth", block: "start" });
889
888
  // 更新 lastScrollTop,确保滚动方向判断准确
890
889
  nextTick(() => {
@@ -919,7 +918,7 @@ const reset = () => {
919
918
  annotationInput.value = "";
920
919
  activeBlockDiv.value = null;
921
920
  isHighlighted.value = false;
922
-
921
+
923
922
  // 清除已渲染页面集合
924
923
  renderedPages.value.clear();
925
924
  };
@@ -930,7 +929,7 @@ const original = () => {
930
929
 
931
930
  // 计算自适应宽度的缩放比例
932
931
  const calculateAutoFitScale = () => {
933
- console.log('[ImagePreview] calculateAutoFitScale 开始:', {
932
+ console.log("[ImagePreview] calculateAutoFitScale 开始:", {
934
933
  autoFitWidth: props.autoFitWidth,
935
934
  hasContainerRef: !!containerRef.value,
936
935
  containerRect: containerRef.value?.getBoundingClientRect(),
@@ -939,16 +938,19 @@ const calculateAutoFitScale = () => {
939
938
  minScale: props.minScale,
940
939
  maxScale: props.maxScale,
941
940
  });
942
-
941
+
943
942
  if (!props.autoFitWidth || !containerRef.value) {
944
- console.log('[ImagePreview] calculateAutoFitScale 返回 1 (条件不满足)');
943
+ console.log("[ImagePreview] calculateAutoFitScale 返回 1 (条件不满足)");
945
944
  return 1;
946
945
  }
947
946
 
948
947
  // 使用第一页的图片尺寸作为基准(所有页面使用相同的缩放比例)
949
948
  const firstPageSize = imageSizes.get(1);
950
949
  if (!firstPageSize || firstPageSize.width === 0) {
951
- console.log('[ImagePreview] calculateAutoFitScale 返回 1 (第一页尺寸无效)', firstPageSize);
950
+ console.log(
951
+ "[ImagePreview] calculateAutoFitScale 返回 1 (第一页尺寸无效)",
952
+ firstPageSize
953
+ );
952
954
  return 1;
953
955
  }
954
956
 
@@ -958,7 +960,10 @@ const calculateAutoFitScale = () => {
958
960
  const containerWidth = containerRect.width - 4;
959
961
 
960
962
  if (containerWidth <= 0) {
961
- console.log('[ImagePreview] calculateAutoFitScale 返回 1 (容器宽度无效)', containerWidth);
963
+ console.log(
964
+ "[ImagePreview] calculateAutoFitScale 返回 1 (容器宽度无效)",
965
+ containerWidth
966
+ );
962
967
  return 1;
963
968
  }
964
969
 
@@ -969,15 +974,21 @@ const calculateAutoFitScale = () => {
969
974
  const imageWidth = isRotated ? firstPageSize.height : firstPageSize.width;
970
975
 
971
976
  if (imageWidth <= 0) {
972
- console.log('[ImagePreview] calculateAutoFitScale 返回 1 (图片宽度无效)', imageWidth);
977
+ console.log(
978
+ "[ImagePreview] calculateAutoFitScale 返回 1 (图片宽度无效)",
979
+ imageWidth
980
+ );
973
981
  return 1;
974
982
  }
975
983
 
976
984
  // 计算缩放比例,使图片宽度完全适应容器宽度
977
985
  const calculatedScale = containerWidth / imageWidth;
978
- const finalScale = Math.max(props.minScale, Math.min(props.maxScale, calculatedScale));
979
-
980
- console.log('[ImagePreview] calculateAutoFitScale 计算结果:', {
986
+ const finalScale = Math.max(
987
+ props.minScale,
988
+ Math.min(props.maxScale, calculatedScale)
989
+ );
990
+
991
+ console.log("[ImagePreview] calculateAutoFitScale 计算结果:", {
981
992
  containerWidth,
982
993
  imageWidth,
983
994
  calculatedScale,
@@ -991,8 +1002,8 @@ const calculateAutoFitScale = () => {
991
1002
  // 图片加载完成处理
992
1003
  const onImageLoad = (event: Event, pageNum: number) => {
993
1004
  const img = event.target as HTMLImageElement;
994
-
995
- console.log('[ImagePreview] 图片加载完成:', {
1005
+
1006
+ console.log("[ImagePreview] 图片加载完成:", {
996
1007
  pageNum,
997
1008
  naturalWidth: img.naturalWidth,
998
1009
  naturalHeight: img.naturalHeight,
@@ -1000,7 +1011,7 @@ const onImageLoad = (event: Event, pageNum: number) => {
1000
1011
  isImageReady: isImageReady.value,
1001
1012
  isCalculatingAutoFit: isCalculatingAutoFit.value,
1002
1013
  });
1003
-
1014
+
1004
1015
  // 存储该页的图片尺寸
1005
1016
  imageSizes.set(pageNum, {
1006
1017
  width: img.naturalWidth,
@@ -1009,10 +1020,10 @@ const onImageLoad = (event: Event, pageNum: number) => {
1009
1020
 
1010
1021
  // 如果是第一页且启用自适应宽度,计算并设置初始缩放比例
1011
1022
  if (pageNum === 1 && props.autoFitWidth) {
1012
- console.log('[ImagePreview] 第一页加载完成,开始计算自适应宽度');
1023
+ console.log("[ImagePreview] 第一页加载完成,开始计算自适应宽度");
1013
1024
  // 重置用户缩放标记
1014
1025
  isUserZooming.value = false;
1015
-
1026
+
1016
1027
  // 确保图片是隐藏的(watch 已经设置了,这里再次确认)
1017
1028
  if (!isImageReady.value) {
1018
1029
  isCalculatingAutoFit.value = true;
@@ -1020,7 +1031,7 @@ const onImageLoad = (event: Event, pageNum: number) => {
1020
1031
 
1021
1032
  // 设置超时保护,防止一直显示 loading(最多等待 3 秒)
1022
1033
  const timeoutId = setTimeout(() => {
1023
- console.warn('自适应宽度计算超时,强制显示图片');
1034
+ console.warn("自适应宽度计算超时,强制显示图片");
1024
1035
  isCalculatingAutoFit.value = false;
1025
1036
  isImageReady.value = true;
1026
1037
  }, 3000);
@@ -1031,38 +1042,51 @@ const onImageLoad = (event: Event, pageNum: number) => {
1031
1042
  // 添加小延迟确保容器完全渲染
1032
1043
  setTimeout(() => {
1033
1044
  try {
1034
- console.log('[ImagePreview] onImageLoad: 开始计算自适应宽度...');
1045
+ console.log("[ImagePreview] onImageLoad: 开始计算自适应宽度...");
1035
1046
  const autoScale = calculateAutoFitScale();
1036
- console.log('[ImagePreview] onImageLoad: 自适应宽度计算结果:', {
1047
+ console.log("[ImagePreview] onImageLoad: 自适应宽度计算结果:", {
1037
1048
  autoScale,
1038
1049
  containerRef: !!containerRef.value,
1039
- containerWidth: containerRef.value?.getBoundingClientRect()?.width,
1050
+ containerWidth:
1051
+ containerRef.value?.getBoundingClientRect()?.width,
1040
1052
  firstPageSize: imageSizes.get(1),
1041
1053
  });
1042
-
1054
+
1043
1055
  if (autoScale > 0) {
1044
1056
  scale.value = autoScale;
1045
1057
  initialAutoFitScale.value = autoScale; // 记录初始自适应缩放比例
1046
1058
  // 记录当前容器宽度,用于后续 resize 检查
1047
1059
  if (containerRef.value) {
1048
- lastContainerWidth = containerRef.value.getBoundingClientRect().width;
1060
+ lastContainerWidth =
1061
+ containerRef.value.getBoundingClientRect().width;
1049
1062
  }
1050
- console.log('[ImagePreview] onImageLoad: 缩放比例已设置:', autoScale);
1063
+ console.log(
1064
+ "[ImagePreview] onImageLoad: 缩放比例已设置:",
1065
+ autoScale
1066
+ );
1051
1067
  } else {
1052
- console.warn('[ImagePreview] onImageLoad: 计算出的缩放比例无效:', autoScale);
1068
+ console.warn(
1069
+ "[ImagePreview] onImageLoad: 计算出的缩放比例无效:",
1070
+ autoScale
1071
+ );
1053
1072
  }
1054
1073
  } catch (error) {
1055
- console.error('[ImagePreview] onImageLoad: 计算自适应宽度失败:', error);
1074
+ console.error(
1075
+ "[ImagePreview] onImageLoad: 计算自适应宽度失败:",
1076
+ error
1077
+ );
1056
1078
  } finally {
1057
1079
  // 清除超时保护
1058
1080
  clearTimeout(timeoutId);
1059
- console.log('[ImagePreview] onImageLoad: 更新状态: isCalculatingAutoFit = false, isImageReady = true');
1081
+ console.log(
1082
+ "[ImagePreview] onImageLoad: 更新状态: isCalculatingAutoFit = false, isImageReady = true"
1083
+ );
1060
1084
  // 无论计算结果如何,都要更新状态,避免一直显示 loading
1061
1085
  isCalculatingAutoFit.value = false;
1062
1086
  // 使用 requestAnimationFrame 确保在下一帧显示,避免闪烁
1063
1087
  requestAnimationFrame(() => {
1064
1088
  isImageReady.value = true;
1065
- console.log('[ImagePreview] onImageLoad: 图片已准备好显示');
1089
+ console.log("[ImagePreview] onImageLoad: 图片已准备好显示");
1066
1090
  });
1067
1091
  }
1068
1092
  }, 100); // 增加延迟,确保所有图片都已加载
@@ -1073,7 +1097,7 @@ const onImageLoad = (event: Event, pageNum: number) => {
1073
1097
  isImageReady.value = true;
1074
1098
  isCalculatingAutoFit.value = false;
1075
1099
  }
1076
-
1100
+
1077
1101
  // 如果第一页已经加载完成,且当前页不是第一页,也应用自适应宽度
1078
1102
  if (pageNum > 1 && props.autoFitWidth && initialAutoFitScale.value !== null) {
1079
1103
  // 确保后续页面也使用相同的缩放比例
@@ -1286,7 +1310,7 @@ const renderTextLayer = (pageNum?: number) => {
1286
1310
  // 清除当前页面所有文本块的高亮样式(除了当前文本块)
1287
1311
  // 这样可以防止多个文本块同时高亮的竞态条件
1288
1312
  clearAllHighlights(blockDiv);
1289
-
1313
+
1290
1314
  // 清除跳转高亮标志(如果之前是通过跳转高亮的)
1291
1315
  if (isHighlighted.value) {
1292
1316
  isHighlighted.value = false;
@@ -1299,11 +1323,23 @@ const renderTextLayer = (pageNum?: number) => {
1299
1323
  const existingAnnotation = getAnnotationForBlock(bbox, targetPage);
1300
1324
  if (existingAnnotation) {
1301
1325
  // 如果有批注,保持批注背景色,但添加蓝色边框表示 hover
1302
- blockDiv.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
1303
- blockDiv.style.setProperty("border", "2px solid rgba(30, 144, 255, 0.8)", "important");
1326
+ blockDiv.style.setProperty(
1327
+ "background-color",
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
+ );
1304
1336
  blockDiv.style.setProperty("border-radius", "3px", "important");
1305
1337
  blockDiv.style.setProperty("padding", "1px 3px", "important");
1306
- 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");
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
+ );
1307
1343
  } else {
1308
1344
  // 如果没有批注,先清除可能残留的样式,再设置 hover 样式(与 PdfPreview 保持一致)
1309
1345
  blockDiv.style.removeProperty("background-color");
@@ -1376,7 +1412,7 @@ const showAnnotationButtonForBlock = (
1376
1412
 
1377
1413
  try {
1378
1414
  const bbox = JSON.parse(bboxStr) as [number, number, number, number];
1379
-
1415
+
1380
1416
  // 从 blocksData 中查找对应的 content
1381
1417
  const pageBlocksData = getPageBlocksData(currentPage.value);
1382
1418
  const blockData = pageBlocksData.find((block) => {
@@ -1389,7 +1425,7 @@ const showAnnotationButtonForBlock = (
1389
1425
  Math.abs(y2 - bbox[3]) < tolerance
1390
1426
  );
1391
1427
  });
1392
-
1428
+
1393
1429
  if (!blockData) return;
1394
1430
 
1395
1431
  const rect = blockDiv.getBoundingClientRect();
@@ -1477,7 +1513,10 @@ const showAnnotationButtonForBlock = (
1477
1513
  * @param excludeBlockDiv 要排除的文本块(不清除它的样式)
1478
1514
  * @param pageNum 要清除的页码,如果不提供则从 excludeBlockDiv 中获取
1479
1515
  */
1480
- const clearAllHighlights = (excludeBlockDiv?: HTMLElement, pageNum?: number) => {
1516
+ const clearAllHighlights = (
1517
+ excludeBlockDiv?: HTMLElement,
1518
+ pageNum?: number
1519
+ ) => {
1481
1520
  // 确定要清除的页码
1482
1521
  let targetPage = pageNum;
1483
1522
  if (!targetPage && excludeBlockDiv) {
@@ -1511,11 +1550,23 @@ const clearAllHighlights = (excludeBlockDiv?: HTMLElement, pageNum?: number) =>
1511
1550
 
1512
1551
  if (existingAnnotation) {
1513
1552
  // 如果有批注,恢复批注样式(不使用 hover 样式)
1514
- el.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
1515
- el.style.setProperty("border", "1px solid rgba(255, 193, 7, 0.7)", "important");
1553
+ el.style.setProperty(
1554
+ "background-color",
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
+ );
1516
1563
  el.style.setProperty("border-radius", "3px", "important");
1517
1564
  el.style.setProperty("padding", "1px 3px", "important");
1518
- el.style.setProperty("box-shadow", "0 1px 2px rgba(255, 193, 7, 0.25)", "important");
1565
+ el.style.setProperty(
1566
+ "box-shadow",
1567
+ "0 1px 2px rgba(255, 193, 7, 0.25)",
1568
+ "important"
1569
+ );
1519
1570
  } else {
1520
1571
  // 如果没有批注,清除所有高亮样式
1521
1572
  el.style.backgroundColor = "transparent";
@@ -1579,17 +1630,29 @@ const restoreBlockStyle = (blockDiv: HTMLElement) => {
1579
1630
 
1580
1631
  if (existingAnnotation) {
1581
1632
  // 如果有批注,保持批注样式(使用 !important 确保优先级)
1582
- blockDiv.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
1583
- blockDiv.style.setProperty("border", "1px solid rgba(255, 193, 7, 0.7)", "important");
1633
+ blockDiv.style.setProperty(
1634
+ "background-color",
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
+ );
1584
1643
  blockDiv.style.setProperty("border-radius", "3px", "important");
1585
1644
  blockDiv.style.setProperty("padding", "1px 3px", "important");
1586
- blockDiv.style.setProperty("box-shadow", "0 1px 2px rgba(255, 193, 7, 0.25)", "important");
1587
-
1645
+ blockDiv.style.setProperty(
1646
+ "box-shadow",
1647
+ "0 1px 2px rgba(255, 193, 7, 0.25)",
1648
+ "important"
1649
+ );
1650
+
1588
1651
  // 确保元素可见
1589
1652
  blockDiv.style.setProperty("display", "block", "important");
1590
1653
  blockDiv.style.setProperty("visibility", "visible", "important");
1591
1654
  blockDiv.style.setProperty("opacity", "1", "important");
1592
-
1655
+
1593
1656
  console.log("restoreBlockStyle: 已设置批注样式", {
1594
1657
  backgroundColor: blockDiv.style.backgroundColor,
1595
1658
  border: blockDiv.style.border,
@@ -1639,12 +1702,7 @@ const hideAnnotationButton = () => {
1639
1702
  const bboxStr = activeBlockDiv.value.dataset.bbox;
1640
1703
  if (bboxStr) {
1641
1704
  try {
1642
- const bbox = JSON.parse(bboxStr) as [
1643
- number,
1644
- number,
1645
- number,
1646
- number
1647
- ];
1705
+ const bbox = JSON.parse(bboxStr) as [number, number, number, number];
1648
1706
  const existingAnnotation = getAnnotationForBlock(bbox);
1649
1707
  if (existingAnnotation) {
1650
1708
  // 如果有批注,恢复批注样式
@@ -1787,6 +1845,7 @@ const openAnnotationInput = (e?: Event) => {
1787
1845
  currentAnnotationBlock.value = {
1788
1846
  bbox,
1789
1847
  content: selectedText,
1848
+ annotationId: existingAnnotation?.id, // 保存已有批注的ID
1790
1849
  };
1791
1850
 
1792
1851
  // 确保弹窗显示
@@ -1896,7 +1955,7 @@ const closeAnnotationInput = () => {
1896
1955
  currentAnnotationBlock.value = null;
1897
1956
  annotationInput.value = "";
1898
1957
  showAnnotationPopup.value = false;
1899
-
1958
+
1900
1959
  // 关闭批注输入弹窗后,恢复文本块的样式
1901
1960
  if (activeBlockDiv.value && !isHighlighted.value) {
1902
1961
  restoreBlockStyle(activeBlockDiv.value);
@@ -1928,16 +1987,22 @@ const saveAnnotation = () => {
1928
1987
  return;
1929
1988
  }
1930
1989
 
1931
- const { bbox, content } = currentAnnotationBlock.value;
1990
+ const { bbox, content, annotationId } = currentAnnotationBlock.value;
1932
1991
  const annotationContent = annotationInput.value.trim();
1933
1992
 
1934
- // 检查是否已有批注
1935
- const existingAnnotation = getAnnotationForBlock(bbox);
1993
+ // 优先使用保存的ID查找批注,如果没有ID则使用bbox查找(向后兼容)
1994
+ let existingAnnotation: AnnotationInfo | null = null;
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
+ }
1936
2003
 
1937
2004
  const annotation: AnnotationInfo = {
1938
- id:
1939
- existingAnnotation?.id ||
1940
- `annotation_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
2005
+ id: existingAnnotation?.id || "",
1941
2006
  blockBbox: bbox,
1942
2007
  blockContent: content,
1943
2008
  blockPage: currentPage.value,
@@ -1969,9 +2034,9 @@ const saveAnnotation = () => {
1969
2034
  */
1970
2035
  const handleScroll = (e: Event) => {
1971
2036
  const container = e.target as HTMLElement;
1972
-
2037
+
1973
2038
  // 检查是否是同步滚动触发的
1974
- const isSyncing = container?.dataset?.syncingScroll === 'true';
2039
+ const isSyncing = container?.dataset?.syncingScroll === "true";
1975
2040
  if (isSyncing) {
1976
2041
  // 即使是被同步滚动触发的,也应该立即更新页码(但不触发翻页动画)
1977
2042
  // 使用 requestAnimationFrame 确保在浏览器渲染后立即更新
@@ -1990,7 +2055,7 @@ const handleScroll = (e: Event) => {
1990
2055
  // 如果没有启用滚动翻页,立即清除标记
1991
2056
  delete container.dataset.syncingScroll;
1992
2057
  }
1993
-
2058
+
1994
2059
  // 同步滚动时,不执行其他逻辑(如隐藏批注按钮等)
1995
2060
  return;
1996
2061
  }
@@ -2031,7 +2096,6 @@ const handleScroll = (e: Event) => {
2031
2096
  }
2032
2097
  };
2033
2098
 
2034
-
2035
2099
  // 记录上次滚动位置,用于判断滚动方向
2036
2100
  let lastScrollTop = 0;
2037
2101
 
@@ -2238,7 +2302,7 @@ const highlightPosition = (
2238
2302
  const isVisible = isElementVisible(elementRef, containerRef.value);
2239
2303
  if (!isVisible) {
2240
2304
  // 标记这是定位滚动,不应该被同步滚动干扰
2241
- containerRef.value.dataset.pageScrolling = 'true';
2305
+ containerRef.value.dataset.pageScrolling = "true";
2242
2306
  elementRef.scrollIntoView({ behavior: "smooth", block: "center" });
2243
2307
  // 延迟清除标记,确保滚动完成
2244
2308
  setTimeout(() => {
@@ -2303,7 +2367,9 @@ const jumpToPosition = (
2303
2367
  retryCount++;
2304
2368
  setTimeout(tryHighlight, retryDelay);
2305
2369
  } else {
2306
- console.warn(`无法找到并高亮指定位置: 页码 ${pageNum}, bbox: [${bbox.join(", ")}]`);
2370
+ console.warn(
2371
+ `无法找到并高亮指定位置: 页码 ${pageNum}, bbox: [${bbox.join(", ")}]`
2372
+ );
2307
2373
  }
2308
2374
  };
2309
2375
 
@@ -2413,7 +2479,7 @@ const handleContainerResize = () => {
2413
2479
  lastContainerWidth = currentWidth;
2414
2480
  }
2415
2481
 
2416
- console.log('[ImagePreview] handleContainerResize 被调用:', {
2482
+ console.log("[ImagePreview] handleContainerResize 被调用:", {
2417
2483
  autoFitWidth: props.autoFitWidth,
2418
2484
  isUserZooming: isUserZooming.value,
2419
2485
  isImageReady: isImageReady.value,
@@ -2431,26 +2497,26 @@ const handleContainerResize = () => {
2431
2497
  resizeTimer = null;
2432
2498
  }
2433
2499
 
2434
- // 隐藏图片,显示 loading
2435
- console.log('[ImagePreview] handleContainerResize: 开始重新计算');
2436
- isImageReady.value = false;
2437
- isCalculatingAutoFit.value = true;
2500
+ // 宽度变化时不显示 loading,只更新缩放比例(避免看起来像重新加载)
2501
+ console.log("[ImagePreview] handleContainerResize: 开始重新计算");
2438
2502
 
2439
2503
  // 立即计算并应用新的缩放比例,避免过渡期间露出底色
2440
2504
  // 使用 requestAnimationFrame 确保在浏览器重绘前更新
2441
2505
  requestAnimationFrame(() => {
2442
2506
  try {
2443
- console.log('[ImagePreview] handleContainerResize: 开始计算自适应宽度...');
2507
+ console.log(
2508
+ "[ImagePreview] handleContainerResize: 开始计算自适应宽度..."
2509
+ );
2444
2510
  const newScale = calculateAutoFitScale();
2445
- console.log('[ImagePreview] handleContainerResize: 计算结果:', newScale);
2446
-
2511
+ console.log("[ImagePreview] handleContainerResize: 计算结果:", newScale);
2512
+
2447
2513
  if (newScale > 0) {
2448
2514
  // 即使变化很小也立即更新,确保过渡期间图片始终填满容器
2449
2515
  scale.value = newScale;
2450
2516
  initialAutoFitScale.value = newScale;
2451
2517
  }
2452
2518
  } catch (error) {
2453
- console.error('[ImagePreview] handleContainerResize: 计算失败:', error);
2519
+ console.error("[ImagePreview] handleContainerResize: 计算失败:", error);
2454
2520
  }
2455
2521
 
2456
2522
  // 在过渡动画完成后再次检查,确保最终状态正确(处理过渡动画期间的连续变化)
@@ -2462,15 +2528,14 @@ const handleContainerResize = () => {
2462
2528
  initialAutoFitScale.value = finalScale;
2463
2529
  }
2464
2530
  } catch (error) {
2465
- console.error('[ImagePreview] handleContainerResize: 最终计算失败:', error);
2531
+ console.error(
2532
+ "[ImagePreview] handleContainerResize: 最终计算失败:",
2533
+ error
2534
+ );
2466
2535
  } finally {
2467
- // 计算完成后,显示图片并隐藏 loading
2468
- console.log('[ImagePreview] handleContainerResize: 更新状态完成');
2469
- isCalculatingAutoFit.value = false;
2536
+ // 计算完成,重置标记(不改变图片显示状态,因为宽度变化时不应该显示loading
2537
+ console.log("[ImagePreview] handleContainerResize: 更新状态完成");
2470
2538
  isResizing = false; // 重置标记
2471
- requestAnimationFrame(() => {
2472
- isImageReady.value = true;
2473
- });
2474
2539
  }
2475
2540
  }, 350); // 350ms 延迟,略大于过渡动画时间(300ms),确保过渡完成后稳定
2476
2541
  });
@@ -2488,14 +2553,14 @@ onMounted(() => {
2488
2553
  // 隐藏图片,显示 loading
2489
2554
  isImageReady.value = false;
2490
2555
  isCalculatingAutoFit.value = true;
2491
-
2556
+
2492
2557
  // 设置超时保护,防止一直显示 loading(最多等待 3 秒)
2493
2558
  const timeoutId = setTimeout(() => {
2494
- console.warn('自适应宽度计算超时,强制显示图片');
2559
+ console.warn("自适应宽度计算超时,强制显示图片");
2495
2560
  isCalculatingAutoFit.value = false;
2496
2561
  isImageReady.value = true;
2497
2562
  }, 3000);
2498
-
2563
+
2499
2564
  nextTick(() => {
2500
2565
  nextTick(() => {
2501
2566
  setTimeout(() => {
@@ -2506,7 +2571,7 @@ onMounted(() => {
2506
2571
  initialAutoFitScale.value = autoScale;
2507
2572
  }
2508
2573
  } catch (error) {
2509
- console.warn('计算自适应宽度失败:', error);
2574
+ console.warn("计算自适应宽度失败:", error);
2510
2575
  } finally {
2511
2576
  // 清除超时保护
2512
2577
  clearTimeout(timeoutId);
@@ -2527,7 +2592,7 @@ onMounted(() => {
2527
2592
 
2528
2593
  // 监听容器尺寸变化(用于响应外部收起/展开操作)
2529
2594
  nextTick(() => {
2530
- if (containerRef.value && typeof ResizeObserver !== 'undefined') {
2595
+ if (containerRef.value && typeof ResizeObserver !== "undefined") {
2531
2596
  resizeObserver = new ResizeObserver((entries) => {
2532
2597
  // 使用防抖,避免频繁触发
2533
2598
  if (resizeDebounceTimer) {
@@ -2544,7 +2609,7 @@ onMounted(() => {
2544
2609
  resizeObserver.observe(containerRef.value);
2545
2610
  } else {
2546
2611
  // 降级方案:监听窗口大小变化
2547
- window.addEventListener('resize', handleContainerResize);
2612
+ window.addEventListener("resize", handleContainerResize);
2548
2613
  }
2549
2614
  });
2550
2615
  });
@@ -2574,7 +2639,7 @@ onBeforeUnmount(() => {
2574
2639
  resizeObserver = null;
2575
2640
  }
2576
2641
  // 移除窗口 resize 监听器(降级方案)
2577
- window.removeEventListener('resize', handleContainerResize);
2642
+ window.removeEventListener("resize", handleContainerResize);
2578
2643
  });
2579
2644
 
2580
2645
  defineExpose({