hookehuyr

feat(PdfViewer): 添加PDF缩放功能和控制按钮

添加缩放功能,支持放大、缩小和重置操作
更新PDF显示宽度以匹配缩放级别
添加缩放控制按钮的样式和交互逻辑
...@@ -18,7 +18,6 @@ declare module 'vue' { ...@@ -18,7 +18,6 @@ declare module 'vue' {
18 CourseCard: typeof import('./components/ui/CourseCard.vue')['default'] 18 CourseCard: typeof import('./components/ui/CourseCard.vue')['default']
19 CourseImageCard: typeof import('./components/ui/CourseImageCard.vue')['default'] 19 CourseImageCard: typeof import('./components/ui/CourseImageCard.vue')['default']
20 CourseList: typeof import('./components/courses/CourseList.vue')['default'] 20 CourseList: typeof import('./components/courses/CourseList.vue')['default']
21 - FilePreviewPopup: typeof import('./components/ui/FilePreviewPopup.vue')['default']
22 FormPage: typeof import('./components/infoEntry/formPage.vue')['default'] 21 FormPage: typeof import('./components/infoEntry/formPage.vue')['default']
23 FrostedGlass: typeof import('./components/ui/FrostedGlass.vue')['default'] 22 FrostedGlass: typeof import('./components/ui/FrostedGlass.vue')['default']
24 GradientHeader: typeof import('./components/ui/GradientHeader.vue')['default'] 23 GradientHeader: typeof import('./components/ui/GradientHeader.vue')['default']
......
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 14:33:07 4 + * @LastEditTime: 2025-10-21 15:20:39
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 -->
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
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" :src="url" :showProgress="true" :progressColor="'#1989fa'" 14 <PDF v-if="url && show && !loadingError" :src="url" :showProgress="true" :progressColor="'#1989fa'"
15 - :showPageTooltip="true" :showBackToTopBtn="true" :scrollThreshold="300" :pdfWidth="'100%'" 15 + :showPageTooltip="true" :showBackToTopBtn="true" :scrollThreshold="300"
16 + :pdfWidth="`${Math.round(100 * zoomLevel)}%`"
16 :rowGap="8" :page="1" :cMapUrl="'https://unpkg.com/pdfjs-dist@3.7.107/cmaps/'" 17 :rowGap="8" :page="1" :cMapUrl="'https://unpkg.com/pdfjs-dist@3.7.107/cmaps/'"
17 :withCredentials="false" :useSystemFonts="true" :stopAtErrors="false" :disableFontFace="false" 18 :withCredentials="false" :useSystemFonts="true" :stopAtErrors="false" :disableFontFace="false"
18 :disableRange="false" :disableStream="false" :disableAutoFetch="false" @onProgress="handleProgress" 19 :disableRange="false" :disableStream="false" :disableAutoFetch="false" @onProgress="handleProgress"
...@@ -35,6 +36,16 @@ ...@@ -35,6 +36,16 @@
35 <!-- 关闭按钮 --> 36 <!-- 关闭按钮 -->
36 <van-button class="close-btn" type="default" icon="cross" round @click="emit('update:show', false)" /> 37 <van-button class="close-btn" type="default" icon="cross" round @click="emit('update:show', false)" />
37 38
39 + <!-- 缩放控制按钮 -->
40 + <div v-if="!loading && !loadingError" class="zoom-controls">
41 + <van-button class="zoom-btn zoom-out-btn" type="default" icon="minus" round @click="zoomOut" />
42 + <div class="zoom-level">{{ Math.round(zoomLevel * 100) }}%</div>
43 + <van-button class="zoom-btn zoom-in-btn" type="default" icon="plus" round @click="zoomIn" />
44 + <van-button class="zoom-btn zoom-reset-btn" type="default" round @click="resetZoom">
45 + <van-icon name="replay" size="16" />
46 + </van-button>
47 + </div>
48 +
38 <!-- 加载遮罩 --> 49 <!-- 加载遮罩 -->
39 <van-overlay :show="loading && !loadingError"> 50 <van-overlay :show="loading && !loadingError">
40 <div class="wrapper" @click.stop> 51 <div class="wrapper" @click.stop>
...@@ -46,7 +57,7 @@ ...@@ -46,7 +57,7 @@
46 57
47 <!-- 加载文字 --> 58 <!-- 加载文字 -->
48 <div class="loading-text"> 59 <div class="loading-text">
49 - <div class="loading-title">正在加载PDF文档</div> 60 + <div class="loading-title">{{ title }}</div>
50 <div class="loading-subtitle"> 61 <div class="loading-subtitle">
51 {{ loadingProgress < 10 ? '正在连接服务器...' : loadingProgress < 50 ? '正在下载文件...' : 62 {{ loadingProgress < 10 ? '正在连接服务器...' : loadingProgress < 50 ? '正在下载文件...' :
52 loadingProgress < 90 ? '正在解析文档...' : '即将完成...' }} </div> 63 loadingProgress < 90 ? '正在解析文档...' : '即将完成...' }} </div>
...@@ -98,6 +109,7 @@ const loading = ref(true); ...@@ -98,6 +109,7 @@ const loading = ref(true);
98 const loadingProgress = ref(0); 109 const loadingProgress = ref(0);
99 const loadingError = ref(false); 110 const loadingError = ref(false);
100 const errorMessage = ref(''); 111 const errorMessage = ref('');
112 +const zoomLevel = ref(1); // 缩放级别,1为100%
101 113
102 /** 114 /**
103 * 监听show属性变化,控制PDF加载 115 * 监听show属性变化,控制PDF加载
...@@ -109,12 +121,14 @@ watch(() => props.show, (newVal) => { ...@@ -109,12 +121,14 @@ watch(() => props.show, (newVal) => {
109 loadingError.value = false; 121 loadingError.value = false;
110 loadingProgress.value = 0; 122 loadingProgress.value = 0;
111 errorMessage.value = ''; 123 errorMessage.value = '';
124 + zoomLevel.value = 1; // 重置缩放级别
112 } else if (!newVal) { 125 } else if (!newVal) {
113 // 弹窗关闭时重置状态 126 // 弹窗关闭时重置状态
114 loading.value = true; 127 loading.value = true;
115 loadingError.value = false; 128 loadingError.value = false;
116 loadingProgress.value = 0; 129 loadingProgress.value = 0;
117 errorMessage.value = ''; 130 errorMessage.value = '';
131 + zoomLevel.value = 1; // 重置缩放级别
118 } 132 }
119 }); 133 });
120 134
...@@ -128,6 +142,7 @@ watch(() => props.url, (newUrl) => { ...@@ -128,6 +142,7 @@ watch(() => props.url, (newUrl) => {
128 loadingError.value = false; 142 loadingError.value = false;
129 loadingProgress.value = 0; 143 loadingProgress.value = 0;
130 errorMessage.value = ''; 144 errorMessage.value = '';
145 + zoomLevel.value = 1; // 重置缩放级别
131 } 146 }
132 }); 147 });
133 148
...@@ -214,6 +229,32 @@ const retryLoad = () => { ...@@ -214,6 +229,32 @@ const retryLoad = () => {
214 loading.value = true; 229 loading.value = true;
215 loadingProgress.value = 0; 230 loadingProgress.value = 0;
216 errorMessage.value = ''; 231 errorMessage.value = '';
232 + zoomLevel.value = 1; // 重置缩放级别
233 +};
234 +
235 +/**
236 + * 放大PDF
237 + */
238 +const zoomIn = () => {
239 + if (zoomLevel.value < 3) { // 最大放大到300%
240 + zoomLevel.value = Math.min(3, zoomLevel.value + 0.25);
241 + }
242 +};
243 +
244 +/**
245 + * 缩小PDF
246 + */
247 +const zoomOut = () => {
248 + if (zoomLevel.value > 0.5) { // 最小缩小到50%
249 + zoomLevel.value = Math.max(0.5, zoomLevel.value - 0.25);
250 + }
251 +};
252 +
253 +/**
254 + * 重置缩放
255 + */
256 +const resetZoom = () => {
257 + zoomLevel.value = 1;
217 }; 258 };
218 259
219 /** 260 /**
...@@ -335,6 +376,67 @@ const addPreventSaveListeners = () => { ...@@ -335,6 +376,67 @@ const addPreventSaveListeners = () => {
335 color: #fff; 376 color: #fff;
336 } 377 }
337 378
379 +/* 缩放控制按钮样式 */
380 +.zoom-controls {
381 + position: fixed;
382 + bottom: 30px;
383 + left: 50%;
384 + transform: translateX(-50%);
385 + z-index: 100;
386 + display: flex;
387 + flex-direction: row;
388 + align-items: center;
389 + gap: 12px;
390 + background: rgba(255, 255, 255, 0.95);
391 + padding: 12px 20px;
392 + border-radius: 25px;
393 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
394 + backdrop-filter: blur(8px);
395 + transition: all 0.3s ease;
396 +}
397 +
398 +.zoom-controls:hover {
399 + background: rgba(255, 255, 255, 1);
400 + box-shadow: 0 6px 30px rgba(0, 0, 0, 0.2);
401 + transform: translateX(-50%) translateY(-2px);
402 +}
403 +
404 +.zoom-btn {
405 + width: 40px;
406 + height: 40px;
407 + padding: 0;
408 + border-radius: 50%;
409 + background: #fff;
410 + border: 1px solid #e8e8e8;
411 + color: #333;
412 + transition: all 0.2s ease;
413 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
414 +}
415 +
416 +.zoom-btn:hover {
417 + background: #1989fa;
418 + color: #fff;
419 + border-color: #1989fa;
420 + transform: scale(1.1);
421 + box-shadow: 0 4px 12px rgba(25, 137, 250, 0.3);
422 +}
423 +
424 +.zoom-btn:active {
425 + transform: scale(0.95);
426 +}
427 +
428 +.zoom-level {
429 + font-size: 14px;
430 + font-weight: 600;
431 + color: #333;
432 + text-align: center;
433 + min-width: 50px;
434 + padding: 8px 12px;
435 + background: rgba(25, 137, 250, 0.1);
436 + border-radius: 20px;
437 + border: 1px solid rgba(25, 137, 250, 0.2);
438 +}
439 +
338 /* 加载遮罩样式 */ 440 /* 加载遮罩样式 */
339 .wrapper { 441 .wrapper {
340 display: flex; 442 display: flex;
......