@koi-br/ocr-web-sdk 1.0.65 → 1.0.67

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-BQn8gr7r.mjs";
1
+ import { g as getAugmentedNamespace, a as getDefaultExportFromCjs } from "./index-BwWdGx71.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.65",
3
+ "version": "1.0.67",
4
4
  "description": "一个支持多种Office文件格式预览的Vue3组件SDK,包括PDF、Word、Excel、图片、OFD、TIF等格式",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -639,6 +639,137 @@ const annotationTextareaRef = ref<any>(); // 批注输入框引用
639
639
  const activeBlockDiv = ref<HTMLElement | null>(null);
640
640
  const isHighlighted = ref(false); // 标识是否为定位点击的高亮
641
641
 
642
+ // ✅ 跳转高亮目标(数据态):用于 renderTextLayer 重建 DOM 后恢复静态高亮
643
+ type JumpHighlightTarget = {
644
+ pageNum: number;
645
+ bbox: [number, number, number, number];
646
+ highlightStyle?: HighlightStyle;
647
+ };
648
+
649
+ const lastJumpHighlightTarget = ref<JumpHighlightTarget | null>(null);
650
+
651
+ const applyStaticJumpHighlightStyle = (
652
+ el: HTMLElement,
653
+ highlightStyle?: HighlightStyle
654
+ ) => {
655
+ // 确保是“静态”状态,不要残留动画
656
+ el.classList.remove("highlight-animated");
657
+ el.style.animation = "";
658
+ el.style.animationFillMode = "";
659
+ el.style.transform = "";
660
+ el.style.transformOrigin = "";
661
+ el.style.transition = "";
662
+
663
+ // ⚠️ 这里的语义要和 highlightPosition() 保持一致:
664
+ // - 如果调用方传了 highlightStyle(哪怕只传了 backgroundColor),就只应用它提供的字段。
665
+ // 避免在“重建后补高亮”时额外加上默认的蓝色 boxShadow/边框,导致看起来像 hover 态。
666
+ // - 如果未传 highlightStyle,才使用默认高亮样式。
667
+ if (highlightStyle) {
668
+ if (highlightStyle.backgroundColor !== undefined) {
669
+ el.style.backgroundColor = highlightStyle.backgroundColor;
670
+ }
671
+ if (highlightStyle.boxShadow !== undefined) {
672
+ el.style.boxShadow = highlightStyle.boxShadow;
673
+ }
674
+ if (highlightStyle.border !== undefined) {
675
+ el.style.border = highlightStyle.border;
676
+ }
677
+ return;
678
+ }
679
+
680
+ el.style.backgroundColor =
681
+ "var(--s-color-brand-primary-transparent-3, rgba(0, 102, 255, .15))";
682
+ el.style.boxShadow = "0 0 0 2px rgba(30, 144, 255, 0.6)";
683
+ el.style.border = "none";
684
+ };
685
+
686
+ const reapplyJumpHighlightIfNeeded = (targetPage: number) => {
687
+ const target = lastJumpHighlightTarget.value;
688
+ if (!target || target.pageNum !== targetPage) return;
689
+
690
+ const textLayer = textLayerRefs.get(targetPage);
691
+ if (!textLayer) return;
692
+
693
+ const tolerance = 2;
694
+ const blockDivs = textLayer.querySelectorAll(".text-block");
695
+
696
+ let matched: HTMLElement | null = null;
697
+ for (let i = 0; i < blockDivs.length; i++) {
698
+ const div = blockDivs[i] as HTMLElement;
699
+ const storedBbox = div.dataset.bbox;
700
+ if (!storedBbox) continue;
701
+
702
+ try {
703
+ const parsed = JSON.parse(storedBbox) as [
704
+ number,
705
+ number,
706
+ number,
707
+ number
708
+ ];
709
+ const isMatch =
710
+ Math.abs(parsed[0] - target.bbox[0]) < tolerance &&
711
+ Math.abs(parsed[1] - target.bbox[1]) < tolerance &&
712
+ Math.abs(parsed[2] - target.bbox[2]) < tolerance &&
713
+ Math.abs(parsed[3] - target.bbox[3]) < tolerance;
714
+
715
+ if (isMatch) {
716
+ matched = div;
717
+ break;
718
+ }
719
+ } catch (_e) {
720
+ // ignore
721
+ }
722
+ }
723
+
724
+ if (!matched) return;
725
+
726
+ // ✅ 输入态也要补回跳转高亮(否则提交触发 renderTextLayer 重建后会丢失 B 的高亮)
727
+ // 但输入态不要抢占 activeBlockDiv/isHighlighted,避免影响批注弹窗锚点与焦点。
728
+ if (currentAnnotationBlock.value) {
729
+ // 如果刚好命中“锁定输入态块”,保持输入态样式优先,不覆盖
730
+ if (lockedAnnotationDiv.value && matched === lockedAnnotationDiv.value) {
731
+ return;
732
+ }
733
+
734
+ applyStaticJumpHighlightStyle(matched, target.highlightStyle);
735
+ return;
736
+ }
737
+
738
+ applyStaticJumpHighlightStyle(matched, target.highlightStyle);
739
+
740
+ // 让状态也恢复一致(避免 renderTextLayer 清空引用后 isHighlighted/activeBlockDiv 脱节)
741
+ activeBlockDiv.value = matched;
742
+ isHighlighted.value = true;
743
+ };
744
+
745
+ // ✅ lastJumpHighlightTarget 的清理交由 clearAllHighlights() 统一负责。
746
+ // hover 等局部交互可能会暂时把 isHighlighted 置为 false,但这不代表用户要取消“跳转高亮”。
747
+ // 只有在真正“全局清高亮”时才清空缓存目标,避免提交触发重建后高亮丢失。
748
+
749
+ // ✅ 批注输入态锁定的文本块(与 activeBlockDiv 解耦,避免被 jump/highlight 覆盖)
750
+ const lockedAnnotationDiv = ref<HTMLElement | null>(null);
751
+
752
+ const LOCKED_ANNOTATION_OUTLINE = "2px solid rgba(24, 144, 255, 0.85)";
753
+ const LOCKED_ANNOTATION_OUTLINE_OFFSET = "1px";
754
+
755
+ const applyLockedAnnotationOutline = (blockDiv: HTMLElement) => {
756
+ blockDiv.style.setProperty(
757
+ "outline",
758
+ LOCKED_ANNOTATION_OUTLINE,
759
+ "important"
760
+ );
761
+ blockDiv.style.setProperty(
762
+ "outline-offset",
763
+ LOCKED_ANNOTATION_OUTLINE_OFFSET,
764
+ "important"
765
+ );
766
+ };
767
+
768
+ const clearLockedAnnotationOutline = (blockDiv: HTMLElement) => {
769
+ blockDiv.style.removeProperty("outline");
770
+ blockDiv.style.removeProperty("outline-offset");
771
+ };
772
+
642
773
  // 根据当前页码过滤blocksData
643
774
  const currentPageBlocksData = computed(() => {
644
775
  if (!props.blocksData || props.blocksData.length === 0) {
@@ -1069,10 +1200,14 @@ const switchToPage = (page: number, autoClearMark: boolean = true) => {
1069
1200
  return;
1070
1201
  }
1071
1202
 
1072
- // 重置文本选择状态和批注状态
1073
- showAnnotationPopup.value = false;
1074
- currentAnnotationBlock.value = null;
1075
- annotationInput.value = "";
1203
+ // 重置文本选择状态和批注状态(若处于输入态先解除锁定,避免 outline 残留)
1204
+ if (lockedAnnotationDiv.value) {
1205
+ closeAnnotationInput();
1206
+ } else {
1207
+ showAnnotationPopup.value = false;
1208
+ currentAnnotationBlock.value = null;
1209
+ annotationInput.value = "";
1210
+ }
1076
1211
  const hadHighlight = isHighlighted.value && activeBlockDiv.value !== null;
1077
1212
  activeBlockDiv.value = null;
1078
1213
  isHighlighted.value = false;
@@ -1134,10 +1269,14 @@ const reset = () => {
1134
1269
 
1135
1270
  position.value = { x: 0, y: 0 };
1136
1271
 
1137
- // 重置文本选择状态和批注状态
1138
- showAnnotationPopup.value = false;
1139
- currentAnnotationBlock.value = null;
1140
- annotationInput.value = "";
1272
+ // 重置文本选择状态和批注状态(若处于输入态先解除锁定,避免 outline 残留)
1273
+ if (lockedAnnotationDiv.value) {
1274
+ closeAnnotationInput();
1275
+ } else {
1276
+ showAnnotationPopup.value = false;
1277
+ currentAnnotationBlock.value = null;
1278
+ annotationInput.value = "";
1279
+ }
1141
1280
  const hadHighlight = isHighlighted.value && activeBlockDiv.value !== null;
1142
1281
  activeBlockDiv.value = null;
1143
1282
  isHighlighted.value = false;
@@ -1475,6 +1614,11 @@ const renderTextLayer = (pageNum?: number) => {
1475
1614
 
1476
1615
  // Hover 和点击事件
1477
1616
  blockDiv.addEventListener("mouseenter", (e) => {
1617
+ // ✅ 输入态:锚点已锁定,禁止 hover 驱动位置/激活块变化
1618
+ if (currentAnnotationBlock.value) {
1619
+ return;
1620
+ }
1621
+
1478
1622
  // 如果未启用 hover 效果,直接返回
1479
1623
  if (!props.enableBlockHover) {
1480
1624
  return;
@@ -1507,6 +1651,11 @@ const renderTextLayer = (pageNum?: number) => {
1507
1651
  });
1508
1652
 
1509
1653
  blockDiv.addEventListener("mouseleave", () => {
1654
+ // ✅ 输入态:不再启动 auto-hide 逻辑
1655
+ if (currentAnnotationBlock.value) {
1656
+ return;
1657
+ }
1658
+
1510
1659
  // 如果未启用 hover 效果,直接返回
1511
1660
  if (!props.enableBlockHover) {
1512
1661
  return;
@@ -1526,6 +1675,9 @@ const renderTextLayer = (pageNum?: number) => {
1526
1675
  textLayer.appendChild(blockDiv);
1527
1676
  });
1528
1677
 
1678
+ // ✅ 页面重建后,把“最后一次跳转高亮”补回到新 DOM 节点上(静态)
1679
+ reapplyJumpHighlightIfNeeded(targetPage);
1680
+
1529
1681
  // 标记该页面已渲染
1530
1682
  renderedPages.value.add(targetPage);
1531
1683
  } catch (error) {
@@ -1568,6 +1720,9 @@ const showAnnotationButtonForBlock = (
1568
1720
  event: MouseEvent,
1569
1721
  blockDiv: HTMLElement
1570
1722
  ) => {
1723
+ // ✅ 输入态:不允许入口浮层继续跟随 hover 更新位置
1724
+ if (currentAnnotationBlock.value) return;
1725
+
1571
1726
  const bboxStr = blockDiv.dataset.bbox || "";
1572
1727
  if (!bboxStr) return;
1573
1728
 
@@ -1724,6 +1879,9 @@ const clearAllHighlights = (
1724
1879
  // 清除引用和标志
1725
1880
  activeBlockDiv.value = null;
1726
1881
  isHighlighted.value = false;
1882
+
1883
+ // ✅ 真正“全局清高亮”时才清空跳转高亮缓存
1884
+ lastJumpHighlightTarget.value = null;
1727
1885
 
1728
1886
  // 清除定时器
1729
1887
  if (highlightTimer) {
@@ -2124,6 +2282,29 @@ const openAnnotationInput = (e?: Event) => {
2124
2282
  // 确保弹窗显示
2125
2283
  showAnnotationPopup.value = true;
2126
2284
 
2285
+ // ✅ 锁定锚点高亮(输入态)
2286
+ if (activeBlockDiv.value) {
2287
+ // 如果之前有锁定块且不是当前块,先解除之前的锁定,避免 outline 残留
2288
+ if (
2289
+ lockedAnnotationDiv.value &&
2290
+ lockedAnnotationDiv.value !== activeBlockDiv.value
2291
+ ) {
2292
+ clearLockedAnnotationOutline(lockedAnnotationDiv.value);
2293
+ restoreBlockStyle(lockedAnnotationDiv.value);
2294
+ }
2295
+
2296
+ lockedAnnotationDiv.value = activeBlockDiv.value;
2297
+
2298
+ // 清除其它块的高亮,避免竞态残留
2299
+ clearAllHighlights(lockedAnnotationDiv.value);
2300
+
2301
+ // 保持该块的 hover/批注样式
2302
+ applyHoverStyle(lockedAnnotationDiv.value, !!existingAnnotation);
2303
+
2304
+ // 使用 outline 作为“锁定态”标识(不依赖 scoped CSS)
2305
+ applyLockedAnnotationOutline(lockedAnnotationDiv.value);
2306
+ }
2307
+
2127
2308
  // 重新计算弹窗位置(输入框更大)
2128
2309
  nextTick(() => {
2129
2310
  adjustAnnotationPopupPosition();
@@ -2169,9 +2350,13 @@ const adjustAnnotationPopupPosition = () => {
2169
2350
  let left: number;
2170
2351
  let top: number;
2171
2352
 
2172
- if (activeBlockDiv.value) {
2353
+ const anchorBlockDiv = currentAnnotationBlock.value
2354
+ ? lockedAnnotationDiv.value
2355
+ : activeBlockDiv.value;
2356
+
2357
+ if (anchorBlockDiv) {
2173
2358
  // 使用文本块位置,和批注按钮使用相同的计算逻辑
2174
- const rect = activeBlockDiv.value.getBoundingClientRect();
2359
+ const rect = anchorBlockDiv.getBoundingClientRect();
2175
2360
  const relativeX =
2176
2361
  rect.left - containerRect.left + containerRef.value.scrollLeft;
2177
2362
  const relativeY =
@@ -2250,10 +2435,30 @@ const closeAnnotationInput = () => {
2250
2435
  annotationInput.value = "";
2251
2436
  showAnnotationPopup.value = false;
2252
2437
 
2253
- // 关闭批注输入弹窗后,恢复文本块的样式
2254
- if (activeBlockDiv.value && !isHighlighted.value) {
2255
- restoreBlockStyle(activeBlockDiv.value);
2256
- activeBlockDiv.value = null;
2438
+ const lockedDiv = lockedAnnotationDiv.value;
2439
+
2440
+ // ✅ 优先清理“输入态锁定块”(不依赖 activeBlockDiv,避免被跳转覆盖后清不到)
2441
+ if (lockedDiv) {
2442
+ clearLockedAnnotationOutline(lockedDiv);
2443
+ restoreBlockStyle(lockedDiv);
2444
+
2445
+ // 如果当前 activeBlock 就是这个锁定块,且不是跳转高亮,则恢复引用
2446
+ if (activeBlockDiv.value === lockedDiv && !isHighlighted.value) {
2447
+ activeBlockDiv.value = null;
2448
+ }
2449
+
2450
+ lockedAnnotationDiv.value = null;
2451
+ return;
2452
+ }
2453
+
2454
+ // 兼容路径:没有锁定块时,按原逻辑处理 activeBlock
2455
+ if (activeBlockDiv.value) {
2456
+ clearLockedAnnotationOutline(activeBlockDiv.value);
2457
+
2458
+ if (!isHighlighted.value) {
2459
+ restoreBlockStyle(activeBlockDiv.value);
2460
+ activeBlockDiv.value = null;
2461
+ }
2257
2462
  }
2258
2463
  };
2259
2464
 
@@ -2549,10 +2754,38 @@ const highlightPosition = (
2549
2754
  // 🔑 关键:清除页面上所有的高亮元素,确保不会有残留
2550
2755
  // 这样可以避免多个高亮框同时存在的问题
2551
2756
  const hadHighlight = activeBlockDiv.value !== null && isHighlighted.value;
2552
-
2757
+
2758
+ // ✅ 如果当前有批注输入态锁定块,先记录它的批注状态,清理后再补回样式
2759
+ const lockedDiv = lockedAnnotationDiv.value;
2760
+ let lockedHasAnnotation = false;
2761
+ if (lockedDiv) {
2762
+ const bboxStr = lockedDiv.dataset.bbox;
2763
+ const pageStr = lockedDiv.dataset.page;
2764
+ const lockedPageNum = pageStr ? parseInt(pageStr, 10) : undefined;
2765
+ if (bboxStr) {
2766
+ try {
2767
+ const bbox = JSON.parse(bboxStr) as [
2768
+ number,
2769
+ number,
2770
+ number,
2771
+ number
2772
+ ];
2773
+ lockedHasAnnotation = !!getAnnotationForBlock(bbox, lockedPageNum);
2774
+ } catch (_e) {
2775
+ lockedHasAnnotation = false;
2776
+ }
2777
+ }
2778
+ }
2779
+
2553
2780
  // 清除所有高亮(包括所有页面的文本块),不排除任何元素
2554
2781
  clearAllHighlights();
2555
2782
 
2783
+ // ✅ 清理后补回锁定块样式(避免 jump/highlight 清掉输入态的背景高亮)
2784
+ if (lockedDiv) {
2785
+ applyHoverStyle(lockedDiv, lockedHasAnnotation);
2786
+ applyLockedAnnotationOutline(lockedDiv);
2787
+ }
2788
+
2556
2789
  // 如果之前有高亮,触发高亮清除事件
2557
2790
  if (hadHighlight) {
2558
2791
  emit("highlight-clear");
@@ -2660,6 +2893,9 @@ const highlightPosition = (
2660
2893
  activeBlockDiv.value = elementRef;
2661
2894
  isHighlighted.value = true;
2662
2895
 
2896
+ // ✅ 记录最后一次“跳转高亮”的目标,用于 renderTextLayer 重建后补回静态高亮
2897
+ lastJumpHighlightTarget.value = { pageNum, bbox, highlightStyle };
2898
+
2663
2899
  // 使用传入的高亮样式,如果没有传入则使用默认样式
2664
2900
  if (highlightStyle) {
2665
2901
  if (highlightStyle.backgroundColor) {
@@ -2814,8 +3050,36 @@ const jumpToPosition = (
2814
3050
  isHighlighted: isHighlighted.value,
2815
3051
  reason: previousPositioningId ? '之前的定位被新定位覆盖' : '清除残留的高亮'
2816
3052
  });
3053
+ // ✅ 如果当前有批注输入态锁定块,先记录它的批注状态,清理后再补回样式
3054
+ const lockedDiv = lockedAnnotationDiv.value;
3055
+ let lockedHasAnnotation = false;
3056
+ if (lockedDiv) {
3057
+ const bboxStr = lockedDiv.dataset.bbox;
3058
+ const pageStr = lockedDiv.dataset.page;
3059
+ const lockedPageNum = pageStr ? parseInt(pageStr, 10) : undefined;
3060
+ if (bboxStr) {
3061
+ try {
3062
+ const lockedBbox = JSON.parse(bboxStr) as [
3063
+ number,
3064
+ number,
3065
+ number,
3066
+ number
3067
+ ];
3068
+ lockedHasAnnotation = !!getAnnotationForBlock(lockedBbox, lockedPageNum);
3069
+ } catch (_e) {
3070
+ lockedHasAnnotation = false;
3071
+ }
3072
+ }
3073
+ }
3074
+
2817
3075
  // 清除所有页面的高亮元素,不排除任何元素
2818
3076
  clearAllHighlights();
3077
+
3078
+ // ✅ 清理后补回锁定块样式(避免跳转清掉输入态的背景高亮)
3079
+ if (lockedDiv) {
3080
+ applyHoverStyle(lockedDiv, lockedHasAnnotation);
3081
+ applyLockedAnnotationOutline(lockedDiv);
3082
+ }
2819
3083
  }
2820
3084
 
2821
3085
  // 🔑 关键:在定位开始时设置标记,禁用同步滚动