@koi-br/ocr-web-sdk 1.0.31 → 1.0.32
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-Bf47RAC6.mjs} +6605 -6603
- package/dist/{index-BZ8ndzte.js → index-D7aLbyEQ.js} +78 -78
- package/dist/index.cjs.js +2 -2
- package/dist/index.esm.js +2 -2
- package/dist/{tiff.min-BaSKqyGR.mjs → tiff.min-CehJk-e3.mjs} +1 -1
- package/dist/{tiff.min-BJf8TYMB.js → tiff.min-TbRkKSGf.js} +1 -1
- package/package.json +1 -1
- package/preview/ImagePreview.vue +155 -76
package/package.json
CHANGED
package/preview/ImagePreview.vue
CHANGED
|
@@ -1121,9 +1121,13 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1121
1121
|
hideTimer = null;
|
|
1122
1122
|
}
|
|
1123
1123
|
|
|
1124
|
-
//
|
|
1125
|
-
|
|
1126
|
-
|
|
1124
|
+
// 清除当前页面所有文本块的高亮样式(除了当前文本块)
|
|
1125
|
+
// 这样可以防止多个文本块同时高亮的竞态条件
|
|
1126
|
+
clearAllHighlights(blockDiv);
|
|
1127
|
+
|
|
1128
|
+
// 清除跳转高亮标志(如果之前是通过跳转高亮的)
|
|
1129
|
+
if (isHighlighted.value) {
|
|
1130
|
+
isHighlighted.value = false;
|
|
1127
1131
|
}
|
|
1128
1132
|
|
|
1129
1133
|
// 设置当前文本块为激活状态
|
|
@@ -1139,7 +1143,10 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1139
1143
|
blockDiv.style.setProperty("padding", "1px 3px", "important");
|
|
1140
1144
|
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
1145
|
} else {
|
|
1142
|
-
//
|
|
1146
|
+
// 如果没有批注,先清除可能残留的样式,再设置 hover 样式(与 PdfPreview 保持一致)
|
|
1147
|
+
blockDiv.style.removeProperty("background-color");
|
|
1148
|
+
blockDiv.style.removeProperty("border");
|
|
1149
|
+
blockDiv.style.removeProperty("box-shadow");
|
|
1143
1150
|
blockDiv.style.backgroundColor =
|
|
1144
1151
|
"var(--s-color-brand-primary-transparent-3, rgba(0, 102, 255, .15))";
|
|
1145
1152
|
blockDiv.style.boxShadow = "0 0 0 2px rgba(30, 144, 255, 0.6)";
|
|
@@ -1303,6 +1310,75 @@ const showAnnotationButtonForBlock = (
|
|
|
1303
1310
|
}
|
|
1304
1311
|
};
|
|
1305
1312
|
|
|
1313
|
+
/**
|
|
1314
|
+
* 清除指定页面所有文本块的高亮样式(除了指定的文本块)
|
|
1315
|
+
* @param excludeBlockDiv 要排除的文本块(不清除它的样式)
|
|
1316
|
+
* @param pageNum 要清除的页码,如果不提供则从 excludeBlockDiv 中获取
|
|
1317
|
+
*/
|
|
1318
|
+
const clearAllHighlights = (excludeBlockDiv?: HTMLElement, pageNum?: number) => {
|
|
1319
|
+
// 确定要清除的页码
|
|
1320
|
+
let targetPage = pageNum;
|
|
1321
|
+
if (!targetPage && excludeBlockDiv) {
|
|
1322
|
+
const pageStr = excludeBlockDiv.dataset.page;
|
|
1323
|
+
targetPage = pageStr ? parseInt(pageStr, 10) : currentPage.value;
|
|
1324
|
+
}
|
|
1325
|
+
if (!targetPage) {
|
|
1326
|
+
targetPage = currentPage.value;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
const textLayer = textLayerRefs.get(targetPage);
|
|
1330
|
+
if (!textLayer) return;
|
|
1331
|
+
|
|
1332
|
+
const blockDivs = textLayer.querySelectorAll(".text-block");
|
|
1333
|
+
blockDivs.forEach((div) => {
|
|
1334
|
+
const el = div as HTMLElement;
|
|
1335
|
+
// 如果是指定的文本块,跳过
|
|
1336
|
+
if (excludeBlockDiv && el === excludeBlockDiv) {
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// 检查是否有批注
|
|
1341
|
+
const bboxStr = el.dataset.bbox;
|
|
1342
|
+
if (!bboxStr) return;
|
|
1343
|
+
|
|
1344
|
+
try {
|
|
1345
|
+
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1346
|
+
const pageStr = el.dataset.page;
|
|
1347
|
+
const pageNum = pageStr ? parseInt(pageStr, 10) : undefined;
|
|
1348
|
+
const existingAnnotation = getAnnotationForBlock(bbox, pageNum);
|
|
1349
|
+
|
|
1350
|
+
if (existingAnnotation) {
|
|
1351
|
+
// 如果有批注,恢复批注样式(不使用 hover 样式)
|
|
1352
|
+
el.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1353
|
+
el.style.setProperty("border", "1px solid rgba(255, 193, 7, 0.7)", "important");
|
|
1354
|
+
el.style.setProperty("border-radius", "3px", "important");
|
|
1355
|
+
el.style.setProperty("padding", "1px 3px", "important");
|
|
1356
|
+
el.style.setProperty("box-shadow", "0 1px 2px rgba(255, 193, 7, 0.25)", "important");
|
|
1357
|
+
} else {
|
|
1358
|
+
// 如果没有批注,清除所有高亮样式
|
|
1359
|
+
el.style.backgroundColor = "transparent";
|
|
1360
|
+
el.style.border = "none";
|
|
1361
|
+
el.style.borderRadius = "2px";
|
|
1362
|
+
el.style.padding = "0";
|
|
1363
|
+
el.style.boxShadow = "none";
|
|
1364
|
+
el.style.removeProperty("background-color");
|
|
1365
|
+
el.style.removeProperty("border");
|
|
1366
|
+
el.style.removeProperty("box-shadow");
|
|
1367
|
+
}
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
// 如果解析失败,清除所有高亮样式
|
|
1370
|
+
el.style.backgroundColor = "transparent";
|
|
1371
|
+
el.style.border = "none";
|
|
1372
|
+
el.style.borderRadius = "2px";
|
|
1373
|
+
el.style.padding = "0";
|
|
1374
|
+
el.style.boxShadow = "none";
|
|
1375
|
+
el.style.removeProperty("background-color");
|
|
1376
|
+
el.style.removeProperty("border");
|
|
1377
|
+
el.style.removeProperty("box-shadow");
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1306
1382
|
/**
|
|
1307
1383
|
* 恢复文本块样式(根据是否有批注)
|
|
1308
1384
|
*/
|
|
@@ -1358,19 +1434,29 @@ const restoreBlockStyle = (blockDiv: HTMLElement) => {
|
|
|
1358
1434
|
computedAfter: window.getComputedStyle(blockDiv).backgroundColor,
|
|
1359
1435
|
});
|
|
1360
1436
|
} else {
|
|
1361
|
-
//
|
|
1437
|
+
// 如果没有批注,恢复透明背景(清除所有高亮相关样式)
|
|
1362
1438
|
blockDiv.style.backgroundColor = "transparent";
|
|
1363
1439
|
blockDiv.style.border = "none";
|
|
1440
|
+
blockDiv.style.borderRadius = "2px"; // 恢复默认值
|
|
1364
1441
|
blockDiv.style.padding = "0";
|
|
1365
1442
|
blockDiv.style.boxShadow = "none";
|
|
1443
|
+
// 清除可能残留的样式属性
|
|
1444
|
+
blockDiv.style.removeProperty("background-color");
|
|
1445
|
+
blockDiv.style.removeProperty("border");
|
|
1446
|
+
blockDiv.style.removeProperty("box-shadow");
|
|
1366
1447
|
}
|
|
1367
1448
|
} catch (error) {
|
|
1368
1449
|
console.error("restoreBlockStyle 错误:", error);
|
|
1369
|
-
//
|
|
1450
|
+
// 如果解析失败,恢复透明背景(清除所有高亮相关样式)
|
|
1370
1451
|
blockDiv.style.backgroundColor = "transparent";
|
|
1371
1452
|
blockDiv.style.border = "none";
|
|
1453
|
+
blockDiv.style.borderRadius = "2px"; // 恢复默认值
|
|
1372
1454
|
blockDiv.style.padding = "0";
|
|
1373
1455
|
blockDiv.style.boxShadow = "none";
|
|
1456
|
+
// 清除可能残留的样式属性
|
|
1457
|
+
blockDiv.style.removeProperty("background-color");
|
|
1458
|
+
blockDiv.style.removeProperty("border");
|
|
1459
|
+
blockDiv.style.removeProperty("box-shadow");
|
|
1374
1460
|
}
|
|
1375
1461
|
};
|
|
1376
1462
|
|
|
@@ -1408,19 +1494,32 @@ const hideAnnotationButton = () => {
|
|
|
1408
1494
|
activeBlockDiv.value.style.boxShadow =
|
|
1409
1495
|
"0 1px 2px rgba(255, 193, 7, 0.25)";
|
|
1410
1496
|
} else {
|
|
1411
|
-
//
|
|
1497
|
+
// 如果没有批注,恢复透明背景(清除所有高亮相关样式)
|
|
1412
1498
|
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1413
1499
|
activeBlockDiv.value.style.border = "none";
|
|
1500
|
+
activeBlockDiv.value.style.borderRadius = "2px"; // 恢复默认值
|
|
1414
1501
|
activeBlockDiv.value.style.padding = "0";
|
|
1415
1502
|
activeBlockDiv.value.style.boxShadow = "none";
|
|
1503
|
+
// 清除可能残留的样式属性
|
|
1504
|
+
activeBlockDiv.value.style.removeProperty("background-color");
|
|
1505
|
+
activeBlockDiv.value.style.removeProperty("border");
|
|
1506
|
+
activeBlockDiv.value.style.removeProperty("box-shadow");
|
|
1416
1507
|
}
|
|
1417
1508
|
} catch (error) {
|
|
1509
|
+
// 如果解析失败,恢复透明背景(清除所有高亮相关样式)
|
|
1418
1510
|
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1419
1511
|
activeBlockDiv.value.style.border = "none";
|
|
1512
|
+
activeBlockDiv.value.style.borderRadius = "2px"; // 恢复默认值
|
|
1420
1513
|
activeBlockDiv.value.style.padding = "0";
|
|
1421
1514
|
activeBlockDiv.value.style.boxShadow = "none";
|
|
1515
|
+
// 清除可能残留的样式属性
|
|
1516
|
+
activeBlockDiv.value.style.removeProperty("background-color");
|
|
1517
|
+
activeBlockDiv.value.style.removeProperty("border");
|
|
1518
|
+
activeBlockDiv.value.style.removeProperty("box-shadow");
|
|
1422
1519
|
}
|
|
1423
1520
|
}
|
|
1521
|
+
// 清除高亮标志
|
|
1522
|
+
isHighlighted.value = false;
|
|
1424
1523
|
activeBlockDiv.value = null;
|
|
1425
1524
|
}
|
|
1426
1525
|
}, 300);
|
|
@@ -1741,10 +1840,55 @@ const handleScroll = (e: Event) => {
|
|
|
1741
1840
|
// 记录上次滚动位置,用于判断滚动方向
|
|
1742
1841
|
let lastScrollTop = 0;
|
|
1743
1842
|
|
|
1843
|
+
/**
|
|
1844
|
+
* 根据滚动位置更新当前页码
|
|
1845
|
+
* 通过找到距离视口中心最近的页面来确定当前页
|
|
1846
|
+
* 使用 getBoundingClientRect() 获取实际渲染位置(考虑了 transform: scale)
|
|
1847
|
+
*/
|
|
1848
|
+
const updateCurrentPageFromScroll = () => {
|
|
1849
|
+
if (!containerRef.value) return;
|
|
1850
|
+
|
|
1851
|
+
const container = containerRef.value;
|
|
1852
|
+
const containerRect = container.getBoundingClientRect();
|
|
1853
|
+
const containerTop = containerRect.top;
|
|
1854
|
+
const containerHeight = container.clientHeight;
|
|
1855
|
+
const containerCenter = containerTop + containerHeight / 2;
|
|
1856
|
+
|
|
1857
|
+
// 遍历所有页面,找到距离视口中心最近的页面
|
|
1858
|
+
let closestPage = 1;
|
|
1859
|
+
let closestDistance = Infinity;
|
|
1860
|
+
|
|
1861
|
+
for (let pageNum = 1; pageNum <= totalPages.value; pageNum++) {
|
|
1862
|
+
const pageElement = container.querySelector(
|
|
1863
|
+
`[data-page-number="${pageNum}"]`
|
|
1864
|
+
) as HTMLElement;
|
|
1865
|
+
|
|
1866
|
+
if (pageElement) {
|
|
1867
|
+
// 使用 getBoundingClientRect() 获取实际渲染位置(考虑了 transform: scale)
|
|
1868
|
+
// 而不是使用 offsetTop(不考虑 transform)
|
|
1869
|
+
const pageRect = pageElement.getBoundingClientRect();
|
|
1870
|
+
const pageTop = pageRect.top;
|
|
1871
|
+
const pageHeight = pageRect.height;
|
|
1872
|
+
const pageCenter = pageTop + pageHeight / 2;
|
|
1873
|
+
const distance = Math.abs(pageCenter - containerCenter);
|
|
1874
|
+
|
|
1875
|
+
if (distance < closestDistance) {
|
|
1876
|
+
closestDistance = distance;
|
|
1877
|
+
closestPage = pageNum;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// 只有当页码真正改变时才更新
|
|
1883
|
+
if (closestPage !== currentPage.value) {
|
|
1884
|
+
currentPage.value = closestPage;
|
|
1885
|
+
emit("page-change", closestPage, totalPages.value);
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
|
|
1744
1889
|
/**
|
|
1745
1890
|
* 处理滚动翻页(通过滚动位置判断当前页)
|
|
1746
|
-
*
|
|
1747
|
-
* 向上滑动:当视口底部到达上一页的底部时,切换到上一页
|
|
1891
|
+
* 使用视口中心最近的页面来确定当前页,更准确
|
|
1748
1892
|
*/
|
|
1749
1893
|
const handleScrollPaging = (e: Event) => {
|
|
1750
1894
|
const container = e.target as HTMLElement;
|
|
@@ -1767,73 +1911,8 @@ const handleScrollPaging = (e: Event) => {
|
|
|
1767
1911
|
return;
|
|
1768
1912
|
}
|
|
1769
1913
|
|
|
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
|
-
}
|
|
1914
|
+
// 更新当前页码(基于视口中心最近的页面)
|
|
1915
|
+
updateCurrentPageFromScroll();
|
|
1837
1916
|
}, 100);
|
|
1838
1917
|
};
|
|
1839
1918
|
|