@koi-br/ocr-web-sdk 1.0.31 → 1.0.33
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-IRUtAb6l.mjs → index-BW-AkzYU.mjs} +9034 -9017
- package/dist/{index-BZ8ndzte.js → index-xK8ReDUk.js} +89 -89
- package/dist/index.cjs.js +2 -2
- package/dist/index.esm.js +2 -2
- package/dist/{tiff.min-BaSKqyGR.mjs → tiff.min-C53DgzT0.mjs} +1 -1
- package/dist/{tiff.min-BJf8TYMB.js → tiff.min-CEQg_SyY.js} +1 -1
- package/package.json +1 -1
- package/preview/ImagePreview.vue +185 -76
package/package.json
CHANGED
package/preview/ImagePreview.vue
CHANGED
|
@@ -123,6 +123,7 @@
|
|
|
123
123
|
:key="pageIndex"
|
|
124
124
|
:data-page-number="pageIndex + 1"
|
|
125
125
|
class="image-page-container"
|
|
126
|
+
:style="getPageContainerStyle(pageIndex + 1)"
|
|
126
127
|
>
|
|
127
128
|
<div
|
|
128
129
|
class="image-wrapper"
|
|
@@ -514,6 +515,35 @@ const getPageBlocksData = (pageNo: number) => {
|
|
|
514
515
|
return props.blocksData.filter((block) => block.pageNo === pageNo);
|
|
515
516
|
};
|
|
516
517
|
|
|
518
|
+
// 计算指定页面缩放后的尺寸(考虑旋转)
|
|
519
|
+
const getPageScaledSize = (pageNo: number) => {
|
|
520
|
+
const pageSize = imageSizes.get(pageNo);
|
|
521
|
+
if (!pageSize || pageSize.width === 0 || pageSize.height === 0) {
|
|
522
|
+
return { width: 0, height: 0 };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const { width, height } = pageSize;
|
|
526
|
+
const scaledWidth = width * scale.value;
|
|
527
|
+
const scaledHeight = height * scale.value;
|
|
528
|
+
|
|
529
|
+
// 如果旋转了 90 度或 270 度,交换宽高
|
|
530
|
+
const normalizedRotation = ((rotation.value % 360) + 360) % 360;
|
|
531
|
+
const isRotated = normalizedRotation === 90 || normalizedRotation === 270;
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
width: isRotated ? scaledHeight : scaledWidth,
|
|
535
|
+
height: isRotated ? scaledWidth : scaledHeight,
|
|
536
|
+
};
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// 获取页面容器的样式(响应式,会根据缩放和旋转自动更新)
|
|
540
|
+
const getPageContainerStyle = (pageNo: number) => {
|
|
541
|
+
const scaledSize = getPageScaledSize(pageNo);
|
|
542
|
+
return {
|
|
543
|
+
height: scaledSize.height > 0 ? `${scaledSize.height}px` : 'auto',
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
|
|
517
547
|
// 隐藏定时器
|
|
518
548
|
let hideTimer: any = null;
|
|
519
549
|
|
|
@@ -1121,9 +1151,13 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1121
1151
|
hideTimer = null;
|
|
1122
1152
|
}
|
|
1123
1153
|
|
|
1124
|
-
//
|
|
1125
|
-
|
|
1126
|
-
|
|
1154
|
+
// 清除当前页面所有文本块的高亮样式(除了当前文本块)
|
|
1155
|
+
// 这样可以防止多个文本块同时高亮的竞态条件
|
|
1156
|
+
clearAllHighlights(blockDiv);
|
|
1157
|
+
|
|
1158
|
+
// 清除跳转高亮标志(如果之前是通过跳转高亮的)
|
|
1159
|
+
if (isHighlighted.value) {
|
|
1160
|
+
isHighlighted.value = false;
|
|
1127
1161
|
}
|
|
1128
1162
|
|
|
1129
1163
|
// 设置当前文本块为激活状态
|
|
@@ -1139,7 +1173,10 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1139
1173
|
blockDiv.style.setProperty("padding", "1px 3px", "important");
|
|
1140
1174
|
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");
|
|
1141
1175
|
} else {
|
|
1142
|
-
//
|
|
1176
|
+
// 如果没有批注,先清除可能残留的样式,再设置 hover 样式(与 PdfPreview 保持一致)
|
|
1177
|
+
blockDiv.style.removeProperty("background-color");
|
|
1178
|
+
blockDiv.style.removeProperty("border");
|
|
1179
|
+
blockDiv.style.removeProperty("box-shadow");
|
|
1143
1180
|
blockDiv.style.backgroundColor =
|
|
1144
1181
|
"var(--s-color-brand-primary-transparent-3, rgba(0, 102, 255, .15))";
|
|
1145
1182
|
blockDiv.style.boxShadow = "0 0 0 2px rgba(30, 144, 255, 0.6)";
|
|
@@ -1303,6 +1340,75 @@ const showAnnotationButtonForBlock = (
|
|
|
1303
1340
|
}
|
|
1304
1341
|
};
|
|
1305
1342
|
|
|
1343
|
+
/**
|
|
1344
|
+
* 清除指定页面所有文本块的高亮样式(除了指定的文本块)
|
|
1345
|
+
* @param excludeBlockDiv 要排除的文本块(不清除它的样式)
|
|
1346
|
+
* @param pageNum 要清除的页码,如果不提供则从 excludeBlockDiv 中获取
|
|
1347
|
+
*/
|
|
1348
|
+
const clearAllHighlights = (excludeBlockDiv?: HTMLElement, pageNum?: number) => {
|
|
1349
|
+
// 确定要清除的页码
|
|
1350
|
+
let targetPage = pageNum;
|
|
1351
|
+
if (!targetPage && excludeBlockDiv) {
|
|
1352
|
+
const pageStr = excludeBlockDiv.dataset.page;
|
|
1353
|
+
targetPage = pageStr ? parseInt(pageStr, 10) : currentPage.value;
|
|
1354
|
+
}
|
|
1355
|
+
if (!targetPage) {
|
|
1356
|
+
targetPage = currentPage.value;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
const textLayer = textLayerRefs.get(targetPage);
|
|
1360
|
+
if (!textLayer) return;
|
|
1361
|
+
|
|
1362
|
+
const blockDivs = textLayer.querySelectorAll(".text-block");
|
|
1363
|
+
blockDivs.forEach((div) => {
|
|
1364
|
+
const el = div as HTMLElement;
|
|
1365
|
+
// 如果是指定的文本块,跳过
|
|
1366
|
+
if (excludeBlockDiv && el === excludeBlockDiv) {
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// 检查是否有批注
|
|
1371
|
+
const bboxStr = el.dataset.bbox;
|
|
1372
|
+
if (!bboxStr) return;
|
|
1373
|
+
|
|
1374
|
+
try {
|
|
1375
|
+
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1376
|
+
const pageStr = el.dataset.page;
|
|
1377
|
+
const pageNum = pageStr ? parseInt(pageStr, 10) : undefined;
|
|
1378
|
+
const existingAnnotation = getAnnotationForBlock(bbox, pageNum);
|
|
1379
|
+
|
|
1380
|
+
if (existingAnnotation) {
|
|
1381
|
+
// 如果有批注,恢复批注样式(不使用 hover 样式)
|
|
1382
|
+
el.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1383
|
+
el.style.setProperty("border", "1px solid rgba(255, 193, 7, 0.7)", "important");
|
|
1384
|
+
el.style.setProperty("border-radius", "3px", "important");
|
|
1385
|
+
el.style.setProperty("padding", "1px 3px", "important");
|
|
1386
|
+
el.style.setProperty("box-shadow", "0 1px 2px rgba(255, 193, 7, 0.25)", "important");
|
|
1387
|
+
} else {
|
|
1388
|
+
// 如果没有批注,清除所有高亮样式
|
|
1389
|
+
el.style.backgroundColor = "transparent";
|
|
1390
|
+
el.style.border = "none";
|
|
1391
|
+
el.style.borderRadius = "2px";
|
|
1392
|
+
el.style.padding = "0";
|
|
1393
|
+
el.style.boxShadow = "none";
|
|
1394
|
+
el.style.removeProperty("background-color");
|
|
1395
|
+
el.style.removeProperty("border");
|
|
1396
|
+
el.style.removeProperty("box-shadow");
|
|
1397
|
+
}
|
|
1398
|
+
} catch (error) {
|
|
1399
|
+
// 如果解析失败,清除所有高亮样式
|
|
1400
|
+
el.style.backgroundColor = "transparent";
|
|
1401
|
+
el.style.border = "none";
|
|
1402
|
+
el.style.borderRadius = "2px";
|
|
1403
|
+
el.style.padding = "0";
|
|
1404
|
+
el.style.boxShadow = "none";
|
|
1405
|
+
el.style.removeProperty("background-color");
|
|
1406
|
+
el.style.removeProperty("border");
|
|
1407
|
+
el.style.removeProperty("box-shadow");
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1306
1412
|
/**
|
|
1307
1413
|
* 恢复文本块样式(根据是否有批注)
|
|
1308
1414
|
*/
|
|
@@ -1358,19 +1464,29 @@ const restoreBlockStyle = (blockDiv: HTMLElement) => {
|
|
|
1358
1464
|
computedAfter: window.getComputedStyle(blockDiv).backgroundColor,
|
|
1359
1465
|
});
|
|
1360
1466
|
} else {
|
|
1361
|
-
//
|
|
1467
|
+
// 如果没有批注,恢复透明背景(清除所有高亮相关样式)
|
|
1362
1468
|
blockDiv.style.backgroundColor = "transparent";
|
|
1363
1469
|
blockDiv.style.border = "none";
|
|
1470
|
+
blockDiv.style.borderRadius = "2px"; // 恢复默认值
|
|
1364
1471
|
blockDiv.style.padding = "0";
|
|
1365
1472
|
blockDiv.style.boxShadow = "none";
|
|
1473
|
+
// 清除可能残留的样式属性
|
|
1474
|
+
blockDiv.style.removeProperty("background-color");
|
|
1475
|
+
blockDiv.style.removeProperty("border");
|
|
1476
|
+
blockDiv.style.removeProperty("box-shadow");
|
|
1366
1477
|
}
|
|
1367
1478
|
} catch (error) {
|
|
1368
1479
|
console.error("restoreBlockStyle 错误:", error);
|
|
1369
|
-
//
|
|
1480
|
+
// 如果解析失败,恢复透明背景(清除所有高亮相关样式)
|
|
1370
1481
|
blockDiv.style.backgroundColor = "transparent";
|
|
1371
1482
|
blockDiv.style.border = "none";
|
|
1483
|
+
blockDiv.style.borderRadius = "2px"; // 恢复默认值
|
|
1372
1484
|
blockDiv.style.padding = "0";
|
|
1373
1485
|
blockDiv.style.boxShadow = "none";
|
|
1486
|
+
// 清除可能残留的样式属性
|
|
1487
|
+
blockDiv.style.removeProperty("background-color");
|
|
1488
|
+
blockDiv.style.removeProperty("border");
|
|
1489
|
+
blockDiv.style.removeProperty("box-shadow");
|
|
1374
1490
|
}
|
|
1375
1491
|
};
|
|
1376
1492
|
|
|
@@ -1408,19 +1524,32 @@ const hideAnnotationButton = () => {
|
|
|
1408
1524
|
activeBlockDiv.value.style.boxShadow =
|
|
1409
1525
|
"0 1px 2px rgba(255, 193, 7, 0.25)";
|
|
1410
1526
|
} else {
|
|
1411
|
-
//
|
|
1527
|
+
// 如果没有批注,恢复透明背景(清除所有高亮相关样式)
|
|
1412
1528
|
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1413
1529
|
activeBlockDiv.value.style.border = "none";
|
|
1530
|
+
activeBlockDiv.value.style.borderRadius = "2px"; // 恢复默认值
|
|
1414
1531
|
activeBlockDiv.value.style.padding = "0";
|
|
1415
1532
|
activeBlockDiv.value.style.boxShadow = "none";
|
|
1533
|
+
// 清除可能残留的样式属性
|
|
1534
|
+
activeBlockDiv.value.style.removeProperty("background-color");
|
|
1535
|
+
activeBlockDiv.value.style.removeProperty("border");
|
|
1536
|
+
activeBlockDiv.value.style.removeProperty("box-shadow");
|
|
1416
1537
|
}
|
|
1417
1538
|
} catch (error) {
|
|
1539
|
+
// 如果解析失败,恢复透明背景(清除所有高亮相关样式)
|
|
1418
1540
|
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1419
1541
|
activeBlockDiv.value.style.border = "none";
|
|
1542
|
+
activeBlockDiv.value.style.borderRadius = "2px"; // 恢复默认值
|
|
1420
1543
|
activeBlockDiv.value.style.padding = "0";
|
|
1421
1544
|
activeBlockDiv.value.style.boxShadow = "none";
|
|
1545
|
+
// 清除可能残留的样式属性
|
|
1546
|
+
activeBlockDiv.value.style.removeProperty("background-color");
|
|
1547
|
+
activeBlockDiv.value.style.removeProperty("border");
|
|
1548
|
+
activeBlockDiv.value.style.removeProperty("box-shadow");
|
|
1422
1549
|
}
|
|
1423
1550
|
}
|
|
1551
|
+
// 清除高亮标志
|
|
1552
|
+
isHighlighted.value = false;
|
|
1424
1553
|
activeBlockDiv.value = null;
|
|
1425
1554
|
}
|
|
1426
1555
|
}, 300);
|
|
@@ -1741,10 +1870,55 @@ const handleScroll = (e: Event) => {
|
|
|
1741
1870
|
// 记录上次滚动位置,用于判断滚动方向
|
|
1742
1871
|
let lastScrollTop = 0;
|
|
1743
1872
|
|
|
1873
|
+
/**
|
|
1874
|
+
* 根据滚动位置更新当前页码
|
|
1875
|
+
* 通过找到距离视口中心最近的页面来确定当前页
|
|
1876
|
+
* 使用 getBoundingClientRect() 获取实际渲染位置(考虑了 transform: scale)
|
|
1877
|
+
*/
|
|
1878
|
+
const updateCurrentPageFromScroll = () => {
|
|
1879
|
+
if (!containerRef.value) return;
|
|
1880
|
+
|
|
1881
|
+
const container = containerRef.value;
|
|
1882
|
+
const containerRect = container.getBoundingClientRect();
|
|
1883
|
+
const containerTop = containerRect.top;
|
|
1884
|
+
const containerHeight = container.clientHeight;
|
|
1885
|
+
const containerCenter = containerTop + containerHeight / 2;
|
|
1886
|
+
|
|
1887
|
+
// 遍历所有页面,找到距离视口中心最近的页面
|
|
1888
|
+
let closestPage = 1;
|
|
1889
|
+
let closestDistance = Infinity;
|
|
1890
|
+
|
|
1891
|
+
for (let pageNum = 1; pageNum <= totalPages.value; pageNum++) {
|
|
1892
|
+
const pageElement = container.querySelector(
|
|
1893
|
+
`[data-page-number="${pageNum}"]`
|
|
1894
|
+
) as HTMLElement;
|
|
1895
|
+
|
|
1896
|
+
if (pageElement) {
|
|
1897
|
+
// 使用 getBoundingClientRect() 获取实际渲染位置(考虑了 transform: scale)
|
|
1898
|
+
// 而不是使用 offsetTop(不考虑 transform)
|
|
1899
|
+
const pageRect = pageElement.getBoundingClientRect();
|
|
1900
|
+
const pageTop = pageRect.top;
|
|
1901
|
+
const pageHeight = pageRect.height;
|
|
1902
|
+
const pageCenter = pageTop + pageHeight / 2;
|
|
1903
|
+
const distance = Math.abs(pageCenter - containerCenter);
|
|
1904
|
+
|
|
1905
|
+
if (distance < closestDistance) {
|
|
1906
|
+
closestDistance = distance;
|
|
1907
|
+
closestPage = pageNum;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
// 只有当页码真正改变时才更新
|
|
1913
|
+
if (closestPage !== currentPage.value) {
|
|
1914
|
+
currentPage.value = closestPage;
|
|
1915
|
+
emit("page-change", closestPage, totalPages.value);
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1744
1919
|
/**
|
|
1745
1920
|
* 处理滚动翻页(通过滚动位置判断当前页)
|
|
1746
|
-
*
|
|
1747
|
-
* 向上滑动:当视口底部到达上一页的底部时,切换到上一页
|
|
1921
|
+
* 使用视口中心最近的页面来确定当前页,更准确
|
|
1748
1922
|
*/
|
|
1749
1923
|
const handleScrollPaging = (e: Event) => {
|
|
1750
1924
|
const container = e.target as HTMLElement;
|
|
@@ -1767,73 +1941,8 @@ const handleScrollPaging = (e: Event) => {
|
|
|
1767
1941
|
return;
|
|
1768
1942
|
}
|
|
1769
1943
|
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
const scrollBottom = scrollTop + clientHeight;
|
|
1773
|
-
|
|
1774
|
-
// 判断滚动方向
|
|
1775
|
-
const isScrollingDown = scrollTop > lastScrollTop;
|
|
1776
|
-
lastScrollTop = scrollTop;
|
|
1777
|
-
|
|
1778
|
-
// 获取当前页的元素
|
|
1779
|
-
const currentPageElement = container.querySelector(
|
|
1780
|
-
`[data-page-number="${currentPage.value}"]`
|
|
1781
|
-
) as HTMLElement;
|
|
1782
|
-
|
|
1783
|
-
if (!currentPageElement) return;
|
|
1784
|
-
|
|
1785
|
-
const currentPageTop = currentPageElement.offsetTop;
|
|
1786
|
-
const currentPageHeight = currentPageElement.offsetHeight;
|
|
1787
|
-
const currentPageBottom = currentPageTop + currentPageHeight;
|
|
1788
|
-
|
|
1789
|
-
let newPage = currentPage.value;
|
|
1790
|
-
|
|
1791
|
-
if (isScrollingDown) {
|
|
1792
|
-
// 向下滑动:当视口顶部到达或超过下一页的顶部时,切换到下一页
|
|
1793
|
-
if (currentPage.value < totalPages.value) {
|
|
1794
|
-
const nextPageElement = container.querySelector(
|
|
1795
|
-
`[data-page-number="${currentPage.value + 1}"]`
|
|
1796
|
-
) as HTMLElement;
|
|
1797
|
-
|
|
1798
|
-
if (nextPageElement) {
|
|
1799
|
-
const nextPageTop = nextPageElement.offsetTop;
|
|
1800
|
-
// 当视口顶部到达或超过下一页的顶部时,切换到下一页
|
|
1801
|
-
if (scrollTop >= nextPageTop) {
|
|
1802
|
-
newPage = currentPage.value + 1;
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
} else {
|
|
1807
|
-
// 向上滑动:当视口底部到达或超过上一页的底部时,切换到上一页
|
|
1808
|
-
if (currentPage.value > 1) {
|
|
1809
|
-
const prevPageElement = container.querySelector(
|
|
1810
|
-
`[data-page-number="${currentPage.value - 1}"]`
|
|
1811
|
-
) as HTMLElement;
|
|
1812
|
-
|
|
1813
|
-
if (prevPageElement) {
|
|
1814
|
-
const prevPageTop = prevPageElement.offsetTop;
|
|
1815
|
-
const prevPageHeight = prevPageElement.offsetHeight;
|
|
1816
|
-
const prevPageBottom = prevPageTop + prevPageHeight;
|
|
1817
|
-
|
|
1818
|
-
// 当视口底部到达或超过上一页的底部时,切换到上一页
|
|
1819
|
-
if (scrollBottom <= prevPageBottom) {
|
|
1820
|
-
newPage = currentPage.value - 1;
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
// 如果页码发生变化,更新当前页
|
|
1827
|
-
if (newPage !== currentPage.value) {
|
|
1828
|
-
isScrollPaging.value = true;
|
|
1829
|
-
currentPage.value = newPage;
|
|
1830
|
-
emit("page-change", newPage, totalPages.value);
|
|
1831
|
-
|
|
1832
|
-
// 延迟解锁,确保页面切换完成
|
|
1833
|
-
setTimeout(() => {
|
|
1834
|
-
isScrollPaging.value = false;
|
|
1835
|
-
}, 100);
|
|
1836
|
-
}
|
|
1944
|
+
// 更新当前页码(基于视口中心最近的页面)
|
|
1945
|
+
updateCurrentPageFromScroll();
|
|
1837
1946
|
}, 100);
|
|
1838
1947
|
};
|
|
1839
1948
|
|