peixue-dev/peidu/docs/fixes/2026-01-23-我的课程与学习记录关联/✅前端代码实现.md

11 KiB

前端代码实现 - 我的课程与学习记录关联

📱 1. API接口定义

peidu/uniapp/src/api/parentAcademy.js 中添加:

// 课程学习相关接口
export default {
  // ... 现有接口 ...
  
  // 开始学习课程
  startLearning(data) {
    return request({
      url: '/api/parent-academy/start-learning',
      method: 'POST',
      data
    })
  },
  
  // 更新学习进度
  updateProgress(data) {
    return request({
      url: '/api/parent-academy/update-progress',
      method: 'POST',
      data
    })
  },
  
  // 完成课程学习
  completeLearning(data) {
    return request({
      url: '/api/parent-academy/complete-learning',
      method: 'POST',
      data
    })
  },
  
  // 获取学习历史
  getLearningHistory(courseId) {
    return request({
      url: '/api/parent-academy/learning-history',
      method: 'GET',
      params: { courseId }
    })
  },
  
  // 提交课程评价
  submitReview(data) {
    return request({
      url: '/api/parent-academy/submit-review',
      method: 'POST',
      data
    })
  }
}

📄 2. 改造 my-courses.vue 页面

2.1 增强课程卡片显示

在课程卡片中添加学习进度条:

<template>
  <view class="course-card" @tap="viewCourse(course)">
    <!-- 现有内容 -->
    
    <!-- 新增: 学习进度条 -->
    <view class="progress-section" v-if="course.learningRecord">
      <view class="progress-bar">
        <view 
          class="progress-fill" 
          :style="{ width: course.learningRecord.learningProgress + '%' }"
        ></view>
      </view>
      <view class="progress-info">
        <text class="progress-text">
          学习进度: {{ course.learningRecord.learningProgress }}%
        </text>
        <text class="points-text" v-if="course.learningRecord.pointsAwarded > 0">
          已获得 {{ course.learningRecord.pointsAwarded }} 积分
        </text>
      </view>
    </view>
    
    <!-- 新增: 完成标记 -->
    <view class="completed-badge" v-if="course.learningRecord && course.learningRecord.isCompleted">
      <text class="badge-icon">✓</text>
      <text class="badge-text">已完成</text>
    </view>
  </view>
</template>

<script>
export default {
  methods: {
    async loadMyCourses() {
      // ... 现有代码 ...
      
      // 转换数据格式时添加学习记录
      this.courseList = list.map(item => {
        const purchase = item.purchase || item
        const course = item.course || item
        const learningRecord = item.learningRecord || null
        
        return {
          // ... 现有字段 ...
          learningRecord: learningRecord ? {
            id: learningRecord.id,
            learningProgress: learningRecord.learningProgress || 0,
            lastPosition: learningRecord.lastPosition || 0,
            isCompleted: learningRecord.isCompleted || 0,
            pointsAwarded: learningRecord.pointsAwarded || 0,
            notes: learningRecord.notes || ''
          } : null
        }
      })
    }
  }
}
</script>

<style scoped>
/* 学习进度条样式 */
.progress-section {
  margin-top: 20rpx;
  padding-top: 20rpx;
  border-top: 1rpx solid #f0f0f0;
}

.progress-bar {
  width: 100%;
  height: 8rpx;
  background: #f0f0f0;
  border-radius: 4rpx;
  overflow: hidden;
  margin-bottom: 15rpx;
}

.progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
  border-radius: 4rpx;
  transition: width 0.3s;
}

.progress-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.progress-text {
  font-size: 24rpx;
  color: #666666;
}

.points-text {
  font-size: 24rpx;
  color: #ff6b6b;
  font-weight: 500;
}

/* 完成标记样式 */
.completed-badge {
  position: absolute;
  top: 20rpx;
  right: 20rpx;
  display: flex;
  align-items: center;
  gap: 8rpx;
  padding: 8rpx 16rpx;
  background: linear-gradient(135deg, #66bb6a 0%, #43a047 100%);
  border-radius: 20rpx;
  box-shadow: 0 2rpx 8rpx rgba(67, 160, 71, 0.3);
}

.badge-icon {
  font-size: 24rpx;
  color: #ffffff;
  font-weight: bold;
}

.badge-text {
  font-size: 22rpx;
  color: #ffffff;
  font-weight: 500;
}
</style>

📺 3. 创建课程学习页面

创建 peidu/uniapp/src/user-package/pages/course/player.vue:

<template>
  <view class="course-player-page">
    <!-- 视频播放器 -->
    <video
      v-if="course.videoUrl"
      :src="course.videoUrl"
      :initial-time="lastPosition"
      class="video-player"
      controls
      @timeupdate="onTimeUpdate"
      @ended="onVideoEnded"
    ></video>
    
    <!-- 课程信息 -->
    <view class="course-info">
      <text class="course-title">{{ course.title }}</text>
      <view class="course-meta">
        <text class="meta-item">时长: {{ course.duration }}分钟</text>
        <text class="meta-item">进度: {{ learningProgress }}%</text>
      </view>
    </view>
    
    <!-- 学习笔记 -->
    <view class="notes-section">
      <text class="section-title">学习笔记</text>
      <textarea
        v-model="notes"
        class="notes-input"
        placeholder="记录你的学习心得..."
        maxlength="500"
      ></textarea>
      <text class="notes-count">{{ notes.length }}/500</text>
    </view>
    
    <!-- 完成按钮 -->
    <view class="action-section">
      <button 
        class="btn-complete"
        :disabled="learningProgress < 90"
        @tap="completeLearning"
      >
        {{ learningProgress >= 90 ? '完成学习' : '请观看至少90%的内容' }}
      </button>
    </view>
  </view>
</template>

<script>
import api from '@/api/index.js'

export default {
  data() {
    return {
      courseId: null,
      purchaseId: null,
      learningRecordId: null,
      course: {},
      lastPosition: 0,
      learningProgress: 0,
      notes: '',
      currentPosition: 0,
      duration: 0,
      updateTimer: null
    }
  },
  
  onLoad(options) {
    this.courseId = options.id
    this.purchaseId = options.purchaseId
    this.loadCourseData()
  },
  
  onUnload() {
    // 页面卸载时保存进度
    this.saveProgress()
    if (this.updateTimer) {
      clearInterval(this.updateTimer)
    }
  },
  
  methods: {
    async loadCourseData() {
      try {
        // 获取课程信息
        const courseData = await api.parentAcademyApi.getCourseDetail(this.courseId)
        this.course = courseData
        
        // 开始学习,获取学习记录
        const learningData = await api.parentAcademyApi.startLearning({
          courseId: this.courseId,
          purchaseId: this.purchaseId
        })
        
        this.learningRecordId = learningData.learningRecordId
        this.lastPosition = learningData.lastPosition || 0
        this.learningProgress = learningData.learningProgress || 0
        
        // 启动定时保存进度(每30秒)
        this.updateTimer = setInterval(() => {
          this.saveProgress()
        }, 30000)
        
      } catch (error) {
        console.error('加载课程数据失败:', error)
        uni.showToast({
          title: '加载失败',
          icon: 'none'
        })
      }
    },
    
    onTimeUpdate(e) {
      this.currentPosition = Math.floor(e.detail.currentTime)
      this.duration = Math.floor(e.detail.duration)
      
      // 计算进度
      if (this.duration > 0) {
        this.learningProgress = Math.floor((this.currentPosition / this.duration) * 100)
      }
    },
    
    async saveProgress() {
      if (!this.learningRecordId || this.currentPosition === 0) {
        return
      }
      
      try {
        await api.parentAcademyApi.updateProgress({
          learningRecordId: this.learningRecordId,
          position: this.currentPosition,
          duration: this.duration
        })
        
        console.log('学习进度已保存:', this.currentPosition)
      } catch (error) {
        console.error('保存进度失败:', error)
      }
    },
    
    onVideoEnded() {
      // 视频播放完成
      this.learningProgress = 100
      this.saveProgress()
    },
    
    async completeLearning() {
      if (this.learningProgress < 90) {
        uni.showToast({
          title: '请观看至少90%的内容',
          icon: 'none'
        })
        return
      }
      
      try {
        uni.showLoading({ title: '提交中...' })
        
        const result = await api.parentAcademyApi.completeLearning({
          learningRecordId: this.learningRecordId,
          notes: this.notes
        })
        
        uni.hideLoading()
        
        // 显示积分奖励
        if (result.pointsAwarded > 0) {
          uni.showModal({
            title: '恭喜完成学习!',
            content: `获得 ${result.pointsAwarded} 积分奖励`,
            showCancel: false,
            success: () => {
              // 返回我的课程页面
              uni.navigateBack()
            }
          })
        } else {
          uni.showToast({
            title: '学习完成',
            icon: 'success'
          })
          setTimeout(() => {
            uni.navigateBack()
          }, 1500)
        }
        
      } catch (error) {
        uni.hideLoading()
        console.error('完成学习失败:', error)
        uni.showToast({
          title: '提交失败',
          icon: 'none'
        })
      }
    }
  }
}
</script>

<style scoped>
.course-player-page {
  min-height: 100vh;
  background: #f5f7fa;
}

.video-player {
  width: 100%;
  height: 420rpx;
}

.course-info {
  background: #ffffff;
  padding: 30rpx;
  margin-bottom: 20rpx;
}

.course-title {
  display: block;
  font-size: 32rpx;
  font-weight: 500;
  color: #333333;
  margin-bottom: 20rpx;
}

.course-meta {
  display: flex;
  gap: 30rpx;
}

.meta-item {
  font-size: 26rpx;
  color: #666666;
}

.notes-section {
  background: #ffffff;
  padding: 30rpx;
  margin-bottom: 20rpx;
}

.section-title {
  display: block;
  font-size: 28rpx;
  font-weight: 500;
  color: #333333;
  margin-bottom: 20rpx;
}

.notes-input {
  width: 100%;
  min-height: 200rpx;
  padding: 20rpx;
  background: #f5f7fa;
  border-radius: 8rpx;
  font-size: 26rpx;
  line-height: 1.6;
}

.notes-count {
  display: block;
  text-align: right;
  font-size: 24rpx;
  color: #999999;
  margin-top: 10rpx;
}

.action-section {
  padding: 30rpx;
}

.btn-complete {
  width: 100%;
  height: 88rpx;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: #ffffff;
  font-size: 30rpx;
  border-radius: 44rpx;
  border: none;
}

.btn-complete[disabled] {
  background: #cccccc;
  color: #999999;
}
</style>

🔄 4. 修改课程跳转逻辑

my-courses.vue 中修改 viewCourse 方法:

viewCourse(course) {
  console.log('查看课程:', course)
  
  // 跳转到课程学习页面
  uni.navigateTo({
    url: `/user-package/pages/course/player?id=${course.id}&purchaseId=${course.purchaseId}`
  })
}

完成后的功能

  1. 断点续播 - 自动从上次学习位置继续
  2. 进度保存 - 每30秒自动保存学习进度
  3. 学习笔记 - 支持记录学习心得
  4. 积分奖励 - 完成学习后自动发放积分
  5. 进度可视化 - 实时显示学习进度百分比