OL-LearnPlatform/src/views/CourseDetail.vue
2025-08-04 02:13:12 +08:00

3162 lines
81 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="course-detail-page">
<!-- 面包屑导航 -->
<div class="breadcrumb">
<div class="container">
<span class="breadcrumb-text">首页 > 课程库 > {{ course?.title || '课程详情' }}</span>
</div>
</div>
<!-- 主要内容区域 -->
<div class="main-content">
<div class="container">
<div class="content-layout">
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<div class="loading-content">
<p>正在加载课程详情...</p>
</div>
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="error-container">
<div class="error-content">
<p>{{ error }}</p>
<button @click="loadCourseDetail" class="retry-btn">重试</button>
</div>
</div>
<!-- 课程内容 -->
<div v-else-if="course" class="course-content">
<!-- 左侧主要内容 -->
<div class="main-column">
<!-- 视频播放器区域 - 未报名状态 -->
<div class="video-player-section">
<div class="video-player unregistered">
<div class="video-background" :style="{ backgroundImage: course?.coverImage || course?.thumbnail ? `url(${course.coverImage || course.thumbnail})` : '' }">
<div class="video-content">
<!-- 课程主标题 -->
<h1 class="course-main-title">暑期名师领学,提高班级教学质量!高效冲分指南</h1>
<!-- 课程统计信息 -->
<div class="course-stats-info">
<span class="stats-item">
<span class="icon-chapters"></span>
共9章54节
</span>
<span class="stats-separator">|</span>
<span class="stats-item">
<span class="icon-duration"></span>
12小时43分钟
</span>
</div>
</div>
<!-- 按钮区域 - 中间偏下位置 -->
<div class="video-buttons-container">
<!-- 报名按钮 -->
<button class="enroll-button" @click="handleEnrollCourse">
立即报名
</button>
<!-- 未开始按钮 -->
<button class="not-started-button">
未开始
</button>
</div>
</div>
</div>
<!-- 底部交互区域 -->
<div class="video-interaction-bar">
<div class="interaction-left">
<button class="interaction-btn">
<span class="icon-like"></span>
<span>541</span>
</button>
<button class="interaction-btn">
<span class="icon-share"></span>
<span>2377</span>
</button>
<button class="interaction-btn">
<span class="icon-note"></span>
</button>
<button class="interaction-btn">
<span class="icon-download"></span>
</button>
</div>
<div class="interaction-right">
<div class="comment-input">
<input type="text" placeholder="想知道点什么?来发表你的看法吧~" />
<button class="send-btn">发送</button>
</div>
</div>
</div>
</div>
<!-- 课程信息区域 -->
<div class="course-info-section">
<!-- 课程标题 -->
<div class="course-header">
<h1 class="course-title">{{ course.title }}</h1>
<!-- 课程元信息 -->
<div class="course-meta">
<div class="meta-left">
<span class="meta-item">
分类:<span class="category-link">{{ course.category?.name || '信息技术' }}</span>
</span>
<span class="meta-separator">|</span>
<span class="meta-item">
<i class="icon-time"></i>
共{{ totalLessons }}章{{ totalSections }}节
</span>
<span class="meta-separator">|</span>
<span class="meta-item">
<i class="icon-duration"></i>
{{ formatTotalDuration() }}
</span>
</div>
<div class="meta-right">
<button class="btn-notes">
<i class="icon-note"></i>
记笔记
</button>
</div>
</div>
</div>
<!-- 课程描述 -->
<div class="course-description">
<p>{{ course.description || '本课程深度聚焦问题让每一位教师了解并学习使用DeepSeek结合办公自动化职业岗位标准以实际工作任务为引导强调课程内容的易用性和岗位要求的匹配性。课程内容与全国计算机等级考试、"1+X"WPS办公应用职业技能等级证书技能大赛紧密结合课程设置紧密对应实际全面共享可为职业工作人员、在校学生、创行教师提供服务与学习支持。' }}</p>
</div>
<!-- 讲师信息 -->
<div class="instructors-section">
<h3 class="section-title">讲师</h3>
<div class="instructors-list">
<div class="instructor-item" v-for="instructor in instructors" :key="instructor.id">
<div class="instructor-avatar">
<SafeAvatar
:src="instructor.avatar"
:name="instructor.name"
:size="50"
/>
</div>
<div class="instructor-info">
<div class="instructor-name">{{ instructor.name }}</div>
<div class="instructor-title">{{ instructor.title }}</div>
</div>
</div>
</div>
</div>
<!-- 课程标签页 -->
<div class="course-tabs">
<div class="tab-nav">
<button class="tab-btn" :class="{ active: activeTab === 'intro' }" @click="activeTab = 'intro'">课程介绍</button>
<button class="tab-btn" :class="{ active: activeTab === 'comments' }" @click="activeTab = 'comments'">评论(1251)</button>
</div>
<!-- 标签页内容区域 -->
<div class="tab-content">
<!-- 课程介绍内容 -->
<div v-if="activeTab === 'intro'" class="tab-pane">
<div class="intro-content">
<img src="/images/courses/课程介绍区.png" alt="课程介绍" class="course-intro-image" />
</div>
</div>
<!-- 评论内容 -->
<div v-if="activeTab === 'comments'" class="tab-pane">
<div class="comments-content">
<div class="comment-stats">
<span class="total-comments">共1251条评论</span>
<div class="comment-filters">
<button class="filter-btn active">全部</button>
<button class="filter-btn">最新</button>
<button class="filter-btn">最热</button>
</div>
</div>
<div class="comment-list">
<div class="comment-item" v-for="comment in displayComments" :key="comment.id">
<div class="comment-avatar">
<img :src="comment.avatar" :alt="comment.username" />
</div>
<div class="comment-content">
<div class="comment-header">
<span class="comment-username">{{ comment.username }}</span>
<span class="comment-time">{{ comment.time }}</span>
</div>
<div class="comment-text">{{ comment.content }}</div>
<div class="comment-actions">
<button class="action-btn">
<i class="icon-like"></i>
{{ comment.likes }}
</button>
<button class="action-btn">回复</button>
</div>
</div>
</div>
</div>
<div class="load-more">
<button class="btn-load-more">加载更多评论</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧边栏 -->
<div class="sidebar">
<!-- 报名学习按钮区域 -->
<div class="enroll-section">
<!-- 课程状态按钮 -->
<button class="btn-course-status" disabled>
课程未开始,暂不可报名
</button>
<!-- 报名学习按钮 -->
<button class="btn-enroll" @click="handleEnrollCourse">
报名学习
</button>
</div>
<!-- 课程章节列表 -->
<div class="course-sections">
<div class="sections-header">
<div class="header-left">
<h3 class="sections-title">课程章节</h3>
</div>
<div class="header-right">
<button class="sort-btn">
<svg width="16" height="16" viewBox="0 0 16 16" class="sort-icon">
<path d="M3 3h10M3 8h7M3 13h4" stroke="currentColor" stroke-width="1.5" fill="none"/>
</svg>
<span class="sort-text">正序</span>
</button>
</div>
</div>
<div class="sections-content">
<div v-if="sectionsLoading" class="sections-loading">
<p>正在加载章节列表...</p>
</div>
<div v-else-if="sectionsError" class="sections-error">
<p>{{ sectionsError }}</p>
<button @click="loadCourseSections" class="retry-btn">重试</button>
</div>
<div v-else-if="courseSections.length > 0" class="sections-list">
<!-- 按章节分组显示 -->
<div v-for="(chapter, chapterIndex) in groupedSections" :key="chapterIndex" class="chapter-section">
<div class="chapter-header" @click="toggleChapter(chapterIndex)">
<div class="chapter-info">
<span class="chapter-number">第{{ chapterIndex + 1 }}章</span>
<span class="chapter-title">{{ chapter.title }}</span>
</div>
<span class="chapter-toggle" :class="{ 'expanded': chapter.expanded }">
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M4 3l4 3-4 3" stroke="currentColor" stroke-width="1.5" fill="none"/>
</svg>
</span>
</div>
<div v-if="chapter.expanded" class="chapter-lessons">
<div v-for="section in chapter.sections" :key="section.id" class="lesson-item">
<div class="lesson-content" :class="{ 'unregistered': !isUserEnrolled }" @click="isUserEnrolled ? handleSectionClick(section) : handleUnregisteredClick(section)">
<div class="lesson-type-badge" :class="[getLessonTypeBadgeClass(section), { 'disabled': !isUserEnrolled }]">
{{ getLessonTypeText(section) }}
</div>
<div class="lesson-info">
<span class="lesson-title" :class="{ 'disabled': !isUserEnrolled }">{{ section.name }}</span>
</div>
<div class="lesson-meta">
<span v-if="isVideoLesson(section)" class="lesson-duration" :class="{ 'disabled': !isUserEnrolled }">{{ formatLessonDuration(section) }}</span>
<div class="lesson-actions">
<!-- 视频播放图标 -->
<button v-if="isVideoLesson(section)"
class="lesson-action-btn video-btn"
:class="{ 'disabled': !isUserEnrolled }"
:disabled="!isUserEnrolled"
@click.stop="isUserEnrolled ? handleSectionClick(section) : handleUnregisteredClick(section)">
<svg width="12" height="12" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5" fill="none"/>
<path d="M6 5l6 3-6 3V5z" fill="currentColor"/>
</svg>
</button>
<!-- 下载图标 -->
<button v-else-if="isResourceLesson(section)"
class="lesson-action-btn download-btn"
:class="{ 'disabled': !isUserEnrolled }"
:disabled="!isUserEnrolled"
@click.stop="isUserEnrolled ? handleDownload(section) : handleUnregisteredClick(section)">
<svg width="12" height="12" viewBox="0 0 16 16">
<path d="M8 1v10M4 7l4 4 4-4M2 14h12" stroke="currentColor" stroke-width="1.5" fill="none"/>
</svg>
</button>
<!-- 编辑图标(作业) -->
<button v-else-if="isHomeworkLesson(section)"
class="lesson-action-btn edit-btn"
:class="{ 'disabled': !isUserEnrolled }"
:disabled="!isUserEnrolled"
@click.stop="isUserEnrolled ? handleHomework(section) : handleUnregisteredClick(section)">
<svg width="12" height="12" viewBox="0 0 16 16">
<path d="M12 1l3 3-8 8-4 1 1-4 8-8z" stroke="currentColor" stroke-width="1.5" fill="none"/>
</svg>
</button>
<!-- 考试图标 -->
<button v-else-if="isExamLesson(section)"
class="lesson-action-btn exam-btn"
:class="{ 'disabled': !isUserEnrolled }"
:disabled="!isUserEnrolled"
@click.stop="isUserEnrolled ? handleExam(section) : handleUnregisteredClick(section)">
<svg width="12" height="12" viewBox="0 0 16 16">
<rect x="2" y="2" width="12" height="12" rx="2" stroke="currentColor" stroke-width="1.5" fill="none"/>
<path d="M6 6h4M6 8h4M6 10h2" stroke="currentColor" stroke-width="1.5"/>
</svg>
</button>
<!-- 完成状态图标 -->
<span v-if="section.completed" class="completion-icon" :class="{ 'disabled': !isUserEnrolled }">
<svg width="14" height="14" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="7" :fill="isUserEnrolled ? '#52c41a' : '#d9d9d9'"/>
<path d="M5 8l2 2 4-4" stroke="white" stroke-width="2" fill="none"/>
</svg>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else class="no-sections">
<p>暂无课程章节</p>
</div>
</div>
</div>
<!-- 更多课程 -->
<div class="more-courses">
<div class="more-courses-header">
<h3>更多课程</h3>
</div>
<div class="more-courses-list">
<!-- 计算机二级考前直播 -->
<div class="course-card">
<div class="course-cover">
<div class="course-image computer-bg">
<div class="course-title-overlay">计算机二级<br>考前直播</div>
<div class="course-tags">
<span class="tag tag-live">在线直播</span>
<span class="tag tag-replay">录播回放</span>
<span class="tag tag-qa">班群答疑</span>
</div>
<div class="live-time">直播时间06/20 · 晚19:00</div>
</div>
</div>
<div class="course-info">
<div class="course-desc">暑期名师领学,提高班级教学质量!高效冲分指南</div>
<div class="course-stats">
<span class="stats-item">
<i class="icon-chapters"></i>
共9章54节
</span>
<span class="stats-item">
<i class="icon-duration"></i>
12小时43分钟
</span>
</div>
<div class="course-footer">
<span class="enrolled-count">324人已报名</span>
<button class="btn-enroll-course">去报名</button>
</div>
</div>
</div>
<!-- 摆脱哑巴英语 -->
<div class="course-card">
<div class="course-cover">
<div class="course-image english-bg">
<div class="course-title-overlay">摆脱哑巴英语</div>
<div class="course-subtitle"># 练就地道口语 #</div>
<div class="course-english">GET RID OF DUMB ENGLISH</div>
</div>
</div>
<div class="course-info">
<div class="course-desc">暑期名师领学,提高班级教学质量!高效冲分指南</div>
<div class="course-stats">
<span class="stats-item">
<i class="icon-chapters"></i>
共9章54节
</span>
<span class="stats-item">
<i class="icon-duration"></i>
12小时43分钟
</span>
</div>
<div class="course-footer">
<span class="enrolled-count">324人已报名</span>
<button class="btn-enroll-course">去报名</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 登录模态框 -->
<LoginModal
v-model:show="loginModalVisible"
@success="handleAuthSuccess"
/>
<!-- 注册模态框 -->
<RegisterModal
v-model:show="registerModalVisible"
@success="handleAuthSuccess"
/>
<!-- 章节预览模态框 -->
<div v-if="previewModalVisible" class="preview-modal-overlay" @click="closePreviewModal">
<div class="preview-modal" @click.stop>
<div class="preview-modal-header">
<h3>{{ previewModalTitle }}</h3>
<button class="close-btn" @click="closePreviewModal">×</button>
</div>
<div class="preview-modal-content">
<div v-if="previewModalType === 'goals'" class="preview-goals">
<ul>
<li>掌握DeepSeek的基本使用方法</li>
<li>了解办公自动化职业岗位标准</li>
<li>提高教学质量和效率</li>
<li>获得实际工作技能</li>
</ul>
</div>
<div v-else-if="previewModalType === 'content'" class="preview-content" v-html="course?.content"></div>
<div v-else class="preview-text">{{ previewModalContent }}</div>
</div>
</div>
</div>
<!-- 报名确认弹窗 -->
<div v-if="enrollConfirmVisible" class="modal-overlay" @click="cancelEnrollment">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>确认报名</h3>
<button class="modal-close" @click="cancelEnrollment">×</button>
</div>
<div class="modal-body">
<p>确定要报名《{{ course?.title }}》课程吗?</p>
<p class="modal-tip">报名后您将获得完整的学习权限</p>
</div>
<div class="modal-footer">
<button class="btn-cancel" @click="cancelEnrollment">取消</button>
<button class="btn-confirm" @click="confirmEnrollment" :disabled="enrollmentLoading">
{{ enrollmentLoading ? '报名中...' : '确认报名' }}
</button>
</div>
</div>
</div>
<!-- 报名成功弹窗 -->
<div v-if="enrollSuccessVisible" class="modal-overlay">
<div class="modal-content success-modal">
<div class="success-icon"></div>
<h3>报名成功</h3>
<p>正在跳转到已报名状态页面...</p>
<p class="success-tip">您将看到彩色可点击的课程章节</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuth } from '@/composables/useAuth'
import { useUserStore } from '@/stores/user'
import { CourseApi } from '@/api/modules/course'
import type { Course, CourseSection } from '@/api/types'
import SafeAvatar from '@/components/common/SafeAvatar.vue'
import LoginModal from '@/components/auth/LoginModal.vue'
import RegisterModal from '@/components/auth/RegisterModal.vue'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const courseId = ref(Number(route.params.id))
const { loginModalVisible, registerModalVisible, handleAuthSuccess, showLoginModal } = useAuth()
// enrollCourse 暂时未使用,后续需要时再启用
// 当前选中的章节
const currentSection = ref<CourseSection | null>(null)
// 课程数据相关状态
const course = ref<Course | null>(null)
const loading = ref(false)
const error = ref('')
// 课程章节数据
const courseSections = ref<CourseSection[]>([])
const sectionsLoading = ref(false)
const sectionsError = ref('')
// 报名状态管理
const isEnrolled = ref(false) // 用户是否已报名该课程
const enrollmentLoading = ref(false) // 报名加载状态
// 计算用户是否已报名
const isUserEnrolled = computed(() => {
// 必须同时满足:用户已登录 AND 已报名该课程
return userStore.isLoggedIn && isEnrolled.value
// 临时测试不同状态:
// return false // 强制显示未报名状态(灰色不可点击)
// return true // 强制显示已报名状态(彩色可点击)
})
// 报名确认弹窗
const enrollConfirmVisible = ref(false)
const enrollSuccessVisible = ref(false)
// 章节分组数据
interface ChapterGroup {
title: string
sections: CourseSection[]
expanded: boolean
}
const groupedSections = ref<ChapterGroup[]>([])
// 生成模拟章节数据(用于演示)
const generateMockSections = (): CourseSection[] => {
return [
// 第一章 - 课前准备 (4个)
{ id: 1, lessonId: courseId.value, name: '开课彩蛋:新开始新征程', outline: 'https://example.com/video1.m3u8', parentId: 0, sort: 1, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '01:03:56' },
{ id: 2, lessonId: courseId.value, name: '课程定位与目标', outline: 'https://example.com/video2.m3u8', parentId: 0, sort: 2, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '00:44:05' },
{ id: 3, lessonId: courseId.value, name: '教学安排及学习建议', outline: 'https://example.com/video3.m3u8', parentId: 0, sort: 3, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '00:52:22' },
{ id: 4, lessonId: courseId.value, name: '课前准备PPT', outline: 'https://example.com/ppt1.ppt', parentId: 0, sort: 4, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
// 第二章 - 程序设计基础知识 (5个)
{ id: 5, lessonId: courseId.value, name: '第一课 程序设计入门', outline: 'https://example.com/video4.m3u8', parentId: 0, sort: 5, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '00:52:22' },
{ id: 6, lessonId: courseId.value, name: '操作PPT', outline: 'https://example.com/ppt2.ppt', parentId: 0, sort: 6, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 7, lessonId: courseId.value, name: '第二课 循环结构', outline: 'https://example.com/video5.m3u8', parentId: 0, sort: 7, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '01:03:56' },
{ id: 8, lessonId: courseId.value, name: '函数&循环', outline: '', parentId: 0, sort: 8, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 9, lessonId: courseId.value, name: '练习题目', outline: '', parentId: 0, sort: 9, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
// 第三章 - 程序的控制结构 (6个)
{ id: 10, lessonId: courseId.value, name: '条件语句详解', outline: 'https://example.com/video6.m3u8', parentId: 0, sort: 10, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:45:30' },
{ id: 11, lessonId: courseId.value, name: '循环语句应用', outline: 'https://example.com/video7.m3u8', parentId: 0, sort: 11, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:38:15' },
{ id: 12, lessonId: courseId.value, name: '控制结构参考资料', outline: 'https://example.com/ppt3.ppt', parentId: 0, sort: 12, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 13, lessonId: courseId.value, name: '条件判断练习', outline: '', parentId: 0, sort: 13, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 14, lessonId: courseId.value, name: '循环结构作业', outline: '', parentId: 0, sort: 14, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 15, lessonId: courseId.value, name: '控制结构测试', outline: '', parentId: 0, sort: 15, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
// 第四章 - 大语言模型介绍 (5个)
{ id: 16, lessonId: courseId.value, name: 'AI发展历程', outline: 'https://example.com/video8.m3u8', parentId: 0, sort: 16, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:12:45' },
{ id: 17, lessonId: courseId.value, name: '大语言模型原理', outline: 'https://example.com/video9.m3u8', parentId: 0, sort: 17, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:58:20' },
{ id: 18, lessonId: courseId.value, name: 'AI模型对比资料', outline: 'https://example.com/ppt4.ppt', parentId: 0, sort: 18, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 19, lessonId: courseId.value, name: 'AI应用场景分析', outline: '', parentId: 0, sort: 19, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 20, lessonId: courseId.value, name: '大语言模型考试', outline: '', parentId: 0, sort: 20, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
// 第五章 - DeepSeek实际应用 (6个)
{ id: 21, lessonId: courseId.value, name: 'DeepSeek平台介绍', outline: 'https://example.com/video10.m3u8', parentId: 0, sort: 21, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:42:10' },
{ id: 22, lessonId: courseId.value, name: 'API接口使用', outline: 'https://example.com/video11.m3u8', parentId: 0, sort: 22, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:55:35' },
{ id: 23, lessonId: courseId.value, name: '实战项目演示', outline: 'https://example.com/video12.m3u8', parentId: 0, sort: 23, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:25:18' },
{ id: 24, lessonId: courseId.value, name: 'DeepSeek开发文档', outline: 'https://example.com/ppt5.ppt', parentId: 0, sort: 24, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 25, lessonId: courseId.value, name: '项目实战作业', outline: '', parentId: 0, sort: 25, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 26, lessonId: courseId.value, name: 'DeepSeek应用考试', outline: '', parentId: 0, sort: 26, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
// 第六章 - 综合项目实战 (5个)
{ id: 27, lessonId: courseId.value, name: '项目需求分析', outline: 'https://example.com/video13.m3u8', parentId: 0, sort: 27, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:35:45' },
{ id: 28, lessonId: courseId.value, name: '系统架构设计', outline: 'https://example.com/video14.m3u8', parentId: 0, sort: 28, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:48:22' },
{ id: 29, lessonId: courseId.value, name: '项目开发指南', outline: 'https://example.com/ppt6.ppt', parentId: 0, sort: 29, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 30, lessonId: courseId.value, name: '综合项目作业', outline: '', parentId: 0, sort: 30, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
{ id: 31, lessonId: courseId.value, name: '期末综合考试', outline: '', parentId: 0, sort: 31, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined }
]
}
// 根据章节数据生成分组
const generateChapterGroups = () => {
// 确保有章节数据
if (courseSections.value.length === 0) {
console.log('没有章节数据,生成模拟数据')
courseSections.value = generateMockSections()
}
console.log('开始生成章节分组,原始数据:', courseSections.value)
console.log('章节数据数量:', courseSections.value.length)
// 手动创建章节分组,符合图片中的结构
const groups: ChapterGroup[] = [
{
title: '第一章 课前准备',
sections: courseSections.value.slice(0, 4), // 前4个项目
expanded: true
},
{
title: '第二章 程序设计基础知识',
sections: courseSections.value.slice(4, 9), // 5个项目
expanded: true
},
{
title: '第三章 程序的控制结构',
sections: courseSections.value.slice(9, 15), // 6个项目
expanded: false
},
{
title: '第四章 大语言模型介绍',
sections: courseSections.value.slice(15, 20), // 5个项目
expanded: false
},
{
title: '第五章 DeepSeek实际应用',
sections: courseSections.value.slice(20, 26), // 6个项目
expanded: false
},
{
title: '第六章 综合项目实战',
sections: courseSections.value.slice(26, 31), // 5个项目
expanded: false
}
]
console.log('生成的章节分组:', groups)
console.log('第一章节数:', groups[0].sections.length)
console.log('第二章节数:', groups[1].sections.length)
groupedSections.value = groups
}
// 获取章节标题
// const getChapterTitle = (chapterIndex: number): string => {
// const titles = [
// '课前准备',
// '程序设计基础知识',
// '程序的控制结构',
// '大话吉模型介绍',
// 'DeepSeek实际应用',
// 'DeepSeek实际应用'
// ]
// return titles[chapterIndex - 1] || '课程内容'
// }
// 预览模态框相关数据
const previewModalVisible = ref(false)
const previewModalTitle = ref('')
const previewModalContent = ref('')
const previewModalType = ref('')
// 新增的响应式数据
const activeTab = ref('intro')
// 讲师数据
const instructors = ref([
{
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'
},
{
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'
},
{
id: 3,
name: '汪波',
title: '教授',
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?ixlib=rb-4.0.3&auto=format&fit=crop&w=60&q=80'
}
])
// 计算属性
const totalLessons = computed(() => {
return groupedSections.value.length
})
const totalSections = computed(() => {
return courseSections.value.length
})
const formatTotalDuration = () => {
// 计算总时长
let totalMinutes = 0
courseSections.value.forEach(section => {
if (section.duration) {
const parts = section.duration.split(':')
if (parts.length === 3) {
const hours = parseInt(parts[0])
const minutes = parseInt(parts[1])
totalMinutes += hours * 60 + minutes
}
}
})
const hours = Math.floor(totalMinutes / 60)
const minutes = totalMinutes % 60
return `${hours}小时${minutes}分钟`
}
const displayComments = ref([
{
id: 1,
username: '学习者小王',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
time: '2天前',
content: '老师讲得很详细,从零基础到实际应用都有涉及,非常适合初学者!',
likes: 23
},
{
id: 2,
username: 'AI爱好者',
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
time: '5天前',
content: '课程内容很实用跟着做了几个项目收获很大。推荐给想学AI的朋友们',
likes: 18
},
{
id: 3,
username: '程序员小李',
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
time: '1周前',
content: 'DeepSeek确实是个很强大的工具通过这个课程学会了很多实用技巧。',
likes: 31
}
])
// 加载课程详情
const loadCourseDetail = async () => {
console.log('开始加载课程详情课程ID:', courseId.value)
if (!courseId.value || isNaN(courseId.value)) {
error.value = '课程ID无效'
console.error('课程ID无效:', courseId.value)
return
}
try {
loading.value = true
error.value = ''
console.log('调用API获取课程详情...')
const response = await CourseApi.getCourseById(courseId.value)
console.log('API响应:', response)
if (response.code === 0 || response.code === 200) {
course.value = response.data
console.log('课程数据设置成功:', course.value)
// 确保讲师和时长信息正确显示
if (course.value) {
if (!course.value.instructor?.name) {
course.value.instructor = {
id: 1,
name: 'DeepSeek技术学院',
title: '讲师',
bio: '',
avatar: '',
rating: 4.8,
studentsCount: 1000,
coursesCount: 10,
experience: '5年教学经验',
education: ['计算机科学硕士'],
certifications: ['高级讲师认证']
}
}
if (!course.value.duration || course.value.duration === '待定') {
course.value.duration = '59天'
}
}
} else {
error.value = response.message || '获取课程详情失败'
console.error('API返回错误:', response)
}
} catch (err) {
console.error('加载课程详情失败:', err)
error.value = '网络错误,请稍后重试'
} finally {
loading.value = false
}
}
// 加载课程章节列表
const loadCourseSections = async () => {
if (!courseId.value || isNaN(courseId.value)) {
sectionsError.value = '课程ID无效'
console.error('课程ID无效:', courseId.value)
return
}
try {
sectionsLoading.value = true
sectionsError.value = ''
console.log('开始加载课程章节课程ID:', courseId.value)
console.log('调用API: CourseApi.getCourseSections')
const response = await CourseApi.getCourseSections(courseId.value)
console.log('章节API响应:', response)
console.log('响应状态码:', response.code)
console.log('响应数据:', response.data)
if (response.code === 0 || response.code === 200) {
courseSections.value = response.data.list || []
console.log('章节数据设置成功,数量:', courseSections.value.length)
console.log('章节详细数据:', courseSections.value)
// 如果API返回的数据为空使用模拟数据
if (courseSections.value.length === 0) {
console.log('API返回数据为空使用模拟数据')
courseSections.value = generateMockSections()
}
// 生成章节分组
generateChapterGroups()
} else {
console.log('API调用失败使用模拟数据')
courseSections.value = generateMockSections()
generateChapterGroups()
sectionsError.value = '' // 清除错误,因为我们有模拟数据
}
} catch (err) {
console.error('加载课程章节失败:', err)
console.log('网络错误,使用模拟数据')
courseSections.value = generateMockSections()
generateChapterGroups()
sectionsError.value = '' // 清除错误,因为我们有模拟数据
} finally {
sectionsLoading.value = false
}
}
// 强制加载模拟数据
// const loadMockData = () => {
// console.log('强制加载模拟数据')
// courseSections.value = generateMockSections()
// generateChapterGroups()
// sectionsError.value = ''
// console.log('模拟数据加载完成,章节数量:', courseSections.value.length)
// console.log('分组数量:', groupedSections.value.length)
// }
// 切换章节展开/折叠
const toggleChapter = (chapterIndex: number) => {
if (groupedSections.value[chapterIndex]) {
groupedSections.value[chapterIndex].expanded = !groupedSections.value[chapterIndex].expanded
}
}
// 格式化时长
// const formatDuration = (sortOrder: number): string => {
// // 根据章节序号模拟时长
// const baseDuration = 5 + (sortOrder * 3) // 基础5分钟 + 序号*3分钟
// const minutes = baseDuration % 60
// const seconds = (sortOrder * 17) % 60 // 模拟秒数
// return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
// }
// 获取课时类型文本
const getLessonTypeText = (section: CourseSection): string => {
if (section.outline && section.outline.includes('ppt')) {
return '资料'
} else if (section.name.includes('作业') || section.name.includes('练习')) {
return '作业'
} else if (section.name.includes('考试') || section.name.includes('测试')) {
return '考试'
}
return '视频' // 默认为视频
}
// 格式化课时时长
const formatLessonDuration = (section: CourseSection): string => {
// 根据课时名称和类型生成合适的时长
const durations = [
'01:03:56', '00:44:05', '00:52:22', '', // 第一章时长
'00:52:22', '', '01:03:56', '', '' // 第二章时长
]
// 根据section.id获取对应时长
const durationIndex = section.id - 1
if (durationIndex >= 0 && durationIndex < durations.length) {
return durations[durationIndex] || ''
}
// 默认时长生成
if (isVideoLesson(section)) {
const minutes = Math.floor(Math.random() * 60) + 10 // 10-70分钟
const seconds = Math.floor(Math.random() * 60)
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
}
return '' // 非视频课时不显示时长
}
// 判断是否为视频课时
const isVideoLesson = (section: CourseSection): boolean => {
return !!(section.outline && section.outline.includes('.m3u8'))
}
// 判断是否为资料课时
const isResourceLesson = (section: CourseSection): boolean => {
return !!(section.outline && section.outline.includes('ppt')) || section.name.includes('PPT')
}
// 判断是否为作业课时
const isHomeworkLesson = (section: CourseSection): boolean => {
return section.name.includes('作业') || section.name.includes('练习') || section.name.includes('题目') || section.name.includes('分析')
}
// 判断是否为考试课时
const isExamLesson = (section: CourseSection): boolean => {
return section.name.includes('考试') || section.name.includes('测试') || section.name.includes('函数&循环')
}
// 获取课时类型徽章样式类
const getLessonTypeBadgeClass = (section: CourseSection): string => {
if (isVideoLesson(section)) {
return 'badge-video'
} else if (isResourceLesson(section)) {
return 'badge-resource'
} else if (isHomeworkLesson(section)) {
return 'badge-homework'
} else if (isExamLesson(section)) {
return 'badge-exam'
}
return 'badge-video' // 默认为视频
}
// 处理下载操作
const handleDownload = (section: CourseSection) => {
console.log('下载资料:', section)
// 这里可以实现下载逻辑
alert(`下载资料: ${section.name}`)
}
// 处理作业操作
const handleHomework = (section: CourseSection) => {
console.log('打开作业:', section)
// 这里可以跳转到作业页面
alert(`打开作业: ${section.name}`)
}
// 处理考试操作
const handleExam = (section: CourseSection) => {
console.log('开始考试:', section)
// 跳转到考前须知页面
router.push({
name: 'ExamNotice',
params: {
courseId: courseId.value,
sectionId: section.id
},
query: {
courseName: course.value?.title || '课程名称',
examName: section.name
}
})
}
// 点击课程章节标题
const handleSectionClick = (section: CourseSection) => {
console.log('点击课程章节:', section)
// 设置当前选中的章节
currentSection.value = section
// 检查是否有视频链接
if (section.outline && section.outline.includes('.m3u8')) {
console.log('获取到视频链接:', section.outline)
// 跳转到已报名区域并播放视频
navigateToEnrolledArea(section.outline, section.name)
} else {
// 如果不是视频,显示预览
previewSection(section)
}
}
// 跳转到已报名区域
const navigateToEnrolledArea = (videoUrl: string, sectionName: string) => {
console.log('跳转到已报名区域,播放视频:', videoUrl)
console.log('章节名称:', sectionName)
console.log('当前章节:', currentSection.value)
// 使用路由跳转到学习页面
router.push({
name: 'CourseStudy',
params: { id: courseId.value },
query: {
videoUrl: encodeURIComponent(videoUrl),
sectionName: encodeURIComponent(sectionName),
sectionId: currentSection.value?.id
}
})
}
// 更新视频播放器(备用方案)
// const updateVideoPlayer = (videoUrl: string, sectionName: string) => {
// console.log('更新视频播放器:', { videoUrl, sectionName })
// // 如果在同一页面内更新视频播放器
// // 可以通过事件总线或状态管理来实现
// // 这里先显示确认信息
// const confirmed = confirm(`即将播放视频: ${sectionName}\n是否继续`)
// if (confirmed) {
// navigateToEnrolledArea(videoUrl, sectionName)
// }
// }
// 预览章节(非视频内容)
const previewSection = (section: CourseSection) => {
console.log('预览章节:', section)
previewModalTitle.value = section.name
previewModalContent.value = `章节ID: ${section.id}\n章节名称: ${section.name}\n内容类型: ${getLessonTypeText(section)}`
previewModalType.value = 'section'
previewModalVisible.value = true
}
// 关闭预览模态框
const closePreviewModal = () => {
previewModalVisible.value = false
previewModalTitle.value = ''
previewModalContent.value = ''
previewModalType.value = ''
}
// 处理课程报名
const handleEnrollCourse = () => {
if (!userStore.isLoggedIn) {
// 未登录,显示登录弹窗
showLoginModal()
return
}
if (isEnrolled.value) {
// 已报名,跳转到学习页面
console.log('用户已报名,跳转到学习页面')
router.push(`/course/${courseId.value}/study`)
return
}
// 未报名,显示报名确认弹窗
console.log('用户未报名,显示报名确认弹窗')
enrollConfirmVisible.value = true
}
// 确认报名
const confirmEnrollment = async () => {
try {
enrollmentLoading.value = true
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000))
// 报名成功
isEnrolled.value = true
enrollConfirmVisible.value = false
enrollSuccessVisible.value = true
// 2秒后跳转到已报名状态的课程详情页面
setTimeout(() => {
enrollSuccessVisible.value = false
// 跳转到已报名状态页面
router.push(`/course/${courseId.value}/enrolled`)
}, 2000)
} catch (error) {
console.error('报名失败:', error)
} finally {
enrollmentLoading.value = false
}
}
// 取消报名
const cancelEnrollment = () => {
enrollConfirmVisible.value = false
}
// 处理未报名用户点击
const handleUnregisteredClick = (section: CourseSection) => {
console.log('未报名用户点击课程:', section.name)
// 显示报名提示
enrollConfirmVisible.value = true
}
// 测试直接API调用
// const testDirectApiCall = async () => {
// console.log('=== 开始测试直接API调用 ===')
// console.log('课程ID:', courseId.value)
// try {
// // 使用axios直接调用API
// const axios = (await import('axios')).default
// const url = `http://110.42.96.65:55510/api/lesson/section/list?lesson_id=${courseId.value}`
// console.log('请求URL:', url)
// const response = await axios.get(url)
// console.log('直接API调用成功:', response.data)
// alert('API调用成功请查看控制台')
// } catch (error) {
// console.error('直接API调用失败:', error)
// alert('API调用失败请查看控制台')
// }
// }
// 初始化模拟状态(用于演示)
const initializeMockState = () => {
// 模拟用户已登录
if (!userStore.isLoggedIn) {
userStore.user = {
id: 1,
username: 'testuser',
email: 'test@example.com',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
role: 'student',
status: 'active',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
userStore.token = 'mock-token'
}
// 模拟用户未报名状态,可以测试完整的报名流程
isEnrolled.value = false // false=未报名状态true=已报名状态
}
onMounted(() => {
console.log('课程详情页加载完成课程ID:', courseId.value)
initializeMockState() // 初始化模拟状态
loadCourseDetail()
loadCourseSections()
})
</script>
<style scoped>
.course-detail-page {
min-height: 100vh;
background: #f5f7fa;
}
.loading-container,
.error-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
text-align: center;
}
.loading-content p,
.error-content p {
font-size: 16px;
color: #666;
margin-bottom: 20px;
}
.retry-btn {
background: #1890ff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.retry-btn:hover {
background: #40a9ff;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 0 32px;
}
.breadcrumb {
background: white;
padding: 12px 0;
border-bottom: 1px solid #e8e8e8;
}
.breadcrumb-text {
color: #666;
font-size: 14px;
}
.main-content {
padding: 20px 0;
}
.content-layout {
display: flex;
gap: 40px;
align-items: flex-start;
}
.course-content {
display: flex;
gap: 30px;
width: 100%;
}
.main-column {
flex: 1;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.sidebar {
width: 370px;
flex-shrink: 0;
}
/* 状态指示器 */
.status-indicator {
background: white;
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.status-item:last-child {
margin-bottom: 0;
}
.status-label {
font-size: 14px;
color: #666;
}
.status-value {
font-size: 14px;
font-weight: 600;
padding: 2px 8px;
border-radius: 4px;
}
.status-success {
background: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
.status-error {
background: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffb3b3;
}
/* 视频播放器区域 */
.video-player-section {
position: relative;
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.video-player.unregistered {
height: 578px;
position: relative;
}
.video-background {
width: 100%;
height: 100%;
position: relative;
/* 背景图片设置 */
background-size: cover;
background-position: center;
background-repeat: no-repeat;
/* 如果没有背景图片,使用默认渐变背景 */
background-image:
radial-gradient(ellipse at 30% 40%, rgba(59, 130, 246, 0.4) 0%, transparent 50%),
radial-gradient(ellipse at 70% 60%, rgba(34, 197, 94, 0.3) 0%, transparent 50%),
radial-gradient(ellipse at 50% 80%, rgba(168, 85, 247, 0.2) 0%, transparent 50%),
linear-gradient(135deg, #1e3a8a 0%, #1e40af 30%, #1d4ed8 60%, #2563eb 100%);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.video-background::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 30%, rgba(59, 130, 246, 0.15) 0%, transparent 40%),
radial-gradient(circle at 80% 70%, rgba(34, 197, 94, 0.1) 0%, transparent 40%),
linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.5) 100%);
backdrop-filter: blur(1px);
}
.video-background::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(ellipse 800px 600px at 40% 50%, rgba(59, 130, 246, 0.08) 0%, transparent 70%),
radial-gradient(ellipse 600px 400px at 60% 30%, rgba(34, 197, 94, 0.06) 0%, transparent 70%);
pointer-events: none;
}
/* 按钮容器 - 中间偏下位置 */
.video-buttons-container {
position: absolute;
bottom: 15%;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
z-index: 10;
}
.video-content {
position: relative;
z-index: 2;
text-align: center;
color: white;
padding: 40px 20px;
max-width: 800px;
width: 100%;
}
.course-main-title {
font-size: 36px;
font-weight: 500;
margin-bottom: 24px;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
line-height: 1.3;
color: #ffffff;
letter-spacing: 0.5px;
}
/* 课程统计信息样式 */
.course-stats-info {
margin-bottom: 36px;
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 12px;
}
.course-stats-info .stats-item {
color: rgba(255, 255, 255, 0.85);
display: flex;
align-items: center;
gap: 6px;
font-weight: 400;
}
.course-stats-info .stats-separator {
color: rgba(255, 255, 255, 0.5);
margin: 0 16px;
}
/* 图标透明度调整 */
.course-stats-info .icon-chapters,
.course-stats-info .icon-duration {
opacity: 0.9;
}
.course-meta-info .meta-separator {
margin: 0 12px;
color: rgba(255, 255, 255, 0.6);
white-space: nowrap;
}
.enroll-button {
background: #1890ff;
color: white;
border: none;
padding: 0;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
width: 112px;
height: 42px;
letter-spacing: 0.2px;
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.3);
text-align: center;
line-height: 42px;
}
.enroll-button:hover {
background: #40a9ff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.4);
}
.not-started-button {
background: #8c8c8c;
color: white;
border: none;
padding: 0;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
cursor: default;
transition: all 0.3s ease;
width: 112px;
height: 42px;
letter-spacing: 0.2px;
box-shadow: 0 2px 8px rgba(140, 140, 140, 0.3);
text-align: center;
line-height: 42px;
}
.not-started-button:hover {
background: #595959;
}
/* 底部交互区域 */
.video-interaction-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
background: #ffffff;
border-top: 1px solid #e5e7eb;
min-height: 60px;
}
.interaction-left {
display: flex;
align-items: center;
gap: 20px;
}
.interaction-btn {
display: flex;
align-items: center;
gap: 6px;
background: none;
border: none;
color: #9ca3af;
font-size: 14px;
cursor: pointer;
padding: 8px 6px;
border-radius: 4px;
transition: all 0.2s;
font-weight: 400;
}
.interaction-btn:hover {
background: #f9fafb;
color: #6b7280;
}
/* 交互按钮图标样式已在各自的图标类中定义 */
.interaction-right {
flex: 1;
max-width: 500px;
margin-left: 40px;
}
.comment-input {
position: relative;
display: flex;
align-items: center;
width: 100%;
}
.comment-input input {
width: 100%;
padding: 12px 80px 12px 20px;
border: 1px solid #e5e7eb;
border-radius: 22px;
font-size: 14px;
background: #f8f9fa;
outline: none;
transition: all 0.2s;
height: 44px;
color: #374151;
box-sizing: border-box;
}
.comment-input input:focus {
border-color: #d1d5db;
background: #ffffff;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.comment-input input::placeholder {
color: #9ca3af;
font-size: 14px;
}
.send-btn {
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
background: #9ca3af;
color: white;
border: none;
padding: 8px 16px;
border-radius: 18px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
height: 36px;
min-width: 60px;
font-weight: 400;
z-index: 1;
}
.send-btn:hover {
background: #6b7280;
}
/* 图标样式 - 使用图片替换 */
.icon-chapters {
width: 16px;
height: 16px;
background-image: url('/images/courses/课程总章数.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
vertical-align: middle;
margin-right: 4px;
flex-shrink: 0;
}
.icon-duration {
width: 16px;
height: 16px;
background-image: url('/images/courses/课程总时长.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
vertical-align: middle;
margin-right: 4px;
flex-shrink: 0;
}
.icon-like {
width: 18px;
height: 18px;
background-image: url('/images/courses/底部交互区1.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
opacity: 0.7;
}
.icon-share {
width: 18px;
height: 18px;
background-image: url('/images/courses/底部交互区2.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
opacity: 0.7;
}
.icon-note {
width: 18px;
height: 18px;
background-image: url('/images/courses/底部交互区3.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
opacity: 0.7;
}
.icon-download {
width: 18px;
height: 18px;
background-image: url('/images/courses/底部交互区4.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
opacity: 0.7;
}
/* 课程信息区域 */
.course-info-section {
padding: 24px;
background: white;
}
.course-header {
margin-bottom: 24px;
}
.course-title {
font-size: 28px;
font-weight: 700;
color: #333;
margin-bottom: 16px;
line-height: 1.3;
}
.course-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.meta-left {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.meta-right {
display: flex;
align-items: center;
}
.meta-item {
font-size: 14px;
color: #666;
display: flex;
align-items: center;
gap: 4px;
}
.meta-separator {
color: #d9d9d9;
margin: 0 4px;
}
.category-link {
color: #1890ff;
text-decoration: none;
cursor: pointer;
}
.category-link:hover {
text-decoration: underline;
}
.icon-time,
.icon-duration,
.icon-note {
width: 14px;
height: 14px;
display: inline-block;
}
/* 这些图标样式已被替换为背景图片 */
.btn-notes {
background: #f8f9fa;
border: 1px solid #e9ecef;
padding: 6px 12px;
border-radius: 4px;
font-size: 14px;
color: #495057;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 4px;
}
.btn-notes:hover {
background: #e9ecef;
border-color: #dee2e6;
}
/* 课程描述 */
.course-description {
margin-bottom: 32px;
line-height: 1.8;
color: #333;
font-size: 15px;
}
.course-content-detail {
margin-top: 16px;
padding: 16px;
background: #f8f9fa;
border-radius: 6px;
}
.course-content-detail h4 {
margin-bottom: 12px;
color: #333;
}
/* 讲师信息 */
.instructors-section {
margin-bottom: 32px;
padding-bottom: 24px;
border-bottom: 1px solid #f0f0f0;
}
.section-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 16px;
}
.instructors-list {
display: flex;
gap: 20px;
align-items: center;
}
.instructor-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.instructor-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
overflow: hidden;
border: 2px solid #f0f0f0;
}
.instructor-info {
text-align: center;
}
.instructor-name {
font-size: 12px;
font-weight: 500;
color: #333;
margin-bottom: 1px;
}
.instructor-title {
font-size: 11px;
color: #999;
}
/* 课程标签页 */
.course-tabs {
margin-top: 24px;
}
.tab-nav {
display: flex;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 24px;
}
.tab-btn {
background: none;
border: none;
padding: 12px 24px;
font-size: 16px;
color: #666;
cursor: pointer;
position: relative;
transition: color 0.3s;
}
.tab-btn.active {
color: #1890ff;
font-weight: 600;
}
.tab-btn.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background: #1890ff;
}
.tab-btn:hover {
color: #1890ff;
}
.tab-content {
min-height: 300px;
}
.intro-content {
text-align: center;
}
.course-intro-image {
width: 100%;
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.intro-content h4 {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 20px 0 12px 0;
}
.intro-content p {
line-height: 1.6;
color: #666;
margin-bottom: 16px;
}
.intro-content ul {
padding-left: 20px;
margin-bottom: 16px;
}
.intro-content li {
line-height: 1.6;
color: #666;
margin-bottom: 8px;
}
/* 右侧边栏课程章节 */
.sidebar .course-sections {
background: white;
border-radius: 8px;
padding: 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.sections-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background: white;
border-bottom: 1px solid #f0f0f0;
}
.header-left {
display: flex;
align-items: center;
}
.sections-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
}
.header-right {
display: flex;
align-items: center;
}
.sections-actions {
display: flex;
align-items: center;
}
.sort-btn {
background: none;
border: none;
color: #999;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 6px;
}
.sort-btn:hover {
background: #f5f5f5;
color: #666;
}
.sort-icon {
width: 16px;
height: 16px;
}
.sort-text {
font-size: 14px;
}
.refresh-btn, .test-btn, .mock-btn {
background: #1890ff;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.3s;
}
.refresh-btn:hover, .test-btn:hover, .mock-btn:hover {
background: #40a9ff;
}
.mock-btn {
background: #52c41a;
}
.mock-btn:hover {
background: #73d13d;
}
.sections-loading,
.sections-error,
.no-sections {
text-align: center;
padding: 16px;
color: #666;
font-size: 14px;
}
.sections-error .retry-btn {
margin-top: 8px;
background: #1890ff;
color: white;
border: none;
padding: 6px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.sections-error .retry-btn:hover {
background: #40a9ff;
}
/* 章节列表样式 */
.sections-content {
background: white;
}
.sections-list {
max-height: 600px;
overflow-y: auto;
}
.sections-list::-webkit-scrollbar {
width: 4px;
}
.sections-list::-webkit-scrollbar-track {
background: transparent;
}
.sections-list::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 2px;
}
.sections-list::-webkit-scrollbar-thumb:hover {
background: #bfbfbf;
}
.chapter-section {
border-bottom: 1px solid #f0f0f0;
}
.chapter-section:last-child {
border-bottom: none;
}
.chapter-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background: white;
cursor: pointer;
transition: background-color 0.2s;
border-bottom: 1px solid #f5f5f5;
}
.chapter-header:hover {
background: #fafafa;
}
.chapter-info {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.chapter-number {
font-size: 14px;
font-weight: 600;
color: #333;
min-width: 60px;
}
.chapter-title {
font-size: 14px;
font-weight: 500;
color: #333;
flex: 1;
}
.chapter-toggle {
color: #999;
transition: transform 0.2s ease;
display: flex;
align-items: center;
padding: 4px;
}
.chapter-toggle.expanded {
transform: rotate(90deg);
}
.chapter-lessons {
background: white;
}
.lesson-item {
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s;
}
.lesson-item:last-child {
border-bottom: none;
}
.lesson-item:hover {
background: #f9f9f9;
}
.lesson-content {
display: flex;
align-items: center;
padding: 12px 20px 12px 40px;
cursor: pointer;
gap: 12px;
}
.lesson-type-badge {
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
font-weight: 500;
min-width: 40px;
text-align: center;
line-height: 1;
flex-shrink: 0;
}
.lesson-info {
flex: 1;
min-width: 0;
}
.lesson-title {
font-size: 14px;
color: #333;
transition: color 0.2s;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.lesson-content:hover .lesson-title {
color: #1890ff;
}
/* 未报名状态的灰色样式 */
.lesson-content.unregistered {
cursor: not-allowed;
}
.lesson-title.disabled {
color: #999;
}
.lesson-duration.disabled {
color: #999;
}
.lesson-type-badge.disabled {
background: #d9d9d9 !important;
color: #999 !important;
}
.lesson-action-btn.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.lesson-action-btn.disabled:hover {
background: none;
}
.lesson-action-btn.disabled svg {
color: #d9d9d9 !important;
}
.completion-icon.disabled {
opacity: 0.5;
}
.lesson-meta {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.lesson-duration {
font-size: 12px;
color: #666;
min-width: 60px;
text-align: right;
}
.lesson-actions {
display: flex;
align-items: center;
gap: 8px;
}
/* 课时类型徽章样式 */
.badge-video {
background: #1890ff;
color: white;
}
.badge-resource {
background: #f5f5f5;
color: #666;
}
.badge-homework {
background: #1890ff;
color: white;
}
.badge-exam {
background: #1890ff;
color: white;
}
/* 课时操作按钮样式 */
.lesson-action-btn {
background: none;
border: none;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.lesson-action-btn:hover {
background: #f0f0f0;
}
.video-btn svg {
color: #1890ff;
}
.download-btn svg {
color: #52c41a;
}
.edit-btn svg {
color: #1890ff;
}
.exam-btn svg {
color: #1890ff;
}
/* 完成状态图标 */
.completion-icon {
display: flex;
align-items: center;
justify-content: center;
}
.lesson-title {
font-size: 13px;
color: #333;
line-height: 1.4;
flex: 1;
font-weight: 400;
}
.lesson-actions {
display: flex;
align-items: center;
gap: 16px;
}
.lesson-duration {
font-size: 12px;
color: #999;
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
min-width: 50px;
text-align: right;
font-weight: 400;
}
.lesson-action-btn {
background: none;
border: none;
color: #52c41a;
cursor: pointer;
padding: 4px;
border-radius: 50%;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
}
.lesson-action-btn:hover {
background: #f6ffed;
transform: scale(1.1);
}
.lesson-action-btn.action-play {
color: #52c41a;
}
.lesson-action-btn.action-play:hover {
background: #f6ffed;
color: #52c41a;
}
.lesson-action-btn.action-download {
color: #52c41a;
}
.lesson-action-btn.action-download:hover {
background: #f6ffed;
color: #52c41a;
}
/* 预览模态框 */
.preview-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.preview-modal {
background: white;
border-radius: 8px;
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.preview-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid #f0f0f0;
background: #fafafa;
}
.preview-modal-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
color: #999;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.3s;
}
.close-btn:hover {
background: #f0f0f0;
color: #666;
}
.preview-modal-content {
padding: 24px;
max-height: 60vh;
overflow-y: auto;
}
.preview-text {
font-size: 16px;
line-height: 1.6;
color: #666;
}
.preview-goals ul {
margin: 0;
padding-left: 20px;
}
.preview-goals li {
font-size: 16px;
line-height: 1.8;
color: #666;
margin-bottom: 8px;
}
.preview-content {
font-size: 16px;
line-height: 1.6;
color: #666;
}
.preview-content h4 {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 16px 0 12px 0;
}
.preview-content ul {
margin: 12px 0;
padding-left: 20px;
}
.preview-content li {
margin-bottom: 8px;
line-height: 1.6;
}
/* 课程大纲样式 */
.course-outline-content {
margin-top: 16px;
}
.outline-list {
list-style: none;
padding-left: 0;
}
.outline-list > li {
margin-bottom: 20px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #1890ff;
}
.outline-list > li > strong {
display: block;
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
}
.outline-list > li > ul {
list-style: none;
padding-left: 0;
margin: 0;
}
.outline-list > li > ul > li {
margin-bottom: 6px;
padding-left: 16px;
color: #666;
font-size: 14px;
line-height: 1.5;
position: relative;
}
.outline-list > li > ul > li:before {
content: "•";
color: #1890ff;
font-weight: bold;
position: absolute;
left: 0;
}
/* 评论区 */
.comments-content {
padding: 0;
}
.comment-stats {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.total-comments {
font-size: 16px;
font-weight: 600;
color: #333;
}
.comment-filters {
display: flex;
gap: 16px;
}
.filter-btn {
background: none;
border: none;
padding: 6px 12px;
font-size: 14px;
color: #666;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
}
.filter-btn.active,
.filter-btn:hover {
background: #e6f7ff;
color: #1890ff;
}
.comment-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.comment-item {
display: flex;
gap: 12px;
}
.comment-avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.comment-content {
flex: 1;
}
.comment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.comment-username {
font-size: 14px;
font-weight: 600;
color: #333;
}
.comment-time {
font-size: 12px;
color: #999;
}
.comment-text {
font-size: 14px;
line-height: 1.6;
color: #666;
margin-bottom: 12px;
}
.comment-actions {
display: flex;
gap: 16px;
}
.action-btn {
background: none;
border: none;
font-size: 12px;
color: #999;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
transition: color 0.3s;
}
.action-btn:hover {
color: #1890ff;
}
.load-more {
text-align: center;
margin-top: 24px;
}
.btn-load-more {
background: #f0f0f0;
border: none;
padding: 8px 24px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-load-more:hover {
background: #d9d9d9;
}
/* 右侧边栏 */
.sidebar {
display: flex;
flex-direction: column;
gap: 20px;
}
.enroll-section {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
text-align: center;
}
.btn-enroll {
width: 100%;
background: #1890ff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-enroll:hover {
background: #40a9ff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
.btn-course-status {
width: 100%;
background: #f5f5f5;
color: #999;
border: 1px solid #e8e8e8;
padding: 12px 24px;
border-radius: 6px;
font-size: 14px;
font-weight: 400;
cursor: not-allowed;
margin-bottom: 12px;
transition: all 0.3s;
}
.btn-course-status:disabled {
background: #f5f5f5;
color: #999;
border: 1px solid #e8e8e8;
}
/* 更多课程 */
.more-courses {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.more-courses-header h3 {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
text-align: center;
}
.more-courses-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.course-card {
border-radius: 12px;
overflow: hidden;
transition: all 0.3s ease;
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.course-card:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.course-cover {
position: relative;
height: 200px;
}
.course-image {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
color: white;
font-weight: 600;
position: relative;
padding: 20px;
box-sizing: border-box;
}
.computer-bg {
background: linear-gradient(135deg, #87CEEB 0%, #4682B4 100%);
}
.english-bg {
background: linear-gradient(135deg, #4A5568 0%, #2D3748 100%);
}
.course-title-overlay {
font-size: 28px;
font-weight: bold;
text-align: left;
line-height: 1.2;
margin-bottom: auto;
}
.course-tags {
display: flex;
gap: 8px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.tag {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
background: #FA8C16;
color: white;
}
.live-time {
font-size: 14px;
color: white;
font-weight: 500;
margin-bottom: 0;
}
.course-subtitle {
font-size: 16px;
color: #FFD700;
margin-bottom: 8px;
font-weight: 600;
}
.course-english {
font-size: 12px;
opacity: 0.9;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: auto;
}
.course-info {
padding: 16px 20px 20px 20px;
}
.course-desc {
font-size: 14px;
color: #333;
line-height: 1.5;
margin-bottom: 12px;
font-weight: 500;
}
.course-stats {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
.stats-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #999;
}
/* 这些图标样式已被替换为背景图片 */
.course-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.enrolled-count {
font-size: 14px;
color: #999;
}
.btn-enroll-course {
background: #1890ff;
color: white;
border: none;
padding: 8px 20px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-enroll-course:hover {
background: #40a9ff;
transform: translateY(-1px);
}
/* 响应式设计 */
@media (max-width: 1399px) and (min-width: 1200px) {
.container {
padding: 0 24px;
max-width: 1200px;
}
.sidebar {
width: 350px;
}
}
@media (max-width: 1199px) and (min-width: 992px) {
.container {
padding: 0 20px;
max-width: 992px;
}
.course-content {
gap: 20px;
}
.sidebar {
width: 320px;
}
}
/* 平板横屏 */
@media (max-width: 1023px) and (min-width: 768px) {
.container {
padding: 0 16px;
max-width: 768px;
}
.course-content {
gap: 16px;
}
.sidebar {
width: 280px;
}
}
/* 平板竖屏及以下 */
@media (max-width: 767px) {
.container {
padding: 0 16px;
max-width: 576px;
}
.course-content {
flex-direction: column;
gap: 16px;
}
.sidebar {
width: 100%;
order: -1;
}
.video-player-section {
height: 400px;
}
}
@media (max-width: 767px) {
.container {
padding: 0 16px;
max-width: 576px;
}
.video-player.unregistered {
height: 400px;
}
.course-main-title {
font-size: 24px;
margin-bottom: 16px;
}
.video-content {
padding: 20px 16px;
}
.course-stats-info {
flex-direction: column;
gap: 8px;
margin-bottom: 24px;
}
.course-stats-info .stats-separator {
display: none;
}
.video-interaction-bar {
flex-direction: column;
gap: 16px;
align-items: stretch;
}
.interaction-left {
justify-content: center;
}
.interaction-right {
margin-left: 0;
max-width: none;
}
.comment-input {
flex-direction: column;
gap: 12px;
}
.comment-input input {
width: 100%;
}
.course-title {
font-size: 20px;
}
.course-meta {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.tab-nav {
overflow-x: auto;
}
.tab-btn {
white-space: nowrap;
padding: 12px 16px;
}
}
/* 报名弹窗样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 8px;
padding: 0;
max-width: 400px;
width: 90%;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid #f0f0f0;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.modal-close {
background: none;
border: none;
font-size: 24px;
color: #999;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.modal-close:hover {
color: #666;
}
.modal-body {
padding: 24px;
}
.modal-body p {
margin: 0 0 12px 0;
color: #333;
line-height: 1.5;
}
.modal-tip {
color: #666;
font-size: 14px;
}
.modal-footer {
display: flex;
gap: 12px;
padding: 20px 24px;
border-top: 1px solid #f0f0f0;
justify-content: flex-end;
}
.btn-cancel {
background: #f5f5f5;
border: 1px solid #d9d9d9;
color: #666;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.btn-cancel:hover {
background: #e6e6e6;
border-color: #bfbfbf;
}
.btn-confirm {
background: #1890ff;
border: 1px solid #1890ff;
color: white;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.btn-confirm:hover:not(:disabled) {
background: #40a9ff;
border-color: #40a9ff;
}
.btn-confirm:disabled {
background: #f5f5f5;
border-color: #d9d9d9;
color: #bfbfbf;
cursor: not-allowed;
}
.success-modal {
text-align: center;
padding: 40px 24px;
}
.success-icon {
width: 60px;
height: 60px;
background: #52c41a;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
font-size: 30px;
color: white;
font-weight: bold;
}
.success-modal h3 {
margin: 0 0 12px 0;
color: #333;
font-size: 18px;
}
.success-modal p {
margin: 0 0 8px 0;
color: #666;
font-size: 14px;
}
.success-modal p:last-child {
margin-bottom: 0;
}
.success-tip {
color: #52c41a !important;
font-weight: 500;
}
/* 手机小屏优化 */
@media (max-width: 480px) {
.container {
padding: 0 12px;
}
.video-player.unregistered {
height: 350px;
}
.course-main-title {
font-size: 20px;
margin-bottom: 12px;
}
.video-content {
padding: 16px 12px;
}
.interaction-left {
gap: 12px;
}
.interaction-btn {
font-size: 12px;
padding: 6px 4px;
}
.video-interaction-bar {
padding: 12px 16px;
gap: 12px;
}
}
/* 超小屏幕优化 */
@media (max-width: 360px) {
.container {
padding: 0 8px;
}
.video-player.unregistered {
height: 300px;
}
.course-main-title {
font-size: 18px;
}
.interaction-btn {
font-size: 11px;
padding: 4px 2px;
}
.interaction-left {
gap: 8px;
}
}
</style>