feat(学习资料): 添加学习资料全屏弹窗和文件展示功能
- 移除原有文件列表内联展示方式 - 新增学习资料入口卡片,点击可打开全屏弹窗 - 实现文件分类展示、在线查看PDF和下载功能 - 添加文件图标识别和类型显示功能 - 优化课程时长显示为"建议时长"
Showing
1 changed file
with
225 additions
and
3 deletions
| ... | @@ -46,7 +46,7 @@ | ... | @@ -46,7 +46,7 @@ |
| 46 | </van-swipe> | 46 | </van-swipe> |
| 47 | </div> | 47 | </div> |
| 48 | <!-- 文件列表展示区域 --> | 48 | <!-- 文件列表展示区域 --> |
| 49 | - <div v-if="course.course_type === 'file'" class="w-full relative bg-white rounded-lg shadow-sm"> | 49 | + <!-- <div v-if="course.course_type === 'file'" class="w-full relative bg-white rounded-lg shadow-sm"> |
| 50 | <div class="p-4 space-y-3"> | 50 | <div class="p-4 space-y-3"> |
| 51 | <div v-for="(item, index) in courseFile?.list" :key="index" | 51 | <div v-for="(item, index) in courseFile?.list" :key="index" |
| 52 | class="group hover:bg-gray-50 transition-colors rounded-lg p-3"> | 52 | class="group hover:bg-gray-50 transition-colors rounded-lg p-3"> |
| ... | @@ -64,7 +64,7 @@ | ... | @@ -64,7 +64,7 @@ |
| 64 | </div> | 64 | </div> |
| 65 | </div> | 65 | </div> |
| 66 | </div> | 66 | </div> |
| 67 | - </div> | 67 | + </div> --> |
| 68 | 68 | ||
| 69 | <!-- 默认展示区 --> | 69 | <!-- 默认展示区 --> |
| 70 | <div v-if="!course.course_type" class="relative" style="border-bottom: 1px solid #e5e7eb;"> | 70 | <div v-if="!course.course_type" class="relative" style="border-bottom: 1px solid #e5e7eb;"> |
| ... | @@ -95,6 +95,24 @@ | ... | @@ -95,6 +95,24 @@ |
| 95 | <!-- <span class="text-gray-300">|</span> --> | 95 | <!-- <span class="text-gray-300">|</span> --> |
| 96 | <!-- <span>没有字段{{ course.studyCount || 0 }}次学习</span> --> | 96 | <!-- <span>没有字段{{ course.studyCount || 0 }}次学习</span> --> |
| 97 | </div> | 97 | </div> |
| 98 | + | ||
| 99 | + <!-- 学习资料入口 --> | ||
| 100 | + <div v-if="course.course_type === 'file' && courseFile?.list && courseFile.list.length > 0" | ||
| 101 | + class="bg-white rounded-lg p-4 mb-4 cursor-pointer hover:bg-gray-50 transition-colors mt-4" | ||
| 102 | + @click="showMaterialsPopup = true"> | ||
| 103 | + <div class="flex items-center justify-between"> | ||
| 104 | + <div class="flex items-center gap-3"> | ||
| 105 | + <div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center"> | ||
| 106 | + <van-icon name="notes" class="text-green-600" size="20" /> | ||
| 107 | + </div> | ||
| 108 | + <div> | ||
| 109 | + <div class="text-base font-medium text-gray-900">学习资料</div> | ||
| 110 | + <div class="text-sm text-gray-500">共{{ courseFile.list.length }}个文件</div> | ||
| 111 | + </div> | ||
| 112 | + </div> | ||
| 113 | + <van-icon name="arrow" class="text-gray-400" /> | ||
| 114 | + </div> | ||
| 115 | + </div> | ||
| 98 | </div> | 116 | </div> |
| 99 | 117 | ||
| 100 | <div class="h-2 bg-gray-100"></div> | 118 | <div class="h-2 bg-gray-100"></div> |
| ... | @@ -229,7 +247,7 @@ | ... | @@ -229,7 +247,7 @@ |
| 229 | <span v-if="course_type_maps[lesson.course_type]" class="mx-2">|</span> | 247 | <span v-if="course_type_maps[lesson.course_type]" class="mx-2">|</span> |
| 230 | <span>开课时间: {{ lesson.schedule_time ? dayjs(lesson.schedule_time).format('YYYY-MM-DD') : '暂无' }}</span> | 248 | <span>开课时间: {{ lesson.schedule_time ? dayjs(lesson.schedule_time).format('YYYY-MM-DD') : '暂无' }}</span> |
| 231 | <span class="mx-2">|</span> | 249 | <span class="mx-2">|</span> |
| 232 | - <span>课程时长: {{ lesson.duration }} 分钟</span> | 250 | + <span v-if="lesson.duration">建议时长: {{ lesson.duration }} 分钟</span> |
| 233 | </div> | 251 | </div> |
| 234 | </div> | 252 | </div> |
| 235 | </div> | 253 | </div> |
| ... | @@ -359,6 +377,109 @@ | ... | @@ -359,6 +377,109 @@ |
| 359 | </div> | 377 | </div> |
| 360 | </div> | 378 | </div> |
| 361 | </van-popup> | 379 | </van-popup> |
| 380 | + | ||
| 381 | + <!-- 学习资料全屏弹窗 --> | ||
| 382 | + <van-popup | ||
| 383 | + v-model:show="showMaterialsPopup" | ||
| 384 | + position="right" | ||
| 385 | + :style="{ width: '100%', height: '100%' }" | ||
| 386 | + :close-on-click-overlay="false" | ||
| 387 | + :lock-scroll="true" | ||
| 388 | + > | ||
| 389 | + <div class="flex flex-col h-full bg-gray-50"> | ||
| 390 | + <!-- 头部导航栏 --> | ||
| 391 | + <div class="bg-white shadow-sm border-b border-gray-100"> | ||
| 392 | + <div class="flex items-center justify-between px-4 py-3"> | ||
| 393 | + <div class="flex items-center gap-3"> | ||
| 394 | + <van-button | ||
| 395 | + @click="showMaterialsPopup = false" | ||
| 396 | + type="default" | ||
| 397 | + size="small" | ||
| 398 | + round | ||
| 399 | + class="w-8 h-8 p-0 bg-gray-100 border-0" | ||
| 400 | + > | ||
| 401 | + <van-icon name="arrow-left" size="16" class="text-gray-600" /> | ||
| 402 | + </van-button> | ||
| 403 | + <div> | ||
| 404 | + <h2 class="text-lg font-medium text-gray-900">学习资料</h2> | ||
| 405 | + <p class="text-xs text-gray-500"> | ||
| 406 | + 共 {{ courseFile?.list ? courseFile.list.length : 0 }} 个文件 | ||
| 407 | + </p> | ||
| 408 | + </div> | ||
| 409 | + </div> | ||
| 410 | + <div class="px-2 py-1 bg-blue-50 rounded-full"> | ||
| 411 | + <span class="text-blue-600 text-sm font-medium"> | ||
| 412 | + {{ courseFile?.list ? courseFile.list.length : 0 }} | ||
| 413 | + </span> | ||
| 414 | + </div> | ||
| 415 | + </div> | ||
| 416 | + </div> | ||
| 417 | + | ||
| 418 | + <!-- 文件列表 --> | ||
| 419 | + <div class="flex-1 overflow-y-auto p-4 pb-safe"> | ||
| 420 | + <div v-if="courseFile?.list && courseFile.list.length > 0" class="space-y-4"> | ||
| 421 | + <FrostedGlass | ||
| 422 | + v-for="(file, index) in courseFile.list" | ||
| 423 | + :key="index" | ||
| 424 | + :bgOpacity="70" | ||
| 425 | + blurLevel="md" | ||
| 426 | + className="p-5 hover:bg-white/80 transition-all duration-300 hover:shadow-xl hover:scale-[1.02] transform" | ||
| 427 | + > | ||
| 428 | + <!-- 文件信息 --> | ||
| 429 | + <div class="flex items-start gap-4 mb-4"> | ||
| 430 | + <div class="w-12 h-12 bg-gradient-to-br from-blue-50 to-indigo-100 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm"> | ||
| 431 | + <van-icon | ||
| 432 | + :name="getFileIcon(file.title || file.name)" | ||
| 433 | + class="text-blue-600" | ||
| 434 | + :size="22" | ||
| 435 | + /> | ||
| 436 | + </div> | ||
| 437 | + <div class="flex-1 min-w-0"> | ||
| 438 | + <h3 class="text-base font-semibold text-gray-900 mb-2 line-clamp-2">{{ file.title || file.name }}</h3> | ||
| 439 | + <div class="flex items-center gap-4 text-sm text-gray-600"> | ||
| 440 | + <div class="flex items-center gap-1"> | ||
| 441 | + <van-icon name="label-o" size="12" style="margin-right: 0.25rem;"/> | ||
| 442 | + <span>{{ getFileType(file.title || file.name) }}</span> | ||
| 443 | + </div> | ||
| 444 | + </div> | ||
| 445 | + </div> | ||
| 446 | + </div> | ||
| 447 | + | ||
| 448 | + <!-- 操作按钮 --> | ||
| 449 | + <div class="flex gap-3" style="margin: 1rem;"> | ||
| 450 | + <!-- PDF文件只显示在线查看按钮 --> | ||
| 451 | + <button | ||
| 452 | + v-if="file.url && file.url.toLowerCase().includes('.pdf')" | ||
| 453 | + @click="showPdf(file)" | ||
| 454 | + class="btn-primary flex-1 py-2.5 text-sm font-medium flex items-center justify-center gap-2" | ||
| 455 | + > | ||
| 456 | + <van-icon name="eye-o" size="16" /> | ||
| 457 | + 在线查看 | ||
| 458 | + </button> | ||
| 459 | + <!-- 非PDF文件只显示下载按钮 --> | ||
| 460 | + <button | ||
| 461 | + v-else | ||
| 462 | + @click="downloadFile(file)" | ||
| 463 | + class="btn-secondary flex-1 py-2.5 text-sm font-medium flex items-center justify-center gap-2" | ||
| 464 | + > | ||
| 465 | + <van-icon name="down" size="16" /> | ||
| 466 | + 下载文件 | ||
| 467 | + </button> | ||
| 468 | + </div> | ||
| 469 | + </FrostedGlass> | ||
| 470 | + </div> | ||
| 471 | + | ||
| 472 | + <!-- 空状态 --> | ||
| 473 | + <div v-else class="flex flex-col items-center justify-center py-16 px-4"> | ||
| 474 | + <div class="w-16 h-16 sm:w-20 sm:h-20 bg-gray-100 rounded-full flex items-center justify-center mb-4"> | ||
| 475 | + <van-icon name="folder-o" :size="28" class="text-gray-400" /> | ||
| 476 | + </div> | ||
| 477 | + <p class="text-gray-500 text-base sm:text-lg mb-2 text-center">暂无学习资料</p> | ||
| 478 | + <p class="text-gray-400 text-sm text-center">请联系老师上传相关资料</p> | ||
| 479 | + </div> | ||
| 480 | + </div> | ||
| 481 | + </div> | ||
| 482 | + </van-popup> | ||
| 362 | </div> | 483 | </div> |
| 363 | </template> | 484 | </template> |
| 364 | 485 | ||
| ... | @@ -368,6 +489,7 @@ import { useRoute, useRouter } from 'vue-router'; | ... | @@ -368,6 +489,7 @@ import { useRoute, useRouter } from 'vue-router'; |
| 368 | import { useTitle } from '@vueuse/core'; | 489 | import { useTitle } from '@vueuse/core'; |
| 369 | import VideoPlayer from '@/components/ui/VideoPlayer.vue'; | 490 | import VideoPlayer from '@/components/ui/VideoPlayer.vue'; |
| 370 | import AudioPlayer from '@/components/ui/AudioPlayer.vue'; | 491 | import AudioPlayer from '@/components/ui/AudioPlayer.vue'; |
| 492 | +import FrostedGlass from '@/components/ui/FrostedGlass.vue'; | ||
| 371 | import dayjs from 'dayjs'; | 493 | import dayjs from 'dayjs'; |
| 372 | import { formatDate } from '@/utils/tools' | 494 | import { formatDate } from '@/utils/tools' |
| 373 | import axios from 'axios'; | 495 | import axios from 'axios'; |
| ... | @@ -795,6 +917,9 @@ const downloadFailInfo = ref({ | ... | @@ -795,6 +917,9 @@ const downloadFailInfo = ref({ |
| 795 | fileUrl: '' | 917 | fileUrl: '' |
| 796 | }); | 918 | }); |
| 797 | 919 | ||
| 920 | +// 学习资料弹窗状态 | ||
| 921 | +const showMaterialsPopup = ref(false); | ||
| 922 | + | ||
| 798 | /** | 923 | /** |
| 799 | * 复制文件URL到剪贴板 | 924 | * 复制文件URL到剪贴板 |
| 800 | * @param {string} url - 要复制的URL | 925 | * @param {string} url - 要复制的URL |
| ... | @@ -1212,6 +1337,103 @@ const closeCheckInDialog = () => { | ... | @@ -1212,6 +1337,103 @@ const closeCheckInDialog = () => { |
| 1212 | showTimeoutTaskList.value = false; | 1337 | showTimeoutTaskList.value = false; |
| 1213 | default_list.value = task_list.value; | 1338 | default_list.value = task_list.value; |
| 1214 | } | 1339 | } |
| 1340 | + | ||
| 1341 | +/** | ||
| 1342 | + * 格式化文件大小 | ||
| 1343 | + * @param {number} size - 文件大小(字节) | ||
| 1344 | + * @returns {string} 格式化后的文件大小 | ||
| 1345 | + */ | ||
| 1346 | +const formatFileSize = (size) => { | ||
| 1347 | + if (!size) return '0 B'; | ||
| 1348 | + const units = ['B', 'KB', 'MB', 'GB']; | ||
| 1349 | + let index = 0; | ||
| 1350 | + let fileSize = size; | ||
| 1351 | + | ||
| 1352 | + while (fileSize >= 1024 && index < units.length - 1) { | ||
| 1353 | + fileSize /= 1024; | ||
| 1354 | + index++; | ||
| 1355 | + } | ||
| 1356 | + | ||
| 1357 | + return `${fileSize.toFixed(1)} ${units[index]}`; | ||
| 1358 | +} | ||
| 1359 | + | ||
| 1360 | +/** | ||
| 1361 | + * 根据文件名获取文件图标 | ||
| 1362 | + * @param {string} fileName - 文件名 | ||
| 1363 | + * @returns {string} 图标名称 | ||
| 1364 | + */ | ||
| 1365 | +const getFileIcon = (fileName) => { | ||
| 1366 | + // 添加空值检查 | ||
| 1367 | + if (!fileName || typeof fileName !== 'string') { | ||
| 1368 | + return 'description'; // 默认图标 | ||
| 1369 | + } | ||
| 1370 | + | ||
| 1371 | + const extension = fileName.split('.').pop().toLowerCase(); | ||
| 1372 | + const iconMap = { | ||
| 1373 | + 'pdf': 'description', | ||
| 1374 | + 'doc': 'description', | ||
| 1375 | + 'docx': 'description', | ||
| 1376 | + 'xls': 'description', | ||
| 1377 | + 'xlsx': 'description', | ||
| 1378 | + 'ppt': 'description', | ||
| 1379 | + 'pptx': 'description', | ||
| 1380 | + 'txt': 'description', | ||
| 1381 | + 'zip': 'bag-o', | ||
| 1382 | + 'rar': 'bag-o', | ||
| 1383 | + '7z': 'bag-o', | ||
| 1384 | + 'mp3': 'music-o', | ||
| 1385 | + 'aac': 'music-o', | ||
| 1386 | + 'wav': 'music-o', | ||
| 1387 | + 'ogg': 'music-o', | ||
| 1388 | + 'mp4': 'video-o', | ||
| 1389 | + 'avi': 'video-o', | ||
| 1390 | + 'mov': 'video-o', | ||
| 1391 | + 'jpg': 'photo-o', | ||
| 1392 | + 'jpeg': 'photo-o', | ||
| 1393 | + 'png': 'photo-o', | ||
| 1394 | + 'gif': 'photo-o' | ||
| 1395 | + }; | ||
| 1396 | + return iconMap[extension] || 'description'; | ||
| 1397 | +} | ||
| 1398 | + | ||
| 1399 | +/** | ||
| 1400 | + * 根据文件名获取文件类型描述 | ||
| 1401 | + * @param {string} fileName - 文件名 | ||
| 1402 | + * @returns {string} 文件类型描述 | ||
| 1403 | + */ | ||
| 1404 | +const getFileType = (fileName) => { | ||
| 1405 | + // 添加空值检查 | ||
| 1406 | + if (!fileName || typeof fileName !== 'string') { | ||
| 1407 | + return '未知文件'; // 默认类型 | ||
| 1408 | + } | ||
| 1409 | + | ||
| 1410 | + const extension = fileName.split('.').pop().toLowerCase(); | ||
| 1411 | + const typeMap = { | ||
| 1412 | + 'pdf': 'PDF文档', | ||
| 1413 | + 'doc': 'Word文档', | ||
| 1414 | + 'docx': 'Word文档', | ||
| 1415 | + 'xls': 'Excel表格', | ||
| 1416 | + 'xlsx': 'Excel表格', | ||
| 1417 | + 'ppt': 'PPT演示', | ||
| 1418 | + 'pptx': 'PPT演示', | ||
| 1419 | + 'txt': '文本文件', | ||
| 1420 | + 'zip': '压缩文件', | ||
| 1421 | + 'rar': '压缩文件', | ||
| 1422 | + '7z': '压缩文件', | ||
| 1423 | + 'mp3': '音频文件', | ||
| 1424 | + 'aac': '音频文件', | ||
| 1425 | + 'wav': '音频文件', | ||
| 1426 | + 'ogg': '音频文件', | ||
| 1427 | + 'mp4': '视频文件', | ||
| 1428 | + 'avi': '视频文件', | ||
| 1429 | + 'mov': '视频文件', | ||
| 1430 | + 'jpg': '图片文件', | ||
| 1431 | + 'jpeg': '图片文件', | ||
| 1432 | + 'png': '图片文件', | ||
| 1433 | + 'gif': '图片文件' | ||
| 1434 | + }; | ||
| 1435 | + return typeMap[extension] || '未知文件'; | ||
| 1436 | +} | ||
| 1215 | </script> | 1437 | </script> |
| 1216 | 1438 | ||
| 1217 | <style lang="less" scoped> | 1439 | <style lang="less" scoped> | ... | ... |
-
Please register or login to post a comment