feat(PdfViewer): 添加PDF加载超时处理机制
添加超时监控功能,当PDF加载超过设定时间或长时间无进度更新时自动停止加载并显示错误信息
Showing
1 changed file
with
71 additions
and
10 deletions
| 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, | ... | ... |
-
Please register or login to post a comment