feat(PdfViewer): 添加PDF缩放功能和控制按钮
添加缩放功能,支持放大、缩小和重置操作 更新PDF显示宽度以匹配缩放级别 添加缩放控制按钮的样式和交互逻辑
Showing
2 changed files
with
105 additions
and
4 deletions
| ... | @@ -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; | ... | ... |
-
Please register or login to post a comment