feat(router): 新增PDF预览独立页面并优化预览体验
将PDF预览从弹窗改为独立页面,提升大文件加载体验 添加返回课程详情页功能并保持学习资料弹窗状态 优化PDF组件关闭逻辑和错误处理
Showing
4 changed files
with
106 additions
and
25 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-22 09:57:57 | 4 | + * @LastEditTime: 2025-10-22 10:49:55 |
| 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 | --> |
| ... | @@ -11,30 +11,30 @@ | ... | @@ -11,30 +11,30 @@ |
| 11 | <div class="pdf-viewer-container"> | 11 | <div class="pdf-viewer-container"> |
| 12 | <!-- PDF内容区域 --> | 12 | <!-- PDF内容区域 --> |
| 13 | <div class="pdf-content" :class="{ 'pdf-no-select': preventSave }"> | 13 | <div class="pdf-content" :class="{ 'pdf-no-select': preventSave }"> |
| 14 | - <PDF v-if="url && show && !loadingError" | 14 | + <PDF v-if="url && show && !loadingError" |
| 15 | ref="pdfRef" | 15 | ref="pdfRef" |
| 16 | - :src="url" | 16 | + :src="url" |
| 17 | - :showProgress="true" | 17 | + :showProgress="true" |
| 18 | :progressColor="'#1989fa'" | 18 | :progressColor="'#1989fa'" |
| 19 | - :showPageTooltip="true" | 19 | + :showPageTooltip="true" |
| 20 | - :showBackToTopBtn="true" | 20 | + :showBackToTopBtn="true" |
| 21 | :scrollThreshold="300" | 21 | :scrollThreshold="300" |
| 22 | :pdfWidth="`${Math.round(100 * zoomLevel)}%`" | 22 | :pdfWidth="`${Math.round(100 * zoomLevel)}%`" |
| 23 | - :rowGap="8" | 23 | + :rowGap="8" |
| 24 | - :page="1" | 24 | + :page="1" |
| 25 | :cMapUrl="'https://unpkg.com/pdfjs-dist@3.7.107/cmaps/'" | 25 | :cMapUrl="'https://unpkg.com/pdfjs-dist@3.7.107/cmaps/'" |
| 26 | - :withCredentials="false" | 26 | + :withCredentials="false" |
| 27 | - :useSystemFonts="true" | 27 | + :useSystemFonts="true" |
| 28 | - :stopAtErrors="false" | 28 | + :stopAtErrors="false" |
| 29 | :disableFontFace="false" | 29 | :disableFontFace="false" |
| 30 | - :disableRange="false" | 30 | + :disableRange="false" |
| 31 | - :disableStream="false" | 31 | + :disableStream="false" |
| 32 | - :disableAutoFetch="false" | 32 | + :disableAutoFetch="false" |
| 33 | @onProgress="handleProgress" | 33 | @onProgress="handleProgress" |
| 34 | - @onComplete="handleComplete" | 34 | + @onComplete="handleComplete" |
| 35 | - @onScroll="handleScroll" | 35 | + @onScroll="handleScroll" |
| 36 | @onPageChange="handlePageChange" | 36 | @onPageChange="handlePageChange" |
| 37 | - @onPdfInit="handlePdfInit" | 37 | + @onPdfInit="handlePdfInit" |
| 38 | @onError="handlePdfError" /> | 38 | @onError="handlePdfError" /> |
| 39 | 39 | ||
| 40 | <!-- 错误状态显示 --> | 40 | <!-- 错误状态显示 --> |
| ... | @@ -151,7 +151,7 @@ watch(() => props.show, (newVal) => { | ... | @@ -151,7 +151,7 @@ watch(() => props.show, (newVal) => { |
| 151 | } else if (!newVal) { | 151 | } else if (!newVal) { |
| 152 | // 弹窗关闭时重置状态并清理资源 | 152 | // 弹窗关闭时重置状态并清理资源 |
| 153 | clearAllTimers(); // 清除计时器 | 153 | clearAllTimers(); // 清除计时器 |
| 154 | - | 154 | + |
| 155 | // 延迟清理,确保PDF组件有时间完成内部清理 | 155 | // 延迟清理,确保PDF组件有时间完成内部清理 |
| 156 | setTimeout(() => { | 156 | setTimeout(() => { |
| 157 | loading.value = false; | 157 | loading.value = false; |
| ... | @@ -160,7 +160,7 @@ watch(() => props.show, (newVal) => { | ... | @@ -160,7 +160,7 @@ watch(() => props.show, (newVal) => { |
| 160 | errorMessage.value = ''; | 160 | errorMessage.value = ''; |
| 161 | zoomLevel.value = 1; // 重置缩放级别 | 161 | zoomLevel.value = 1; // 重置缩放级别 |
| 162 | scrollPosition.value = { x: 0, y: 0 }; // 重置滚动位置 | 162 | scrollPosition.value = { x: 0, y: 0 }; // 重置滚动位置 |
| 163 | - | 163 | + |
| 164 | // 触发关闭销毁事件,通知父组件清理引用 | 164 | // 触发关闭销毁事件,通知父组件清理引用 |
| 165 | emit('onClose'); | 165 | emit('onClose'); |
| 166 | }, 100); // 100ms延迟,给PDF组件时间清理 | 166 | }, 100); // 100ms延迟,给PDF组件时间清理 |
| ... | @@ -333,7 +333,7 @@ const retryLoad = () => { | ... | @@ -333,7 +333,7 @@ const retryLoad = () => { |
| 333 | const handleClose = () => { | 333 | const handleClose = () => { |
| 334 | // 先清理所有计时器 | 334 | // 先清理所有计时器 |
| 335 | clearAllTimers(); | 335 | clearAllTimers(); |
| 336 | - | 336 | + |
| 337 | // 如果PDF组件存在,尝试清理其内部状态 | 337 | // 如果PDF组件存在,尝试清理其内部状态 |
| 338 | if (pdfRef.value && typeof pdfRef.value.destroy === 'function') { | 338 | if (pdfRef.value && typeof pdfRef.value.destroy === 'function') { |
| 339 | try { | 339 | try { |
| ... | @@ -342,10 +342,10 @@ const handleClose = () => { | ... | @@ -342,10 +342,10 @@ const handleClose = () => { |
| 342 | console.warn('PDF组件销毁时出现警告:', error); | 342 | console.warn('PDF组件销毁时出现警告:', error); |
| 343 | } | 343 | } |
| 344 | } | 344 | } |
| 345 | - | 345 | + |
| 346 | // 使用nextTick确保在DOM更新前完成清理 | 346 | // 使用nextTick确保在DOM更新前完成清理 |
| 347 | nextTick(() => { | 347 | nextTick(() => { |
| 348 | - emit('update:show', false); | 348 | + emit('onClose', true); |
| 349 | }); | 349 | }); |
| 350 | }; | 350 | }; |
| 351 | 351 | ... | ... |
| ... | @@ -226,6 +226,14 @@ export const routes = [ | ... | @@ -226,6 +226,14 @@ export const routes = [ |
| 226 | } | 226 | } |
| 227 | }, | 227 | }, |
| 228 | { | 228 | { |
| 229 | + path: '/pdfPreview', | ||
| 230 | + name: 'PdfPreview', | ||
| 231 | + component: () => import('@/views/study/PdfPreviewPage.vue'), | ||
| 232 | + meta: { | ||
| 233 | + title: 'PDF预览', | ||
| 234 | + } | ||
| 235 | + }, | ||
| 236 | + { | ||
| 229 | path: '/profile/studyCourse/:id', | 237 | path: '/profile/studyCourse/:id', |
| 230 | component: () => import('@/views/profile/StudyCoursePage.vue'), | 238 | component: () => import('@/views/profile/StudyCoursePage.vue'), |
| 231 | meta: { | 239 | meta: { | ... | ... |
src/views/study/PdfPreviewPage.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-10-22 10:45:51 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-10-22 10:54:10 | ||
| 5 | + * @FilePath: /mlaj/src/views/study/PdfPreviewPage.vue | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <div class="pdf-preview-page"> | ||
| 10 | + <PdfViewer :show="true" :url="pdfUrl" :title="pdfTitle" @onClose="handleClose" /> | ||
| 11 | + </div> | ||
| 12 | +</template> | ||
| 13 | + | ||
| 14 | +<script setup> | ||
| 15 | +import { computed } from 'vue' | ||
| 16 | +import { useRoute, useRouter } from 'vue-router' | ||
| 17 | +import PdfViewer from '@/components/ui/PdfViewer.vue' | ||
| 18 | + | ||
| 19 | +const route = useRoute() | ||
| 20 | +const router = useRouter() | ||
| 21 | + | ||
| 22 | +// 从路由查询参数获取PDF的URL和标题 | ||
| 23 | +const pdfUrl = computed(() => { | ||
| 24 | + const url = route.query.url || '' | ||
| 25 | + // 兼容可能存在的编码 | ||
| 26 | + return typeof url === 'string' ? decodeURIComponent(url) : '' | ||
| 27 | +}) | ||
| 28 | + | ||
| 29 | +const pdfTitle = computed(() => { | ||
| 30 | + const title = route.query.title || '' | ||
| 31 | + return typeof title === 'string' ? decodeURIComponent(title) : '' | ||
| 32 | +}) | ||
| 33 | + | ||
| 34 | +const handleClose = () => { | ||
| 35 | + const returnId = route.query.returnId | ||
| 36 | + const openMaterials = route.query.openMaterials | ||
| 37 | + if (returnId) { | ||
| 38 | + // 使用 replace 跳回学习详情页并打开学习资料弹框,避免历史中保留PDF预览 | ||
| 39 | + router.replace({ path: `/studyDetail/${returnId}`, query: { openMaterials: openMaterials || '1' } }) | ||
| 40 | + } else { | ||
| 41 | + // 无返回ID时,使用 replace 导航到学习页,避免返回进入PDF预览 | ||
| 42 | + router.replace({ path: '/study' }) | ||
| 43 | + } | ||
| 44 | +} | ||
| 45 | +</script> | ||
| 46 | + | ||
| 47 | +<style scoped> | ||
| 48 | +.pdf-preview-page { | ||
| 49 | + height: 100vh; | ||
| 50 | + width: 100vw; | ||
| 51 | + background: #fff; | ||
| 52 | + display: flex; | ||
| 53 | + flex-direction: column; | ||
| 54 | +} | ||
| 55 | +</style> |
| ... | @@ -260,8 +260,7 @@ | ... | @@ -260,8 +260,7 @@ |
| 260 | </div> | 260 | </div> |
| 261 | </van-popup> | 261 | </van-popup> |
| 262 | 262 | ||
| 263 | - <!-- PDF预览 --> | 263 | + <!-- PDF预览改为独立页面,点击资源时跳转到 /pdfPreview --> |
| 264 | - <PdfViewer v-model:show="pdfShow" :url="pdfUrl" :title="pdfTitle" @onLoad="onPdfLoad" @onProgress="onPdfProgress" @onComplete="onPdfComplete" /> | ||
| 265 | 264 | ||
| 266 | <!-- Office 文档预览弹窗 --> | 265 | <!-- Office 文档预览弹窗 --> |
| 267 | <van-popup | 266 | <van-popup |
| ... | @@ -877,7 +876,10 @@ const popupVideoPlayerRef = ref(null); // 弹窗视频播放器引用 | ... | @@ -877,7 +876,10 @@ const popupVideoPlayerRef = ref(null); // 弹窗视频播放器引用 |
| 877 | const showPdf = ({ title, url, meta_id }) => { | 876 | const showPdf = ({ title, url, meta_id }) => { |
| 878 | pdfTitle.value = title; | 877 | pdfTitle.value = title; |
| 879 | pdfUrl.value = url; | 878 | pdfUrl.value = url; |
| 880 | - pdfShow.value = true; | 879 | + // 跳转到PDF预览页面,并带上返回的课程ID和打开资料弹框的标记 |
| 880 | + const encodedUrl = encodeURIComponent(url); | ||
| 881 | + const encodedTitle = encodeURIComponent(title); | ||
| 882 | + router.replace({ name: 'PdfPreview', query: { url: encodedUrl, title: encodedTitle, returnId: courseId.value, openMaterials: '1' } }); | ||
| 881 | // 新增记录 | 883 | // 新增记录 |
| 882 | let paramsObj = { | 884 | let paramsObj = { |
| 883 | schedule_id: courseId.value, | 885 | schedule_id: courseId.value, |
| ... | @@ -1270,6 +1272,22 @@ const downloadFailInfo = ref({ | ... | @@ -1270,6 +1272,22 @@ const downloadFailInfo = ref({ |
| 1270 | // 学习资料弹窗状态 | 1272 | // 学习资料弹窗状态 |
| 1271 | const showMaterialsPopup = ref(false); | 1273 | const showMaterialsPopup = ref(false); |
| 1272 | 1274 | ||
| 1275 | +// 路由参数监听:如果openMaterials=1,则打开学习资料弹框 | ||
| 1276 | +watch(() => route.query.openMaterials, (val) => { | ||
| 1277 | + if (val === '1' || val === 1 || val === true) { | ||
| 1278 | + showMaterialsPopup.value = true; | ||
| 1279 | + } | ||
| 1280 | +}, { immediate: true }); | ||
| 1281 | + | ||
| 1282 | +// 监听弹框关闭:关闭时移除URL中的openMaterials参数,防止刷新再次打开 | ||
| 1283 | +watch(showMaterialsPopup, (val, oldVal) => { | ||
| 1284 | + if (oldVal && !val) { | ||
| 1285 | + const newQuery = { ...route.query }; | ||
| 1286 | + delete newQuery.openMaterials; | ||
| 1287 | + router.replace({ path: route.path, query: newQuery }); | ||
| 1288 | + } | ||
| 1289 | +}); | ||
| 1290 | + | ||
| 1273 | /** | 1291 | /** |
| 1274 | * 复制文件地址到剪贴板 | 1292 | * 复制文件地址到剪贴板 |
| 1275 | * @param {string} url - 要复制的文件地址 | 1293 | * @param {string} url - 要复制的文件地址 | ... | ... |
-
Please register or login to post a comment