|
@@ -0,0 +1,331 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="my-learning-page">
|
|
|
|
|
+ <!-- <el-page-header content="我的学习"></el-page-header> -->
|
|
|
|
|
+ <Breadcrumb />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 学习统计卡片 -->
|
|
|
|
|
+ <div class="learning-stats">
|
|
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
|
|
+ <div class="stat-item">
|
|
|
|
|
+ <!-- <i class="el-icon-video-play"></i> -->
|
|
|
|
|
+ <el-icon><VideoPlay /></el-icon>
|
|
|
|
|
+ <div class="stat-info">
|
|
|
|
|
+ <p class="stat-label">已购课程</p>
|
|
|
|
|
+ <p class="stat-value">{{ myCourses.length }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
|
|
+ <div class="stat-item">
|
|
|
|
|
+ <el-icon><Clock /></el-icon>
|
|
|
|
|
+ <div class="stat-info">
|
|
|
|
|
+ <p class="stat-label">学习时长</p>
|
|
|
|
|
+ <p class="stat-value">{{ studyTime }} 小时</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
|
|
+ <div class="stat-item">
|
|
|
|
|
+ <el-icon><Check /></el-icon>
|
|
|
|
|
+ <div class="stat-info">
|
|
|
|
|
+ <p class="stat-label">已完成课程</p>
|
|
|
|
|
+ <p class="stat-value">{{ completedCourses }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 已购课程列表 -->
|
|
|
|
|
+ <div class="my-courses">
|
|
|
|
|
+ <h2>我的课程</h2>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="myCourses.length === 0" class="no-courses">
|
|
|
|
|
+ <el-empty description="你还没有购买任何课程"></el-empty>
|
|
|
|
|
+ <el-button type="primary" @click="$router.push('/')">去选课</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else class="course-list">
|
|
|
|
|
+ <el-card
|
|
|
|
|
+ v-for="course in myCourses"
|
|
|
|
|
+ :key="course.id"
|
|
|
|
|
+ class="my-course-card"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="course-header">
|
|
|
|
|
+ <img :src="course.cover" :alt="course.title" class="course-cover">
|
|
|
|
|
+ <div class="course-info">
|
|
|
|
|
+ <h3>{{ course.title }}</h3>
|
|
|
|
|
+ <p>讲师:{{ course.teacher }}</p>
|
|
|
|
|
+ <div class="progress-container">
|
|
|
|
|
+ <el-progress
|
|
|
|
|
+ :percentage="course.progress"
|
|
|
|
|
+ :status="course.progress === 100 ? 'success' : 'processing'"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ ></el-progress>
|
|
|
|
|
+ <span class="progress-text">{{ course.progress }}%</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="course-actions">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ icon="el-icon-play-circle"
|
|
|
|
|
+ @click="continueLearning(course.id)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 继续学习
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ icon="el-icon-star-off"
|
|
|
|
|
+ @click="toggleCollect(course.id)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ course.isCollected ? '取消收藏' : '收藏' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 最近学习记录 -->
|
|
|
|
|
+ <div class="recent-learning" v-if="recentRecords.length > 0">
|
|
|
|
|
+ <h2>最近学习</h2>
|
|
|
|
|
+ <el-table :data="recentRecords" border style="width: 100%">
|
|
|
|
|
+ <el-table-column label="课程" prop="courseTitle"></el-table-column>
|
|
|
|
|
+ <el-table-column label="最近学习视频" prop="videoTitle"></el-table-column>
|
|
|
|
|
+ <el-table-column label="学习时间" prop="studyTime"></el-table-column>
|
|
|
|
|
+ <el-table-column label="操作">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ @click="continueLearning(scope.row.courseId)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 继续学习
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+ // import { VideoPlay, Clock, Check } from '@element-plus/icons-vue'
|
|
|
|
|
+ import { ElProgress } from 'element-plus'
|
|
|
|
|
+import { ref, computed } from 'vue'
|
|
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
|
|
+
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+
|
|
|
|
|
+// 模拟已购课程数据(实际项目中从 Pinia 或接口获取)
|
|
|
|
|
+const myCourses = ref([
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 1,
|
|
|
|
|
+ title: 'Vue 3 从入门到精通',
|
|
|
|
|
+ cover: 'https://picsum.photos/400/225?random=1',
|
|
|
|
|
+ teacher: '张三',
|
|
|
|
|
+ progress: 65, // 学习进度(百分比)
|
|
|
|
|
+ isCollected: true // 是否收藏
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 2,
|
|
|
|
|
+ title: 'JavaScript 高级编程',
|
|
|
|
|
+ cover: 'https://picsum.photos/400/225?random=2',
|
|
|
|
|
+ teacher: '李四',
|
|
|
|
|
+ progress: 30,
|
|
|
|
|
+ isCollected: false
|
|
|
|
|
+ }
|
|
|
|
|
+])
|
|
|
|
|
+
|
|
|
|
|
+// 模拟最近学习记录
|
|
|
|
|
+const recentRecords = ref([
|
|
|
|
|
+ {
|
|
|
|
|
+ courseId: 1,
|
|
|
|
|
+ courseTitle: 'Vue 3 从入门到精通',
|
|
|
|
|
+ videoTitle: 'Composition API 基础',
|
|
|
|
|
+ studyTime: '2025-01-15 19:30'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ courseId: 2,
|
|
|
|
|
+ courseTitle: 'JavaScript 高级编程',
|
|
|
|
|
+ videoTitle: '闭包与作用域',
|
|
|
|
|
+ studyTime: '2025-01-14 16:45'
|
|
|
|
|
+ }
|
|
|
|
|
+])
|
|
|
|
|
+
|
|
|
|
|
+// 计算属性:学习时长(模拟数据,实际可累加视频观看时长)
|
|
|
|
|
+const studyTime = computed(() => {
|
|
|
|
|
+ // 简单模拟:每个课程进度每10%对应1小时学习时长
|
|
|
|
|
+ return myCourses.value.reduce((total, course) => {
|
|
|
|
|
+ return total + Math.floor(course.progress / 10)
|
|
|
|
|
+ }, 0)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 计算属性:已完成课程数(进度100%)
|
|
|
|
|
+const completedCourses = computed(() => {
|
|
|
|
|
+ return myCourses.value.filter(course => course.progress === 100).length
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 继续学习(跳转到课程详情页)
|
|
|
|
|
+const continueLearning = (courseId) => {
|
|
|
|
|
+ router.push(`/course/${courseId}`)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 收藏/取消收藏课程
|
|
|
|
|
+const toggleCollect = (courseId) => {
|
|
|
|
|
+ const course = myCourses.value.find(item => item.id === courseId)
|
|
|
|
|
+ if (course) {
|
|
|
|
|
+ course.isCollected = !course.isCollected
|
|
|
|
|
+ // 实际项目中这里需要调用接口保存状态到后端
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.my-learning-page {
|
|
|
|
|
+ max-width: 1200px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 学习统计卡片
|
|
|
|
|
+.learning-stats {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ margin-bottom: 30px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+
|
|
|
|
|
+ .stat-card {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 200px;
|
|
|
|
|
+
|
|
|
|
|
+ .stat-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 15px 0;
|
|
|
|
|
+
|
|
|
|
|
+ i {
|
|
|
|
|
+ font-size: 2rem;
|
|
|
|
|
+ color: $primary-color;
|
|
|
|
|
+ margin-right: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .stat-label {
|
|
|
|
|
+ color: $text-light-color;
|
|
|
|
|
+ font-size: 0.9rem;
|
|
|
|
|
+ margin-bottom: 5px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .stat-value {
|
|
|
|
|
+ font-size: 1.8rem;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 我的课程列表
|
|
|
|
|
+.my-courses {
|
|
|
|
|
+ h2 {
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
|
+ border-bottom: 2px solid $border-color;
|
|
|
|
|
+ padding-bottom: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .no-courses {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 50px 0;
|
|
|
|
|
+
|
|
|
|
|
+ .el-empty {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .course-list {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .my-course-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+
|
|
|
|
|
+ .course-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 15px;
|
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
|
+
|
|
|
|
|
+ .course-cover {
|
|
|
|
|
+ width: 120px;
|
|
|
|
|
+ height: 80px;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .course-info {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+
|
|
|
|
|
+ h3 {
|
|
|
|
|
+ font-size: 1.1rem;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ display: -webkit-box;
|
|
|
|
|
+ -webkit-line-clamp: 1;
|
|
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ p {
|
|
|
|
|
+ color: $text-light-color;
|
|
|
|
|
+ font-size: 0.9rem;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .progress-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+
|
|
|
|
|
+ .progress-text {
|
|
|
|
|
+ font-size: 0.8rem;
|
|
|
|
|
+ color: $text-light-color;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .course-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-top: auto;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 最近学习记录
|
|
|
|
|
+.recent-learning {
|
|
|
|
|
+ margin-top: 40px;
|
|
|
|
|
+
|
|
|
|
|
+ h2 {
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
|
+ border-bottom: 2px solid $border-color;
|
|
|
|
|
+ padding-bottom: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 响应式调整
|
|
|
|
|
+@media (max-width: 768px) {
|
|
|
|
|
+ .learning-stats {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .my-courses .course-list {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|