370 lines
8.5 KiB
Vue
370 lines
8.5 KiB
Vue
<template>
|
|
<view class="course-detail">
|
|
<!-- 课程头部 -->
|
|
<view class="course-header">
|
|
<image :src="course.coverImage" class="cover-image" mode="aspectFill"></image>
|
|
<view class="course-info">
|
|
<view class="course-title">{{ course.courseName }}</view>
|
|
<view class="course-meta">
|
|
<text class="meta-item">{{ course.courseType === 'companion' ? '陪伴员' : course.courseType === 'manager' ? '管理师' : '分销员' }}</text>
|
|
<text class="meta-item">{{ course.level === 'basic' ? '基础' : course.level === 'advanced' ? '进阶' : '专家' }}</text>
|
|
<text class="meta-item">{{ course.duration }}分钟</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 学习进度 -->
|
|
<view class="progress-section" v-if="learningRecord">
|
|
<view class="progress-header">
|
|
<text class="progress-title">学习进度</text>
|
|
<text class="progress-text">{{ learningRecord.progress }}%</text>
|
|
</view>
|
|
<view class="progress-bar">
|
|
<view class="progress-fill" :style="{ width: learningRecord.progress + '%' }"></view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 课程内容 -->
|
|
<view class="course-content">
|
|
<view class="section-title">课程介绍</view>
|
|
<view class="content-text">{{ course.description }}</view>
|
|
|
|
<view class="section-title">课程目标</view>
|
|
<view class="content-text">{{ course.objectives }}</view>
|
|
|
|
<view class="section-title">适用对象</view>
|
|
<view class="content-text">{{ course.targetAudience }}</view>
|
|
</view>
|
|
|
|
<!-- 视频播放器 -->
|
|
<view class="video-section" v-if="course.videoUrl">
|
|
<video
|
|
:src="course.videoUrl"
|
|
class="video-player"
|
|
controls
|
|
@timeupdate="onVideoTimeUpdate"
|
|
@ended="onVideoEnded"
|
|
></video>
|
|
</view>
|
|
|
|
<!-- 课程资料 -->
|
|
<view class="materials-section" v-if="course.materials">
|
|
<view class="section-title">课程资料</view>
|
|
<view class="material-item" v-for="(material, index) in materials" :key="index" @click="downloadMaterial(material)">
|
|
<text class="material-icon">📄</text>
|
|
<text class="material-name">{{ material.name }}</text>
|
|
<text class="material-size">{{ material.size }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 底部操作栏 -->
|
|
<view class="bottom-bar">
|
|
<button v-if="!learningRecord" class="btn-primary" @click="startCourse">开始学习</button>
|
|
<button v-else-if="learningRecord.progress < 100" class="btn-primary" @click="continueCourse">继续学习</button>
|
|
<button v-else class="btn-success" @click="goToExam">参加考试</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
data() {
|
|
return {
|
|
courseId: null,
|
|
course: {},
|
|
learningRecord: null,
|
|
materials: [],
|
|
videoCurrentTime: 0
|
|
};
|
|
},
|
|
onLoad(options) {
|
|
this.courseId = options.id;
|
|
this.loadCourseDetail();
|
|
this.loadLearningRecord();
|
|
},
|
|
methods: {
|
|
// 加载课程详情
|
|
async loadCourseDetail() {
|
|
try {
|
|
const res = await this.$http.get(`/api/training/courses/${this.courseId}`);
|
|
this.course = res.data;
|
|
if (this.course.materials) {
|
|
this.materials = JSON.parse(this.course.materials);
|
|
}
|
|
} catch (error) {
|
|
uni.showToast({ title: '加载失败', icon: 'none' });
|
|
}
|
|
},
|
|
|
|
// 加载学习记录
|
|
async loadLearningRecord() {
|
|
try {
|
|
const res = await this.$http.get('/api/training/learning-records', {
|
|
params: { courseId: this.courseId }
|
|
});
|
|
this.learningRecord = res.data;
|
|
} catch (error) {
|
|
console.log('暂无学习记录');
|
|
}
|
|
},
|
|
|
|
// 开始学习
|
|
async startCourse() {
|
|
try {
|
|
await this.$http.post('/api/training/start-course', {
|
|
courseId: this.courseId
|
|
});
|
|
uni.showToast({ title: '开始学习', icon: 'success' });
|
|
this.loadLearningRecord();
|
|
} catch (error) {
|
|
uni.showToast({ title: '操作失败', icon: 'none' });
|
|
}
|
|
},
|
|
|
|
// 继续学习
|
|
continueCourse() {
|
|
uni.showToast({ title: '继续学习', icon: 'success' });
|
|
},
|
|
|
|
// 视频播放进度更新
|
|
async onVideoTimeUpdate(e) {
|
|
this.videoCurrentTime = e.detail.currentTime;
|
|
const duration = e.detail.duration;
|
|
const progress = Math.floor((this.videoCurrentTime / duration) * 100);
|
|
|
|
// 每10%更新一次进度
|
|
if (progress % 10 === 0 && progress !== this.learningRecord?.progress) {
|
|
await this.updateProgress(progress);
|
|
}
|
|
},
|
|
|
|
// 视频播放结束
|
|
async onVideoEnded() {
|
|
await this.updateProgress(100);
|
|
await this.completeCourse();
|
|
},
|
|
|
|
// 更新学习进度
|
|
async updateProgress(progress) {
|
|
try {
|
|
await this.$http.post('/api/training/update-progress', {
|
|
courseId: this.courseId,
|
|
progress: progress
|
|
});
|
|
this.learningRecord.progress = progress;
|
|
} catch (error) {
|
|
console.log('更新进度失败');
|
|
}
|
|
},
|
|
|
|
// 完成课程
|
|
async completeCourse() {
|
|
try {
|
|
await this.$http.post('/api/training/complete-course', {
|
|
courseId: this.courseId
|
|
});
|
|
uni.showToast({ title: '恭喜完成课程!', icon: 'success' });
|
|
this.loadLearningRecord();
|
|
} catch (error) {
|
|
console.log('完成课程失败');
|
|
}
|
|
},
|
|
|
|
// 下载资料
|
|
downloadMaterial(material) {
|
|
uni.downloadFile({
|
|
url: material.url,
|
|
success: (res) => {
|
|
uni.showToast({ title: '下载成功', icon: 'success' });
|
|
}
|
|
});
|
|
},
|
|
|
|
// 前往考试
|
|
goToExam() {
|
|
uni.navigateTo({
|
|
url: `/pages/training/exam?courseType=${this.course.courseType}&level=${this.course.level}`
|
|
});
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.course-detail {
|
|
min-height: 100vh;
|
|
background: #f5f5f5;
|
|
padding-bottom: 120rpx;
|
|
}
|
|
|
|
.course-header {
|
|
background: #fff;
|
|
margin-bottom: 20rpx;
|
|
|
|
.cover-image {
|
|
width: 100%;
|
|
height: 400rpx;
|
|
}
|
|
|
|
.course-info {
|
|
padding: 30rpx;
|
|
|
|
.course-title {
|
|
font-size: 36rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.course-meta {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
|
|
.meta-item {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
padding: 8rpx 16rpx;
|
|
background: #f5f5f5;
|
|
border-radius: 8rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.progress-section {
|
|
background: #fff;
|
|
padding: 30rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.progress-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 20rpx;
|
|
|
|
.progress-title {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
}
|
|
|
|
.progress-text {
|
|
font-size: 28rpx;
|
|
color: #7dd3c0;
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 12rpx;
|
|
background: #f0f0f0;
|
|
border-radius: 6rpx;
|
|
overflow: hidden;
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #7dd3c0, #5bc0ad);
|
|
transition: width 0.3s;
|
|
}
|
|
}
|
|
}
|
|
|
|
.course-content {
|
|
background: #fff;
|
|
padding: 30rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.section-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
margin: 30rpx 0 20rpx;
|
|
|
|
&:first-child {
|
|
margin-top: 0;
|
|
}
|
|
}
|
|
|
|
.content-text {
|
|
font-size: 28rpx;
|
|
color: #666;
|
|
line-height: 1.8;
|
|
}
|
|
}
|
|
|
|
.video-section {
|
|
background: #fff;
|
|
padding: 30rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.video-player {
|
|
width: 100%;
|
|
height: 400rpx;
|
|
}
|
|
}
|
|
|
|
.materials-section {
|
|
background: #fff;
|
|
padding: 30rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.section-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.material-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20rpx;
|
|
background: #f5f5f5;
|
|
border-radius: 12rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.material-icon {
|
|
font-size: 40rpx;
|
|
margin-right: 20rpx;
|
|
}
|
|
|
|
.material-name {
|
|
flex: 1;
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
}
|
|
|
|
.material-size {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
}
|
|
|
|
.bottom-bar {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
padding: 20rpx 30rpx;
|
|
background: #fff;
|
|
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
|
|
button {
|
|
width: 100%;
|
|
height: 88rpx;
|
|
border-radius: 44rpx;
|
|
font-size: 32rpx;
|
|
border: none;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, #7dd3c0, #5bc0ad);
|
|
color: #fff;
|
|
}
|
|
|
|
.btn-success {
|
|
background: linear-gradient(135deg, #52c41a, #389e0d);
|
|
color: #fff;
|
|
}
|
|
}
|
|
</style>
|