fix: 打包和样式调整

This commit is contained in:
Wxp 2025-08-19 19:47:12 +08:00
parent d6e76b7c73
commit f06aef9913
4 changed files with 375 additions and 152 deletions

View File

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 250 KiB

View File

@ -192,7 +192,7 @@
<!-- 错误状态 -->
<div v-else-if="commentsError" class="comments-error">
<p>{{ commentsError }}</p>
<button @click="loadCourseComments" class="retry-btn">重试</button>
<button @click="loadComments" class="retry-btn">重试</button>
</div>
<!-- 评论列表 -->
@ -211,8 +211,8 @@
<!-- 评论图片 -->
<div v-if="comment.images.length > 0" class="comment-images">
<img v-for="(image, index) in comment.images" :key="index"
:src="image" :alt="`评论图片${index + 1}`" />
<img v-for="(image, index) in comment.images" :key="index" :src="image"
:alt="`评论图片${index + 1}`" />
</div>
<div class="comment-actions">
@ -222,7 +222,9 @@
</button>
<button class="action-btn like-btn">
<svg width="14" height="14" viewBox="0 0 14 14" class="like-icon">
<path d="M7 12.5L6.125 11.75C3.5 9.375 1.75 7.75 1.75 5.75C1.75 4.25 2.875 3.125 4.375 3.125C5.25 3.125 6.125 3.5 7 4.25C7.875 3.5 8.75 3.125 9.625 3.125C11.125 3.125 12.25 4.25 12.25 5.75C12.25 7.75 10.5 9.375 7.875 11.75L7 12.5Z" fill="currentColor"/>
<path
d="M7 12.5L6.125 11.75C3.5 9.375 1.75 7.75 1.75 5.75C1.75 4.25 2.875 3.125 4.375 3.125C5.25 3.125 6.125 3.5 7 4.25C7.875 3.5 8.75 3.125 9.625 3.125C11.125 3.125 12.25 4.25 12.25 5.75C12.25 7.75 10.5 9.375 7.875 11.75L7 12.5Z"
fill="currentColor" />
</svg>
{{ comment.likeCount }}
</button>
@ -417,7 +419,7 @@ import { ref, onMounted, onUnmounted, computed, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { CourseApi } from '@/api/modules/course'
import type { Course, CourseSection, SectionVideo, VideoQuality, CourseComment, Instructor } from '@/api/types'
import type { Course, CourseSection, VideoQuality, CourseComment, Instructor } from '@/api/types'
import SafeAvatar from '@/components/common/SafeAvatar.vue'
import LearningProgressStats from '@/components/common/LearningProgressStats.vue'
import NotesModal from '@/components/common/NotesModal.vue'
@ -439,11 +441,8 @@ const currentSection = ref<CourseSection | null>(null)
const currentVideoUrl = ref<string>('')
//
const currentVideo = ref<SectionVideo | null>(null)
const videoQualities = ref<VideoQuality[]>([])
const currentQuality = ref<string>('360') // 360p
const videoLoading = ref(false)
const showQualityMenu = ref(false)
//
const comments = ref<CourseComment[]>([])
@ -451,9 +450,34 @@ const commentsLoading = ref(false)
const commentsError = ref('')
//
const instructors = ref<Instructor[]>([])
const instructorsLoading = ref(false)
const instructorsError = ref('')
const instructors = ref<Instructor[]>([
{
id: 1,
name: '汪波',
title: '教授',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=60&q=80',
rating: 4.8,
studentsCount: 1200,
coursesCount: 15,
experience: '10年教学经验',
education: ['博士学位'],
certifications: ['AI专家认证'],
bio: '人工智能领域专家,拥有丰富的教学经验'
},
{
id: 2,
name: '李老师',
title: '副教授',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=60&q=80',
rating: 4.6,
studentsCount: 800,
coursesCount: 12,
experience: '8年教学经验',
education: ['硕士学位'],
certifications: ['深度学习认证'],
bio: '深度学习专家,专注于实用技术教学'
}
])
//
const newComment = ref('')
@ -557,7 +581,7 @@ const totalSections = computed(() => {
const formatTotalDuration = () => {
//
let totalMinutes = 0
courseSections.value.forEach(section => {
courseSections.value.forEach((section: any) => {
if (section.duration) {
const parts = section.duration.split(':')
if (parts.length === 3) {
@ -680,7 +704,7 @@ const loadCourseSections = async () => {
console.log('✅ 分组数据:', groupedSections.value)
// 使
if (!FORCE_LOCAL_VIDEO) {
const firstVideo = courseSections.value.find(s => s.outline && (s.outline.includes('.m3u8') || s.outline.includes('.mp4')))
const firstVideo = courseSections.value.find((s: any) => s.outline && (s.outline.includes('.m3u8') || s.outline.includes('.mp4')))
if (firstVideo) {
currentSection.value = firstVideo
currentVideoUrl.value = getVideoUrl(firstVideo)
@ -736,77 +760,53 @@ const toggleChapter = (chapterIndex: number) => {
groupedSections.value[chapterIndex].expanded = !groupedSections.value[chapterIndex].expanded
}
//
const loadSectionVideo = async (section: CourseSection) => {
//
const loadComments = async () => {
try {
videoLoading.value = true
console.log('🔍 加载章节视频章节ID:', section.id)
commentsLoading.value = true
commentsError.value = ''
const response = await CourseApi.getSectionVideos(courseId.value, section.id)
console.log('🔍 视频API响应:', response)
if (response.code === 0 || response.code === 200) {
if (response.data && response.data.length > 0) {
const video = response.data[0] //
currentVideo.value = video
videoQualities.value = video.qualities
currentQuality.value = video.defaultQuality
// URL
const defaultQualityVideo = video.qualities.find(q => q.value === video.defaultQuality)
if (defaultQualityVideo) {
currentVideoUrl.value = defaultQualityVideo.url
console.log('✅ 设置视频URL:', currentVideoUrl.value)
//
await updateVideoPlayer()
}
} else {
console.warn('⚠️ 没有找到视频数据')
}
} else {
console.error('❌ 获取视频失败:', response.message)
//
const mockComments: CourseComment[] = [
{
id: '1',
content: '这个课程非常有用,老师讲解得很清楚!',
userId: '1',
userName: '学习者小王',
userAvatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
userTag: '学员',
images: [],
isTop: false,
likeCount: 23,
createTime: '2024-01-15T10:30:00Z',
timeAgo: '2天前'
},
{
id: '2',
content: '通过这个课程学到了很多实用的AI知识推荐',
userId: '2',
userName: 'AI爱好者',
userAvatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
userTag: '学员',
images: [],
isTop: false,
likeCount: 18,
createTime: '2024-01-12T14:20:00Z',
timeAgo: '5天前'
}
]
comments.value = mockComments
console.log('评论加载成功')
} catch (error) {
console.error('❌ 加载章节视频失败:', error)
console.error('加载评论失败:', error)
commentsError.value = '加载评论失败,请重试'
} finally {
videoLoading.value = false
commentsLoading.value = false
}
}
//
const changeVideoQuality = async (quality: string) => {
if (!currentVideo.value) return
const qualityVideo = currentVideo.value.qualities.find(q => q.value === quality)
if (qualityVideo) {
currentQuality.value = quality
currentVideoUrl.value = qualityVideo.url
console.log('🔍 切换清晰度到:', quality, 'URL:', qualityVideo.url)
//
await updateVideoPlayer()
}
}
//
const updateVideoPlayer = async () => {
if (!currentVideoUrl.value) {
console.warn('⚠️ 视频URL为空无法更新播放器')
return
}
try {
console.log('🔍 更新 DPlayer 视频源:', currentVideoUrl.value)
if (videoPlayerRef.value) {
// 使 DPlayer initializePlayer
await videoPlayerRef.value.initializePlayer(currentVideoUrl.value)
}
} catch (error) {
console.error('❌ 更新播放器失败:', error)
}
}
//
const getChapterNumber = (num: number) => {
@ -935,7 +935,7 @@ const handleVideoPlaySection = async (section: CourseSection) => {
if (!section.completed) {
section.completed = true
//
const completed = courseSections.value.filter(s => s.completed).length
const completed = courseSections.value.filter((s: any) => s.completed).length
completedLessons.value = completed
progress.value = Math.round((completed / courseSections.value.length) * 100)
}
@ -952,7 +952,7 @@ const handleDownload = (section: CourseSection) => {
//
if (!section.completed) {
section.completed = true
const completed = courseSections.value.filter(s => s.completed).length
const completed = courseSections.value.filter((s: any) => s.completed).length
completedLessons.value = completed
progress.value = Math.round((completed / courseSections.value.length) * 100)
}
@ -1073,8 +1073,7 @@ onMounted(async () => {
}
loadCourseDetail()
loadCourseSections()
loadCourseComments() //
loadCourseInstructors() //
loadComments() //
})
//
@ -1160,10 +1159,9 @@ onUnmounted(() => {
}
/* 课程信息区域 */
.course-info-section {
/* padding: 24px 0; */
padding: 0;
}
.course-header {
@ -1909,7 +1907,7 @@ onUnmounted(() => {
/* 课程标签页 */
.course-tabs {
/* margin-top: 32px; */
margin-top: 0;
}
.tab-nav {

View File

@ -3,11 +3,7 @@
<!-- 横幅图区域 -->
<div class="banner-section">
<div class="banner-container">
<img
src="/images/Featured_resources/精选资源轮播.png"
alt="精选资源横幅"
class="banner-image"
/>
<img src="/images/Featured_resources/精选资源轮播.png" alt="精选资源横幅" class="banner-image" />
</div>
</div>
@ -18,26 +14,18 @@
<section class="featured-videos">
<h2 class="section-title">精选视频</h2>
<div class="featured-grid">
<div
v-for="video in featuredVideos"
:key="video.id"
class="featured-card"
>
<div v-for="video in featuredVideos" :key="video.id" class="featured-card" @click="handleVideoClick(video)">
<div class="card-image">
<img
:src="video.image"
:alt="video.title"
class="video-thumbnail"
/>
<img :src="video.image" :alt="video.title" class="video-thumbnail" />
<div class="duration-badge">
<img src="/images/Featured_resources/duration.png" alt="时长" class="duration-icon">
42:52
</div>
<!-- <div class="play-button">
<div class="play-button">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M8 5V19L19 12L8 5Z" fill="white" />
</svg>
</div> -->
</div>
</div>
<div class="card-content">
<h3 class="card-title">{{ video.title }}</h3>
@ -51,37 +39,25 @@
<h2 class="section-title">全部视频</h2>
<!-- 筛选标签 -->
<div class="filter-tabs">
<button
v-for="tab in videoTabs"
:key="tab.id"
:class="['filter-tab', { active: activeVideoTab === tab.id }]"
@click="activeVideoTab = tab.id"
>
<button v-for="tab in videoTabs" :key="tab.id"
:class="['filter-tab', { active: activeVideoTab === tab.id }]" @click="activeVideoTab = tab.id">
{{ tab.name }}
</button>
</div>
<!-- 视频网格 -->
<div class="video-grid">
<div
v-for="video in allVideos"
:key="video.id"
class="video-card"
>
<div v-for="video in allVideos" :key="video.id" class="video-card" @click="handleVideoClick(video)">
<div class="card-image">
<img
:src="video.image"
:alt="video.title"
class="video-thumbnail"
/>
<img :src="video.image" :alt="video.title" class="video-thumbnail" />
<div class="duration-badge">
<img src="/images/Featured_resources/duration.png" alt="时长" class="duration-icon">
42:52
</div>
<!-- <div class="play-button">
<div class="play-button">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M8 5V19L19 12L8 5Z" fill="white" />
</svg>
</div> -->
</div>
</div>
<div class="card-content">
<h3 class="card-title">{{ video.title }}</h3>
@ -98,28 +74,16 @@
<h2 class="section-title">全部图片</h2>
<!-- 筛选标签 -->
<div class="filter-tabs">
<button
v-for="tab in imageTabs"
:key="tab.id"
:class="['filter-tab', { active: activeImageTab === tab.id }]"
@click="activeImageTab = tab.id"
>
<button v-for="tab in imageTabs" :key="tab.id"
:class="['filter-tab', { active: activeImageTab === tab.id }]" @click="activeImageTab = tab.id">
{{ tab.name }}
</button>
</div>
<!-- 图片网格 -->
<div class="image-grid">
<div
v-for="image in allImages"
:key="image.id"
class="image-card"
>
<div v-for="image in allImages" :key="image.id" class="image-card">
<div class="card-image">
<img
:src="image.image"
:alt="image.title"
class="image-thumbnail"
/>
<img :src="image.image" :alt="image.title" class="image-thumbnail" />
</div>
<div class="card-content">
<h3 class="card-title">{{ image.title }}</h3>
@ -132,28 +96,65 @@
</section>
</div>
</div>
<!-- 视频播放弹窗 -->
<div v-if="showVideoModal" class="video-modal-overlay" @click="closeVideoModal">
<div class="video-modal" @click.stop>
<div class="video-modal-header">
<h3 class="video-modal-title">{{ currentVideo?.title || '视频播放' }}</h3>
<button class="close-btn" @click="closeVideoModal">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
</div>
<div class="video-modal-body">
<DPlayerVideo ref="videoPlayerRef" :video-url="currentVideoUrl" :placeholder-image="currentVideo?.image"
:placeholder-text="'点击播放视频'" :title="currentVideo?.title || '视频播放'" @play="handleVideoPlay"
@pause="handleVideoPause" @ended="handleVideoEnded" @error="handleVideoError" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, nextTick } from 'vue'
import DPlayerVideo from '@/components/course/DPlayerVideo.vue'
//
const showVideoModal = ref(false)
const currentVideo = ref<any>(null)
const currentVideoUrl = ref('')
const videoPlayerRef = ref<InstanceType<typeof DPlayerVideo>>()
//
const VIDEO_CONFIG = {
// 使
LOCAL: '/video/first.mp4',
// HLS使
HLS: 'http://110.42.96.65:55513/learn/index.m3u8'
}
//
const featuredVideos = ref([
{
id: 1,
title: '西安工业大学内部资源之一',
image: '/images/Featured_resources/精选视频1.png'
image: '/images/Featured_resources/精选视频1.png',
videoUrl: VIDEO_CONFIG.LOCAL // URL
},
{
id: 2,
title: '华南工业大学内部资源之一',
image: '/images/Featured_resources/精选视频2.png'
image: '/images/Featured_resources/精选视频2.png',
videoUrl: VIDEO_CONFIG.LOCAL
},
{
id: 3,
title: '西安工业大学内部资源之一',
image: '/images/Featured_resources/精品视频3.png'
image: '/images/Featured_resources/精品视频3.png',
videoUrl: VIDEO_CONFIG.LOCAL
}
])
@ -173,42 +174,50 @@ const allVideos = ref([
{
id: 1,
title: '北京工业大学内部资源之一',
image: '/images/Featured_resources/全部视频1.png'
image: '/images/Featured_resources/全部视频1.png',
videoUrl: VIDEO_CONFIG.LOCAL
},
{
id: 2,
title: '北京工业大学内部资源之一',
image: '/images/Featured_resources/全部视频2.png'
image: '/images/Featured_resources/全部视频2.png',
videoUrl: VIDEO_CONFIG.LOCAL
},
{
id: 3,
title: '西安工业大学内部资源之一',
image: '/images/Featured_resources/全部视频3.png'
image: '/images/Featured_resources/全部视频3.png',
videoUrl: VIDEO_CONFIG.LOCAL
},
{
id: 4,
title: '北京工业大学内部资源之一',
image: '/images/Featured_resources/全部视频4.png'
image: '/images/Featured_resources/全部视频4.png',
videoUrl: VIDEO_CONFIG.LOCAL
},
{
id: 5,
title: '中国工业大学内部资源之一',
image: '/images/Featured_resources/全部视频5.png'
image: '/images/Featured_resources/全部视频5.png',
videoUrl: VIDEO_CONFIG.LOCAL
},
{
id: 6,
title: '西安工业大学内部资源之一',
image: '/images/Featured_resources/全部视频6.png'
image: '/images/Featured_resources/全部视频6.png',
videoUrl: VIDEO_CONFIG.LOCAL
},
{
id: 7,
title: '西安工业大学内部资源之一',
image: '/images/Featured_resources/全部视频7.png'
image: '/images/Featured_resources/全部视频7.png',
videoUrl: VIDEO_CONFIG.LOCAL
},
{
id: 8,
title: '内蒙古工业大学内部资源之一',
image: '/images/Featured_resources/全部视频8.png'
image: '/images/Featured_resources/全部视频8.png',
videoUrl: VIDEO_CONFIG.LOCAL
}
])
@ -266,6 +275,52 @@ const allImages = ref([
image: '/images/Featured_resources/全部图片8.png'
}
])
//
const handleVideoClick = async (video: any) => {
console.log('点击视频:', video.title)
currentVideo.value = video
currentVideoUrl.value = video.videoUrl || VIDEO_CONFIG.LOCAL
showVideoModal.value = true
//
await nextTick()
if (videoPlayerRef.value) {
await videoPlayerRef.value.initializePlayer(currentVideoUrl.value)
}
}
const closeVideoModal = () => {
showVideoModal.value = false
currentVideo.value = null
currentVideoUrl.value = ''
//
if (videoPlayerRef.value) {
videoPlayerRef.value.destroy()
}
}
// DPlayer
const handleVideoPlay = () => {
console.log('视频开始播放')
}
const handleVideoPause = () => {
console.log('视频暂停')
}
const handleVideoEnded = () => {
console.log('视频播放结束')
}
const handleVideoError = (error: any) => {
console.error('视频播放错误:', error)
//
if (currentVideoUrl.value !== VIDEO_CONFIG.LOCAL) {
currentVideoUrl.value = VIDEO_CONFIG.LOCAL
}
}
</script>
<style scoped>
@ -663,4 +718,150 @@ const allImages = ref([
font-size: 12px;
}
}
/* 视频播放弹窗样式 */
.video-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
padding: 20px;
}
.video-modal {
background: white;
border-radius: 12px;
overflow: hidden;
max-width: 90vw;
max-height: 90vh;
width: 1000px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
animation: modalFadeIn 0.3s ease-out;
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.video-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid #e5e7eb;
background: #f9fafb;
}
.video-modal-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
line-height: 1.4;
}
.close-btn {
background: none;
border: none;
padding: 8px;
border-radius: 6px;
cursor: pointer;
color: #666;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
background: #e5e7eb;
color: #333;
}
.video-modal-body {
padding: 0;
background: #000;
position: relative;
height: 60vh;
min-height: 400px;
}
/* 播放按钮显示样式 */
.play-button {
opacity: 0;
transition: opacity 0.3s ease;
}
.featured-card:hover .play-button,
.video-card:hover .play-button {
opacity: 1;
}
.featured-card .play-button {
width: 48px;
height: 48px;
}
.video-card .play-button {
width: 40px;
height: 40px;
}
/* 响应式设计 - 弹窗 */
@media (max-width: 768px) {
.video-modal {
width: 95vw;
max-width: none;
}
.video-modal-header {
padding: 16px 20px;
}
.video-modal-title {
font-size: 16px;
}
.video-modal-body {
height: 50vh;
min-height: 300px;
}
}
@media (max-width: 480px) {
.video-modal-overlay {
padding: 10px;
}
.video-modal {
width: 100%;
}
.video-modal-header {
padding: 12px 16px;
}
.video-modal-title {
font-size: 14px;
}
.video-modal-body {
height: 40vh;
min-height: 250px;
}
}
</style>

View File

@ -298,7 +298,13 @@ const loadCourses = async () => {
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '课程设计专家'
bio: '课程设计专家',
rating: 4.7,
studentsCount: 567,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '课程设计博士'],
certifications: ['高级课程设计师', '教育专家']
},
status: 'published',
createdAt: '2024-03-10T09:15:00Z',
@ -328,7 +334,13 @@ const loadCourses = async () => {
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '教育研究专家'
bio: '教育研究专家',
rating: 4.5,
studentsCount: 432,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '教育研究博士'],
certifications: ['高级教育研究专家', '学术顾问']
},
status: 'published',
createdAt: '2024-01-25T16:45:00Z',
@ -358,7 +370,13 @@ const loadCourses = async () => {
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '心理咨询专家'
bio: '心理咨询专家',
rating: 4.9,
studentsCount: 678,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '心理学博士'],
certifications: ['高级心理咨询师', '心理治疗师']
},
status: 'published',
createdAt: '2024-02-05T11:20:00Z',
@ -388,7 +406,13 @@ const loadCourses = async () => {
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '教育评估专家'
bio: '教育评估专家',
rating: 4.4,
studentsCount: 345,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '教育评估博士'],
certifications: ['高级教育评估师', '测量技术专家']
},
status: 'published',
createdAt: '2024-03-15T13:10:00Z',