2025-08-16 19:51:39 +08:00
|
|
|
|
<template>
|
|
|
|
|
<div class="course-detail-page">
|
|
|
|
|
|
|
|
|
|
<!-- 主要内容区域 -->
|
|
|
|
|
<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="banner-title-section">
|
|
|
|
|
<div class="banner-content">
|
|
|
|
|
<div class="banner-text">
|
|
|
|
|
<span class="main-text">暑期名师领学,提高班级教学质量!高效冲分指南</span>
|
2025-09-06 02:18:07 +08:00
|
|
|
|
<div v-if="(course as any)?.izAi === 1" class="ai-companion-tag">
|
2025-08-16 19:51:39 +08:00
|
|
|
|
<img src="/images/aiCompanion/AI伴学标签@2x.png" alt="AI伴学" class="tag-image">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-06 02:18:07 +08:00
|
|
|
|
<!-- <div class="banner-button">
|
2025-08-16 19:51:39 +08:00
|
|
|
|
<img src="/images/aiCompanion/切换@2x.png" alt="切换" class="button-icon-image">
|
|
|
|
|
<span class="button-text">普通</span>
|
2025-09-06 02:18:07 +08:00
|
|
|
|
</div> -->
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 视频播放器区域 - 未报名状态 -->
|
|
|
|
|
<div class="video-player-section">
|
|
|
|
|
<div class="video-player unregistered">
|
|
|
|
|
<div class="video-background" style="background-image: url('/images/aiCompanion/背景色@2x.png');">
|
|
|
|
|
<div class="video-content">
|
2025-09-02 16:29:25 +08:00
|
|
|
|
<!-- AI伴学模式 -->
|
|
|
|
|
<template v-if="(course as any)?.izAi === 1">
|
|
|
|
|
<!-- 锁定图标 -->
|
|
|
|
|
<div class="lock-icon">
|
|
|
|
|
<img src="/images/aiCompanion/lock.png" alt="">
|
|
|
|
|
</div>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-09-02 16:29:25 +08:00
|
|
|
|
<!-- 主要信息 -->
|
|
|
|
|
<div class="wisdom-points-info">
|
|
|
|
|
<h2 class="main-message">完整课程内容,需使用智点兑换</h2>
|
|
|
|
|
|
|
|
|
|
<!-- 课程统计信息 -->
|
|
|
|
|
<div class="course-stats-info">
|
|
|
|
|
<span class="stats-item">
|
|
|
|
|
<span class="icon-chapters"></span>
|
|
|
|
|
共9章54节
|
|
|
|
|
</span>
|
|
|
|
|
<span class="stats-item">
|
|
|
|
|
<span class="icon-duration"></span>
|
|
|
|
|
12小时43分钟
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
2025-09-02 16:29:25 +08:00
|
|
|
|
<!-- 兑换按钮 -->
|
|
|
|
|
<div class="exchange-button-container">
|
|
|
|
|
<button class="exchange-button" @click="handleExchangeCourse">
|
|
|
|
|
消耗29智点 | 立即兑换
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-09-02 16:29:25 +08:00
|
|
|
|
<!-- 用户智点信息 -->
|
|
|
|
|
<div class="user-points-info">
|
|
|
|
|
<div class="points-display">您的智点:101.5</div>
|
|
|
|
|
<div class="get-more-points">获取更多智点 >></div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<!-- 普通模式 -->
|
|
|
|
|
<template v-else>
|
|
|
|
|
<!-- 课程信息 -->
|
|
|
|
|
<div class="normal-course-info">
|
|
|
|
|
<h2 class="normal-main-message">{{ (course as any)?.name || course?.title || '课程名称' }}</h2>
|
|
|
|
|
|
|
|
|
|
<!-- 课程统计信息 -->
|
|
|
|
|
<div class="course-stats-info">
|
|
|
|
|
<span class="stats-item">
|
|
|
|
|
<span class="icon-chapters"></span>
|
|
|
|
|
共9章54节
|
|
|
|
|
</span>
|
|
|
|
|
<span class="stats-item">
|
|
|
|
|
<span class="icon-duration"></span>
|
|
|
|
|
12小时43分钟
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 立即报名按钮 -->
|
|
|
|
|
<div class="enroll-button-container">
|
|
|
|
|
<button class="enroll-button" @click="handleEnrollCourse">
|
|
|
|
|
<span>立即报名</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</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>
|
|
|
|
|
<!-- 分割线 -->
|
|
|
|
|
<div class="split-line"></div>
|
|
|
|
|
|
|
|
|
|
<button class="interaction-btn">
|
|
|
|
|
<span class="icon-share"></span>
|
|
|
|
|
<span class="share-text">2377</span>
|
|
|
|
|
</button>
|
|
|
|
|
<button class="interaction-btn">
|
|
|
|
|
<span class="icon-notes"></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">
|
|
|
|
|
|
|
|
|
|
<!-- 课程元信息 -->
|
|
|
|
|
<div class="course-meta">
|
|
|
|
|
<div class="meta-row">
|
|
|
|
|
<span class="meta-item">
|
|
|
|
|
分类:<span class="category-link">{{ course.category?.name || '信息技术' }}</span>
|
|
|
|
|
</span>
|
|
|
|
|
<div class="meta-right">
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="meta-row">
|
|
|
|
|
<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>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-08-30 03:57:24 +08:00
|
|
|
|
<!-- 课程描述
|
2025-08-16 19:51:39 +08:00
|
|
|
|
<div class="course-description">
|
|
|
|
|
<p>{{ course.description ||
|
|
|
|
|
'本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。课程内容与全国计算机等级考试、"1+X"WPS办公应用职业技能等级证书,技能大赛紧密结合,课程设置紧密对应实际全面共享,可为职业工作人员、在校学生、创行教师提供服务与学习支持。'
|
2025-08-18 12:03:30 +08:00
|
|
|
|
}}</p>
|
2025-08-30 03:57:24 +08:00
|
|
|
|
</div> -->
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
<!-- 讲师信息 -->
|
|
|
|
|
<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">
|
|
|
|
|
<div class="safe-avatar" style="width: 50px; height: 50px;">
|
|
|
|
|
<img :src="instructor.avatar" :alt="instructor.name">
|
|
|
|
|
</div>
|
|
|
|
|
</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-info-divider"></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' }"
|
2025-08-30 03:57:24 +08:00
|
|
|
|
@click="activeTab = 'comments'">评论({{ commentsCount }})</button>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</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">
|
2025-08-18 12:03:30 +08:00
|
|
|
|
<!-- 发布评论区域 -->
|
|
|
|
|
<div class="post-comment-section">
|
|
|
|
|
<div class="comment-input-wrapper">
|
|
|
|
|
<div class="user-avatar">
|
|
|
|
|
<img src="/images/activity/6.png" alt="用户头像" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="comment-input-area">
|
|
|
|
|
<textarea v-model="newComment" placeholder="写下你的评论..." class="comment-textarea"
|
|
|
|
|
@input="adjustTextareaHeight" @click="handleTextareaClick"></textarea>
|
|
|
|
|
<div class="comment-toolbar">
|
|
|
|
|
<div class="toolbar-left">
|
|
|
|
|
<button class="toolbar-btn">
|
|
|
|
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="toolbar-btn">
|
|
|
|
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="toolbar-btn">
|
|
|
|
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="toolbar-right">
|
|
|
|
|
<button class="btn-submit" @click="submitComment">
|
|
|
|
|
发布
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</div>
|
2025-08-18 12:03:30 +08:00
|
|
|
|
</div>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
<div class="comment-list">
|
2025-08-30 03:57:24 +08:00
|
|
|
|
<!-- 加载状态 -->
|
|
|
|
|
<div v-if="commentsLoading" class="comments-loading">
|
|
|
|
|
<p>正在加载评论...</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 错误状态 -->
|
|
|
|
|
<div v-else-if="commentsError" class="comments-error">
|
|
|
|
|
<p>{{ commentsError }}</p>
|
|
|
|
|
<button @click="loadCourseComments" class="retry-btn">重试</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 无评论状态 -->
|
|
|
|
|
<div v-else-if="displayComments.length === 0" class="no-comments">
|
|
|
|
|
<p>暂无评论,快来发表第一条评论吧!</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 评论列表 -->
|
|
|
|
|
<div v-else class="comment-item" v-for="comment in displayComments" :key="comment.id">
|
2025-08-16 19:51:39 +08:00
|
|
|
|
<div class="comment-avatar">
|
2025-08-30 03:57:24 +08:00
|
|
|
|
<SafeAvatar :src="comment.avatar" :name="comment.username" :size="40" />
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
<div class="comment-content">
|
|
|
|
|
<div class="comment-header">
|
|
|
|
|
<span class="comment-username">{{ comment.username }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="comment-text">{{ comment.content }}</div>
|
|
|
|
|
<div class="comment-actions">
|
|
|
|
|
<button class="action-btn">
|
|
|
|
|
<span>2025.07.23 16:28</span>
|
|
|
|
|
</button>
|
2025-08-18 12:03:30 +08:00
|
|
|
|
<button v-if="comment.type === 'note'" class="action-btn">
|
|
|
|
|
<span class="top">置顶评论</span>
|
|
|
|
|
</button>
|
|
|
|
|
<button v-else class="action-btn"
|
|
|
|
|
@click="startReply(comment.id, comment.username)">回复</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 回复输入区域 -->
|
|
|
|
|
<div v-if="replyingTo === comment.id" class="reply-input-section">
|
|
|
|
|
<div class="reply-input-header">
|
|
|
|
|
<span class="reply-to-text">回复 @{{ replyToUsername }}</span>
|
|
|
|
|
<button class="cancel-reply-btn" @click="cancelReply">取消</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="reply-input-container">
|
|
|
|
|
<textarea v-model="replyText" placeholder="写下你的回复..." class="reply-textarea"
|
|
|
|
|
@input="adjustReplyTextareaHeight" @click="handleReplyTextareaClick"></textarea>
|
|
|
|
|
<div class="reply-toolbar">
|
|
|
|
|
<div class="toolbar-left">
|
|
|
|
|
<button class="toolbar-btn">
|
|
|
|
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="toolbar-btn">
|
|
|
|
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="toolbar-btn">
|
|
|
|
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="toolbar-right">
|
|
|
|
|
<button class="btn-submit" @click="submitReply" :disabled="!replyText.trim()">
|
|
|
|
|
发布
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 回复区域示例 -->
|
2025-08-30 14:05:01 +08:00
|
|
|
|
<div class="comment-replies" v-if="String(comment.id) === '1'">
|
2025-08-18 12:03:30 +08:00
|
|
|
|
<!-- 讲师回复 -->
|
|
|
|
|
<div class="reply-item instructor-reply">
|
|
|
|
|
<div class="reply-avatar">
|
|
|
|
|
<img src="/images/activity/6.png" alt="讲师头像" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="reply-content">
|
|
|
|
|
<div class="reply-main">
|
|
|
|
|
<div class="reply-header">
|
|
|
|
|
<span class="reply-username">张老师</span>
|
|
|
|
|
<span class="reply-badge instructor">讲师</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="reply-text">感谢您的反馈!我们会继续优化课程内容,让学习体验更好。</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="reply-footer">
|
|
|
|
|
<span class="reply-time">2025.07.23 17:30</span>
|
|
|
|
|
<div class="reply-actions">
|
|
|
|
|
<button class="reply-action-btn" @click="startReply(1, '张老师')">回复</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 用户回复 -->
|
|
|
|
|
<div class="reply-item user-reply">
|
|
|
|
|
<div class="reply-avatar">
|
|
|
|
|
<img src="/images/activity/7.png" alt="用户头像" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="reply-content">
|
|
|
|
|
<div class="reply-main">
|
|
|
|
|
<div class="reply-header">
|
|
|
|
|
<span class="reply-username">李同学</span>
|
|
|
|
|
<span class="reply-badge user">学员</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="reply-text">同意楼上的观点,这个课程确实很有帮助!</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="reply-footer">
|
|
|
|
|
<span class="reply-time">2025.07.23 18:15</span>
|
|
|
|
|
<div class="reply-actions">
|
|
|
|
|
<button class="reply-action-btn" @click="startReply(1, '李同学')">回复</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-08-18 12:03:30 +08:00
|
|
|
|
<div class="comment-item">
|
|
|
|
|
<div class="comment-avatar">
|
|
|
|
|
<img
|
|
|
|
|
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80"
|
|
|
|
|
alt="张老师" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="comment-content">
|
|
|
|
|
<div class="comment-header">
|
|
|
|
|
<span class="comment-username">张老师</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="comment-text">这个课程内容很实用,讲解得很清楚,对初学者很有帮助!111</div>
|
|
|
|
|
<div class="comment-image-container">
|
|
|
|
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
|
|
|
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
|
|
|
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
|
|
|
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
|
|
|
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
|
|
|
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
|
|
|
|
<div class="image-overlay">
|
|
|
|
|
<span class="more-images-text">+6</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="comment-actions">
|
|
|
|
|
<div class="note-icon-container">
|
|
|
|
|
<img src="/images/courses/comments-note.png" alt="笔记" class="note-icon">
|
|
|
|
|
<span>笔记</span>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="comment-time">2025.07.23 16:28</span>
|
|
|
|
|
<button class="action-btn" @click="startReply(1, '张老师')">回复</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 回复输入区域 -->
|
|
|
|
|
<div v-if="replyingTo === 1" class="reply-input-section">
|
|
|
|
|
<div class="reply-input-header">
|
|
|
|
|
<span class="reply-to-text">回复 @{{ replyToUsername }}</span>
|
|
|
|
|
<button class="cancel-reply-btn" @click="cancelReply">取消</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="reply-input-container">
|
|
|
|
|
<textarea v-model="replyText" placeholder="写下你的回复..." class="reply-textarea"
|
|
|
|
|
@input="adjustReplyTextareaHeight" @click="handleReplyTextareaClick"></textarea>
|
|
|
|
|
<div class="reply-toolbar">
|
|
|
|
|
<div class="toolbar-left">
|
|
|
|
|
<button class="toolbar-btn">
|
|
|
|
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="toolbar-btn">
|
|
|
|
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
|
|
|
|
</button>
|
|
|
|
|
<button class="toolbar-btn">
|
|
|
|
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="toolbar-right">
|
|
|
|
|
<button class="btn-submit" @click="submitReply" :disabled="!replyText.trim()">
|
|
|
|
|
发布
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 左侧边栏 -->
|
|
|
|
|
<div class="sidebar">
|
|
|
|
|
<div class="sidebar-title">
|
|
|
|
|
<h2>学习进度</h2>
|
|
|
|
|
<img src="/images/aiCompanion/fold.png" alt="">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 学习进度 -->
|
|
|
|
|
<div class="progress-section">
|
|
|
|
|
<div class="progress-header">
|
|
|
|
|
<h3 class="progress-title">学习进度</h3>
|
|
|
|
|
<p class="progress-subtitle">实时跟踪您的学习状态和完成情况</p>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 三个圆形进度图表 -->
|
|
|
|
|
<div class="progress-circles">
|
|
|
|
|
<!-- 课程进度 -->
|
|
|
|
|
<div class="progress-item">
|
|
|
|
|
<div class="circle-container">
|
|
|
|
|
<svg class="progress-circle" width="80" height="80" viewBox="0 0 80 80">
|
|
|
|
|
<!-- 背景圆环 -->
|
|
|
|
|
<circle cx="40" cy="40" r="32" stroke="##E2F5FF" stroke-width="8" fill="none"
|
|
|
|
|
class="progress-bg" />
|
|
|
|
|
<!-- 进度圆环 -->
|
|
|
|
|
<circle cx="40" cy="40" r="32" stroke="#078BD2" stroke-width="8" fill="none"
|
|
|
|
|
stroke-linecap="round" class="progress-fill" :stroke-dasharray="circumference"
|
|
|
|
|
:stroke-dashoffset="circumference - (circumference * videoProgress / 100)" />
|
|
|
|
|
</svg>
|
|
|
|
|
<!-- 中心图标和文字 -->
|
|
|
|
|
<div class="circle-content">
|
|
|
|
|
<div class="progress-icon">
|
|
|
|
|
<img src="/images/courses/course-icon.png" alt="课程图标" class="course-icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="progress-label">课程</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="progress-percentage">{{ videoProgress.toFixed(1) }}%</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 作业进度 -->
|
|
|
|
|
<div class="progress-item">
|
|
|
|
|
<div class="circle-container">
|
|
|
|
|
<svg class="progress-circle" width="80" height="80" viewBox="0 0 80 80">
|
|
|
|
|
<!-- 背景圆环 -->
|
|
|
|
|
<circle cx="40" cy="40" r="32" stroke="#d9ecff" stroke-width="8" fill="none"
|
|
|
|
|
class="progress-bg" />
|
|
|
|
|
<!-- 进度圆环 -->
|
|
|
|
|
<circle cx="40" cy="40" r="32" stroke="#078BD2" stroke-width="8" fill="none"
|
|
|
|
|
stroke-linecap="round" class="progress-fill" :stroke-dasharray="circumference"
|
|
|
|
|
:stroke-dashoffset="circumference - (circumference * exerciseProgress / 100)" />
|
|
|
|
|
</svg>
|
|
|
|
|
<!-- 中心图标和文字 -->
|
|
|
|
|
<div class="circle-content">
|
|
|
|
|
<div class="progress-icon">
|
|
|
|
|
<img src="/images/courses/homework-icon.png" alt="作业图标" class="homework-icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="progress-label">作业</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="progress-percentage">{{ exerciseProgress.toFixed(1) }}%</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 考试进度 -->
|
|
|
|
|
<div class="progress-item">
|
|
|
|
|
<div class="circle-container">
|
|
|
|
|
<svg class="progress-circle" width="80" height="80" viewBox="0 0 80 80">
|
|
|
|
|
<!-- 背景圆环 -->
|
|
|
|
|
<circle cx="40" cy="40" r="32" stroke="#d9ecff" stroke-width="8" fill="none"
|
|
|
|
|
class="progress-bg" />
|
|
|
|
|
<!-- 进度圆环 -->
|
|
|
|
|
<circle cx="40" cy="40" r="32" stroke="#078BD2" stroke-width="8" fill="none"
|
|
|
|
|
stroke-linecap="round" class="progress-fill" :stroke-dasharray="circumference"
|
|
|
|
|
:stroke-dashoffset="circumference - (circumference * examProgress / 100)" />
|
|
|
|
|
</svg>
|
|
|
|
|
<!-- 中心图标和文字 -->
|
|
|
|
|
<div class="circle-content">
|
|
|
|
|
<div class="progress-icon">
|
|
|
|
|
<img src="/images/courses/examination-icon.png" alt="考试图标" class="exam-icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="progress-label">考试</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="progress-percentage">{{ examProgress.toFixed(1) }}%</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 总体学习进度条 -->
|
|
|
|
|
<div class="overall-progress">
|
|
|
|
|
<div class="progress-bar-container">
|
|
|
|
|
<div class="progress-bar">
|
|
|
|
|
<div class="progress-bar-fill" :style="{ width: overallProgress + '%' }"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="progress-info">
|
|
|
|
|
<div class="progress-text">
|
|
|
|
|
<span class="progress-title">学习总进度</span>
|
|
|
|
|
<span class="progress-value">{{ overallProgress.toFixed(1) }}%</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="progress-count">
|
|
|
|
|
<span class="current">{{ completedLessons }}</span>
|
|
|
|
|
<span class="separator">/</span>
|
|
|
|
|
<span class="total">{{ totalSections }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</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">
|
2025-08-26 18:51:00 +08:00
|
|
|
|
<!-- 按章节分组显示 - 未报名状态,灰色不可点击 -->
|
2025-08-16 19:51:39 +08:00
|
|
|
|
<div v-for="(chapter, chapterIndex) in groupedSections" :key="chapterIndex" class="chapter-section">
|
|
|
|
|
<div class="chapter-header" @click="toggleChapter(chapterIndex)">
|
|
|
|
|
<div class="chapter-info">
|
2025-08-26 18:51:00 +08:00
|
|
|
|
<span class="chapter-title">第{{ getChapterNumber(chapterIndex + 1) }}章 {{ chapter.title
|
|
|
|
|
}}</span>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</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">
|
2025-08-26 18:51:00 +08:00
|
|
|
|
<!-- 未报名状态:灰色不可点击 -->
|
|
|
|
|
<div class="lesson-content unregistered" @click="handleUnregisteredClick(section)">
|
|
|
|
|
<div class="lesson-type-badge disabled" :class="getLessonTypeBadgeClass(section)">
|
2025-08-16 19:51:39 +08:00
|
|
|
|
{{ getLessonTypeText(section) }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="lesson-info">
|
2025-08-26 18:51:00 +08:00
|
|
|
|
<span class="lesson-title disabled">{{ section.name }}</span>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</div>
|
|
|
|
|
<div class="lesson-meta">
|
2025-08-26 18:51:00 +08:00
|
|
|
|
<span v-if="isVideoLesson(section)" class="lesson-duration disabled">{{
|
|
|
|
|
formatLessonDuration(section) }}</span>
|
2025-08-16 19:51:39 +08:00
|
|
|
|
<div class="lesson-actions">
|
2025-08-26 18:51:00 +08:00
|
|
|
|
<!-- 视频播放图标 - 不可点击 -->
|
|
|
|
|
<button v-if="isVideoLesson(section)" class="lesson-action-btn video-btn disabled" disabled
|
|
|
|
|
@click.stop="handleUnregisteredClick(section)">
|
2025-08-16 19:51:39 +08:00
|
|
|
|
<img src="/public/images/courses/video-enroll.png" alt="视频" width="14" height="14">
|
|
|
|
|
</button>
|
2025-08-26 18:51:00 +08:00
|
|
|
|
<!-- 下载图标 - 不可点击 -->
|
|
|
|
|
<button v-else-if="isResourceLesson(section)" class="lesson-action-btn download-btn disabled" disabled
|
|
|
|
|
@click.stop="handleUnregisteredClick(section)">
|
|
|
|
|
<img src="/public/images/courses/download-enroll.png" alt="资料" width="14" height="14">
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</button>
|
2025-08-26 18:51:00 +08:00
|
|
|
|
<!-- 编辑图标(作业) - 不可点击 -->
|
|
|
|
|
<button v-else-if="isHomeworkLesson(section)" class="lesson-action-btn edit-btn disabled" disabled
|
|
|
|
|
@click.stop="handleUnregisteredClick(section)">
|
|
|
|
|
<img src="/public/images/courses/homework-enroll.png" alt="作业" width="14" height="14">
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</button>
|
2025-08-26 18:51:00 +08:00
|
|
|
|
<!-- 考试图标 - 不可点击 -->
|
|
|
|
|
<button v-else-if="isExamLesson(section)" class="lesson-action-btn exam-btn disabled" disabled
|
|
|
|
|
@click.stop="handleUnregisteredClick(section)">
|
|
|
|
|
<img src="/public/images/courses/examination-enroll.png" alt="考试" width="14"
|
|
|
|
|
height="14">
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</button>
|
|
|
|
|
</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">
|
|
|
|
|
<img src="/images/courses/course-activities1.png" alt="">
|
|
|
|
|
</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 computer-bg">
|
|
|
|
|
<img src="/images/courses/course-activities2.png" alt="">
|
|
|
|
|
</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">兑换后将消耗29智点,获得完整的学习权限</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">
|
2025-08-26 18:51:00 +08:00
|
|
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
2025-08-16 19:51:39 +08:00
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
2025-09-04 23:15:29 +08:00
|
|
|
|
import { useMessage } from 'naive-ui'
|
2025-08-16 19:51:39 +08:00
|
|
|
|
import { useAuth } from '@/composables/useAuth'
|
|
|
|
|
import { useUserStore } from '@/stores/user'
|
|
|
|
|
import { CourseApi } from '@/api/modules/course'
|
2025-08-30 03:57:24 +08:00
|
|
|
|
import type { Course, CourseSection, CourseComment } from '@/api/types'
|
|
|
|
|
import SafeAvatar from '@/components/common/SafeAvatar.vue'
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
import LoginModal from '@/components/auth/LoginModal.vue'
|
|
|
|
|
import RegisterModal from '@/components/auth/RegisterModal.vue'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const router = useRouter()
|
2025-09-04 23:15:29 +08:00
|
|
|
|
const message = useMessage()
|
2025-08-16 19:51:39 +08:00
|
|
|
|
const userStore = useUserStore()
|
2025-08-26 18:51:00 +08:00
|
|
|
|
const courseId = ref(String(route.query.courseId) || '1')
|
2025-08-16 19:51:39 +08:00
|
|
|
|
const { loginModalVisible, registerModalVisible, handleAuthSuccess, showLoginModal } = useAuth()
|
|
|
|
|
// enrollCourse 暂时未使用,后续需要时再启用
|
|
|
|
|
|
|
|
|
|
// 当前选中的章节
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// const currentSection = ref<CourseSection | null>(null)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 课程数据相关状态
|
|
|
|
|
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 RegistrationStatus = ref(false)
|
|
|
|
|
|
|
|
|
|
// 学习进度相关数据
|
|
|
|
|
const completedLessons = ref(0)
|
|
|
|
|
const videoProgress = ref(0)
|
|
|
|
|
const exerciseProgress = ref(0)
|
|
|
|
|
const examProgress = ref(0)
|
|
|
|
|
|
|
|
|
|
// 处理记笔记点击事件
|
|
|
|
|
// const handleNotesClick = () => {
|
|
|
|
|
// if (isUserEnrolled.value) {
|
|
|
|
|
// // 已报名,执行记笔记逻辑
|
|
|
|
|
// console.log('开始记笔记')
|
|
|
|
|
// // 这里可以添加打开笔记模态框的逻辑
|
|
|
|
|
// } else if (userStore.isLoggedIn) {
|
|
|
|
|
// // 已登录但未报名,提示去报名
|
|
|
|
|
// enrollConfirmVisible.value = true
|
|
|
|
|
// } else {
|
|
|
|
|
// // 未登录,显示登录模态框
|
|
|
|
|
// showLoginModal()
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// 计算用户是否已报名
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// const isUserEnrolled = computed(() => {
|
|
|
|
|
// // 必须同时满足:用户已登录 AND 已报名该课程
|
|
|
|
|
// return userStore.isLoggedIn && isEnrolled.value
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// // 临时测试不同状态:
|
|
|
|
|
// // return false // 强制显示未报名状态(灰色不可点击)
|
|
|
|
|
// // return true // 强制显示已报名状态(彩色可点击)
|
|
|
|
|
// })
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 报名确认弹窗
|
|
|
|
|
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个)
|
2025-08-16 20:39:56 +08:00
|
|
|
|
{ id: '1', lessonId: String(courseId.value), name: '开课彩蛋:新开始新征程', outline: 'https://example.com/video1.m3u8', type: 0, 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: String(courseId.value), name: '课程定位与目标', outline: 'https://example.com/video2.m3u8', type: 0, 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: String(courseId.value), name: '教学安排及学习建议', outline: 'https://example.com/video3.m3u8', type: 0, 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: String(courseId.value), name: '课前准备PPT', outline: 'https://example.com/ppt1.ppt', type: 1, parentId: '0', sort: 4, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 第二章 - 程序设计基础知识 (5个)
|
2025-08-16 20:39:56 +08:00
|
|
|
|
{ id: '5', lessonId: String(courseId.value), name: '第一课 程序设计入门', outline: 'https://example.com/video4.m3u8', type: 0, 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: String(courseId.value), name: '操作PPT', outline: 'https://example.com/ppt2.ppt', type: 1, parentId: '0', sort: 6, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
|
|
|
{ id: '7', lessonId: String(courseId.value), name: '第二课 循环结构', outline: 'https://example.com/video5.m3u8', type: 0, 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: String(courseId.value), name: '函数&循环', outline: 'https://example.com/video5.m3u8', type: 0, parentId: '0', sort: 8, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
|
|
|
{ id: '9', lessonId: String(courseId.value), name: '第三课 条件结构', outline: 'https://example.com/video6.m3u8', type: 0, parentId: '0', sort: 9, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:45:30' },
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 第三章 - 实战项目 (6个)
|
2025-08-16 20:39:56 +08:00
|
|
|
|
{ id: '10', lessonId: String(courseId.value), name: '项目一:计算器开发', outline: 'https://example.com/video7.m3u8', type: 0, parentId: '0', sort: 10, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:20:15' },
|
|
|
|
|
{ id: '11', lessonId: String(courseId.value), name: '项目源码下载', outline: 'https://example.com/source1.zip', type: 1, parentId: '0', sort: 11, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
|
|
|
{ id: '12', lessonId: String(courseId.value), name: '项目二:数据管理系统', outline: 'https://example.com/video8.m3u8', type: 0, parentId: '0', sort: 12, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:45:20' },
|
|
|
|
|
{ id: '13', lessonId: String(courseId.value), name: '作业:完成个人项目', outline: '', type: 0, parentId: '0', sort: 13, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
|
|
|
{ id: '14', lessonId: String(courseId.value), name: '项目三:Web应用开发', outline: 'https://example.com/video9.m3u8', type: 0, parentId: '0', sort: 14, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '02:10:45' },
|
|
|
|
|
{ id: '15', lessonId: String(courseId.value), name: '期末考试', outline: '', type: 0, parentId: '0', sort: 15, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 第四章 - 高级应用 (4个)
|
2025-08-16 20:39:56 +08:00
|
|
|
|
{ id: '16', lessonId: String(courseId.value), name: '高级特性介绍', outline: 'https://example.com/video10.m3u8', type: 0, parentId: '0', sort: 16, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:55:30' },
|
|
|
|
|
{ id: '17', lessonId: String(courseId.value), name: '性能优化技巧', outline: 'https://example.com/video11.m3u8', type: 0, parentId: '0', sort: 17, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:15:20' },
|
|
|
|
|
{ id: '18', lessonId: String(courseId.value), name: '部署与发布', outline: 'https://example.com/video12.m3u8', type: 0, parentId: '0', sort: 18, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:40:15' },
|
|
|
|
|
{ id: '19', lessonId: String(courseId.value), name: '课程总结', outline: 'https://example.com/video13.m3u8', type: 0, parentId: '0', sort: 19, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:30:10' },
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 第五章 - 拓展学习 (3个)
|
2025-08-16 20:39:56 +08:00
|
|
|
|
{ id: '20', lessonId: String(courseId.value), name: '行业发展趋势', outline: 'https://example.com/video14.m3u8', type: 0, parentId: '0', sort: 20, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:35:45' },
|
|
|
|
|
{ id: '21', lessonId: String(courseId.value), name: '学习资源推荐', outline: 'https://example.com/resources.pdf', type: 1, parentId: '0', sort: 21, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
|
|
|
{ id: '22', lessonId: String(courseId.value), name: '结业证书申请', outline: '', type: 0, parentId: '0', sort: 22, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 第六章 - 答疑与交流 (2个)
|
2025-08-16 20:39:56 +08:00
|
|
|
|
{ id: '23', lessonId: String(courseId.value), name: '常见问题解答', outline: 'https://example.com/video15.m3u8', type: 0, parentId: '0', sort: 23, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:25:30' },
|
|
|
|
|
{ id: '24', lessonId: String(courseId.value), name: '在线答疑直播', outline: 'https://example.com/live1.m3u8', type: 0, parentId: '0', sort: 24, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:30:00' }
|
2025-08-16 19:51:39 +08:00
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
// 将章节按章分组 - 根据后端数据结构重新实现
|
2025-08-16 19:51:39 +08:00
|
|
|
|
const groupSectionsByChapter = (sections: CourseSection[]) => {
|
2025-08-26 18:51:00 +08:00
|
|
|
|
console.log('🔍 开始分组章节数据:', sections)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
const groups: ChapterGroup[] = []
|
2025-08-26 18:51:00 +08:00
|
|
|
|
|
|
|
|
|
// 找出所有一级章节(level=1,这些是父章节)
|
|
|
|
|
const parentChapters = sections.filter(section => section.level === 1)
|
|
|
|
|
console.log('🔍 找到一级章节:', parentChapters)
|
|
|
|
|
|
|
|
|
|
// 按sortOrder降序排序一级章节(sortOrder越大越靠前)
|
|
|
|
|
parentChapters.sort((a, b) => b.sort - a.sort)
|
|
|
|
|
|
|
|
|
|
// 为每个一级章节创建分组
|
|
|
|
|
parentChapters.forEach((parentChapter, index) => {
|
|
|
|
|
// 找出该章节下的所有子章节(level=2,parentId匹配)
|
|
|
|
|
const childSections = sections.filter(section =>
|
|
|
|
|
section.level === 2 && section.parentId === parentChapter.id
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 按sortOrder降序排序子章节(sortOrder越大越靠前)
|
|
|
|
|
childSections.sort((a, b) => b.sort - a.sort)
|
|
|
|
|
|
|
|
|
|
console.log(`🔍 章节"${parentChapter.name}"的子章节:`, childSections)
|
|
|
|
|
|
|
|
|
|
// 创建章节分组
|
|
|
|
|
groups.push({
|
|
|
|
|
title: parentChapter.name, // 使用后端返回的章节名称
|
|
|
|
|
sections: childSections.length > 0 ? childSections : [parentChapter], // 如果有子章节就用子章节,否则用父章节本身
|
|
|
|
|
expanded: index === 0 // 默认展开第一章
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 如果没有找到一级章节,可能所有章节都是同级的,直接作为一个组
|
|
|
|
|
if (groups.length === 0 && sections.length > 0) {
|
|
|
|
|
console.log('🔍 没有找到层级结构,将所有章节作为一组')
|
|
|
|
|
// 按sortOrder降序排序(sortOrder越大越靠前)
|
|
|
|
|
const sortedSections = [...sections].sort((a, b) => b.sort - a.sort)
|
|
|
|
|
groups.push({
|
|
|
|
|
title: '课程章节',
|
|
|
|
|
sections: sortedSections,
|
|
|
|
|
expanded: true
|
|
|
|
|
})
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
console.log('✅ 章节分组完成:', groups)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
return groups
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 根据章节数据生成分组
|
|
|
|
|
// const generateChapterGroups = () => {
|
|
|
|
|
// // 确保有章节数据
|
|
|
|
|
// if (courseSections.value.length === 0) {
|
|
|
|
|
// console.log('没有章节数据,生成模拟数据')
|
|
|
|
|
// courseSections.value = generateMockSections()
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// console.log('开始生成章节分组,原始数据:', courseSections.value)
|
|
|
|
|
// console.log('章节数据数量:', courseSections.value.length)
|
|
|
|
|
|
|
|
|
|
// // 使用统一的分组函数
|
|
|
|
|
// groupedSections.value = groupSectionsByChapter(courseSections.value)
|
|
|
|
|
// console.log('生成的章节分组:', groupedSections.value)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// 获取章节标题(已弃用,使用groupSectionsByChapter替代)
|
|
|
|
|
// 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-1507003211169-0a1dd7228f2d?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 circumference = computed(() => 2 * Math.PI * 32) // r=32
|
|
|
|
|
|
|
|
|
|
// 计算总体进度
|
|
|
|
|
const overallProgress = computed(() => {
|
|
|
|
|
if (totalSections.value === 0) return 0
|
|
|
|
|
return (completedLessons.value / totalSections.value) * 100
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const formatTotalDuration = () => {
|
|
|
|
|
// 计算总时长
|
|
|
|
|
let totalMinutes = 0
|
|
|
|
|
courseSections.value.forEach((section: CourseSection) => {
|
|
|
|
|
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}分钟`
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-30 03:57:24 +08:00
|
|
|
|
// 真实评论数据
|
|
|
|
|
const comments = ref<CourseComment[]>([])
|
|
|
|
|
const commentsLoading = ref(false)
|
|
|
|
|
const commentsError = ref('')
|
|
|
|
|
|
|
|
|
|
// 为了保持兼容性,将真实评论数据转换为显示格式
|
|
|
|
|
const displayComments = computed(() => {
|
|
|
|
|
return comments.value.map(comment => ({
|
|
|
|
|
id: comment.id,
|
|
|
|
|
username: comment.userName,
|
|
|
|
|
avatar: comment.userAvatar,
|
|
|
|
|
time: comment.timeAgo,
|
|
|
|
|
content: comment.content,
|
|
|
|
|
likes: comment.likeCount || 0,
|
|
|
|
|
type: comment.isTop ? 'note' : 'comment'
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 评论数量计算属性
|
|
|
|
|
const commentsCount = computed(() => {
|
|
|
|
|
return comments.value.length
|
|
|
|
|
})
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-08-18 12:03:30 +08:00
|
|
|
|
// 新评论内容
|
|
|
|
|
const newComment = ref('')
|
|
|
|
|
|
|
|
|
|
// 自动调整textarea高度
|
|
|
|
|
const adjustTextareaHeight = (event: Event) => {
|
|
|
|
|
const textarea = event.target as HTMLTextAreaElement
|
|
|
|
|
textarea.style.height = 'auto'
|
|
|
|
|
textarea.style.height = textarea.scrollHeight + 'px'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 点击textarea时调整高度
|
|
|
|
|
const handleTextareaClick = (event: MouseEvent) => {
|
|
|
|
|
const textarea = event.target as HTMLTextAreaElement
|
|
|
|
|
// 如果当前高度是40px,则调整到60px
|
|
|
|
|
if (textarea.style.height === '40px' || textarea.style.height === '') {
|
|
|
|
|
textarea.style.height = '60px'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-30 03:57:24 +08:00
|
|
|
|
// 加载课程评论列表
|
|
|
|
|
const loadCourseComments = async () => {
|
|
|
|
|
if (!courseId.value || courseId.value.trim() === '') {
|
|
|
|
|
commentsError.value = '课程ID无效'
|
|
|
|
|
console.error('课程ID无效:', courseId.value)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
commentsLoading.value = true
|
|
|
|
|
commentsError.value = ''
|
|
|
|
|
|
|
|
|
|
console.log('调用API获取课程评论...')
|
|
|
|
|
const response = await CourseApi.getCourseComments(courseId.value)
|
|
|
|
|
console.log('评论API响应:', response)
|
|
|
|
|
|
|
|
|
|
if (response.code === 0 || response.code === 200) {
|
|
|
|
|
if (response.data && Array.isArray(response.data)) {
|
|
|
|
|
// 按置顶状态和时间排序:置顶评论在前,然后按时间倒序
|
|
|
|
|
const sortedComments = response.data.sort((a, b) => {
|
|
|
|
|
// 先按置顶状态排序
|
|
|
|
|
if (a.isTop !== b.isTop) {
|
|
|
|
|
return a.isTop ? -1 : 1 // 置顶的在前
|
|
|
|
|
}
|
|
|
|
|
// 再按创建时间倒序排序
|
|
|
|
|
return new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
comments.value = sortedComments
|
|
|
|
|
console.log('✅ 评论数据设置成功:', comments.value)
|
|
|
|
|
} else {
|
|
|
|
|
console.log('⚠️ API返回的评论数据为空')
|
|
|
|
|
comments.value = []
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.log('⚠️ API返回错误')
|
|
|
|
|
commentsError.value = response.message || '获取评论失败'
|
|
|
|
|
comments.value = []
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('加载课程评论失败:', err)
|
|
|
|
|
commentsError.value = '获取评论失败'
|
|
|
|
|
comments.value = []
|
|
|
|
|
} finally {
|
|
|
|
|
commentsLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-18 12:03:30 +08:00
|
|
|
|
// 提交评论函数
|
|
|
|
|
const submitComment = () => {
|
|
|
|
|
if (newComment.value.trim()) {
|
|
|
|
|
// 这里可以调用API提交评论
|
2025-08-30 03:57:24 +08:00
|
|
|
|
console.log('提交评论:', newComment.value)
|
|
|
|
|
// 提交成功后重新加载评论列表
|
|
|
|
|
// await CourseApi.submitComment(courseId.value, newComment.value)
|
|
|
|
|
// loadCourseComments()
|
|
|
|
|
newComment.value = ''
|
2025-08-18 12:03:30 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 回复相关函数
|
2025-08-30 14:05:01 +08:00
|
|
|
|
const startReply = (commentId: string | number, username: string) => {
|
2025-08-18 12:03:30 +08:00
|
|
|
|
replyingTo.value = commentId
|
|
|
|
|
replyToUsername.value = username
|
|
|
|
|
replyText.value = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cancelReply = () => {
|
|
|
|
|
replyingTo.value = null
|
|
|
|
|
replyToUsername.value = ''
|
|
|
|
|
replyText.value = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const submitReply = () => {
|
|
|
|
|
if (replyText.value.trim() && replyingTo.value) {
|
|
|
|
|
const newReplyObj = {
|
|
|
|
|
id: Date.now(),
|
|
|
|
|
username: '当前用户',
|
|
|
|
|
avatar: 'https://via.placeholder.com/40x40/1890ff/ffffff?text=我',
|
|
|
|
|
time: '刚刚',
|
|
|
|
|
content: replyText.value,
|
|
|
|
|
likes: 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这里可以调用API提交回复
|
|
|
|
|
console.log('回复已提交:', newReplyObj)
|
|
|
|
|
console.log('回复给评论ID:', replyingTo.value)
|
|
|
|
|
console.log('回复给用户:', replyToUsername.value)
|
|
|
|
|
|
|
|
|
|
// 清空回复状态
|
|
|
|
|
cancelReply()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 回复文本框高度调整
|
|
|
|
|
const adjustReplyTextareaHeight = (event: Event) => {
|
|
|
|
|
const textarea = event.target as HTMLTextAreaElement
|
|
|
|
|
textarea.style.height = '40px'
|
|
|
|
|
textarea.style.height = textarea.scrollHeight + 'px'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleReplyTextareaClick = (event: MouseEvent) => {
|
|
|
|
|
const textarea = event.target as HTMLTextAreaElement
|
|
|
|
|
if (textarea.style.height === '40px' || !textarea.style.height) {
|
|
|
|
|
textarea.style.height = '60px'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 回复相关数据
|
2025-08-30 14:05:01 +08:00
|
|
|
|
const replyingTo = ref<string | number | null>(null)
|
2025-08-18 12:03:30 +08:00
|
|
|
|
const replyText = ref('')
|
|
|
|
|
const replyToUsername = ref('')
|
|
|
|
|
|
2025-08-16 19:51:39 +08:00
|
|
|
|
// 加载课程详情
|
|
|
|
|
const loadCourseDetail = async () => {
|
|
|
|
|
console.log('开始加载课程详情,课程ID:', courseId.value)
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
if (!courseId.value) courseId.value = '1'
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
if (!courseId.value || isNaN(Number(courseId.value))) {
|
2025-08-16 20:39:56 +08:00
|
|
|
|
console.log('课程ID无效,使用模拟数据')
|
|
|
|
|
loadMockCourseData()
|
2025-08-16 19:51:39 +08:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
loading.value = true
|
|
|
|
|
error.value = ''
|
|
|
|
|
|
|
|
|
|
console.log('调用API获取课程详情...')
|
2025-08-26 18:51:00 +08:00
|
|
|
|
const response = await CourseApi.getCourseById(courseId.value)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
console.log('API响应:', response)
|
|
|
|
|
|
|
|
|
|
if (response.code === 0 || response.code === 200) {
|
|
|
|
|
course.value = response.data
|
|
|
|
|
console.log('课程数据设置成功:', course.value)
|
2025-09-02 16:29:25 +08:00
|
|
|
|
console.log('课程AI模式:', (course.value as any)?.izAi)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 确保讲师和时长信息正确显示
|
|
|
|
|
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 {
|
2025-08-16 20:39:56 +08:00
|
|
|
|
console.log('API返回错误,使用模拟数据')
|
|
|
|
|
loadMockCourseData()
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('加载课程详情失败:', err)
|
2025-08-16 20:39:56 +08:00
|
|
|
|
console.log('API调用失败,使用模拟数据')
|
|
|
|
|
loadMockCourseData()
|
2025-08-16 19:51:39 +08:00
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载课程章节列表
|
|
|
|
|
const loadCourseSections = async () => {
|
2025-08-26 18:51:00 +08:00
|
|
|
|
if (!courseId.value || isNaN(Number(courseId.value))) {
|
2025-08-16 20:39:56 +08:00
|
|
|
|
console.log('课程ID无效,使用模拟章节数据')
|
|
|
|
|
loadMockData()
|
2025-08-16 19:51:39 +08:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
sectionsLoading.value = true
|
|
|
|
|
sectionsError.value = ''
|
|
|
|
|
|
|
|
|
|
console.log('调用API获取课程章节...')
|
2025-08-26 18:51:00 +08:00
|
|
|
|
const response = await CourseApi.getCourseSections(courseId.value)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
console.log('章节API响应:', response)
|
|
|
|
|
|
|
|
|
|
if (response.code === 0 || response.code === 200) {
|
2025-08-26 18:51:00 +08:00
|
|
|
|
if (response.data && response.data.list && Array.isArray(response.data.list)) {
|
|
|
|
|
console.log('✅ API返回的原始章节数据:', response.data.list)
|
|
|
|
|
console.log('✅ 章节数据数量:', response.data.list.length)
|
|
|
|
|
|
|
|
|
|
courseSections.value = response.data.list
|
|
|
|
|
groupedSections.value = groupSectionsByChapter(response.data.list)
|
|
|
|
|
|
|
|
|
|
console.log('✅ 设置后的courseSections:', courseSections.value)
|
|
|
|
|
console.log('✅ 设置后的groupedSections:', groupedSections.value)
|
|
|
|
|
console.log('✅ groupedSections长度:', groupedSections.value.length)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
} else {
|
2025-08-26 18:51:00 +08:00
|
|
|
|
console.log('❌ API返回的章节数据为空或格式错误')
|
|
|
|
|
console.log('❌ response.data:', response.data)
|
|
|
|
|
sectionsError.value = '暂无课程章节数据'
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-08-26 18:51:00 +08:00
|
|
|
|
console.log('API返回错误')
|
|
|
|
|
sectionsError.value = response.message || '获取课程章节失败'
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('加载课程章节失败:', err)
|
2025-08-26 18:51:00 +08:00
|
|
|
|
sectionsError.value = '网络错误,请稍后重试'
|
2025-08-16 19:51:39 +08:00
|
|
|
|
} finally {
|
|
|
|
|
sectionsLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 20:39:56 +08:00
|
|
|
|
// 加载模拟课程数据
|
|
|
|
|
const loadMockCourseData = () => {
|
|
|
|
|
console.log('加载模拟课程数据')
|
|
|
|
|
course.value = {
|
|
|
|
|
id: String(courseId.value) || '1',
|
|
|
|
|
title: 'DeepSeek办公自动化职业岗位标准课程',
|
|
|
|
|
description: '本课程将帮助您掌握DeepSeek的基本使用方法,了解办公自动化职业岗位标准,提高教学质量和效率,获得实际工作技能。',
|
|
|
|
|
instructor: {
|
|
|
|
|
id: 1,
|
|
|
|
|
name: 'DeepSeek技术学院',
|
|
|
|
|
title: '讲师',
|
|
|
|
|
bio: '专注于AI技术应用与教学',
|
|
|
|
|
avatar: '/images/aiCompanion/AI小助手@2x.png',
|
|
|
|
|
rating: 4.8,
|
|
|
|
|
studentsCount: 1000,
|
|
|
|
|
coursesCount: 10,
|
|
|
|
|
experience: '5年教学经验',
|
|
|
|
|
education: ['计算机科学硕士'],
|
|
|
|
|
certifications: ['高级讲师认证']
|
|
|
|
|
},
|
|
|
|
|
duration: '12小时43分钟',
|
|
|
|
|
totalLessons: 54,
|
|
|
|
|
rating: 4.8,
|
|
|
|
|
studentsCount: 1000,
|
|
|
|
|
price: 0,
|
|
|
|
|
originalPrice: 299,
|
|
|
|
|
category: {
|
|
|
|
|
id: 1,
|
|
|
|
|
name: 'AI技术',
|
|
|
|
|
slug: 'ai-technology',
|
|
|
|
|
description: 'AI技术',
|
|
|
|
|
icon: 'https://example.com/ai-icon.png'
|
|
|
|
|
},
|
|
|
|
|
level: 'beginner',
|
|
|
|
|
tags: ['AI', '办公自动化', 'DeepSeek'],
|
|
|
|
|
thumbnail: '/images/aiCompanion/bg.png',
|
|
|
|
|
content: '课程内容详细介绍...',
|
|
|
|
|
objectives: ['掌握DeepSeek的基本使用方法', '了解办公自动化职业岗位标准', '提高教学质量和效率'],
|
|
|
|
|
requirements: ['基础计算机操作能力', '对AI技术感兴趣'],
|
|
|
|
|
createdAt: new Date().toISOString(),
|
|
|
|
|
updatedAt: new Date().toISOString(),
|
|
|
|
|
ratingCount: 0,
|
|
|
|
|
currency: 'CNY',
|
|
|
|
|
language: '中文',
|
|
|
|
|
skills: ['AI', '办公自动化', 'DeepSeek'],
|
|
|
|
|
status: 'published'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 19:51:39 +08:00
|
|
|
|
// 加载模拟数据
|
|
|
|
|
const loadMockData = () => {
|
|
|
|
|
console.log('加载模拟章节数据')
|
|
|
|
|
const mockSections = generateMockSections()
|
|
|
|
|
courseSections.value = mockSections
|
|
|
|
|
groupedSections.value = groupSectionsByChapter(mockSections)
|
|
|
|
|
|
|
|
|
|
// 计算学习进度
|
|
|
|
|
const completed = mockSections.filter(section => section.completed).length
|
|
|
|
|
completedLessons.value = completed
|
|
|
|
|
|
|
|
|
|
// 计算各类进度
|
|
|
|
|
const videoSections = mockSections.filter(section => isVideoLesson(section))
|
|
|
|
|
const exerciseSections = mockSections.filter(section => isHomeworkLesson(section))
|
|
|
|
|
const examSections = mockSections.filter(section => isExamLesson(section))
|
|
|
|
|
|
|
|
|
|
videoProgress.value = videoSections.length > 0 ? Math.round((videoSections.filter(s => s.completed).length / videoSections.length) * 100) : 0
|
|
|
|
|
exerciseProgress.value = exerciseSections.length > 0 ? Math.round((exerciseSections.filter(s => s.completed).length / exerciseSections.length) * 100) : 0
|
|
|
|
|
examProgress.value = examSections.length > 0 ? Math.round((examSections.filter(s => s.completed).length / examSections.length) * 100) : 0
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
// 获取章节编号
|
|
|
|
|
const getChapterNumber = (num: number) => {
|
|
|
|
|
const numbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
|
|
|
|
|
return numbers[num - 1] || num.toString()
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 19:51:39 +08:00
|
|
|
|
// 切换章节展开/折叠
|
|
|
|
|
const toggleChapter = (chapterIndex: number) => {
|
|
|
|
|
console.log('点击切换章节,章节索引:', chapterIndex)
|
|
|
|
|
|
|
|
|
|
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')}`
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
// 获取课程类型文本
|
|
|
|
|
const getLessonTypeText = (section: CourseSection) => {
|
|
|
|
|
if (isVideoLesson(section)) return '视频'
|
|
|
|
|
if (isResourceLesson(section)) return '资料'
|
|
|
|
|
if (isHomeworkLesson(section)) return '作业'
|
|
|
|
|
if (isExamLesson(section)) return '考试'
|
|
|
|
|
return '视频'
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
// 格式化课程时长
|
|
|
|
|
const formatLessonDuration = (section: CourseSection) => {
|
|
|
|
|
if (!section.duration) return ''
|
|
|
|
|
return section.duration
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
// 课程类型判断函数 - 与CourseExchanged保持一致
|
|
|
|
|
const isVideoLesson = (section: CourseSection) => {
|
|
|
|
|
console.log('检查章节类型:', section.name, 'type:', section.type, 'outline:', section.outline)
|
|
|
|
|
// 优先根据type字段判断:0=视频
|
|
|
|
|
if (section.type === 0) {
|
2025-08-16 19:51:39 +08:00
|
|
|
|
return true
|
|
|
|
|
}
|
2025-08-26 18:51:00 +08:00
|
|
|
|
// 如果type为null,则根据outline判断
|
|
|
|
|
return section.outline && (section.outline.includes('.m3u8') || section.outline.includes('.mp4'))
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
const isResourceLesson = (section: CourseSection) => {
|
|
|
|
|
// 优先根据type字段判断:1=资料
|
|
|
|
|
if (section.type === 1) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// 如果type为null,则根据outline或名称判断
|
|
|
|
|
return section.outline && (section.outline.includes('.pdf') || section.outline.includes('.ppt') || section.outline.includes('.zip'))
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
const isHomeworkLesson = (section: CourseSection) => {
|
|
|
|
|
// 优先根据type字段判断:3=作业
|
|
|
|
|
if (section.type === 3) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// 如果type为null,则根据名称判断
|
|
|
|
|
return section.name.includes('作业') || section.name.includes('练习')
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
const isExamLesson = (section: CourseSection) => {
|
|
|
|
|
// 优先根据type字段判断:2=考试
|
|
|
|
|
if (section.type === 2) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// 如果type为null,则根据名称判断
|
|
|
|
|
return section.name.includes('考试') || section.name.includes('测试')
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
// 获取课程类型样式类
|
|
|
|
|
const getLessonTypeBadgeClass = (section: CourseSection) => {
|
|
|
|
|
if (isVideoLesson(section)) return 'badge-video'
|
|
|
|
|
if (isResourceLesson(section)) return 'badge-resource'
|
|
|
|
|
if (isHomeworkLesson(section)) return 'badge-homework'
|
|
|
|
|
if (isExamLesson(section)) return 'badge-exam'
|
|
|
|
|
return 'badge-default'
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理下载操作
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// const handleDownload = (section: CourseSection) => {
|
|
|
|
|
// console.log('下载资料:', section)
|
|
|
|
|
// // 这里可以实现下载逻辑
|
|
|
|
|
// alert(`下载资料: ${section.name}`)
|
|
|
|
|
// }
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 处理作业操作
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// const handleHomework = (section: CourseSection) => {
|
|
|
|
|
// console.log('打开作业:', section)
|
|
|
|
|
|
|
|
|
|
// // 跳转到练习页面
|
|
|
|
|
// router.push({
|
|
|
|
|
// name: 'Practice',
|
|
|
|
|
// params: {
|
|
|
|
|
// courseId: courseId.value,
|
|
|
|
|
// sectionId: section.id
|
|
|
|
|
// },
|
|
|
|
|
// query: {
|
|
|
|
|
// courseName: course.value?.title || '课程名称',
|
|
|
|
|
// practiceName: section.name
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 处理考试操作
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// 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
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 点击课程章节标题
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// const handleSectionClick = (section: CourseSection) => {
|
|
|
|
|
// console.log('点击课程章节:', section)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// // 设置当前选中的章节
|
|
|
|
|
// currentSection.value = section
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// // 检查是否有视频链接
|
|
|
|
|
// if (section.outline && section.outline.includes('.m3u8')) {
|
|
|
|
|
// console.log('获取到视频链接:', section.outline)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// // 跳转到已报名区域并播放视频
|
|
|
|
|
// navigateToEnrolledArea(section.outline, section.name)
|
|
|
|
|
// } else {
|
|
|
|
|
// // 如果不是视频,显示预览
|
|
|
|
|
// previewSection(section)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 跳转到已报名区域
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// 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
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 更新视频播放器(备用方案)
|
|
|
|
|
// const updateVideoPlayer = (videoUrl: string, sectionName: string) => {
|
|
|
|
|
// console.log('更新视频播放器:', { videoUrl, sectionName })
|
|
|
|
|
|
|
|
|
|
// // 如果在同一页面内更新视频播放器
|
|
|
|
|
// // 可以通过事件总线或状态管理来实现
|
|
|
|
|
|
|
|
|
|
// // 这里先显示确认信息
|
|
|
|
|
// const confirmed = confirm(`即将播放视频: ${sectionName}\n是否继续?`)
|
|
|
|
|
// if (confirmed) {
|
|
|
|
|
// navigateToEnrolledArea(videoUrl, sectionName)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// 预览章节(非视频内容)
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// const previewSection = (section: CourseSection) => {
|
|
|
|
|
// console.log('预览章节:', section)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-08-26 19:07:48 +08:00
|
|
|
|
// previewModalTitle.value = section.name
|
|
|
|
|
// previewModalContent.value = `章节ID: ${section.id}\n章节名称: ${section.name}\n内容类型: ${getLessonTypeText(section)}`
|
|
|
|
|
// previewModalType.value = 'section'
|
|
|
|
|
// previewModalVisible.value = true
|
|
|
|
|
// }
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
// 关闭预览模态框
|
|
|
|
|
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
|
|
|
|
|
|
2025-09-04 23:15:29 +08:00
|
|
|
|
console.log('🚀 开始报名课程,课程ID:', courseId.value)
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-09-04 23:15:29 +08:00
|
|
|
|
// 调用真实的报名API
|
|
|
|
|
const response = await CourseApi.enrollCourse(courseId.value)
|
|
|
|
|
console.log('📊 报名API响应:', response)
|
|
|
|
|
|
|
|
|
|
if (response.code === 200 || response.code === 0) {
|
|
|
|
|
console.log('✅ 报名成功:', response.data)
|
|
|
|
|
|
|
|
|
|
// 报名成功
|
|
|
|
|
isEnrolled.value = true
|
|
|
|
|
enrollConfirmVisible.value = false
|
|
|
|
|
enrollSuccessVisible.value = true
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
2025-09-04 23:15:29 +08:00
|
|
|
|
// 2秒后跳转到已兑换课程页面
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
enrollSuccessVisible.value = false
|
|
|
|
|
// 跳转到已兑换课程页面
|
|
|
|
|
router.push(`/course/${courseId.value}/exchanged`)
|
|
|
|
|
}, 2000)
|
|
|
|
|
} else {
|
|
|
|
|
console.error('❌ 报名失败:', response.message)
|
|
|
|
|
message.error(response.message || '报名失败,请稍后重试')
|
|
|
|
|
enrollConfirmVisible.value = false
|
|
|
|
|
}
|
2025-08-16 19:51:39 +08:00
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-09-04 23:15:29 +08:00
|
|
|
|
console.error('❌ 报名失败:', error)
|
|
|
|
|
message.error('报名失败,请稍后重试')
|
|
|
|
|
enrollConfirmVisible.value = false
|
2025-08-16 19:51:39 +08:00
|
|
|
|
} finally {
|
|
|
|
|
enrollmentLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 取消报名
|
|
|
|
|
const cancelEnrollment = () => {
|
|
|
|
|
enrollConfirmVisible.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理未报名用户点击
|
|
|
|
|
const handleUnregisteredClick = (section: CourseSection) => {
|
|
|
|
|
console.log('未报名用户点击课程:', section.name)
|
|
|
|
|
// 显示报名提示
|
|
|
|
|
enrollConfirmVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理课程兑换
|
|
|
|
|
const handleExchangeCourse = () => {
|
|
|
|
|
if (!userStore.isLoggedIn) {
|
|
|
|
|
// 未登录,显示登录弹窗
|
|
|
|
|
showLoginModal()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示兑换确认弹窗
|
|
|
|
|
console.log('用户点击兑换课程')
|
|
|
|
|
enrollConfirmVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-02 16:29:25 +08:00
|
|
|
|
// 处理普通模式课程报名
|
|
|
|
|
const handleEnrollCourse = () => {
|
|
|
|
|
if (!userStore.isLoggedIn) {
|
|
|
|
|
// 未登录,显示登录弹窗
|
|
|
|
|
showLoginModal()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示报名确认弹窗
|
|
|
|
|
console.log('用户点击报名课程')
|
|
|
|
|
enrollConfirmVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 19:51:39 +08:00
|
|
|
|
// 测试直接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=已报名状态
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 18:51:00 +08:00
|
|
|
|
// 监听路由参数变化
|
|
|
|
|
watch(() => route.query.courseId, (newCourseId) => {
|
|
|
|
|
if (newCourseId && typeof newCourseId === 'string') {
|
|
|
|
|
courseId.value = newCourseId
|
|
|
|
|
console.log('路由参数变化,重新加载课程数据,课程ID:', courseId.value)
|
|
|
|
|
loadCourseDetail()
|
|
|
|
|
loadCourseSections()
|
|
|
|
|
}
|
|
|
|
|
}, { immediate: false })
|
|
|
|
|
|
2025-08-16 19:51:39 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
console.log('课程详情页加载完成,课程ID:', courseId.value)
|
|
|
|
|
initializeMockState() // 初始化模拟状态
|
|
|
|
|
loadCourseDetail()
|
|
|
|
|
loadCourseSections()
|
2025-08-30 03:57:24 +08:00
|
|
|
|
loadCourseComments() // 加载评论
|
2025-08-16 19:51:39 +08:00
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.course-detail-page {
|
|
|
|
|
padding-top: 30px;
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
background: #F6F6F6;
|
|
|
|
|
background-image: url('/images/aiCompanion/背景色@2x.png');
|
|
|
|
|
background-size: cover;
|
|
|
|
|
background-position: center;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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 {
|
2025-09-01 17:56:41 +08:00
|
|
|
|
max-width: none;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding-left: 120px;
|
|
|
|
|
padding-right: 125px;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breadcrumb {
|
|
|
|
|
padding: 12px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breadcrumb-text {
|
|
|
|
|
color: #666;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-content {
|
|
|
|
|
padding-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-layout {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 40px;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-content {
|
|
|
|
|
display: flex;
|
2025-09-01 17:56:41 +08:00
|
|
|
|
gap: 64px;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
width: 100%;
|
|
|
|
|
flex-direction: row-reverse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-column {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar {
|
|
|
|
|
width: 370px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
padding-top: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-title {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-title h2 {
|
|
|
|
|
font-family: PingFangSC, PingFang SC;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
color: #000000;
|
|
|
|
|
line-height: 26px;
|
|
|
|
|
text-align: justify;
|
|
|
|
|
font-style: normal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-title img {
|
|
|
|
|
width: 14px;
|
|
|
|
|
height: 14px;
|
|
|
|
|
/* background: #999999; */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 学习进度区域 */
|
|
|
|
|
.progress-section {
|
|
|
|
|
margin-top: 5px;
|
|
|
|
|
background: #ffffff90;
|
|
|
|
|
padding: 24px;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
border: 1px solid white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 进度头部样式 */
|
|
|
|
|
.progress-header {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-title {
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
/* margin: 0 0 8px 0; */
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-subtitle {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
margin: 0;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 三个圆形进度图表 */
|
|
|
|
|
.progress-circles {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 22px;
|
|
|
|
|
gap: 40px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
text-align: center;
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.circle-container {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 80px;
|
|
|
|
|
height: 80px;
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-circle {
|
|
|
|
|
transform: rotate(-90deg);
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-bg {
|
|
|
|
|
opacity: 0.4;
|
|
|
|
|
stroke: #E2F5FF;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-fill {
|
|
|
|
|
transition: stroke-dashoffset 0.8s ease-in-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.circle-content {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 58%;
|
|
|
|
|
left: 50%;
|
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-icon {
|
|
|
|
|
/* margin-bottom: 4px; */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-label {
|
|
|
|
|
margin-top: -5px;
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: #999;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-percentage {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
color: #999;
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 总体进度条 */
|
|
|
|
|
.overall-progress {
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-bar-container {
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-bar {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 8px;
|
|
|
|
|
background: #E2F5FF;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-bar-fill {
|
|
|
|
|
height: 100%;
|
|
|
|
|
background: #078BD2;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
transition: width 0.8s ease-in-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-text {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-title {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #999;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-value {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #999;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-count {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.current {
|
|
|
|
|
color: #333;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.separator {
|
|
|
|
|
color: #999;
|
|
|
|
|
margin: 0 2px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.total {
|
|
|
|
|
color: #999;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 状态指示器 */
|
|
|
|
|
.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;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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-image: url('/images/aiCompanion/bg.png');
|
|
|
|
|
background-size: cover;
|
|
|
|
|
background-position: center;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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: 20%;
|
|
|
|
|
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: 42px 20px;
|
|
|
|
|
max-width: 800px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-main-title {
|
|
|
|
|
font-size: 26px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
|
|
|
line-height: 1.3;
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 课程统计信息样式 */
|
|
|
|
|
.course-stats-info {
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-stats-info .stats-item {
|
|
|
|
|
color: #999999;
|
|
|
|
|
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: 14px 24px;
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
border-top: 1px solid #e5e7eb;
|
|
|
|
|
min-height: 60px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.interaction-left {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.interaction-btn {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
background: none;
|
|
|
|
|
border: none;
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
padding: 8px 2px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.interaction-btn:hover {
|
|
|
|
|
background: #f9fafb;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.split-line {
|
|
|
|
|
height: 12px;
|
|
|
|
|
width: 2px;
|
|
|
|
|
background: #f3f3f3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 交互按钮图标样式已在各自的图标类中定义 */
|
|
|
|
|
|
|
|
|
|
.interaction-right {
|
|
|
|
|
flex: 1;
|
|
|
|
|
max-width: 650px;
|
|
|
|
|
margin-left: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-input {
|
|
|
|
|
position: relative;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-input input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 12px 80px 12px 20px;
|
|
|
|
|
border: 1px solid #F1F1F1;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
background: #F1F1F1;
|
|
|
|
|
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: 0;
|
|
|
|
|
top: 50%;
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
background: #9A9A9A;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
border-radius: 0 10px 10px 0;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
height: 42px;
|
|
|
|
|
min-width: 70px;
|
|
|
|
|
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: 16px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
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: 16px;
|
|
|
|
|
height: 16px;
|
|
|
|
|
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-time {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.share-text {
|
|
|
|
|
margin-right: 35px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.icon-note {
|
|
|
|
|
width: 18px !important;
|
|
|
|
|
height: 18px !important;
|
|
|
|
|
background-image: url('/images/courses/note.png');
|
|
|
|
|
background-size: contain;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
background-position: center;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.icon-notes {
|
|
|
|
|
width: 32px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
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: 32px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
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 0; */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-header {
|
|
|
|
|
padding-top: 18px;
|
|
|
|
|
background: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-title {
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
color: #333;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
line-height: 1.3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-meta {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.meta-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.meta-row:first-child {
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.meta-right {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
width: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.meta-item {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #999999;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 3px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.meta-separator {
|
|
|
|
|
color: #d9d9d9;
|
|
|
|
|
width: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.category-link {
|
|
|
|
|
color: #0088D1;
|
|
|
|
|
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: #fff;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
color: #000;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-notes:hover {
|
|
|
|
|
background: #e9ecef;
|
|
|
|
|
border-color: #dee2e6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 课程描述 */
|
|
|
|
|
.course-description {
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
color: #999;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-content-detail {
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-content-detail h4 {
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 讲师信息 */
|
|
|
|
|
.instructors-section {
|
|
|
|
|
padding-bottom: 24px;
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-style: normal;
|
|
|
|
|
color: #000;
|
|
|
|
|
padding-top: 12px;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
line-height: 22px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.instructors-list {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 30px;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.instructor-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.instructor-avatar {
|
|
|
|
|
width: 50px;
|
|
|
|
|
height: 50px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
border: 2px solid #f0f0f0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.safe-avatar {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.safe-avatar img {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.instructor-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.instructor-info {
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.instructor-name {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #000;
|
|
|
|
|
margin-bottom: 1px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.instructor-title {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 分隔线样式 */
|
|
|
|
|
.course-info-divider {
|
|
|
|
|
height: 1px;
|
|
|
|
|
margin: 10px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 课程标签页 */
|
|
|
|
|
.course-tabs {
|
|
|
|
|
padding: 14px 24px;
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
|
margin-bottom: 40px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tab-nav {
|
|
|
|
|
display: flex;
|
|
|
|
|
border-bottom: 2px solid #E6E6E6;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tab-btn {
|
|
|
|
|
background: none;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 12px 0 12px 0;
|
|
|
|
|
margin-right: 54px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #333;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
position: relative;
|
|
|
|
|
transition: color 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tab-btn.active {
|
|
|
|
|
color: #008BD7;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tab-btn.active::after {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
bottom: -1px;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
height: 2px;
|
|
|
|
|
background: #008BD7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tab-btn:hover {
|
|
|
|
|
color: #008BD7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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 {
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
padding: 0;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sections-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 0 0 10px 0;
|
|
|
|
|
/* 透明 */
|
|
|
|
|
background: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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 0;
|
|
|
|
|
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;
|
|
|
|
|
padding: 12px 20px 20px 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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: 10px 16px;
|
|
|
|
|
background: #F5F8FB;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: background-color 0.2s;
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chapter-header:hover {
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chapter-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chapter-number {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #333;
|
|
|
|
|
min-width: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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: 3px 0 3px 0;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lesson-type-badge {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
padding: 3px 0;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
min-width: 32px;
|
|
|
|
|
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: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lesson-duration.disabled {
|
|
|
|
|
color: #E1E1E1;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lesson-type-badge.disabled {
|
|
|
|
|
background: #fff !important;
|
|
|
|
|
color: #C0C0C0 !important;
|
|
|
|
|
border: 1px solid #E1E1E1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lesson-meta {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
align-items: center;
|
|
|
|
|
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,
|
|
|
|
|
.video-btn img {
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.download-btn svg,
|
|
|
|
|
.download-btn img {
|
|
|
|
|
color: #52c41a;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.edit-btn svg,
|
|
|
|
|
.edit-btn img {
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exam-btn svg,
|
|
|
|
|
.exam-btn img {
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
display: inline-block;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 完成状态图标 */
|
|
|
|
|
.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;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
|
background: none;
|
|
|
|
|
border: none;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
transition: color 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn:hover {
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn span {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn .top {
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
color: #FF304B;
|
|
|
|
|
background-color: #FFF4F4;
|
|
|
|
|
border-radius: 30px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.enroll-section {
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-top: -10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-enroll {
|
|
|
|
|
width: 100%;
|
|
|
|
|
background: #E3F6FF;
|
|
|
|
|
color: #75AEC4;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
border: 1px solid #377E9F;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-enroll:hover {
|
|
|
|
|
background: #40a9ff;
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-course-status {
|
|
|
|
|
width: 100%;
|
|
|
|
|
background: #EFEFEF;
|
|
|
|
|
color: #A1B2B2;
|
|
|
|
|
border: 1px solid #6c6a6a;
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-course-status:disabled {
|
|
|
|
|
background: #EFEFEF;
|
|
|
|
|
color: #A1B2B2;
|
|
|
|
|
border: 1px solid #9FA6A6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 更多课程 */
|
|
|
|
|
.more-courses {
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 30px 45px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.more-courses-header h3 {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #333;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
position: relative;
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.more-courses-header h3::before,
|
|
|
|
|
.more-courses-header h3::after {
|
|
|
|
|
content: "";
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 50%;
|
|
|
|
|
width: 34%;
|
|
|
|
|
height: 1px;
|
|
|
|
|
background-color: #E1E1E1;
|
|
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.more-courses-header h3::before {
|
|
|
|
|
left: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.more-courses-header h3::after {
|
|
|
|
|
right: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.more-courses-list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-card {
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
background: white;
|
|
|
|
|
border: 1px solid #E1E1E1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.computer-bg {
|
|
|
|
|
background: linear-gradient(135deg, #87CEEB 0%, #4682B4 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.computer-bg img {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.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 16px 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-desc {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #333;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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: #0088D1;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 6px 10px;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-enroll-course:hover {
|
|
|
|
|
background: #40a9ff;
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.instructors-section,
|
|
|
|
|
.course-description,
|
|
|
|
|
.course-header {
|
|
|
|
|
padding-left: 24px;
|
|
|
|
|
padding-right: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 响应式设计 */
|
2025-09-01 17:56:41 +08:00
|
|
|
|
/* 大屏幕 - 使用120px左右边距 */
|
|
|
|
|
@media (min-width: 1400px) {
|
|
|
|
|
.container {
|
|
|
|
|
padding-left: 120px;
|
|
|
|
|
padding-right: 120px;
|
|
|
|
|
max-width: none;
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-16 19:51:39 +08:00
|
|
|
|
@media (max-width: 1399px) and (min-width: 1200px) {
|
|
|
|
|
.container {
|
|
|
|
|
padding: 0 24px;
|
|
|
|
|
max-width: 1200px;
|
2025-09-01 17:56:41 +08:00
|
|
|
|
margin: 0 auto;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar {
|
|
|
|
|
width: 350px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1199px) and (min-width: 992px) {
|
|
|
|
|
.container {
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
max-width: 992px;
|
2025-09-01 17:56:41 +08:00
|
|
|
|
margin: 0 auto;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-content {
|
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar {
|
|
|
|
|
width: 320px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 平板横屏 */
|
|
|
|
|
@media (max-width: 1023px) and (min-width: 768px) {
|
|
|
|
|
.container {
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
max-width: 768px;
|
2025-09-01 17:56:41 +08:00
|
|
|
|
margin: 0 auto;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-content {
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar {
|
|
|
|
|
width: 280px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 平板竖屏及以下 */
|
|
|
|
|
@media (max-width: 767px) {
|
|
|
|
|
.container {
|
|
|
|
|
padding: 0 16px;
|
|
|
|
|
max-width: 576px;
|
2025-09-01 17:56:41 +08:00
|
|
|
|
margin: 0 auto;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
2025-09-01 17:56:41 +08:00
|
|
|
|
margin: 0 auto;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 进度图表响应式 */
|
|
|
|
|
.progress-circles {
|
|
|
|
|
gap: 24px;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.circle-container {
|
|
|
|
|
width: 70px;
|
|
|
|
|
height: 70px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-circle {
|
|
|
|
|
width: 70px;
|
|
|
|
|
height: 70px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-label {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-percentage {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-title,
|
|
|
|
|
.progress-value {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-count {
|
|
|
|
|
font-size: 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;
|
2025-09-01 17:56:41 +08:00
|
|
|
|
margin: 0 auto;
|
|
|
|
|
max-width: 480px;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 进度图表小屏响应式 */
|
|
|
|
|
.progress-circles {
|
|
|
|
|
gap: 16px;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.circle-container {
|
|
|
|
|
width: 60px;
|
|
|
|
|
height: 60px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-circle {
|
|
|
|
|
width: 60px;
|
|
|
|
|
height: 60px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-percentage {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-count {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 超小屏幕优化 */
|
|
|
|
|
@media (max-width: 360px) {
|
|
|
|
|
.container {
|
|
|
|
|
padding: 0 8px;
|
2025-09-01 17:56:41 +08:00
|
|
|
|
margin: 0 auto;
|
|
|
|
|
max-width: 360px;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.video-player.unregistered {
|
|
|
|
|
height: 300px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-main-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.interaction-btn {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
padding: 4px 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.interaction-left {
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 智点兑换界面样式 */
|
|
|
|
|
.lock-icon {
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lock-icon img {
|
|
|
|
|
width: 30px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.wisdom-points-info {
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-bottom: 32px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-message {
|
|
|
|
|
color: white;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exchange-button-container {
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exchange-button {
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: 1px solid #FFE2B0;
|
|
|
|
|
color: #FFE2B0;
|
|
|
|
|
padding: 10px 20px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exchange-button:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.2);
|
|
|
|
|
border-color: rgba(255, 255, 255, 0.5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-points-info {
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.points-display {
|
|
|
|
|
color: white;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
opacity: 0.9;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.get-more-points {
|
|
|
|
|
color: white;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.get-more-points:hover {
|
|
|
|
|
color: #81D4FA;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 横幅标题区域样式 */
|
|
|
|
|
.banner-title-section {
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.banner-content {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
color: #000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.banner-text {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-text {
|
|
|
|
|
color: #000;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.ai-companion-tag {
|
|
|
|
|
width: 64px;
|
2025-08-18 12:03:30 +08:00
|
|
|
|
height: 20px;
|
2025-08-16 19:51:39 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tag-image {
|
|
|
|
|
height: 100%;
|
|
|
|
|
width: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tag-text {
|
|
|
|
|
color: white;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.banner-button {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: 1px solid #0088D1;
|
|
|
|
|
width: 70px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.button-icon {
|
|
|
|
|
color: white;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.button-icon-image {
|
|
|
|
|
height: 8px;
|
|
|
|
|
width: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.button-text {
|
|
|
|
|
color: #0088D1;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
2025-08-18 12:03:30 +08:00
|
|
|
|
|
|
|
|
|
/* 评论区样式 */
|
|
|
|
|
.comments-content {
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 发布评论区域 */
|
|
|
|
|
.post-comment-section {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-input-wrapper {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-avatar img {
|
|
|
|
|
width: 40px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-input-area {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-textarea {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border: 1px solid #E6E6E6;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
resize: none;
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
height: 40px;
|
|
|
|
|
min-height: 40px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
transition: height 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-textarea:focus {
|
|
|
|
|
outline: none;
|
|
|
|
|
border-color: #1890ff;
|
|
|
|
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-textarea::placeholder {
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-toolbar {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toolbar-left {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toolbar-btn {
|
|
|
|
|
background: none;
|
|
|
|
|
border: none;
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
border: 1.5px solid #E6E6E6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toolbar-btn:hover {
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toolbar-icon {
|
|
|
|
|
width: 12px;
|
|
|
|
|
height: 12px;
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toolbar-right {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-submit {
|
|
|
|
|
background: #0088D1;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 3px 10px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
transition: background-color 0.3s;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-submit:hover:not(:disabled) {
|
|
|
|
|
background: #40a9ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-submit:disabled {
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
color: #bfbfbf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 回复输入区域样式 */
|
|
|
|
|
.reply-input-section {
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
border: 1px solid #e9ecef;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-input-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-to-text {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #666;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cancel-reply-btn {
|
|
|
|
|
background: none;
|
|
|
|
|
border: none;
|
|
|
|
|
color: #999;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cancel-reply-btn:hover {
|
|
|
|
|
background: #f0f0f0;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-input-container {
|
|
|
|
|
background: white;
|
|
|
|
|
border: 1px solid #E6E6E6;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-textarea {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
resize: none;
|
|
|
|
|
font-family: inherit;
|
|
|
|
|
height: 40px;
|
|
|
|
|
min-height: 40px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
transition: height 0.3s ease;
|
|
|
|
|
background: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-textarea:focus {
|
|
|
|
|
outline: none;
|
|
|
|
|
border-color: #1890ff;
|
|
|
|
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-textarea::placeholder {
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-toolbar {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
border-top: 1px solid #f0f0f0;
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-username {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #666666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-time {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-text {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
color: #333;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-image-container {
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-image-container img {
|
|
|
|
|
width: 84px;
|
|
|
|
|
height: 84px;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.image-overlay {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: calc(6 * (84px + 16px));
|
|
|
|
|
width: 84px;
|
|
|
|
|
height: 84px;
|
|
|
|
|
background: rgba(0, 0, 0, 0.6);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.more-images-text {
|
|
|
|
|
color: white;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comment-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.note-icon-container {
|
|
|
|
|
width: 50px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
background: #EDEDED;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.note-icon-container img {
|
|
|
|
|
width: 10px;
|
|
|
|
|
height: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.note-icon-container span {
|
|
|
|
|
color: #666666;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
|
background: none;
|
|
|
|
|
border: none;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
transition: color 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn:hover {
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn span {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn .top {
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
color: #FF304B;
|
|
|
|
|
background-color: #FFF4F4;
|
|
|
|
|
border-radius: 30px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 回复和二级评论样式 */
|
|
|
|
|
.comment-replies {
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-item:last-child {
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-avatar {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-avatar img {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-main {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-username {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-badge {
|
|
|
|
|
width: 32px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
line-height: 20px;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-badge.instructor {
|
|
|
|
|
background: #EEF9FF;
|
|
|
|
|
color: #008BD7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-badge.user {
|
|
|
|
|
background: #f6ffed;
|
|
|
|
|
color: #52c41a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-time {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-text {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
color: #333;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-footer {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: left;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
gap: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-action-btn {
|
|
|
|
|
background: none;
|
|
|
|
|
border: none;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: color 0.3s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reply-action-btn:hover {
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
}
|
2025-08-30 03:57:24 +08:00
|
|
|
|
|
|
|
|
|
/* 评论状态样式 */
|
|
|
|
|
.comments-loading, .comments-error, .no-comments {
|
|
|
|
|
padding: 40px 20px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
color: #666;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comments-error {
|
|
|
|
|
color: #ff4d4f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comments-error .retry-btn {
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
background: #1890ff;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
transition: background 0.2s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.comments-error .retry-btn:hover {
|
|
|
|
|
background: #40a9ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.no-comments {
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
2025-09-02 16:29:25 +08:00
|
|
|
|
|
|
|
|
|
/* 普通模式样式 */
|
|
|
|
|
.normal-course-info {
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.normal-main-message {
|
|
|
|
|
color: white;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.enroll-button-container {
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.enroll-button {
|
|
|
|
|
width: 112px;
|
|
|
|
|
height: 42px;
|
|
|
|
|
background: #0088D1;
|
|
|
|
|
border: 1px solid #0088D1;
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.enroll-button span {
|
|
|
|
|
width: 64px;
|
|
|
|
|
height: 22px;
|
|
|
|
|
font-family: PingFangSC, PingFang SC;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
line-height: 22px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
font-style: normal;
|
|
|
|
|
text-transform: none;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.enroll-button:hover {
|
|
|
|
|
background: #0077B8;
|
|
|
|
|
border-color: #0077B8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.enroll-button:active {
|
|
|
|
|
background: #006BA1;
|
|
|
|
|
border-color: #006BA1;
|
|
|
|
|
}
|
2025-08-16 19:51:39 +08:00
|
|
|
|
</style>
|