@koi-br/ocr-web-sdk 1.0.41 → 1.0.43
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/README.md +0 -117
- package/dist/{index-B1W65EDF.mjs → index-D0vHyxHV.mjs} +9103 -9463
- package/dist/{index-OEZMBeqD.js → index-DUXpkXKO.js} +89 -89
- package/dist/index.cjs.js +2 -2
- package/dist/index.esm.js +9 -11
- package/dist/src/index.d.ts +0 -1
- package/dist/{tiff.min-D_NeHpVc.mjs → tiff.min-BzP8AM0k.mjs} +1 -1
- package/dist/{tiff.min-qFtp92t-.js → tiff.min-CTWTP_jY.js} +1 -1
- package/package.json +1 -1
- package/preview/ImagePreview.vue +88 -570
- package/dist/src/composables/useSyncScroll.d.ts +0 -111
package/preview/ImagePreview.vue
CHANGED
|
@@ -116,31 +116,13 @@
|
|
|
116
116
|
@mouseleave="stopPan"
|
|
117
117
|
@scroll="handleScroll"
|
|
118
118
|
>
|
|
119
|
-
|
|
120
|
-
<div v-if="isCalculatingAutoFit && autoFitWidth" class="auto-fit-loading">
|
|
121
|
-
<div class="loading-spinner"></div>
|
|
122
|
-
<div class="loading-text">加载中...</div>
|
|
123
|
-
<!-- 调试信息(开发环境显示) -->
|
|
124
|
-
<div style="font-size: 12px; color: #999; margin-top: 8px; text-align: center;">
|
|
125
|
-
isImageReady: {{ isImageReady }},
|
|
126
|
-
isCalculatingAutoFit: {{ isCalculatingAutoFit }},
|
|
127
|
-
autoFitWidth: {{ autoFitWidth }},
|
|
128
|
-
scale: {{ scale }}
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
|
|
132
|
-
<div
|
|
133
|
-
class="image-wrapper-container"
|
|
134
|
-
:style="containerStyle"
|
|
135
|
-
:class="{ 'image-hidden': !isImageReady && autoFitWidth }"
|
|
136
|
-
>
|
|
119
|
+
<div class="image-wrapper-container" :style="containerStyle">
|
|
137
120
|
<!-- 渲染所有图片页面 -->
|
|
138
121
|
<div
|
|
139
122
|
v-for="(imageUrl, pageIndex) in imageUrls"
|
|
140
123
|
:key="pageIndex"
|
|
141
124
|
:data-page-number="pageIndex + 1"
|
|
142
125
|
class="image-page-container"
|
|
143
|
-
:style="getPageContainerStyle(pageIndex + 1)"
|
|
144
126
|
>
|
|
145
127
|
<div
|
|
146
128
|
class="image-wrapper"
|
|
@@ -407,48 +389,12 @@ const position = ref({ x: 0, y: 0 });
|
|
|
407
389
|
const isPanning = ref(false);
|
|
408
390
|
const lastPosition = ref({ x: 0, y: 0 });
|
|
409
391
|
const initialAutoFitScale = ref<number | null>(null); // 记录初始自适应缩放比例
|
|
410
|
-
const isCalculatingAutoFit = ref(false); // 标记是否正在计算自适应宽度(显示 loading)
|
|
411
|
-
const isImageReady = ref(false); // 标记图片是否已准备好显示(自适应宽度计算完成)
|
|
412
|
-
|
|
413
|
-
// 监听图片URL变化,当有新图片时立即隐藏(等待自适应宽度计算)
|
|
414
|
-
watch(
|
|
415
|
-
() => imageUrls.value,
|
|
416
|
-
(newUrls, oldUrls) => {
|
|
417
|
-
console.log('[ImagePreview] imageUrls changed:', {
|
|
418
|
-
newUrls: newUrls?.length,
|
|
419
|
-
oldUrls: oldUrls?.length,
|
|
420
|
-
autoFitWidth: props.autoFitWidth,
|
|
421
|
-
isImageReady: isImageReady.value,
|
|
422
|
-
isCalculatingAutoFit: isCalculatingAutoFit.value,
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
// 如果有新的图片URL,且启用自适应宽度,立即隐藏图片
|
|
426
|
-
if (newUrls && newUrls.length > 0 && props.autoFitWidth) {
|
|
427
|
-
console.log('[ImagePreview] 设置图片隐藏,等待自适应宽度计算');
|
|
428
|
-
isImageReady.value = false;
|
|
429
|
-
isCalculatingAutoFit.value = true;
|
|
430
|
-
} else if (!props.autoFitWidth) {
|
|
431
|
-
// 如果没有启用自适应宽度,立即显示
|
|
432
|
-
console.log('[ImagePreview] 未启用自适应宽度,立即显示图片');
|
|
433
|
-
isImageReady.value = true;
|
|
434
|
-
isCalculatingAutoFit.value = false;
|
|
435
|
-
}
|
|
436
|
-
},
|
|
437
|
-
{ immediate: true }
|
|
438
|
-
);
|
|
439
392
|
const isUserZooming = ref(false); // 标记用户是否主动缩放
|
|
440
393
|
|
|
441
394
|
// 滚动翻页相关
|
|
442
395
|
let scrollPagingTimer: any = null;
|
|
443
396
|
const isScrollPaging = ref(false); // 标记是否正在进行滚动翻页
|
|
444
397
|
|
|
445
|
-
// ResizeObserver 相关
|
|
446
|
-
let resizeObserver: ResizeObserver | null = null;
|
|
447
|
-
let resizeTimer: any = null; // handleContainerResize 内部的定时器
|
|
448
|
-
let resizeDebounceTimer: any = null; // ResizeObserver 的防抖定时器
|
|
449
|
-
let isResizing = false; // 标记是否正在处理 resize
|
|
450
|
-
let lastContainerWidth = 0; // 记录上次容器宽度,用于判断是否真的变化了
|
|
451
|
-
|
|
452
398
|
// 图片和容器引用
|
|
453
399
|
const containerRef = ref<HTMLElement>();
|
|
454
400
|
const imageRefs = new Map<number, HTMLImageElement>();
|
|
@@ -568,35 +514,6 @@ const getPageBlocksData = (pageNo: number) => {
|
|
|
568
514
|
return props.blocksData.filter((block) => block.pageNo === pageNo);
|
|
569
515
|
};
|
|
570
516
|
|
|
571
|
-
// 计算指定页面缩放后的尺寸(考虑旋转)
|
|
572
|
-
const getPageScaledSize = (pageNo: number) => {
|
|
573
|
-
const pageSize = imageSizes.get(pageNo);
|
|
574
|
-
if (!pageSize || pageSize.width === 0 || pageSize.height === 0) {
|
|
575
|
-
return { width: 0, height: 0 };
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const { width, height } = pageSize;
|
|
579
|
-
const scaledWidth = width * scale.value;
|
|
580
|
-
const scaledHeight = height * scale.value;
|
|
581
|
-
|
|
582
|
-
// 如果旋转了 90 度或 270 度,交换宽高
|
|
583
|
-
const normalizedRotation = ((rotation.value % 360) + 360) % 360;
|
|
584
|
-
const isRotated = normalizedRotation === 90 || normalizedRotation === 270;
|
|
585
|
-
|
|
586
|
-
return {
|
|
587
|
-
width: isRotated ? scaledHeight : scaledWidth,
|
|
588
|
-
height: isRotated ? scaledWidth : scaledHeight,
|
|
589
|
-
};
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
// 获取页面容器的样式(响应式,会根据缩放和旋转自动更新)
|
|
593
|
-
const getPageContainerStyle = (pageNo: number) => {
|
|
594
|
-
const scaledSize = getPageScaledSize(pageNo);
|
|
595
|
-
return {
|
|
596
|
-
height: scaledSize.height > 0 ? `${scaledSize.height}px` : 'auto',
|
|
597
|
-
};
|
|
598
|
-
};
|
|
599
|
-
|
|
600
517
|
// 隐藏定时器
|
|
601
518
|
let hideTimer: any = null;
|
|
602
519
|
|
|
@@ -890,18 +807,10 @@ const switchToPage = (page: number) => {
|
|
|
890
807
|
`[data-page-number="${page}"]`
|
|
891
808
|
) as HTMLElement;
|
|
892
809
|
if (pageElement) {
|
|
893
|
-
// 标记这是翻页滚动,不应该被同步滚动干扰
|
|
894
|
-
containerRef.value.dataset.pageScrolling = 'true';
|
|
895
810
|
pageElement.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
896
811
|
// 更新 lastScrollTop,确保滚动方向判断准确
|
|
897
812
|
nextTick(() => {
|
|
898
813
|
lastScrollTop = containerRef.value?.scrollTop || 0;
|
|
899
|
-
// 延迟清除标记,确保滚动完成
|
|
900
|
-
setTimeout(() => {
|
|
901
|
-
if (containerRef.value) {
|
|
902
|
-
delete containerRef.value.dataset.pageScrolling;
|
|
903
|
-
}
|
|
904
|
-
}, 500); // scrollIntoView 的 smooth 动画通常需要 300-500ms
|
|
905
814
|
});
|
|
906
815
|
}
|
|
907
816
|
}
|
|
@@ -937,25 +846,13 @@ const original = () => {
|
|
|
937
846
|
|
|
938
847
|
// 计算自适应宽度的缩放比例
|
|
939
848
|
const calculateAutoFitScale = () => {
|
|
940
|
-
console.log('[ImagePreview] calculateAutoFitScale 开始:', {
|
|
941
|
-
autoFitWidth: props.autoFitWidth,
|
|
942
|
-
hasContainerRef: !!containerRef.value,
|
|
943
|
-
containerRect: containerRef.value?.getBoundingClientRect(),
|
|
944
|
-
firstPageSize: imageSizes.get(1),
|
|
945
|
-
rotation: rotation.value,
|
|
946
|
-
minScale: props.minScale,
|
|
947
|
-
maxScale: props.maxScale,
|
|
948
|
-
});
|
|
949
|
-
|
|
950
849
|
if (!props.autoFitWidth || !containerRef.value) {
|
|
951
|
-
console.log('[ImagePreview] calculateAutoFitScale 返回 1 (条件不满足)');
|
|
952
850
|
return 1;
|
|
953
851
|
}
|
|
954
852
|
|
|
955
853
|
// 使用第一页的图片尺寸作为基准(所有页面使用相同的缩放比例)
|
|
956
854
|
const firstPageSize = imageSizes.get(1);
|
|
957
855
|
if (!firstPageSize || firstPageSize.width === 0) {
|
|
958
|
-
console.log('[ImagePreview] calculateAutoFitScale 返回 1 (第一页尺寸无效)', firstPageSize);
|
|
959
856
|
return 1;
|
|
960
857
|
}
|
|
961
858
|
|
|
@@ -965,7 +862,6 @@ const calculateAutoFitScale = () => {
|
|
|
965
862
|
const containerWidth = containerRect.width - 4;
|
|
966
863
|
|
|
967
864
|
if (containerWidth <= 0) {
|
|
968
|
-
console.log('[ImagePreview] calculateAutoFitScale 返回 1 (容器宽度无效)', containerWidth);
|
|
969
865
|
return 1;
|
|
970
866
|
}
|
|
971
867
|
|
|
@@ -976,38 +872,20 @@ const calculateAutoFitScale = () => {
|
|
|
976
872
|
const imageWidth = isRotated ? firstPageSize.height : firstPageSize.width;
|
|
977
873
|
|
|
978
874
|
if (imageWidth <= 0) {
|
|
979
|
-
console.log('[ImagePreview] calculateAutoFitScale 返回 1 (图片宽度无效)', imageWidth);
|
|
980
875
|
return 1;
|
|
981
876
|
}
|
|
982
877
|
|
|
983
878
|
// 计算缩放比例,使图片宽度完全适应容器宽度
|
|
984
879
|
const calculatedScale = containerWidth / imageWidth;
|
|
985
|
-
const finalScale = Math.max(props.minScale, Math.min(props.maxScale, calculatedScale));
|
|
986
|
-
|
|
987
|
-
console.log('[ImagePreview] calculateAutoFitScale 计算结果:', {
|
|
988
|
-
containerWidth,
|
|
989
|
-
imageWidth,
|
|
990
|
-
calculatedScale,
|
|
991
|
-
finalScale,
|
|
992
|
-
});
|
|
993
880
|
|
|
994
881
|
// 确保缩放比例在允许的范围内
|
|
995
|
-
return
|
|
882
|
+
return Math.max(props.minScale, Math.min(props.maxScale, calculatedScale));
|
|
996
883
|
};
|
|
997
884
|
|
|
998
885
|
// 图片加载完成处理
|
|
999
886
|
const onImageLoad = (event: Event, pageNum: number) => {
|
|
1000
887
|
const img = event.target as HTMLImageElement;
|
|
1001
888
|
|
|
1002
|
-
console.log('[ImagePreview] 图片加载完成:', {
|
|
1003
|
-
pageNum,
|
|
1004
|
-
naturalWidth: img.naturalWidth,
|
|
1005
|
-
naturalHeight: img.naturalHeight,
|
|
1006
|
-
autoFitWidth: props.autoFitWidth,
|
|
1007
|
-
isImageReady: isImageReady.value,
|
|
1008
|
-
isCalculatingAutoFit: isCalculatingAutoFit.value,
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
889
|
// 存储该页的图片尺寸
|
|
1012
890
|
imageSizes.set(pageNum, {
|
|
1013
891
|
width: img.naturalWidth,
|
|
@@ -1016,69 +894,22 @@ const onImageLoad = (event: Event, pageNum: number) => {
|
|
|
1016
894
|
|
|
1017
895
|
// 如果是第一页且启用自适应宽度,计算并设置初始缩放比例
|
|
1018
896
|
if (pageNum === 1 && props.autoFitWidth) {
|
|
1019
|
-
console.log('[ImagePreview] 第一页加载完成,开始计算自适应宽度');
|
|
1020
897
|
// 重置用户缩放标记
|
|
1021
898
|
isUserZooming.value = false;
|
|
1022
|
-
|
|
1023
|
-
// 确保图片是隐藏的(watch 已经设置了,这里再次确认)
|
|
1024
|
-
if (!isImageReady.value) {
|
|
1025
|
-
isCalculatingAutoFit.value = true;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
// 设置超时保护,防止一直显示 loading(最多等待 3 秒)
|
|
1029
|
-
const timeoutId = setTimeout(() => {
|
|
1030
|
-
console.warn('自适应宽度计算超时,强制显示图片');
|
|
1031
|
-
isCalculatingAutoFit.value = false;
|
|
1032
|
-
isImageReady.value = true;
|
|
1033
|
-
}, 3000);
|
|
1034
899
|
|
|
1035
900
|
// 使用双重 nextTick 确保容器尺寸已确定
|
|
1036
901
|
nextTick(() => {
|
|
1037
902
|
nextTick(() => {
|
|
1038
903
|
// 添加小延迟确保容器完全渲染
|
|
1039
904
|
setTimeout(() => {
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
autoScale,
|
|
1045
|
-
containerRef: !!containerRef.value,
|
|
1046
|
-
containerWidth: containerRef.value?.getBoundingClientRect()?.width,
|
|
1047
|
-
firstPageSize: imageSizes.get(1),
|
|
1048
|
-
});
|
|
1049
|
-
|
|
1050
|
-
if (autoScale > 0) {
|
|
1051
|
-
scale.value = autoScale;
|
|
1052
|
-
initialAutoFitScale.value = autoScale; // 记录初始自适应缩放比例
|
|
1053
|
-
// 记录当前容器宽度,用于后续 resize 检查
|
|
1054
|
-
if (containerRef.value) {
|
|
1055
|
-
lastContainerWidth = containerRef.value.getBoundingClientRect().width;
|
|
1056
|
-
}
|
|
1057
|
-
console.log('[ImagePreview] onImageLoad: 缩放比例已设置:', autoScale);
|
|
1058
|
-
} else {
|
|
1059
|
-
console.warn('[ImagePreview] onImageLoad: 计算出的缩放比例无效:', autoScale);
|
|
1060
|
-
}
|
|
1061
|
-
} catch (error) {
|
|
1062
|
-
console.error('[ImagePreview] onImageLoad: 计算自适应宽度失败:', error);
|
|
1063
|
-
} finally {
|
|
1064
|
-
// 清除超时保护
|
|
1065
|
-
clearTimeout(timeoutId);
|
|
1066
|
-
console.log('[ImagePreview] onImageLoad: 更新状态: isCalculatingAutoFit = false, isImageReady = true');
|
|
1067
|
-
// 无论计算结果如何,都要更新状态,避免一直显示 loading
|
|
1068
|
-
isCalculatingAutoFit.value = false;
|
|
1069
|
-
// 使用 requestAnimationFrame 确保在下一帧显示,避免闪烁
|
|
1070
|
-
requestAnimationFrame(() => {
|
|
1071
|
-
isImageReady.value = true;
|
|
1072
|
-
console.log('[ImagePreview] onImageLoad: 图片已准备好显示');
|
|
1073
|
-
});
|
|
905
|
+
const autoScale = calculateAutoFitScale();
|
|
906
|
+
if (autoScale !== 1 && autoScale > 0) {
|
|
907
|
+
scale.value = autoScale;
|
|
908
|
+
initialAutoFitScale.value = autoScale; // 记录初始自适应缩放比例
|
|
1074
909
|
}
|
|
1075
910
|
}, 100); // 增加延迟,确保所有图片都已加载
|
|
1076
911
|
});
|
|
1077
912
|
});
|
|
1078
|
-
} else if (!props.autoFitWidth) {
|
|
1079
|
-
// 如果没有启用自适应宽度,立即显示图片
|
|
1080
|
-
isImageReady.value = true;
|
|
1081
|
-
isCalculatingAutoFit.value = false;
|
|
1082
913
|
}
|
|
1083
914
|
|
|
1084
915
|
// 如果第一页已经加载完成,且当前页不是第一页,也应用自适应宽度
|
|
@@ -1290,13 +1121,9 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1290
1121
|
hideTimer = null;
|
|
1291
1122
|
}
|
|
1292
1123
|
|
|
1293
|
-
//
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
// 清除跳转高亮标志(如果之前是通过跳转高亮的)
|
|
1298
|
-
if (isHighlighted.value) {
|
|
1299
|
-
isHighlighted.value = false;
|
|
1124
|
+
// 如果有之前激活的文本块,先恢复其样式
|
|
1125
|
+
if (activeBlockDiv.value && activeBlockDiv.value !== blockDiv) {
|
|
1126
|
+
restoreBlockStyle(activeBlockDiv.value);
|
|
1300
1127
|
}
|
|
1301
1128
|
|
|
1302
1129
|
// 设置当前文本块为激活状态
|
|
@@ -1312,10 +1139,7 @@ const renderTextLayer = (pageNum?: number) => {
|
|
|
1312
1139
|
blockDiv.style.setProperty("padding", "1px 3px", "important");
|
|
1313
1140
|
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");
|
|
1314
1141
|
} else {
|
|
1315
|
-
//
|
|
1316
|
-
blockDiv.style.removeProperty("background-color");
|
|
1317
|
-
blockDiv.style.removeProperty("border");
|
|
1318
|
-
blockDiv.style.removeProperty("box-shadow");
|
|
1142
|
+
// 如果没有批注,使用 hover 样式(与 PdfPreview 保持一致)
|
|
1319
1143
|
blockDiv.style.backgroundColor =
|
|
1320
1144
|
"var(--s-color-brand-primary-transparent-3, rgba(0, 102, 255, .15))";
|
|
1321
1145
|
blockDiv.style.boxShadow = "0 0 0 2px rgba(30, 144, 255, 0.6)";
|
|
@@ -1479,75 +1303,6 @@ const showAnnotationButtonForBlock = (
|
|
|
1479
1303
|
}
|
|
1480
1304
|
};
|
|
1481
1305
|
|
|
1482
|
-
/**
|
|
1483
|
-
* 清除指定页面所有文本块的高亮样式(除了指定的文本块)
|
|
1484
|
-
* @param excludeBlockDiv 要排除的文本块(不清除它的样式)
|
|
1485
|
-
* @param pageNum 要清除的页码,如果不提供则从 excludeBlockDiv 中获取
|
|
1486
|
-
*/
|
|
1487
|
-
const clearAllHighlights = (excludeBlockDiv?: HTMLElement, pageNum?: number) => {
|
|
1488
|
-
// 确定要清除的页码
|
|
1489
|
-
let targetPage = pageNum;
|
|
1490
|
-
if (!targetPage && excludeBlockDiv) {
|
|
1491
|
-
const pageStr = excludeBlockDiv.dataset.page;
|
|
1492
|
-
targetPage = pageStr ? parseInt(pageStr, 10) : currentPage.value;
|
|
1493
|
-
}
|
|
1494
|
-
if (!targetPage) {
|
|
1495
|
-
targetPage = currentPage.value;
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
const textLayer = textLayerRefs.get(targetPage);
|
|
1499
|
-
if (!textLayer) return;
|
|
1500
|
-
|
|
1501
|
-
const blockDivs = textLayer.querySelectorAll(".text-block");
|
|
1502
|
-
blockDivs.forEach((div) => {
|
|
1503
|
-
const el = div as HTMLElement;
|
|
1504
|
-
// 如果是指定的文本块,跳过
|
|
1505
|
-
if (excludeBlockDiv && el === excludeBlockDiv) {
|
|
1506
|
-
return;
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
// 检查是否有批注
|
|
1510
|
-
const bboxStr = el.dataset.bbox;
|
|
1511
|
-
if (!bboxStr) return;
|
|
1512
|
-
|
|
1513
|
-
try {
|
|
1514
|
-
const bbox = JSON.parse(bboxStr) as [number, number, number, number];
|
|
1515
|
-
const pageStr = el.dataset.page;
|
|
1516
|
-
const pageNum = pageStr ? parseInt(pageStr, 10) : undefined;
|
|
1517
|
-
const existingAnnotation = getAnnotationForBlock(bbox, pageNum);
|
|
1518
|
-
|
|
1519
|
-
if (existingAnnotation) {
|
|
1520
|
-
// 如果有批注,恢复批注样式(不使用 hover 样式)
|
|
1521
|
-
el.style.setProperty("background-color", "rgba(255, 243, 205, 0.5)", "important");
|
|
1522
|
-
el.style.setProperty("border", "1px solid rgba(255, 193, 7, 0.7)", "important");
|
|
1523
|
-
el.style.setProperty("border-radius", "3px", "important");
|
|
1524
|
-
el.style.setProperty("padding", "1px 3px", "important");
|
|
1525
|
-
el.style.setProperty("box-shadow", "0 1px 2px rgba(255, 193, 7, 0.25)", "important");
|
|
1526
|
-
} else {
|
|
1527
|
-
// 如果没有批注,清除所有高亮样式
|
|
1528
|
-
el.style.backgroundColor = "transparent";
|
|
1529
|
-
el.style.border = "none";
|
|
1530
|
-
el.style.borderRadius = "2px";
|
|
1531
|
-
el.style.padding = "0";
|
|
1532
|
-
el.style.boxShadow = "none";
|
|
1533
|
-
el.style.removeProperty("background-color");
|
|
1534
|
-
el.style.removeProperty("border");
|
|
1535
|
-
el.style.removeProperty("box-shadow");
|
|
1536
|
-
}
|
|
1537
|
-
} catch (error) {
|
|
1538
|
-
// 如果解析失败,清除所有高亮样式
|
|
1539
|
-
el.style.backgroundColor = "transparent";
|
|
1540
|
-
el.style.border = "none";
|
|
1541
|
-
el.style.borderRadius = "2px";
|
|
1542
|
-
el.style.padding = "0";
|
|
1543
|
-
el.style.boxShadow = "none";
|
|
1544
|
-
el.style.removeProperty("background-color");
|
|
1545
|
-
el.style.removeProperty("border");
|
|
1546
|
-
el.style.removeProperty("box-shadow");
|
|
1547
|
-
}
|
|
1548
|
-
});
|
|
1549
|
-
};
|
|
1550
|
-
|
|
1551
1306
|
/**
|
|
1552
1307
|
* 恢复文本块样式(根据是否有批注)
|
|
1553
1308
|
*/
|
|
@@ -1603,29 +1358,19 @@ const restoreBlockStyle = (blockDiv: HTMLElement) => {
|
|
|
1603
1358
|
computedAfter: window.getComputedStyle(blockDiv).backgroundColor,
|
|
1604
1359
|
});
|
|
1605
1360
|
} else {
|
|
1606
|
-
//
|
|
1361
|
+
// 如果没有批注,恢复透明背景
|
|
1607
1362
|
blockDiv.style.backgroundColor = "transparent";
|
|
1608
1363
|
blockDiv.style.border = "none";
|
|
1609
|
-
blockDiv.style.borderRadius = "2px"; // 恢复默认值
|
|
1610
1364
|
blockDiv.style.padding = "0";
|
|
1611
1365
|
blockDiv.style.boxShadow = "none";
|
|
1612
|
-
// 清除可能残留的样式属性
|
|
1613
|
-
blockDiv.style.removeProperty("background-color");
|
|
1614
|
-
blockDiv.style.removeProperty("border");
|
|
1615
|
-
blockDiv.style.removeProperty("box-shadow");
|
|
1616
1366
|
}
|
|
1617
1367
|
} catch (error) {
|
|
1618
1368
|
console.error("restoreBlockStyle 错误:", error);
|
|
1619
|
-
//
|
|
1369
|
+
// 如果解析失败,恢复透明背景
|
|
1620
1370
|
blockDiv.style.backgroundColor = "transparent";
|
|
1621
1371
|
blockDiv.style.border = "none";
|
|
1622
|
-
blockDiv.style.borderRadius = "2px"; // 恢复默认值
|
|
1623
1372
|
blockDiv.style.padding = "0";
|
|
1624
1373
|
blockDiv.style.boxShadow = "none";
|
|
1625
|
-
// 清除可能残留的样式属性
|
|
1626
|
-
blockDiv.style.removeProperty("background-color");
|
|
1627
|
-
blockDiv.style.removeProperty("border");
|
|
1628
|
-
blockDiv.style.removeProperty("box-shadow");
|
|
1629
1374
|
}
|
|
1630
1375
|
};
|
|
1631
1376
|
|
|
@@ -1663,32 +1408,19 @@ const hideAnnotationButton = () => {
|
|
|
1663
1408
|
activeBlockDiv.value.style.boxShadow =
|
|
1664
1409
|
"0 1px 2px rgba(255, 193, 7, 0.25)";
|
|
1665
1410
|
} else {
|
|
1666
|
-
//
|
|
1411
|
+
// 如果没有批注,恢复透明背景
|
|
1667
1412
|
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1668
1413
|
activeBlockDiv.value.style.border = "none";
|
|
1669
|
-
activeBlockDiv.value.style.borderRadius = "2px"; // 恢复默认值
|
|
1670
1414
|
activeBlockDiv.value.style.padding = "0";
|
|
1671
1415
|
activeBlockDiv.value.style.boxShadow = "none";
|
|
1672
|
-
// 清除可能残留的样式属性
|
|
1673
|
-
activeBlockDiv.value.style.removeProperty("background-color");
|
|
1674
|
-
activeBlockDiv.value.style.removeProperty("border");
|
|
1675
|
-
activeBlockDiv.value.style.removeProperty("box-shadow");
|
|
1676
1416
|
}
|
|
1677
1417
|
} catch (error) {
|
|
1678
|
-
// 如果解析失败,恢复透明背景(清除所有高亮相关样式)
|
|
1679
1418
|
activeBlockDiv.value.style.backgroundColor = "transparent";
|
|
1680
1419
|
activeBlockDiv.value.style.border = "none";
|
|
1681
|
-
activeBlockDiv.value.style.borderRadius = "2px"; // 恢复默认值
|
|
1682
1420
|
activeBlockDiv.value.style.padding = "0";
|
|
1683
1421
|
activeBlockDiv.value.style.boxShadow = "none";
|
|
1684
|
-
// 清除可能残留的样式属性
|
|
1685
|
-
activeBlockDiv.value.style.removeProperty("background-color");
|
|
1686
|
-
activeBlockDiv.value.style.removeProperty("border");
|
|
1687
|
-
activeBlockDiv.value.style.removeProperty("box-shadow");
|
|
1688
1422
|
}
|
|
1689
1423
|
}
|
|
1690
|
-
// 清除高亮标志
|
|
1691
|
-
isHighlighted.value = false;
|
|
1692
1424
|
activeBlockDiv.value = null;
|
|
1693
1425
|
}
|
|
1694
1426
|
}, 300);
|
|
@@ -1975,33 +1707,6 @@ const saveAnnotation = () => {
|
|
|
1975
1707
|
* 处理滚动事件(隐藏批注按钮和文本块高亮,以及滚动翻页)
|
|
1976
1708
|
*/
|
|
1977
1709
|
const handleScroll = (e: Event) => {
|
|
1978
|
-
const container = e.target as HTMLElement;
|
|
1979
|
-
|
|
1980
|
-
// 检查是否是同步滚动触发的
|
|
1981
|
-
const isSyncing = container?.dataset?.syncingScroll === 'true';
|
|
1982
|
-
if (isSyncing) {
|
|
1983
|
-
// 即使是被同步滚动触发的,也应该立即更新页码(但不触发翻页动画)
|
|
1984
|
-
// 使用 requestAnimationFrame 确保在浏览器渲染后立即更新
|
|
1985
|
-
if (
|
|
1986
|
-
props.enableScrollPaging &&
|
|
1987
|
-
totalPages.value > 1 &&
|
|
1988
|
-
!isScrollPaging.value
|
|
1989
|
-
) {
|
|
1990
|
-
// 立即更新页码,不等待防抖
|
|
1991
|
-
requestAnimationFrame(() => {
|
|
1992
|
-
updateCurrentPageFromScroll();
|
|
1993
|
-
// 清除标记(在页码更新后)
|
|
1994
|
-
delete container.dataset.syncingScroll;
|
|
1995
|
-
});
|
|
1996
|
-
} else {
|
|
1997
|
-
// 如果没有启用滚动翻页,立即清除标记
|
|
1998
|
-
delete container.dataset.syncingScroll;
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
// 同步滚动时,不执行其他逻辑(如隐藏批注按钮等)
|
|
2002
|
-
return;
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
1710
|
if (hideTimer) {
|
|
2006
1711
|
clearTimeout(hideTimer);
|
|
2007
1712
|
}
|
|
@@ -2023,74 +1728,23 @@ const handleScroll = (e: Event) => {
|
|
|
2023
1728
|
scale.value = initialAutoFitScale.value;
|
|
2024
1729
|
}
|
|
2025
1730
|
|
|
2026
|
-
//
|
|
1731
|
+
// 滚动翻页功能
|
|
2027
1732
|
if (
|
|
2028
1733
|
props.enableScrollPaging &&
|
|
2029
1734
|
totalPages.value > 1 &&
|
|
2030
1735
|
!isScrollPaging.value
|
|
2031
1736
|
) {
|
|
2032
|
-
// 立即更新页码(使用 requestAnimationFrame 确保在渲染后更新)
|
|
2033
|
-
requestAnimationFrame(() => {
|
|
2034
|
-
updateCurrentPageFromScroll();
|
|
2035
|
-
});
|
|
2036
|
-
// 同时使用防抖处理其他逻辑(如事件触发)
|
|
2037
1737
|
handleScrollPaging(e);
|
|
2038
1738
|
}
|
|
2039
1739
|
};
|
|
2040
1740
|
|
|
2041
|
-
|
|
2042
1741
|
// 记录上次滚动位置,用于判断滚动方向
|
|
2043
1742
|
let lastScrollTop = 0;
|
|
2044
1743
|
|
|
2045
|
-
/**
|
|
2046
|
-
* 根据滚动位置更新当前页码
|
|
2047
|
-
* 通过找到距离视口中心最近的页面来确定当前页
|
|
2048
|
-
* 使用 getBoundingClientRect() 获取实际渲染位置(考虑了 transform: scale)
|
|
2049
|
-
*/
|
|
2050
|
-
const updateCurrentPageFromScroll = () => {
|
|
2051
|
-
if (!containerRef.value) return;
|
|
2052
|
-
|
|
2053
|
-
const container = containerRef.value;
|
|
2054
|
-
const containerRect = container.getBoundingClientRect();
|
|
2055
|
-
const containerTop = containerRect.top;
|
|
2056
|
-
const containerHeight = container.clientHeight;
|
|
2057
|
-
const containerCenter = containerTop + containerHeight / 2;
|
|
2058
|
-
|
|
2059
|
-
// 遍历所有页面,找到距离视口中心最近的页面
|
|
2060
|
-
let closestPage = 1;
|
|
2061
|
-
let closestDistance = Infinity;
|
|
2062
|
-
|
|
2063
|
-
for (let pageNum = 1; pageNum <= totalPages.value; pageNum++) {
|
|
2064
|
-
const pageElement = container.querySelector(
|
|
2065
|
-
`[data-page-number="${pageNum}"]`
|
|
2066
|
-
) as HTMLElement;
|
|
2067
|
-
|
|
2068
|
-
if (pageElement) {
|
|
2069
|
-
// 使用 getBoundingClientRect() 获取实际渲染位置(考虑了 transform: scale)
|
|
2070
|
-
// 而不是使用 offsetTop(不考虑 transform)
|
|
2071
|
-
const pageRect = pageElement.getBoundingClientRect();
|
|
2072
|
-
const pageTop = pageRect.top;
|
|
2073
|
-
const pageHeight = pageRect.height;
|
|
2074
|
-
const pageCenter = pageTop + pageHeight / 2;
|
|
2075
|
-
const distance = Math.abs(pageCenter - containerCenter);
|
|
2076
|
-
|
|
2077
|
-
if (distance < closestDistance) {
|
|
2078
|
-
closestDistance = distance;
|
|
2079
|
-
closestPage = pageNum;
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
// 只有当页码真正改变时才更新
|
|
2085
|
-
if (closestPage !== currentPage.value) {
|
|
2086
|
-
currentPage.value = closestPage;
|
|
2087
|
-
emit("page-change", closestPage, totalPages.value);
|
|
2088
|
-
}
|
|
2089
|
-
};
|
|
2090
|
-
|
|
2091
1744
|
/**
|
|
2092
1745
|
* 处理滚动翻页(通过滚动位置判断当前页)
|
|
2093
|
-
*
|
|
1746
|
+
* 向下滑动:当视口顶部到达下一页的顶部时,切换到下一页
|
|
1747
|
+
* 向上滑动:当视口底部到达上一页的底部时,切换到上一页
|
|
2094
1748
|
*/
|
|
2095
1749
|
const handleScrollPaging = (e: Event) => {
|
|
2096
1750
|
const container = e.target as HTMLElement;
|
|
@@ -2107,16 +1761,80 @@ const handleScrollPaging = (e: Event) => {
|
|
|
2107
1761
|
}
|
|
2108
1762
|
|
|
2109
1763
|
// 使用防抖,避免频繁触发
|
|
2110
|
-
// 减少延迟到50ms,提高响应速度
|
|
2111
1764
|
scrollPagingTimer = setTimeout(() => {
|
|
2112
1765
|
// 再次检查是否正在翻页
|
|
2113
1766
|
if (isScrollPaging.value) {
|
|
2114
1767
|
return;
|
|
2115
1768
|
}
|
|
2116
1769
|
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
1770
|
+
const scrollTop = container.scrollTop;
|
|
1771
|
+
const clientHeight = container.clientHeight;
|
|
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
|
+
}
|
|
1837
|
+
}, 100);
|
|
2120
1838
|
};
|
|
2121
1839
|
|
|
2122
1840
|
/**
|
|
@@ -2376,99 +2094,6 @@ watch(
|
|
|
2376
2094
|
{ deep: true }
|
|
2377
2095
|
);
|
|
2378
2096
|
|
|
2379
|
-
/**
|
|
2380
|
-
* 处理容器尺寸变化,重新计算自适应缩放比例
|
|
2381
|
-
*/
|
|
2382
|
-
const handleContainerResize = () => {
|
|
2383
|
-
// 如果禁用了自适应宽度,或者用户主动缩放过,不自动调整
|
|
2384
|
-
if (!props.autoFitWidth || isUserZooming.value) {
|
|
2385
|
-
return;
|
|
2386
|
-
}
|
|
2387
|
-
|
|
2388
|
-
// 如果第一页图片还没有加载完成,不处理
|
|
2389
|
-
const firstPageSize = imageSizes.get(1);
|
|
2390
|
-
if (!firstPageSize || firstPageSize.width === 0) {
|
|
2391
|
-
return;
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
// 如果正在计算中或正在处理 resize,跳过(避免重复计算)
|
|
2395
|
-
if (isCalculatingAutoFit.value || isResizing) {
|
|
2396
|
-
return;
|
|
2397
|
-
}
|
|
2398
|
-
|
|
2399
|
-
// 检查容器尺寸是否真的变化了(避免无意义的重复计算)
|
|
2400
|
-
if (containerRef.value) {
|
|
2401
|
-
const currentWidth = containerRef.value.getBoundingClientRect().width;
|
|
2402
|
-
// 如果宽度变化小于 5px,认为是渲染抖动,不处理
|
|
2403
|
-
if (Math.abs(currentWidth - lastContainerWidth) < 5) {
|
|
2404
|
-
return;
|
|
2405
|
-
}
|
|
2406
|
-
lastContainerWidth = currentWidth;
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
console.log('[ImagePreview] handleContainerResize 被调用:', {
|
|
2410
|
-
autoFitWidth: props.autoFitWidth,
|
|
2411
|
-
isUserZooming: isUserZooming.value,
|
|
2412
|
-
isImageReady: isImageReady.value,
|
|
2413
|
-
isCalculatingAutoFit: isCalculatingAutoFit.value,
|
|
2414
|
-
hasFirstPageSize: !!imageSizes.get(1),
|
|
2415
|
-
containerWidth: containerRef.value?.getBoundingClientRect()?.width,
|
|
2416
|
-
});
|
|
2417
|
-
|
|
2418
|
-
// 标记正在处理 resize
|
|
2419
|
-
isResizing = true;
|
|
2420
|
-
|
|
2421
|
-
// 清除之前的定时器
|
|
2422
|
-
if (resizeTimer) {
|
|
2423
|
-
clearTimeout(resizeTimer);
|
|
2424
|
-
resizeTimer = null;
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
// 隐藏图片,显示 loading
|
|
2428
|
-
console.log('[ImagePreview] handleContainerResize: 开始重新计算');
|
|
2429
|
-
isImageReady.value = false;
|
|
2430
|
-
isCalculatingAutoFit.value = true;
|
|
2431
|
-
|
|
2432
|
-
// 立即计算并应用新的缩放比例,避免过渡期间露出底色
|
|
2433
|
-
// 使用 requestAnimationFrame 确保在浏览器重绘前更新
|
|
2434
|
-
requestAnimationFrame(() => {
|
|
2435
|
-
try {
|
|
2436
|
-
console.log('[ImagePreview] handleContainerResize: 开始计算自适应宽度...');
|
|
2437
|
-
const newScale = calculateAutoFitScale();
|
|
2438
|
-
console.log('[ImagePreview] handleContainerResize: 计算结果:', newScale);
|
|
2439
|
-
|
|
2440
|
-
if (newScale > 0) {
|
|
2441
|
-
// 即使变化很小也立即更新,确保过渡期间图片始终填满容器
|
|
2442
|
-
scale.value = newScale;
|
|
2443
|
-
initialAutoFitScale.value = newScale;
|
|
2444
|
-
}
|
|
2445
|
-
} catch (error) {
|
|
2446
|
-
console.error('[ImagePreview] handleContainerResize: 计算失败:', error);
|
|
2447
|
-
}
|
|
2448
|
-
|
|
2449
|
-
// 在过渡动画完成后再次检查,确保最终状态正确(处理过渡动画期间的连续变化)
|
|
2450
|
-
resizeTimer = setTimeout(() => {
|
|
2451
|
-
try {
|
|
2452
|
-
const finalScale = calculateAutoFitScale();
|
|
2453
|
-
if (finalScale > 0 && Math.abs(finalScale - scale.value) > 0.01) {
|
|
2454
|
-
scale.value = finalScale;
|
|
2455
|
-
initialAutoFitScale.value = finalScale;
|
|
2456
|
-
}
|
|
2457
|
-
} catch (error) {
|
|
2458
|
-
console.error('[ImagePreview] handleContainerResize: 最终计算失败:', error);
|
|
2459
|
-
} finally {
|
|
2460
|
-
// 计算完成后,显示图片并隐藏 loading
|
|
2461
|
-
console.log('[ImagePreview] handleContainerResize: 更新状态完成');
|
|
2462
|
-
isCalculatingAutoFit.value = false;
|
|
2463
|
-
isResizing = false; // 重置标记
|
|
2464
|
-
requestAnimationFrame(() => {
|
|
2465
|
-
isImageReady.value = true;
|
|
2466
|
-
});
|
|
2467
|
-
}
|
|
2468
|
-
}, 350); // 350ms 延迟,略大于过渡动画时间(300ms),确保过渡完成后稳定
|
|
2469
|
-
});
|
|
2470
|
-
};
|
|
2471
|
-
|
|
2472
2097
|
/**
|
|
2473
2098
|
* 组件挂载时的初始化
|
|
2474
2099
|
*/
|
|
@@ -2478,68 +2103,19 @@ onMounted(() => {
|
|
|
2478
2103
|
if (firstPageImage && firstPageImage.complete && props.autoFitWidth) {
|
|
2479
2104
|
const firstPageSize = imageSizes.get(1);
|
|
2480
2105
|
if (firstPageSize && firstPageSize.width > 0) {
|
|
2481
|
-
// 隐藏图片,显示 loading
|
|
2482
|
-
isImageReady.value = false;
|
|
2483
|
-
isCalculatingAutoFit.value = true;
|
|
2484
|
-
|
|
2485
|
-
// 设置超时保护,防止一直显示 loading(最多等待 3 秒)
|
|
2486
|
-
const timeoutId = setTimeout(() => {
|
|
2487
|
-
console.warn('自适应宽度计算超时,强制显示图片');
|
|
2488
|
-
isCalculatingAutoFit.value = false;
|
|
2489
|
-
isImageReady.value = true;
|
|
2490
|
-
}, 3000);
|
|
2491
|
-
|
|
2492
2106
|
nextTick(() => {
|
|
2493
2107
|
nextTick(() => {
|
|
2494
2108
|
setTimeout(() => {
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
initialAutoFitScale.value = autoScale;
|
|
2500
|
-
}
|
|
2501
|
-
} catch (error) {
|
|
2502
|
-
console.warn('计算自适应宽度失败:', error);
|
|
2503
|
-
} finally {
|
|
2504
|
-
// 清除超时保护
|
|
2505
|
-
clearTimeout(timeoutId);
|
|
2506
|
-
// 无论计算结果如何,都要更新状态,避免一直显示 loading
|
|
2507
|
-
isCalculatingAutoFit.value = false;
|
|
2508
|
-
requestAnimationFrame(() => {
|
|
2509
|
-
isImageReady.value = true;
|
|
2510
|
-
});
|
|
2109
|
+
const autoScale = calculateAutoFitScale();
|
|
2110
|
+
if (autoScale !== 1 && autoScale > 0) {
|
|
2111
|
+
scale.value = autoScale;
|
|
2112
|
+
initialAutoFitScale.value = autoScale;
|
|
2511
2113
|
}
|
|
2512
2114
|
}, 100);
|
|
2513
2115
|
});
|
|
2514
2116
|
});
|
|
2515
2117
|
}
|
|
2516
|
-
} else if (!props.autoFitWidth) {
|
|
2517
|
-
// 如果没有启用自适应宽度,立即显示图片
|
|
2518
|
-
isImageReady.value = true;
|
|
2519
2118
|
}
|
|
2520
|
-
|
|
2521
|
-
// 监听容器尺寸变化(用于响应外部收起/展开操作)
|
|
2522
|
-
nextTick(() => {
|
|
2523
|
-
if (containerRef.value && typeof ResizeObserver !== 'undefined') {
|
|
2524
|
-
resizeObserver = new ResizeObserver((entries) => {
|
|
2525
|
-
// 使用防抖,避免频繁触发
|
|
2526
|
-
if (resizeDebounceTimer) {
|
|
2527
|
-
clearTimeout(resizeDebounceTimer);
|
|
2528
|
-
}
|
|
2529
|
-
resizeDebounceTimer = setTimeout(() => {
|
|
2530
|
-
// 使用 entries 参数立即获取新尺寸,避免延迟
|
|
2531
|
-
for (const entry of entries) {
|
|
2532
|
-
// 立即响应尺寸变化,避免过渡期间露出底色
|
|
2533
|
-
handleContainerResize();
|
|
2534
|
-
}
|
|
2535
|
-
}, 100); // 100ms 防抖,避免频繁触发
|
|
2536
|
-
});
|
|
2537
|
-
resizeObserver.observe(containerRef.value);
|
|
2538
|
-
} else {
|
|
2539
|
-
// 降级方案:监听窗口大小变化
|
|
2540
|
-
window.addEventListener('resize', handleContainerResize);
|
|
2541
|
-
}
|
|
2542
|
-
});
|
|
2543
2119
|
});
|
|
2544
2120
|
|
|
2545
2121
|
/**
|
|
@@ -2554,20 +2130,6 @@ onBeforeUnmount(() => {
|
|
|
2554
2130
|
clearTimeout(scrollPagingTimer);
|
|
2555
2131
|
scrollPagingTimer = null;
|
|
2556
2132
|
}
|
|
2557
|
-
if (resizeTimer) {
|
|
2558
|
-
clearTimeout(resizeTimer);
|
|
2559
|
-
resizeTimer = null;
|
|
2560
|
-
}
|
|
2561
|
-
if (resizeDebounceTimer) {
|
|
2562
|
-
clearTimeout(resizeDebounceTimer);
|
|
2563
|
-
resizeDebounceTimer = null;
|
|
2564
|
-
}
|
|
2565
|
-
if (resizeObserver) {
|
|
2566
|
-
resizeObserver.disconnect();
|
|
2567
|
-
resizeObserver = null;
|
|
2568
|
-
}
|
|
2569
|
-
// 移除窗口 resize 监听器(降级方案)
|
|
2570
|
-
window.removeEventListener('resize', handleContainerResize);
|
|
2571
2133
|
});
|
|
2572
2134
|
|
|
2573
2135
|
defineExpose({
|
|
@@ -2576,7 +2138,6 @@ defineExpose({
|
|
|
2576
2138
|
goToPage: switchToPage, // 暴露翻页方法给父组件
|
|
2577
2139
|
getCurrentPage: () => currentPage.value, // 获取当前页码
|
|
2578
2140
|
getTotalPages: () => totalPages.value, // 获取总页数
|
|
2579
|
-
getContainer: () => containerRef.value, // 暴露容器引用,用于同步滚动
|
|
2580
2141
|
});
|
|
2581
2142
|
</script>
|
|
2582
2143
|
|
|
@@ -2595,47 +2156,6 @@ defineExpose({
|
|
|
2595
2156
|
overflow: auto;
|
|
2596
2157
|
}
|
|
2597
2158
|
|
|
2598
|
-
// 自适应宽度计算 Loading
|
|
2599
|
-
.auto-fit-loading {
|
|
2600
|
-
position: absolute;
|
|
2601
|
-
top: 50%;
|
|
2602
|
-
left: 50%;
|
|
2603
|
-
transform: translate(-50%, -50%);
|
|
2604
|
-
z-index: 1000;
|
|
2605
|
-
display: flex;
|
|
2606
|
-
flex-direction: column;
|
|
2607
|
-
align-items: center;
|
|
2608
|
-
gap: 12px;
|
|
2609
|
-
padding: 20px 30px;
|
|
2610
|
-
}
|
|
2611
|
-
|
|
2612
|
-
.loading-spinner {
|
|
2613
|
-
width: 32px;
|
|
2614
|
-
height: 32px;
|
|
2615
|
-
border: 3px solid #e5e7eb;
|
|
2616
|
-
border-top-color: #1890ff;
|
|
2617
|
-
border-radius: 50%;
|
|
2618
|
-
animation: spin 0.8s linear infinite;
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
@keyframes spin {
|
|
2622
|
-
to {
|
|
2623
|
-
transform: rotate(360deg);
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2626
|
-
|
|
2627
|
-
.loading-text {
|
|
2628
|
-
font-size: 14px;
|
|
2629
|
-
color: #666;
|
|
2630
|
-
white-space: nowrap;
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
// 图片隐藏状态(自适应宽度计算完成前)
|
|
2634
|
-
// 使用 display: none 确保图片完全不可见,不会在加载过程中显示
|
|
2635
|
-
.image-wrapper-container.image-hidden {
|
|
2636
|
-
display: none !important;
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
2159
|
// 页码信息样式
|
|
2640
2160
|
.page-info {
|
|
2641
2161
|
display: inline-flex;
|
|
@@ -2688,8 +2208,6 @@ defineExpose({
|
|
|
2688
2208
|
margin: 0;
|
|
2689
2209
|
padding: 0;
|
|
2690
2210
|
line-height: 0;
|
|
2691
|
-
// 添加平滑过渡,避免切换时露出底色
|
|
2692
|
-
transition: transform 0.3s ease;
|
|
2693
2211
|
|
|
2694
2212
|
img {
|
|
2695
2213
|
display: block; // 移除图片底部默认间隙
|