feat(pdf): 添加PDF预览功能
引入vue-pdf-embed库,新增PDF预览页面,支持在课程详情页中直接预览PDF文件。添加了新的路由和组件,并更新了相关依赖。
Showing
7 changed files
with
339 additions
and
4 deletions
| ... | @@ -22,6 +22,7 @@ | ... | @@ -22,6 +22,7 @@ |
| 22 | "vconsole": "^3.15.1", | 22 | "vconsole": "^3.15.1", |
| 23 | "video.js": "^7.21.7", | 23 | "video.js": "^7.21.7", |
| 24 | "vue": "^3.5.13", | 24 | "vue": "^3.5.13", |
| 25 | + "vue-pdf-embed": "^2.1.2", | ||
| 25 | "vue-router": "^4.5.0", | 26 | "vue-router": "^4.5.0", |
| 26 | "weixin-js-sdk": "^1.6.5" | 27 | "weixin-js-sdk": "^1.6.5" |
| 27 | }, | 28 | }, |
| ... | @@ -947,6 +948,177 @@ | ... | @@ -947,6 +948,177 @@ |
| 947 | "@jridgewell/sourcemap-codec": "^1.4.14" | 948 | "@jridgewell/sourcemap-codec": "^1.4.14" |
| 948 | } | 949 | } |
| 949 | }, | 950 | }, |
| 951 | + "node_modules/@napi-rs/canvas": { | ||
| 952 | + "version": "0.1.70", | ||
| 953 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.70.tgz", | ||
| 954 | + "integrity": "sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==", | ||
| 955 | + "optional": true, | ||
| 956 | + "engines": { | ||
| 957 | + "node": ">= 10" | ||
| 958 | + }, | ||
| 959 | + "optionalDependencies": { | ||
| 960 | + "@napi-rs/canvas-android-arm64": "0.1.70", | ||
| 961 | + "@napi-rs/canvas-darwin-arm64": "0.1.70", | ||
| 962 | + "@napi-rs/canvas-darwin-x64": "0.1.70", | ||
| 963 | + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.70", | ||
| 964 | + "@napi-rs/canvas-linux-arm64-gnu": "0.1.70", | ||
| 965 | + "@napi-rs/canvas-linux-arm64-musl": "0.1.70", | ||
| 966 | + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.70", | ||
| 967 | + "@napi-rs/canvas-linux-x64-gnu": "0.1.70", | ||
| 968 | + "@napi-rs/canvas-linux-x64-musl": "0.1.70", | ||
| 969 | + "@napi-rs/canvas-win32-x64-msvc": "0.1.70" | ||
| 970 | + } | ||
| 971 | + }, | ||
| 972 | + "node_modules/@napi-rs/canvas-android-arm64": { | ||
| 973 | + "version": "0.1.70", | ||
| 974 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.70.tgz", | ||
| 975 | + "integrity": "sha512-I/YOuQ0wbkVYxVaYtCgN42WKTYxNqFA0gTcTrHIGG1jfpDSyZWII/uHcjOo4nzd19io6Y4+/BqP8E5hJgf9OmQ==", | ||
| 976 | + "cpu": [ | ||
| 977 | + "arm64" | ||
| 978 | + ], | ||
| 979 | + "optional": true, | ||
| 980 | + "os": [ | ||
| 981 | + "android" | ||
| 982 | + ], | ||
| 983 | + "engines": { | ||
| 984 | + "node": ">= 10" | ||
| 985 | + } | ||
| 986 | + }, | ||
| 987 | + "node_modules/@napi-rs/canvas-darwin-arm64": { | ||
| 988 | + "version": "0.1.70", | ||
| 989 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.70.tgz", | ||
| 990 | + "integrity": "sha512-4pPGyXetHIHkw2TOJHujt3mkCP8LdDu8+CT15ld9Id39c752RcI0amDHSuMLMQfAjvusA9B5kKxazwjMGjEJpQ==", | ||
| 991 | + "cpu": [ | ||
| 992 | + "arm64" | ||
| 993 | + ], | ||
| 994 | + "optional": true, | ||
| 995 | + "os": [ | ||
| 996 | + "darwin" | ||
| 997 | + ], | ||
| 998 | + "engines": { | ||
| 999 | + "node": ">= 10" | ||
| 1000 | + } | ||
| 1001 | + }, | ||
| 1002 | + "node_modules/@napi-rs/canvas-darwin-x64": { | ||
| 1003 | + "version": "0.1.70", | ||
| 1004 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.70.tgz", | ||
| 1005 | + "integrity": "sha512-+2N6Os9LbkmDMHL+raknrUcLQhsXzc5CSXRbXws9C3pv/mjHRVszQ9dhFUUe9FjfPhCJznO6USVdwOtu7pOrzQ==", | ||
| 1006 | + "cpu": [ | ||
| 1007 | + "x64" | ||
| 1008 | + ], | ||
| 1009 | + "optional": true, | ||
| 1010 | + "os": [ | ||
| 1011 | + "darwin" | ||
| 1012 | + ], | ||
| 1013 | + "engines": { | ||
| 1014 | + "node": ">= 10" | ||
| 1015 | + } | ||
| 1016 | + }, | ||
| 1017 | + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { | ||
| 1018 | + "version": "0.1.70", | ||
| 1019 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.70.tgz", | ||
| 1020 | + "integrity": "sha512-QjscX9OaKq/990sVhSMj581xuqLgiaPVMjjYvWaCmAJRkNQ004QfoSMEm3FoTqM4DRoquP8jvuEXScVJsc1rqQ==", | ||
| 1021 | + "cpu": [ | ||
| 1022 | + "arm" | ||
| 1023 | + ], | ||
| 1024 | + "optional": true, | ||
| 1025 | + "os": [ | ||
| 1026 | + "linux" | ||
| 1027 | + ], | ||
| 1028 | + "engines": { | ||
| 1029 | + "node": ">= 10" | ||
| 1030 | + } | ||
| 1031 | + }, | ||
| 1032 | + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { | ||
| 1033 | + "version": "0.1.70", | ||
| 1034 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.70.tgz", | ||
| 1035 | + "integrity": "sha512-LNakMOwwqwiHIwMpnMAbFRczQMQ7TkkMyATqFCOtUJNlE6LPP/QiUj/mlFrNbUn/hctqShJ60gWEb52ZTALbVw==", | ||
| 1036 | + "cpu": [ | ||
| 1037 | + "arm64" | ||
| 1038 | + ], | ||
| 1039 | + "optional": true, | ||
| 1040 | + "os": [ | ||
| 1041 | + "linux" | ||
| 1042 | + ], | ||
| 1043 | + "engines": { | ||
| 1044 | + "node": ">= 10" | ||
| 1045 | + } | ||
| 1046 | + }, | ||
| 1047 | + "node_modules/@napi-rs/canvas-linux-arm64-musl": { | ||
| 1048 | + "version": "0.1.70", | ||
| 1049 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.70.tgz", | ||
| 1050 | + "integrity": "sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg==", | ||
| 1051 | + "cpu": [ | ||
| 1052 | + "arm64" | ||
| 1053 | + ], | ||
| 1054 | + "optional": true, | ||
| 1055 | + "os": [ | ||
| 1056 | + "linux" | ||
| 1057 | + ], | ||
| 1058 | + "engines": { | ||
| 1059 | + "node": ">= 10" | ||
| 1060 | + } | ||
| 1061 | + }, | ||
| 1062 | + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { | ||
| 1063 | + "version": "0.1.70", | ||
| 1064 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.70.tgz", | ||
| 1065 | + "integrity": "sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q==", | ||
| 1066 | + "cpu": [ | ||
| 1067 | + "riscv64" | ||
| 1068 | + ], | ||
| 1069 | + "optional": true, | ||
| 1070 | + "os": [ | ||
| 1071 | + "linux" | ||
| 1072 | + ], | ||
| 1073 | + "engines": { | ||
| 1074 | + "node": ">= 10" | ||
| 1075 | + } | ||
| 1076 | + }, | ||
| 1077 | + "node_modules/@napi-rs/canvas-linux-x64-gnu": { | ||
| 1078 | + "version": "0.1.70", | ||
| 1079 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.70.tgz", | ||
| 1080 | + "integrity": "sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ==", | ||
| 1081 | + "cpu": [ | ||
| 1082 | + "x64" | ||
| 1083 | + ], | ||
| 1084 | + "optional": true, | ||
| 1085 | + "os": [ | ||
| 1086 | + "linux" | ||
| 1087 | + ], | ||
| 1088 | + "engines": { | ||
| 1089 | + "node": ">= 10" | ||
| 1090 | + } | ||
| 1091 | + }, | ||
| 1092 | + "node_modules/@napi-rs/canvas-linux-x64-musl": { | ||
| 1093 | + "version": "0.1.70", | ||
| 1094 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.70.tgz", | ||
| 1095 | + "integrity": "sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg==", | ||
| 1096 | + "cpu": [ | ||
| 1097 | + "x64" | ||
| 1098 | + ], | ||
| 1099 | + "optional": true, | ||
| 1100 | + "os": [ | ||
| 1101 | + "linux" | ||
| 1102 | + ], | ||
| 1103 | + "engines": { | ||
| 1104 | + "node": ">= 10" | ||
| 1105 | + } | ||
| 1106 | + }, | ||
| 1107 | + "node_modules/@napi-rs/canvas-win32-x64-msvc": { | ||
| 1108 | + "version": "0.1.70", | ||
| 1109 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.70.tgz", | ||
| 1110 | + "integrity": "sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ==", | ||
| 1111 | + "cpu": [ | ||
| 1112 | + "x64" | ||
| 1113 | + ], | ||
| 1114 | + "optional": true, | ||
| 1115 | + "os": [ | ||
| 1116 | + "win32" | ||
| 1117 | + ], | ||
| 1118 | + "engines": { | ||
| 1119 | + "node": ">= 10" | ||
| 1120 | + } | ||
| 1121 | + }, | ||
| 950 | "node_modules/@nodelib/fs.scandir": { | 1122 | "node_modules/@nodelib/fs.scandir": { |
| 951 | "version": "2.1.5", | 1123 | "version": "2.1.5", |
| 952 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", | 1124 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", |
| ... | @@ -3098,6 +3270,17 @@ | ... | @@ -3098,6 +3270,17 @@ |
| 3098 | "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", | 3270 | "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", |
| 3099 | "dev": true | 3271 | "dev": true |
| 3100 | }, | 3272 | }, |
| 3273 | + "node_modules/pdfjs-dist": { | ||
| 3274 | + "version": "4.10.38", | ||
| 3275 | + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz", | ||
| 3276 | + "integrity": "sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==", | ||
| 3277 | + "engines": { | ||
| 3278 | + "node": ">=20" | ||
| 3279 | + }, | ||
| 3280 | + "optionalDependencies": { | ||
| 3281 | + "@napi-rs/canvas": "^0.1.65" | ||
| 3282 | + } | ||
| 3283 | + }, | ||
| 3101 | "node_modules/picocolors": { | 3284 | "node_modules/picocolors": { |
| 3102 | "version": "1.1.1", | 3285 | "version": "1.1.1", |
| 3103 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", | 3286 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", |
| ... | @@ -4327,6 +4510,17 @@ | ... | @@ -4327,6 +4510,17 @@ |
| 4327 | } | 4510 | } |
| 4328 | } | 4511 | } |
| 4329 | }, | 4512 | }, |
| 4513 | + "node_modules/vue-pdf-embed": { | ||
| 4514 | + "version": "2.1.2", | ||
| 4515 | + "resolved": "https://registry.npmjs.org/vue-pdf-embed/-/vue-pdf-embed-2.1.2.tgz", | ||
| 4516 | + "integrity": "sha512-/j++oknFBY9x/MgEFBo9tSuOXS0Z9COlywwLhMREhiGfmuQqpnGy5T+SwVIXxR1tmdzM/lHog8JL7HOAgXT1aw==", | ||
| 4517 | + "dependencies": { | ||
| 4518 | + "pdfjs-dist": "^4.10.38" | ||
| 4519 | + }, | ||
| 4520 | + "peerDependencies": { | ||
| 4521 | + "vue": "^3.3.0" | ||
| 4522 | + } | ||
| 4523 | + }, | ||
| 4330 | "node_modules/vue-router": { | 4524 | "node_modules/vue-router": { |
| 4331 | "version": "4.5.0", | 4525 | "version": "4.5.0", |
| 4332 | "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", | 4526 | "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", | ... | ... |
| ... | @@ -29,6 +29,7 @@ | ... | @@ -29,6 +29,7 @@ |
| 29 | "vconsole": "^3.15.1", | 29 | "vconsole": "^3.15.1", |
| 30 | "video.js": "^7.21.7", | 30 | "video.js": "^7.21.7", |
| 31 | "vue": "^3.5.13", | 31 | "vue": "^3.5.13", |
| 32 | + "vue-pdf-embed": "^2.1.2", | ||
| 32 | "vue-router": "^4.5.0", | 33 | "vue-router": "^4.5.0", |
| 33 | "weixin-js-sdk": "^1.6.5" | 34 | "weixin-js-sdk": "^1.6.5" |
| 34 | }, | 35 | }, | ... | ... |
| ... | @@ -20,6 +20,7 @@ declare module 'vue' { | ... | @@ -20,6 +20,7 @@ declare module 'vue' { |
| 20 | GradientHeader: typeof import('./components/ui/GradientHeader.vue')['default'] | 20 | GradientHeader: typeof import('./components/ui/GradientHeader.vue')['default'] |
| 21 | LiveStreamCard: typeof import('./components/ui/LiveStreamCard.vue')['default'] | 21 | LiveStreamCard: typeof import('./components/ui/LiveStreamCard.vue')['default'] |
| 22 | MenuItem: typeof import('./components/ui/MenuItem.vue')['default'] | 22 | MenuItem: typeof import('./components/ui/MenuItem.vue')['default'] |
| 23 | + PdfViewer: typeof import('./components/ui/PdfViewer.vue')['default'] | ||
| 23 | ReviewPopup: typeof import('./components/courses/ReviewPopup.vue')['default'] | 24 | ReviewPopup: typeof import('./components/courses/ReviewPopup.vue')['default'] |
| 24 | RouterLink: typeof import('vue-router')['RouterLink'] | 25 | RouterLink: typeof import('vue-router')['RouterLink'] |
| 25 | RouterView: typeof import('vue-router')['RouterView'] | 26 | RouterView: typeof import('vue-router')['RouterView'] | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2025-03-20 20:36:36 | 2 | * @Date: 2025-03-20 20:36:36 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-05-08 10:56:28 | 4 | + * @LastEditTime: 2025-05-08 11:14:46 |
| 5 | * @FilePath: /mlaj/src/main.js | 5 | * @FilePath: /mlaj/src/main.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -18,10 +18,10 @@ import { library } from '@fortawesome/fontawesome-svg-core' | ... | @@ -18,10 +18,10 @@ import { library } from '@fortawesome/fontawesome-svg-core' |
| 18 | /* import font awesome icon component */ | 18 | /* import font awesome icon component */ |
| 19 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' | 19 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' |
| 20 | /* import specific icons */ | 20 | /* import specific icons */ |
| 21 | -import { faCirclePause, faCirclePlay, faPlay, faPause, faBackwardStep, faForwardStep, faVolumeUp, faRedo, faRepeat, faList, faChevronDown, faVolumeOff, faXmark, faFileAlt } from '@fortawesome/free-solid-svg-icons' | 21 | +import { faCirclePause, faCirclePlay, faPlay, faPause, faBackwardStep, faForwardStep, faVolumeUp, faRedo, faRepeat, faList, faChevronDown, faVolumeOff, faXmark, faFileAlt, faTimes, faEye, faFilePdf, faExternalLinkAlt, faSpinner, faExclamationCircle } from '@fortawesome/free-solid-svg-icons' |
| 22 | 22 | ||
| 23 | /* add icons to the library */ | 23 | /* add icons to the library */ |
| 24 | -library.add(faCirclePause, faCirclePlay, faPlay, faPause, faBackwardStep, faForwardStep, faVolumeUp, faRedo, faRepeat, faList, faChevronDown, faVolumeOff, faXmark, faFileAlt) | 24 | +library.add(faCirclePause, faCirclePlay, faPlay, faPause, faBackwardStep, faForwardStep, faVolumeUp, faRedo, faRepeat, faList, faChevronDown, faVolumeOff, faXmark, faFileAlt, faTimes, faEye, faFilePdf, faExternalLinkAlt, faSpinner, faExclamationCircle) |
| 25 | 25 | ||
| 26 | const app = createApp(App) | 26 | const app = createApp(App) |
| 27 | // 屏蔽警告信息 | 27 | // 屏蔽警告信息 | ... | ... |
| ... | @@ -219,5 +219,11 @@ export const routes = [ | ... | @@ -219,5 +219,11 @@ export const routes = [ |
| 219 | title: '课程集合页面', | 219 | title: '课程集合页面', |
| 220 | } | 220 | } |
| 221 | }, | 221 | }, |
| 222 | + { | ||
| 223 | + path: '/pdf-preview', | ||
| 224 | + name: 'pdf-preview', | ||
| 225 | + component: () => import('../views/study/PdfPreviewPage.vue'), | ||
| 226 | + meta: { title: 'PDF预览' }, | ||
| 227 | + }, | ||
| 222 | ...checkinRoutes, | 228 | ...checkinRoutes, |
| 223 | ] | 229 | ] | ... | ... |
src/views/study/PdfPreviewPage.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2024-01-17 | ||
| 3 | + * @Description: PDF预览页面 | ||
| 4 | +--> | ||
| 5 | +<template> | ||
| 6 | + <div class="pdf-preview-page bg-white min-h-screen flex flex-col"> | ||
| 7 | + <!-- 顶部导航栏 --> | ||
| 8 | + <div class="flex items-center justify-between p-4 bg-white border-b sticky top-0 z-10"> | ||
| 9 | + <div class="flex items-center space-x-2"> | ||
| 10 | + <font-awesome-icon icon="file-pdf" class="text-red-500 text-xl" /> | ||
| 11 | + <span class="text-gray-900 font-medium">{{ title }}</span> | ||
| 12 | + </div> | ||
| 13 | + <div class="flex items-center space-x-4"> | ||
| 14 | + <a :href="pdfUrl" target="_blank" class="text-blue-600 hover:text-blue-800"> | ||
| 15 | + <font-awesome-icon icon="external-link-alt" class="mr-1" /> | ||
| 16 | + 新窗口打开 | ||
| 17 | + </a> | ||
| 18 | + <button @click="goBack" class="text-gray-500 hover:text-gray-700"> | ||
| 19 | + <font-awesome-icon icon="times" /> | ||
| 20 | + </button> | ||
| 21 | + </div> | ||
| 22 | + </div> | ||
| 23 | + | ||
| 24 | + <!-- PDF内容区域 --> | ||
| 25 | + <div class="flex-1 overflow-y-auto bg-gray-100 p-4"> | ||
| 26 | + <div v-for="pageNum in pageNums" :key="pageNum" class="mb-4" ref="pageRefs"> | ||
| 27 | + <VuePdfEmbed | ||
| 28 | + v-if="pageVisibility[pageNum]" | ||
| 29 | + :source="{ url: pdfUrl }" | ||
| 30 | + :page="pageNum" | ||
| 31 | + :scale="1.5" | ||
| 32 | + :render-text="true" | ||
| 33 | + style="width: 100%;" | ||
| 34 | + /> | ||
| 35 | + </div> | ||
| 36 | + </div> | ||
| 37 | + </div> | ||
| 38 | +</template> | ||
| 39 | + | ||
| 40 | +<script setup> | ||
| 41 | +import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue'; | ||
| 42 | +import { useRoute, useRouter } from 'vue-router'; | ||
| 43 | +import VuePdfEmbed, { useVuePdfEmbed } from 'vue-pdf-embed'; | ||
| 44 | + | ||
| 45 | +const route = useRoute(); | ||
| 46 | +const router = useRouter(); | ||
| 47 | + | ||
| 48 | +// 获取路由参数 | ||
| 49 | +const title = ref(route.query.title || 'PDF预览'); | ||
| 50 | +const pdfUrl = ref(route.query.url || ''); | ||
| 51 | + | ||
| 52 | +// PDF页面相关 | ||
| 53 | +const pageRefs = ref([]); | ||
| 54 | +const pageVisibility = ref({}); | ||
| 55 | +let pageIntersectionObserver; | ||
| 56 | + | ||
| 57 | +// 使用PDF嵌入组件 | ||
| 58 | +const { doc } = useVuePdfEmbed({ | ||
| 59 | + source: { url: pdfUrl.value }, | ||
| 60 | +}); | ||
| 61 | + | ||
| 62 | +// 计算总页数 | ||
| 63 | +const pageNums = computed(() => | ||
| 64 | + doc.value | ||
| 65 | + ? [...Array(doc.value.numPages + 1).keys()].slice(1) | ||
| 66 | + : [] | ||
| 67 | +); | ||
| 68 | + | ||
| 69 | +// 重置页面交叉观察器 | ||
| 70 | +const resetPageIntersectionObserver = () => { | ||
| 71 | + pageIntersectionObserver?.disconnect(); | ||
| 72 | + pageIntersectionObserver = new IntersectionObserver((entries) => { | ||
| 73 | + entries.forEach((entry) => { | ||
| 74 | + if (entry.isIntersecting) { | ||
| 75 | + const index = pageRefs.value.indexOf(entry.target); | ||
| 76 | + const pageNum = pageNums.value[index]; | ||
| 77 | + pageVisibility.value[pageNum] = true; | ||
| 78 | + } | ||
| 79 | + }); | ||
| 80 | + }); | ||
| 81 | + pageRefs.value.forEach((element) => { | ||
| 82 | + if (element) { | ||
| 83 | + pageIntersectionObserver.observe(element); | ||
| 84 | + } | ||
| 85 | + }); | ||
| 86 | +}; | ||
| 87 | + | ||
| 88 | +// 返回上一页 | ||
| 89 | +const goBack = () => { | ||
| 90 | + router.back(); | ||
| 91 | +}; | ||
| 92 | + | ||
| 93 | +// 监听页数变化 | ||
| 94 | +watch(pageNums, (newPageNums) => { | ||
| 95 | + if (newPageNums.length > 0) { | ||
| 96 | + pageVisibility.value = { [newPageNums[0]]: true }; | ||
| 97 | + nextTick(resetPageIntersectionObserver); | ||
| 98 | + } | ||
| 99 | +}); | ||
| 100 | + | ||
| 101 | +// 组件卸载前清理 | ||
| 102 | +onBeforeUnmount(() => { | ||
| 103 | + pageIntersectionObserver?.disconnect(); | ||
| 104 | +}); | ||
| 105 | +</script> | ||
| 106 | + | ||
| 107 | +<style lang="less" scoped> | ||
| 108 | +.pdf-preview-page { | ||
| 109 | + .pdf-content { | ||
| 110 | + background-color: #f3f4f6; | ||
| 111 | + } | ||
| 112 | +} | ||
| 113 | +</style> |
| ... | @@ -56,7 +56,16 @@ | ... | @@ -56,7 +56,16 @@ |
| 56 | </div> | 56 | </div> |
| 57 | <div class="flex-1 min-w-0"> | 57 | <div class="flex-1 min-w-0"> |
| 58 | <h3 class="text-sm font-medium text-gray-900 truncate">{{ item.title }}</h3> | 58 | <h3 class="text-sm font-medium text-gray-900 truncate">{{ item.title }}</h3> |
| 59 | - <a :href="item.url" target="_blank" class="text-sm text-blue-600 hover:text-blue-800 hover:underline truncate block mt-1">打开文件</a> | 59 | + <!-- PDF文件预览 --> |
| 60 | + <template v-if="item.url.toLowerCase().endsWith('.pdf')"> | ||
| 61 | + <button @click="openPdfViewer(item)" class="mt-2 w-full text-left text-blue-600 hover:text-blue-800 hover:underline"> | ||
| 62 | + <font-awesome-icon icon="eye" class="mr-1" /> | ||
| 63 | + 预览PDF | ||
| 64 | + </button> | ||
| 65 | + </template> | ||
| 66 | + <template v-else> | ||
| 67 | + <a :href="item.url" target="_blank" class="text-sm text-blue-600 hover:text-blue-800 hover:underline truncate block mt-1">打开文件</a> | ||
| 68 | + </template> | ||
| 60 | </div> | 69 | </div> |
| 61 | </div> | 70 | </div> |
| 62 | </div> | 71 | </div> |
| ... | @@ -339,6 +348,17 @@ const commentList = ref([]); | ... | @@ -339,6 +348,17 @@ const commentList = ref([]); |
| 339 | const courseFile = ref({}); | 348 | const courseFile = ref({}); |
| 340 | 349 | ||
| 341 | // 处理课程切换 | 350 | // 处理课程切换 |
| 351 | +// 打开PDF预览 | ||
| 352 | +const openPdfViewer = (item) => { | ||
| 353 | + router.push({ | ||
| 354 | + name: 'pdf-preview', | ||
| 355 | + query: { | ||
| 356 | + url: item.url, | ||
| 357 | + title: item.title | ||
| 358 | + } | ||
| 359 | + }); | ||
| 360 | +}; | ||
| 361 | + | ||
| 342 | const handleLessonClick = async (lesson) => { | 362 | const handleLessonClick = async (lesson) => { |
| 343 | showCatalog.value = false; // 关闭目录弹窗 | 363 | showCatalog.value = false; // 关闭目录弹窗 |
| 344 | isPlaying.value = false; // 重置播放状态 | 364 | isPlaying.value = false; // 重置播放状态 | ... | ... |
-
Please register or login to post a comment