|
|
@@ -2,13 +2,18 @@
|
|
|
<div class="mobile-pdf-viewer">
|
|
|
<div v-if="loading" class="loading-container">
|
|
|
<div class="loading-spinner"></div>
|
|
|
- <div class="loading-text">正在加载文档...</div>
|
|
|
+ <div class="loading-text">
|
|
|
+ {{ progress > 0 ? `正在加载文档... ${progress}%` : '正在加载文档...' }}
|
|
|
+ </div>
|
|
|
+ <div v-if="retryCount > 0" class="retry-info">
|
|
|
+ 重试中 ({{ retryCount }}/{{ maxRetries }})
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
- <div v-if="error" class="error-container">
|
|
|
+ <div v-if="error && !loading" class="error-container">
|
|
|
<div class="error-icon">!</div>
|
|
|
<div class="error-text">{{ error }}</div>
|
|
|
- <button class="retry-btn" @click="loadPdf">重试</button>
|
|
|
+ <button class="retry-btn" @click="() => { retryCount = 0; loadPdf(true); }">重试</button>
|
|
|
</div>
|
|
|
|
|
|
<div ref="pdfContainer" class="pdf-container">
|
|
|
@@ -22,11 +27,7 @@ import { ref, onMounted, watch, onUnmounted } from 'vue';
|
|
|
import * as pdfjsLib from 'pdfjs-dist';
|
|
|
|
|
|
// 设置 worker
|
|
|
-// 注意:在 Vite 中,我们需要正确引入 worker
|
|
|
-// 使用 CDN 或者本地构建的 worker
|
|
|
-// 这里尝试使用 import 方式,如果失败则回退到 CDN
|
|
|
try {
|
|
|
- // 尝试动态导入 worker
|
|
|
const workerUrl = new URL('pdfjs-dist/build/pdf.worker.min.js', import.meta.url).href;
|
|
|
pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;
|
|
|
} catch (e) {
|
|
|
@@ -46,14 +47,32 @@ const error = ref(null);
|
|
|
const progress = ref(0);
|
|
|
const pdfContainer = ref(null);
|
|
|
const currentUrl = ref('');
|
|
|
+const isLoading = ref(false); // 防止重复加载的标志
|
|
|
+const retryCount = ref(0); // 重试次数
|
|
|
+const maxRetries = 3; // 最大重试次数
|
|
|
let pdfDoc = null;
|
|
|
|
|
|
-const loadPdf = async () => {
|
|
|
- if (!props.url) return;
|
|
|
- // 如果正在加载当前URL,或者是已加载完成的URL,则跳过
|
|
|
- if (loading.value && props.url === currentUrl.value) return;
|
|
|
+const loadPdf = async (isRetry = false) => {
|
|
|
+ if (!props.url) {
|
|
|
+ console.log('📄 [MobilePdfViewer] 没有URL,跳过加载');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 防止重复加载:如果正在加载相同的URL,直接返回
|
|
|
+ if (isLoading.value && props.url === currentUrl.value) {
|
|
|
+ console.log('📄 [MobilePdfViewer] 正在加载相同URL,跳过重复请求:', props.url);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果URL没有变化且已经加载完成,跳过
|
|
|
+ if (!isRetry && props.url === currentUrl.value && !loading.value && !error.value) {
|
|
|
+ console.log('📄 [MobilePdfViewer] URL未变化且已加载完成,跳过:', props.url);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
+ console.log('📄 [MobilePdfViewer] 开始加载PDF:', props.url);
|
|
|
currentUrl.value = props.url;
|
|
|
+ isLoading.value = true;
|
|
|
loading.value = true;
|
|
|
error.value = null;
|
|
|
progress.value = 0;
|
|
|
@@ -68,46 +87,65 @@ const loadPdf = async () => {
|
|
|
|
|
|
loadingTask.onProgress = (p) => {
|
|
|
if (p.total > 0) {
|
|
|
- progress.value = p.loaded / p.total;
|
|
|
+ progress.value = Math.floor((p.loaded / p.total) * 100);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
pdfDoc = await loadingTask.promise;
|
|
|
- console.log(`PDF 加载成功,共 ${pdfDoc.numPages} 页`);
|
|
|
+ console.log(`📄 [MobilePdfViewer] PDF 加载成功,共 ${pdfDoc.numPages} 页`);
|
|
|
|
|
|
- // 渲染所有页面
|
|
|
- for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) {
|
|
|
- await renderPage(pageNum);
|
|
|
+ // 分块加载策略:先加载第一页快速显示,然后逐页加载剩余页面
|
|
|
+ if (pdfDoc.numPages > 0) {
|
|
|
+ // 第一页优先加载
|
|
|
+ await renderPage(1);
|
|
|
+ progress.value = Math.floor((1 / pdfDoc.numPages) * 100);
|
|
|
+
|
|
|
+ // 加载剩余页面
|
|
|
+ for (let pageNum = 2; pageNum <= pdfDoc.numPages; pageNum++) {
|
|
|
+ await renderPage(pageNum);
|
|
|
+ progress.value = Math.floor((pageNum / pdfDoc.numPages) * 100);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
loading.value = false;
|
|
|
+ isLoading.value = false;
|
|
|
+ retryCount.value = 0; // 重置重试次数
|
|
|
+ console.log('📄 [MobilePdfViewer] PDF 加载完成');
|
|
|
} catch (err) {
|
|
|
- console.error('PDF 加载失败:', err);
|
|
|
- error.value = '文档加载失败,请稍后重试';
|
|
|
- loading.value = false;
|
|
|
+ console.error('📄 [MobilePdfViewer] PDF 加载失败:', err);
|
|
|
+ isLoading.value = false;
|
|
|
+
|
|
|
+ // 错误重试机制
|
|
|
+ if (retryCount.value < maxRetries) {
|
|
|
+ retryCount.value++;
|
|
|
+ console.log(`📄 [MobilePdfViewer] 准备第 ${retryCount.value} 次重试...`);
|
|
|
+ error.value = `加载失败,正在重试 (${retryCount.value}/${maxRetries})...`;
|
|
|
+
|
|
|
+ // 指数退避策略:等待时间随重试次数增加
|
|
|
+ const delay = Math.min(1000 * Math.pow(2, retryCount.value - 1), 5000);
|
|
|
+ setTimeout(() => {
|
|
|
+ loadPdf(true);
|
|
|
+ }, delay);
|
|
|
+ } else {
|
|
|
+ error.value = '文档加载失败,请点击重试按钮';
|
|
|
+ loading.value = false;
|
|
|
+ retryCount.value = 0;
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const renderPage = async (pageNum) => {
|
|
|
- if (!pdfDoc) return; // 确保文档存在
|
|
|
+ if (!pdfDoc) return;
|
|
|
|
|
|
try {
|
|
|
const page = await pdfDoc.getPage(pageNum);
|
|
|
-
|
|
|
- // 计算缩放比例,使页面宽度适应屏幕
|
|
|
- const containerWidth = pdfContainer.value ? pdfContainer.value.clientWidth : window.innerWidth;
|
|
|
- // 默认使用 1.5 倍缩放以获得更好的清晰度,然后通过 CSS 缩小
|
|
|
const viewport = page.getViewport({ scale: 1.5 });
|
|
|
|
|
|
- // 创建 canvas
|
|
|
const canvas = document.createElement('canvas');
|
|
|
const context = canvas.getContext('2d');
|
|
|
|
|
|
- // 设置 canvas 尺寸
|
|
|
canvas.height = viewport.height;
|
|
|
canvas.width = viewport.width;
|
|
|
-
|
|
|
- // 设置 CSS 样式以适应容器宽度
|
|
|
canvas.style.width = '100%';
|
|
|
canvas.style.height = 'auto';
|
|
|
canvas.style.display = 'block';
|
|
|
@@ -118,7 +156,6 @@ const renderPage = async (pageNum) => {
|
|
|
pdfContainer.value.appendChild(canvas);
|
|
|
}
|
|
|
|
|
|
- // 渲染页面
|
|
|
const renderContext = {
|
|
|
canvasContext: context,
|
|
|
viewport: viewport
|
|
|
@@ -126,29 +163,32 @@ const renderPage = async (pageNum) => {
|
|
|
|
|
|
await page.render(renderContext).promise;
|
|
|
} catch (err) {
|
|
|
- console.error(`渲染第 ${pageNum} 页失败:`, err);
|
|
|
+ console.error(`📄 [MobilePdfViewer] 渲染第 ${pageNum} 页失败:`, err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 监听URL变化,只在真正变化时重新加载
|
|
|
watch(() => props.url, (newUrl, oldUrl) => {
|
|
|
- if (newUrl !== oldUrl) {
|
|
|
+ if (newUrl && newUrl !== oldUrl) {
|
|
|
+ console.log('📄 [MobilePdfViewer] URL变化,重新加载:', { oldUrl, newUrl });
|
|
|
+ retryCount.value = 0; // 重置重试次数
|
|
|
loadPdf();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
onMounted(() => {
|
|
|
- // 稍微延迟加载,确保容器已准备好
|
|
|
- setTimeout(() => {
|
|
|
- if (props.url) {
|
|
|
- loadPdf();
|
|
|
- }
|
|
|
- }, 100);
|
|
|
+ // 组件挂载后立即检查并加载
|
|
|
+ if (props.url) {
|
|
|
+ console.log('📄 [MobilePdfViewer] 组件挂载,开始加载');
|
|
|
+ loadPdf();
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
if (pdfDoc) {
|
|
|
pdfDoc.destroy();
|
|
|
}
|
|
|
+ isLoading.value = false;
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
@@ -191,6 +231,13 @@ onUnmounted(() => {
|
|
|
.loading-text {
|
|
|
color: #6b7280;
|
|
|
font-size: 12px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.retry-info {
|
|
|
+ color: #f59e0b;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.error-container {
|