hookehuyr

feat(PdfViewer): 添加PDF加载超时处理机制

添加超时监控功能,当PDF加载超过设定时间或长时间无进度更新时自动停止加载并显示错误信息
1 <!-- 1 <!--
2 * @Date: 2025-01-21 2 * @Date: 2025-01-21
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-10-21 15:20:39 4 + * @LastEditTime: 2025-10-22 09:57:57
5 * @FilePath: /mlaj/src/components/ui/PdfViewer.vue 5 * @FilePath: /mlaj/src/components/ui/PdfViewer.vue
6 * @Description: PDF预览组件 - 使用pdf-vue3库 6 * @Description: PDF预览组件 - 使用pdf-vue3库
7 --> 7 -->
...@@ -112,12 +112,19 @@ const errorMessage = ref(''); ...@@ -112,12 +112,19 @@ const errorMessage = ref('');
112 const zoomLevel = ref(1); // 缩放级别,1为100% 112 const zoomLevel = ref(1); // 缩放级别,1为100%
113 const scrollPosition = ref({ x: 0, y: 0 }); // 记录滚动位置 113 const scrollPosition = ref({ x: 0, y: 0 }); // 记录滚动位置
114 114
115 +// 超时处理相关
116 +const loadingTimeout = ref(30000); // 超时时间,默认30秒
117 +const timeoutTimer = ref(null); // 超时计时器
118 +const lastProgressTime = ref(0); // 最后一次进度更新时间
119 +const progressCheckInterval = ref(null); // 进度检查间隔器
120 +
115 /** 121 /**
116 * 监听show属性变化,控制PDF加载 122 * 监听show属性变化,控制PDF加载
117 */ 123 */
118 watch(() => props.show, (newVal) => { 124 watch(() => props.show, (newVal) => {
119 if (newVal && props.url) { 125 if (newVal && props.url) {
120 // 重置所有状态 126 // 重置所有状态
127 + clearAllTimers(); // 清除之前的计时器
121 loading.value = true; 128 loading.value = true;
122 loadingError.value = false; 129 loadingError.value = false;
123 loadingProgress.value = 0; 130 loadingProgress.value = 0;
...@@ -125,6 +132,7 @@ watch(() => props.show, (newVal) => { ...@@ -125,6 +132,7 @@ watch(() => props.show, (newVal) => {
125 zoomLevel.value = 1; // 重置缩放级别 132 zoomLevel.value = 1; // 重置缩放级别
126 } else if (!newVal) { 133 } else if (!newVal) {
127 // 弹窗关闭时重置状态 134 // 弹窗关闭时重置状态
135 + clearAllTimers(); // 清除计时器
128 loading.value = true; 136 loading.value = true;
129 loadingError.value = false; 137 loadingError.value = false;
130 loadingProgress.value = 0; 138 loadingProgress.value = 0;
...@@ -139,6 +147,7 @@ watch(() => props.show, (newVal) => { ...@@ -139,6 +147,7 @@ watch(() => props.show, (newVal) => {
139 watch(() => props.url, (newUrl) => { 147 watch(() => props.url, (newUrl) => {
140 if (newUrl && props.show) { 148 if (newUrl && props.show) {
141 // URL变化时重置状态 149 // URL变化时重置状态
150 + clearAllTimers(); // 清除之前的计时器
142 loading.value = true; 151 loading.value = true;
143 loadingError.value = false; 152 loadingError.value = false;
144 loadingProgress.value = 0; 153 loadingProgress.value = 0;
...@@ -153,6 +162,8 @@ watch(() => props.url, (newUrl) => { ...@@ -153,6 +162,8 @@ watch(() => props.url, (newUrl) => {
153 */ 162 */
154 const handleProgress = (loadRatio) => { 163 const handleProgress = (loadRatio) => {
155 loadingProgress.value = Math.round(loadRatio); 164 loadingProgress.value = Math.round(loadRatio);
165 + // 更新最后进度时间
166 + lastProgressTime.value = Date.now();
156 emit('onProgress', loadRatio); 167 emit('onProgress', loadRatio);
157 }; 168 };
158 169
...@@ -160,6 +171,8 @@ const handleProgress = (loadRatio) => { ...@@ -160,6 +171,8 @@ const handleProgress = (loadRatio) => {
160 * 处理PDF下载完成 171 * 处理PDF下载完成
161 */ 172 */
162 const handleComplete = () => { 173 const handleComplete = () => {
174 + // 清除所有计时器
175 + clearAllTimers();
163 loading.value = false; 176 loading.value = false;
164 loadingError.value = false; 177 loadingError.value = false;
165 loadingProgress.value = 100; 178 loadingProgress.value = 100;
...@@ -168,9 +181,53 @@ const handleComplete = () => { ...@@ -168,9 +181,53 @@ const handleComplete = () => {
168 }; 181 };
169 182
170 /** 183 /**
171 - * 处理PDF滚动事件 184 + * 清除所有计时器
172 - * @param {number} scrollOffset - 滚动偏移量
173 */ 185 */
186 +const clearAllTimers = () => {
187 + if (timeoutTimer.value) {
188 + clearTimeout(timeoutTimer.value);
189 + timeoutTimer.value = null;
190 + }
191 + if (progressCheckInterval.value) {
192 + clearInterval(progressCheckInterval.value);
193 + progressCheckInterval.value = null;
194 + }
195 +};
196 +
197 +/**
198 + * 启动超时监控
199 + */
200 +const startTimeoutMonitoring = () => {
201 + // 清除之前的计时器
202 + clearAllTimers();
203 +
204 + // 记录开始时间
205 + lastProgressTime.value = Date.now();
206 +
207 + // 启动进度检查间隔器,每5秒检查一次
208 + progressCheckInterval.value = setInterval(() => {
209 + const now = Date.now();
210 + const timeSinceLastProgress = now - lastProgressTime.value;
211 +
212 + // 如果超过设定的超时时间且进度没有变化,则触发超时
213 + if (timeSinceLastProgress > loadingTimeout.value && loading.value) {
214 + clearAllTimers();
215 + loadingError.value = true;
216 + errorMessage.value = 'PDF加载超时,请检查网络连接或文件是否存在';
217 + loading.value = false;
218 + }
219 + }, 5000); // 每5秒检查一次
220 +
221 + // 设置总体超时计时器(作为备用)
222 + timeoutTimer.value = setTimeout(() => {
223 + if (loading.value) {
224 + clearAllTimers();
225 + loadingError.value = true;
226 + errorMessage.value = 'PDF加载超时,请检查网络连接或文件是否存在';
227 + loading.value = false;
228 + }
229 + }, loadingTimeout.value + 5000); // 比进度检查多5秒作为缓冲
230 +};
174 const handleScroll = (scrollOffset) => { 231 const handleScroll = (scrollOffset) => {
175 // 记录当前滚动位置 232 // 记录当前滚动位置
176 const pdfContainer = document.querySelector('.pdf-viewer-container .pdf-content'); 233 const pdfContainer = document.querySelector('.pdf-viewer-container .pdf-content');
...@@ -195,6 +252,9 @@ const handlePageChange = (page) => { ...@@ -195,6 +252,9 @@ const handlePageChange = (page) => {
195 * @param {Object} pdf - PDF文档对象 252 * @param {Object} pdf - PDF文档对象
196 */ 253 */
197 const handlePdfInit = (pdf) => { 254 const handlePdfInit = (pdf) => {
255 + // 启动超时监控
256 + startTimeoutMonitoring();
257 +
198 // PDF初始化完成后的处理 258 // PDF初始化完成后的处理
199 loadingError.value = false; 259 loadingError.value = false;
200 nextTick(() => { 260 nextTick(() => {
...@@ -233,6 +293,7 @@ const handlePdfError = (error) => { ...@@ -233,6 +293,7 @@ const handlePdfError = (error) => {
233 * 重试加载PDF 293 * 重试加载PDF
234 */ 294 */
235 const retryLoad = () => { 295 const retryLoad = () => {
296 + clearAllTimers(); // 清除之前的计时器
236 loadingError.value = false; 297 loadingError.value = false;
237 loading.value = true; 298 loading.value = true;
238 loadingProgress.value = 0; 299 loadingProgress.value = 0;
...@@ -250,17 +311,17 @@ const zoomIn = () => { ...@@ -250,17 +311,17 @@ const zoomIn = () => {
250 if (pdfContainer) { 311 if (pdfContainer) {
251 const centerX = pdfContainer.scrollLeft + pdfContainer.clientWidth / 2; 312 const centerX = pdfContainer.scrollLeft + pdfContainer.clientWidth / 2;
252 const centerY = pdfContainer.scrollTop + pdfContainer.clientHeight / 2; 313 const centerY = pdfContainer.scrollTop + pdfContainer.clientHeight / 2;
253 - 314 +
254 const oldZoom = zoomLevel.value; 315 const oldZoom = zoomLevel.value;
255 zoomLevel.value = Math.min(3, zoomLevel.value + 0.25); 316 zoomLevel.value = Math.min(3, zoomLevel.value + 0.25);
256 const newZoom = zoomLevel.value; 317 const newZoom = zoomLevel.value;
257 - 318 +
258 // 使用nextTick确保DOM更新后再调整滚动位置 319 // 使用nextTick确保DOM更新后再调整滚动位置
259 nextTick(() => { 320 nextTick(() => {
260 const zoomRatio = newZoom / oldZoom; 321 const zoomRatio = newZoom / oldZoom;
261 const newScrollLeft = centerX * zoomRatio - pdfContainer.clientWidth / 2; 322 const newScrollLeft = centerX * zoomRatio - pdfContainer.clientWidth / 2;
262 const newScrollTop = centerY * zoomRatio - pdfContainer.clientHeight / 2; 323 const newScrollTop = centerY * zoomRatio - pdfContainer.clientHeight / 2;
263 - 324 +
264 pdfContainer.scrollTo({ 325 pdfContainer.scrollTo({
265 left: Math.max(0, newScrollLeft), 326 left: Math.max(0, newScrollLeft),
266 top: Math.max(0, newScrollTop), 327 top: Math.max(0, newScrollTop),
...@@ -281,17 +342,17 @@ const zoomOut = () => { ...@@ -281,17 +342,17 @@ const zoomOut = () => {
281 if (pdfContainer) { 342 if (pdfContainer) {
282 const centerX = pdfContainer.scrollLeft + pdfContainer.clientWidth / 2; 343 const centerX = pdfContainer.scrollLeft + pdfContainer.clientWidth / 2;
283 const centerY = pdfContainer.scrollTop + pdfContainer.clientHeight / 2; 344 const centerY = pdfContainer.scrollTop + pdfContainer.clientHeight / 2;
284 - 345 +
285 const oldZoom = zoomLevel.value; 346 const oldZoom = zoomLevel.value;
286 zoomLevel.value = Math.max(0.5, zoomLevel.value - 0.25); 347 zoomLevel.value = Math.max(0.5, zoomLevel.value - 0.25);
287 const newZoom = zoomLevel.value; 348 const newZoom = zoomLevel.value;
288 - 349 +
289 // 使用nextTick确保DOM更新后再调整滚动位置 350 // 使用nextTick确保DOM更新后再调整滚动位置
290 nextTick(() => { 351 nextTick(() => {
291 const zoomRatio = newZoom / oldZoom; 352 const zoomRatio = newZoom / oldZoom;
292 const newScrollLeft = centerX * zoomRatio - pdfContainer.clientWidth / 2; 353 const newScrollLeft = centerX * zoomRatio - pdfContainer.clientWidth / 2;
293 const newScrollTop = centerY * zoomRatio - pdfContainer.clientHeight / 2; 354 const newScrollTop = centerY * zoomRatio - pdfContainer.clientHeight / 2;
294 - 355 +
295 pdfContainer.scrollTo({ 356 pdfContainer.scrollTo({
296 left: Math.max(0, newScrollLeft), 357 left: Math.max(0, newScrollLeft),
297 top: Math.max(0, newScrollTop), 358 top: Math.max(0, newScrollTop),
...@@ -310,7 +371,7 @@ const resetZoom = () => { ...@@ -310,7 +371,7 @@ const resetZoom = () => {
310 if (pdfContainer) { 371 if (pdfContainer) {
311 // 重置到顶部中央位置 372 // 重置到顶部中央位置
312 zoomLevel.value = 1; 373 zoomLevel.value = 1;
313 - 374 +
314 nextTick(() => { 375 nextTick(() => {
315 pdfContainer.scrollTo({ 376 pdfContainer.scrollTo({
316 left: 0, 377 left: 0,
......