@koi-br/ocr-web-sdk 1.0.36 → 1.0.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koi-br/ocr-web-sdk",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "一个支持多种Office文件格式预览的Vue3组件SDK,包括PDF、Word、Excel、图片、OFD、TIF等格式",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -116,7 +116,17 @@
116
116
  @mouseleave="stopPan"
117
117
  @scroll="handleScroll"
118
118
  >
119
- <div class="image-wrapper-container" :style="containerStyle">
119
+ <!-- 自适应宽度计算 Loading -->
120
+ <div v-if="isCalculatingAutoFit && autoFitWidth" class="auto-fit-loading">
121
+ <div class="loading-spinner"></div>
122
+ <div class="loading-text">加载中...</div>
123
+ </div>
124
+
125
+ <div
126
+ class="image-wrapper-container"
127
+ :style="containerStyle"
128
+ :class="{ 'image-hidden': !isImageReady && autoFitWidth }"
129
+ >
120
130
  <!-- 渲染所有图片页面 -->
121
131
  <div
122
132
  v-for="(imageUrl, pageIndex) in imageUrls"
@@ -390,6 +400,24 @@ const position = ref({ x: 0, y: 0 });
390
400
  const isPanning = ref(false);
391
401
  const lastPosition = ref({ x: 0, y: 0 });
392
402
  const initialAutoFitScale = ref<number | null>(null); // 记录初始自适应缩放比例
403
+ const isCalculatingAutoFit = ref(false); // 标记是否正在计算自适应宽度(显示 loading)
404
+ const isImageReady = ref(false); // 标记图片是否已准备好显示(自适应宽度计算完成)
405
+
406
+ // 监听图片URL变化,当有新图片时立即隐藏(等待自适应宽度计算)
407
+ watch(
408
+ () => imageUrls.value,
409
+ (newUrls, oldUrls) => {
410
+ // 如果有新的图片URL,且启用自适应宽度,立即隐藏图片
411
+ if (newUrls && newUrls.length > 0 && props.autoFitWidth) {
412
+ isImageReady.value = false;
413
+ isCalculatingAutoFit.value = true;
414
+ } else if (!props.autoFitWidth) {
415
+ // 如果没有启用自适应宽度,立即显示
416
+ isImageReady.value = true;
417
+ }
418
+ },
419
+ { immediate: true }
420
+ );
393
421
  const isUserZooming = ref(false); // 标记用户是否主动缩放
394
422
 
395
423
  // 滚动翻页相关
@@ -841,10 +869,18 @@ const switchToPage = (page: number) => {
841
869
  `[data-page-number="${page}"]`
842
870
  ) as HTMLElement;
843
871
  if (pageElement) {
872
+ // 标记这是翻页滚动,不应该被同步滚动干扰
873
+ containerRef.value.dataset.pageScrolling = 'true';
844
874
  pageElement.scrollIntoView({ behavior: "smooth", block: "start" });
845
875
  // 更新 lastScrollTop,确保滚动方向判断准确
846
876
  nextTick(() => {
847
877
  lastScrollTop = containerRef.value?.scrollTop || 0;
878
+ // 延迟清除标记,确保滚动完成
879
+ setTimeout(() => {
880
+ if (containerRef.value) {
881
+ delete containerRef.value.dataset.pageScrolling;
882
+ }
883
+ }, 500); // scrollIntoView 的 smooth 动画通常需要 300-500ms
848
884
  });
849
885
  }
850
886
  }
@@ -930,6 +966,10 @@ const onImageLoad = (event: Event, pageNum: number) => {
930
966
  if (pageNum === 1 && props.autoFitWidth) {
931
967
  // 重置用户缩放标记
932
968
  isUserZooming.value = false;
969
+
970
+ // 隐藏图片,显示 loading
971
+ isImageReady.value = false;
972
+ isCalculatingAutoFit.value = true;
933
973
 
934
974
  // 使用双重 nextTick 确保容器尺寸已确定
935
975
  nextTick(() => {
@@ -941,9 +981,18 @@ const onImageLoad = (event: Event, pageNum: number) => {
941
981
  scale.value = autoScale;
942
982
  initialAutoFitScale.value = autoScale; // 记录初始自适应缩放比例
943
983
  }
984
+ // 计算完成后,显示图片并隐藏 loading
985
+ isCalculatingAutoFit.value = false;
986
+ // 使用 requestAnimationFrame 确保在下一帧显示,避免闪烁
987
+ requestAnimationFrame(() => {
988
+ isImageReady.value = true;
989
+ });
944
990
  }, 100); // 增加延迟,确保所有图片都已加载
945
991
  });
946
992
  });
993
+ } else if (!props.autoFitWidth) {
994
+ // 如果没有启用自适应宽度,立即显示图片
995
+ isImageReady.value = true;
947
996
  }
948
997
 
949
998
  // 如果第一页已经加载完成,且当前页不是第一页,也应用自适应宽度
@@ -1840,6 +1889,33 @@ const saveAnnotation = () => {
1840
1889
  * 处理滚动事件(隐藏批注按钮和文本块高亮,以及滚动翻页)
1841
1890
  */
1842
1891
  const handleScroll = (e: Event) => {
1892
+ const container = e.target as HTMLElement;
1893
+
1894
+ // 检查是否是同步滚动触发的
1895
+ const isSyncing = container?.dataset?.syncingScroll === 'true';
1896
+ if (isSyncing) {
1897
+ // 即使是被同步滚动触发的,也应该立即更新页码(但不触发翻页动画)
1898
+ // 使用 requestAnimationFrame 确保在浏览器渲染后立即更新
1899
+ if (
1900
+ props.enableScrollPaging &&
1901
+ totalPages.value > 1 &&
1902
+ !isScrollPaging.value
1903
+ ) {
1904
+ // 立即更新页码,不等待防抖
1905
+ requestAnimationFrame(() => {
1906
+ updateCurrentPageFromScroll();
1907
+ // 清除标记(在页码更新后)
1908
+ delete container.dataset.syncingScroll;
1909
+ });
1910
+ } else {
1911
+ // 如果没有启用滚动翻页,立即清除标记
1912
+ delete container.dataset.syncingScroll;
1913
+ }
1914
+
1915
+ // 同步滚动时,不执行其他逻辑(如隐藏批注按钮等)
1916
+ return;
1917
+ }
1918
+
1843
1919
  if (hideTimer) {
1844
1920
  clearTimeout(hideTimer);
1845
1921
  }
@@ -1861,16 +1937,22 @@ const handleScroll = (e: Event) => {
1861
1937
  scale.value = initialAutoFitScale.value;
1862
1938
  }
1863
1939
 
1864
- // 滚动翻页功能
1940
+ // 滚动翻页功能(用户主动滚动时)
1865
1941
  if (
1866
1942
  props.enableScrollPaging &&
1867
1943
  totalPages.value > 1 &&
1868
1944
  !isScrollPaging.value
1869
1945
  ) {
1946
+ // 立即更新页码(使用 requestAnimationFrame 确保在渲染后更新)
1947
+ requestAnimationFrame(() => {
1948
+ updateCurrentPageFromScroll();
1949
+ });
1950
+ // 同时使用防抖处理其他逻辑(如事件触发)
1870
1951
  handleScrollPaging(e);
1871
1952
  }
1872
1953
  };
1873
1954
 
1955
+
1874
1956
  // 记录上次滚动位置,用于判断滚动方向
1875
1957
  let lastScrollTop = 0;
1876
1958
 
@@ -1939,6 +2021,7 @@ const handleScrollPaging = (e: Event) => {
1939
2021
  }
1940
2022
 
1941
2023
  // 使用防抖,避免频繁触发
2024
+ // 减少延迟到50ms,提高响应速度
1942
2025
  scrollPagingTimer = setTimeout(() => {
1943
2026
  // 再次检查是否正在翻页
1944
2027
  if (isScrollPaging.value) {
@@ -1947,7 +2030,7 @@ const handleScrollPaging = (e: Event) => {
1947
2030
 
1948
2031
  // 更新当前页码(基于视口中心最近的页面)
1949
2032
  updateCurrentPageFromScroll();
1950
- }, 100);
2033
+ }, 50);
1951
2034
  };
1952
2035
 
1953
2036
  /**
@@ -2228,6 +2311,10 @@ const handleContainerResize = () => {
2228
2311
  resizeTimer = null;
2229
2312
  }
2230
2313
 
2314
+ // 隐藏图片,显示 loading
2315
+ isImageReady.value = false;
2316
+ isCalculatingAutoFit.value = true;
2317
+
2231
2318
  // 立即计算并应用新的缩放比例,避免过渡期间露出底色
2232
2319
  // 使用 requestAnimationFrame 确保在浏览器重绘前更新
2233
2320
  requestAnimationFrame(() => {
@@ -2245,6 +2332,11 @@ const handleContainerResize = () => {
2245
2332
  scale.value = finalScale;
2246
2333
  initialAutoFitScale.value = finalScale;
2247
2334
  }
2335
+ // 计算完成后,显示图片并隐藏 loading
2336
+ isCalculatingAutoFit.value = false;
2337
+ requestAnimationFrame(() => {
2338
+ isImageReady.value = true;
2339
+ });
2248
2340
  }, 350); // 350ms 延迟,略大于过渡动画时间(300ms),确保过渡完成后稳定
2249
2341
  });
2250
2342
  };
@@ -2258,6 +2350,9 @@ onMounted(() => {
2258
2350
  if (firstPageImage && firstPageImage.complete && props.autoFitWidth) {
2259
2351
  const firstPageSize = imageSizes.get(1);
2260
2352
  if (firstPageSize && firstPageSize.width > 0) {
2353
+ // 隐藏图片,显示 loading
2354
+ isImageReady.value = false;
2355
+ isCalculatingAutoFit.value = true;
2261
2356
  nextTick(() => {
2262
2357
  nextTick(() => {
2263
2358
  setTimeout(() => {
@@ -2266,10 +2361,18 @@ onMounted(() => {
2266
2361
  scale.value = autoScale;
2267
2362
  initialAutoFitScale.value = autoScale;
2268
2363
  }
2364
+ // 计算完成后,显示图片并隐藏 loading
2365
+ isCalculatingAutoFit.value = false;
2366
+ requestAnimationFrame(() => {
2367
+ isImageReady.value = true;
2368
+ });
2269
2369
  }, 100);
2270
2370
  });
2271
2371
  });
2272
2372
  }
2373
+ } else if (!props.autoFitWidth) {
2374
+ // 如果没有启用自适应宽度,立即显示图片
2375
+ isImageReady.value = true;
2273
2376
  }
2274
2377
 
2275
2378
  // 监听容器尺寸变化(用于响应外部收起/展开操作)
@@ -2339,6 +2442,47 @@ defineExpose({
2339
2442
  overflow: auto;
2340
2443
  }
2341
2444
 
2445
+ // 自适应宽度计算 Loading
2446
+ .auto-fit-loading {
2447
+ position: absolute;
2448
+ top: 50%;
2449
+ left: 50%;
2450
+ transform: translate(-50%, -50%);
2451
+ z-index: 1000;
2452
+ display: flex;
2453
+ flex-direction: column;
2454
+ align-items: center;
2455
+ gap: 12px;
2456
+ padding: 20px 30px;
2457
+ }
2458
+
2459
+ .loading-spinner {
2460
+ width: 32px;
2461
+ height: 32px;
2462
+ border: 3px solid #e5e7eb;
2463
+ border-top-color: #1890ff;
2464
+ border-radius: 50%;
2465
+ animation: spin 0.8s linear infinite;
2466
+ }
2467
+
2468
+ @keyframes spin {
2469
+ to {
2470
+ transform: rotate(360deg);
2471
+ }
2472
+ }
2473
+
2474
+ .loading-text {
2475
+ font-size: 14px;
2476
+ color: #666;
2477
+ white-space: nowrap;
2478
+ }
2479
+
2480
+ // 图片隐藏状态(自适应宽度计算完成前)
2481
+ // 使用 display: none 确保图片完全不可见,不会在加载过程中显示
2482
+ .image-wrapper-container.image-hidden {
2483
+ display: none !important;
2484
+ }
2485
+
2342
2486
  // 页码信息样式
2343
2487
  .page-info {
2344
2488
  display: inline-flex;