This commit is contained in:
那些花儿
2025-09-10 08:46:52 +08:00
parent 5a55444209
commit ab6323a7de
31 changed files with 1884 additions and 704 deletions
@@ -0,0 +1,271 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="课程详情" placeholder fixed></van-nav-bar>
<div v-if="loading" class="loading-container">
<van-loading type="spinner" size="24px">加载中...</van-loading>
</div>
<div v-else-if="course">
<!-- 课程封面 -->
<div class="course-cover">
<van-image v-if="course.cover" :src="course.cover" fit="cover" :alt="course.title">
<template v-slot:error>
<van-icon name="photo-fail" size="48" color="#ddd"></van-icon>
</template>
</van-image>
<van-icon v-else name="graduation" size="48" color="#ddd" ></van-icon>
</div>
<!-- 课程信息 -->
<van-cell-group class="course-info">
<div class="course-title">{{ course.title || '未命名课程' }}</div>
<div class="course-desc">{{ course.description || '暂无描述' }}</div>
<div class="course-meta">
<van-tag type="primary" size="mini">{{ course.category || '未分类' }}</van-tag>
<span class="course-date">{{ formatDate(course.createdAt) }}</span>
</div>
</van-cell-group>
<!-- 学习进度 -->
<van-cell-group title="学习进度" class="progress-section">
<van-cell>
<template #default>
<div class="progress-container">
<van-progress :percentage="progressPercentage" stroke-width="6" color="#1989fa" />
<span class="progress-text">{{ progressPercentage }}%</span>
</div>
</template>
</van-cell>
</van-cell-group>
<!-- 课程章节 -->
<van-cell-group title="课程章节" class="chapters-section">
<van-collapse v-model="activeChapters">
<van-collapse-item
v-for="(chapter, index) in course.chapters"
:key="chapter.id"
:name="index"
:title="chapter.title || `第${index + 1}章`"
>
<van-cell
v-for="video in chapter.videos"
:key="video.id"
:title="video.title || '未命名视频'"
:label="formatDuration(video.duration)"
is-link
@click="playVideo(video.id, video.title)"
>
<template #icon>
<van-icon
:name="isVideoCompleted(video.id) ? 'success' : 'play-circle-o'"
:color="isVideoCompleted(video.id) ? '#07c160' : '#1989fa'"
/>
</template>
<template #right-icon v-if="isVideoCompleted(video.id)">
<van-tag type="success" size="mini">已完成</van-tag>
</template>
</van-cell>
<van-empty v-if="!chapter.videos || !chapter.videos.length" description="暂无视频" />
</van-collapse-item>
</van-collapse>
<van-empty v-if="!course.chapters || !course.chapters.length" description="暂无章节内容" />
</van-cell-group>
</div>
<van-empty v-else description="课程不存在" />
</div>
<style>
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
}
.course-cover {
width: 100%;
height: 200px;
background: linear-gradient(45deg, #f0f2f5, #e9ecef);
display: flex;
align-items: center;
justify-content: center;
}
.course-cover .van-image {
width: 100%;
height: 100%;
}
.course-info {
margin-bottom: 12px;
}
.course-info .van-cell-group {
padding: 16px;
}
.course-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
line-height: 1.4;
}
.course-desc {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-bottom: 12px;
}
.course-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
padding-top: 12px;
border-top: 1px solid #eee;
}
.course-date {
color: #999;
}
.progress-section {
margin-bottom: 12px;
}
.progress-container {
display: flex;
align-items: center;
width: 100%;
}
.progress-container .van-progress {
flex: 1;
margin-right: 12px;
}
.progress-text {
font-size: 14px;
color: #333;
min-width: 40px;
}
.chapters-section {
margin-bottom: 12px;
}
</style>
<script>
const vue = new Vue({
el: "#app",
store,
data() {
return {
course: null,
loading: true,
courseId: null,
activeChapters: [],
progressData: null
}
},
computed: {
progressPercentage() {
if (!this.progressData || !this.progressData.totalVideos) return 0;
return Math.round(this.progressData.completedVideos / this.progressData.totalVideos * 100);
}
},
methods: {
historyBack,
// 加载课程详情
async loadCourseDetail() {
if (!this.courseId) {
this.$toast.fail('缺少课程ID参数');
this.loading = false;
return;
}
try {
const { code, data, msg } = await $.get('/platform/h5/edu/course/detail/data', {
courseId: this.courseId
});
if (code === 0 && data) {
this.course = data;
this.loadCourseProgress();
} else {
this.$toast.fail(msg || '加载课程详情失败');
}
} catch (error) {
this.$toast.fail('网络错误');
}
this.loading = false;
},
// 加载课程进度
async loadCourseProgress() {
try {
const { code, data } = await $.get('/platform/h5/edu/course/progress', {
courseId: this.courseId
});
if (code === 0 && data) {
this.progressData = data;
}
} catch (error) {
console.error('加载进度失败:', error);
}
},
// 检查视频是否已完成
isVideoCompleted(videoId) {
return this.progressData &&
this.progressData.completedVideoIds &&
this.progressData.completedVideoIds.includes(videoId);
},
// 播放视频
playVideo(videoId, videoTitle) {
pjaxReplace(`/platform/h5/edu/video/play?videoId=${videoId}&courseId=${this.courseId}&title=${encodeURIComponent(videoTitle || '')}`);
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0');
},
// 格式化时长
formatDuration(seconds) {
if (!seconds) return '00:00';
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return String(minutes).padStart(2, '0') + ':' + String(remainingSeconds).padStart(2, '0');
}
},
mounted() {
// 获取URL参数
const urlParams = new URLSearchParams(window.location.search);
this.courseId = urlParams.get('courseId');
this.loadCourseDetail();
}
});
</script>
<!--#
}
#-->
@@ -0,0 +1,234 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
.course-item {
background: rgba(255, 255, 255, 0.95);
border-radius: 6px;
margin: 12px 16px;
overflow: hidden;
display: flex;
align-items: stretch;
position: relative;
}
.course-item:active {
transform: translateY(2px) scale(0.98);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.course-cover {
width: 100px;
height: 100px;
border-radius: 16px;
overflow: hidden;
margin: 16px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
}
.course-cover::after {
content: '';
position: absolute;
inset: 2px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
}
.course-cover .van-image {
width: 100%;
height: 100%;
border-radius: 14px;
z-index: 1;
position: relative;
}
.course-cover .van-icon {
z-index: 2;
position: relative;
color: rgba(255, 255, 255, 0.9);
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
}
.course-info {
flex: 1;
min-width: 0;
padding: 16px 16px 16px 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.course-title {
font-size: 18px;
font-weight: 700;
color: #2c3e50;
margin-bottom: 8px;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
letter-spacing: -0.02em;
}
.course-desc {
font-size: 14px;
color: #64748b;
line-height: 1.5;
margin-bottom: 12px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
font-weight: 400;
}
.course-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
}
.course-date {
color: #94a3b8;
font-weight: 500;
font-size: 11px;
}
</style>
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="教育培训课程" placeholder fixed>
<template #right>
<van-icon name="clock-o" @click="goToHistory"></van-icon>
</template>
</van-nav-bar>
<van-sticky offset-top="46px">
<van-tabs v-model="pageForm.category" @change="doSearch">
<van-tab name="" title="全部"></van-tab>
<van-tab name="理论学习" title="理论学习"></van-tab>
<van-tab name="技能培训" title="技能培训"></van-tab>
<van-tab name="安全教育" title="安全教育"></van-tab>
<van-tab name="职业发展" title="职业发展"></van-tab>
</van-tabs>
</van-sticky>
<van-pull-refresh v-model="tableRefreshing" @refresh="onRefresh">
<van-list v-model="tableLoading" :finished="tableFinished" finished-text="没有更多了" @load="onLoad">
<div class="course-item" v-for="(course, index) in tableData" :key="course.id"
@click="viewCourse(course.id)">
<div class="course-cover">
<van-image v-if="course.cover" :src="course.cover" fit="cover" :alt="course.title">
<template v-slot:error>
<van-icon name="photo-fail" size="32" color="#ddd"></van-icon>
</template>
</van-image>
<van-icon v-else name="play-circle-o" size="48" color="#ddd"></van-icon>
</div>
<div class="course-info">
<div class="course-title">{{ course.title || '未命名课程' }}</div>
<div class="course-desc">{{ course.description || '暂无描述' }}</div>
<div class="course-meta">
<van-tag type="primary" size="mini">{{ course.category || '未分类' }}</van-tag>
<span class="course-date">{{ formatDate(course.createdAt) }}</span>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
<van-empty v-if="!tableLoading && !tableData.length" description="暂无课程"></van-empty>
</div>
<script>
const vue = new Vue({
el: "#app",
store,
dicts:['TRAIN_EDU_COURSE_TYPE'],
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
category: ""
},
tableData: [],
tableLoading: false,
tableFinished: false,
tableRefreshing: false
}
},
methods: {
historyBack,
// 刷新
onRefresh() {
this.tableFinished = false;
this.tableLoading = true
this.pageForm.pageNumber = 1;
this.onLoad();
},
// 加载更多
async onLoad() {
if(this.tableRefreshing){
this.tableData = []
this.tableRefreshing = false
}
this.$axios.post('/platform/h5/edu/courses/list', this.pageForm).then((res) => {
if (res.code === 0) {
this.tableData = this.tableData.concat(res.data.list)
this.pageForm.totalCount = res.data.totalCount
if (this.tableData.length >= this.pageForm.totalCount) {
this.tableFinished = true
}
this.pageForm.pageNumber++;
}
}).finally(() => {
this.tableLoading = false
this.tableRefreshing = false
})
},
// 搜索
doSearch() {
this.pageForm.pageNumber = 1;
this.tableData = [];
this.tableFinished = false;
this.tableLoading = true;
this.onLoad();
},
// 查看课程详情
viewCourse(courseId) {
pjaxReplace(`/platform/h5/edu/course/detail?courseId=` + courseId);
},
// 前往学习历史
goToHistory() {
pjaxReplace('/platform/h5/edu/history');
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0');
}
}
});
</script>
<!--#
}
#-->
@@ -0,0 +1,385 @@
<%
layout('/platform/zhghh5/layout/layout.html',{
title: '学习历史',
keywords: '学习历史,视频学习,在线教育',
description: '查看学习历史记录和进度统计'
}){
%>
<style>
.history-container {
background-color: #f5f5f5;
min-height: 100vh;
}
.stats-card {
background: white;
border-radius: 12px;
padding: 20px;
margin: 15px;
margin-bottom: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.stats-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 15px;
display: flex;
align-items: center;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 24px;
font-weight: 700;
color: #1989fa;
margin-bottom: 5px;
}
.stat-label {
font-size: 12px;
color: #666;
}
.history-item {
background: white;
border-radius: 12px;
margin: 15px;
margin-bottom: 10px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.history-header {
padding: 15px 20px;
border-bottom: 1px solid #f8f9fa;
display: flex;
align-items: center;
justify-content: space-between;
}
.course-info {
flex: 1;
}
.course-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 5px;
}
.course-meta {
font-size: 12px;
color: #999;
display: flex;
align-items: center;
}
.course-category {
background: #e3f2fd;
color: #1976d2;
padding: 2px 6px;
border-radius: 8px;
margin-right: 10px;
}
.progress-info {
text-align: right;
min-width: 80px;
}
.progress-text {
font-size: 14px;
font-weight: 600;
color: #28a745;
margin-bottom: 5px;
}
.progress-bar {
width: 60px;
height: 4px;
background: #e9ecef;
border-radius: 2px;
overflow: hidden;
margin-left: auto;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #28a745, #20c997);
border-radius: 2px;
}
.video-list {
padding: 0 20px 15px;
}
.video-item {
display: flex;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #f8f9fa;
cursor: pointer;
}
.video-item:last-child {
border-bottom: none;
}
.video-icon {
width: 36px;
height: 36px;
background: linear-gradient(135deg, #1989fa, #1976d2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
margin-right: 12px;
font-size: 14px;
}
.video-icon.completed {
background: linear-gradient(135deg, #28a745, #20c997);
}
.video-info {
flex: 1;
}
.video-title {
font-size: 14px;
color: #333;
margin-bottom: 4px;
line-height: 1.4;
}
.video-meta {
font-size: 12px;
color: #999;
display: flex;
align-items: center;
}
.video-duration {
margin-right: 15px;
}
.video-progress {
color: #28a745;
}
.watch-time {
font-size: 12px;
color: #666;
text-align: right;
}
</style>
<div id="app" class="history-container">
<van-nav-bar
title="学习历史"
left-text="返回"
left-arrow
@click-left="onClickLeft"
/>
<van-tabs v-model="activeTab" @change="onTabChange">
<van-tab title="全部" name="all"></van-tab>
<van-tab title="已完成" name="completed"></van-tab>
<van-tab title="学习中" name="in_progress"></van-tab>
<van-tab title="最近观看" name="recent"></van-tab>
</van-tabs>
<!-- 学习统计 -->
<div v-if="showStats && stats" class="stats-card">
<div class="stats-title">
<van-icon name="chart-trending-o" style="margin-right: 8px; color: #1989fa;"/>
学习统计
</div>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-number">{{ stats.totalCourses || 0 }}</div>
<div class="stat-label">学习课程</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ stats.completedVideos || 0 }}</div>
<div class="stat-label">完成视频</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ Math.round((stats.totalWatchTime || 0) / 60) }}</div>
<div class="stat-label">学习时长(分)</div>
</div>
</div>
</div>
<!-- 学习历史列表 -->
<van-loading v-if="loading" type="spinner" color="#1989fa" style="margin: 40px auto; display: block;">
加载中...
</van-loading>
<van-empty v-else-if="historyList.length === 0" description="暂无学习记录">
<van-button round type="primary" @click="loadData">刷新</van-button>
</van-empty>
<div v-else>
<div v-for="course in historyList" :key="course.courseId" class="history-item">
<div class="history-header" @click="viewCourse(course.courseId)">
<div class="course-info">
<div class="course-title">{{ course.courseTitle || '未命名课程' }}</div>
<div class="course-meta">
<span class="course-category">{{ course.courseCategory || '未分类' }}</span>
<span>最后学习:{{ formatDate(course.lastWatchTime) }}</span>
</div>
</div>
<div class="progress-info">
<div class="progress-text">{{ getProgressPercent(course) }}%</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: getProgressPercent(course) + '%' }"></div>
</div>
</div>
</div>
<div v-if="course.videos && course.videos.length > 0" class="video-list">
<div
v-for="video in course.videos"
:key="video.videoId"
class="video-item"
@click="playVideo(video.videoId, course.courseId, video.videoTitle)"
>
<div class="video-icon" :class="{ completed: video.isCompleted }">
<van-icon :name="video.isCompleted ? 'success' : 'play-circle-o'" />
</div>
<div class="video-info">
<div class="video-title">{{ video.videoTitle || '未命名视频' }}</div>
<div class="video-meta">
<span class="video-duration">{{ formatDuration(video.duration) }}</span>
<span class="video-progress">
{{ getVideoProgress(video) }}
</span>
</div>
</div>
<div class="watch-time">
{{ formatDate(video.lastWatchTime) }}
</div>
</div>
</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data() {
return {
activeTab: 'all',
loading: false,
showStats: false,
stats: null,
historyList: []
};
},
mounted() {
this.loadData();
},
methods: {
onClickLeft() {
window.location.href = '${base}/platform/h5/edu/courses';
},
onTabChange(name) {
this.activeTab = name;
this.loadData();
},
async loadData() {
this.loading = true;
try {
// 如果是全部标签,先加载统计信息
if (this.activeTab === 'all') {
await this.loadStats();
this.showStats = true;
} else {
this.showStats = false;
}
// 加载学习历史
await this.loadHistory();
} catch (error) {
this.$toast('加载失败,请重试');
} finally {
this.loading = false;
}
},
async loadStats() {
try {
const response = await this.$http.get('${base}/platform/h5/edu/study/stats');
if (response.data.code === 0) {
this.stats = response.data.data;
}
} catch (error) {
console.error('加载统计失败:', error);
}
},
async loadHistory() {
try {
const response = await this.$http.get('${base}/platform/h5/edu/study/history', {
params: { filter: this.activeTab }
});
if (response.data.code === 0) {
this.historyList = response.data.data || [];
} else {
this.$toast(response.data.msg || '加载失败');
}
} catch (error) {
this.$toast('网络错误,请检查网络连接');
}
},
viewCourse(courseId) {
window.location.href = `${base}/platform/h5/edu/course/detail?courseId=${courseId}`;
},
playVideo(videoId, courseId, videoTitle) {
window.location.href = `${base}/platform/h5/edu/video/play?videoId=${videoId}&courseId=${courseId}&title=${encodeURIComponent(videoTitle)}`;
},
getProgressPercent(course) {
if (!course.videos || course.videos.length === 0) return 0;
const completedCount = course.videos.filter(v => v.isCompleted).length;
return Math.round((completedCount / course.videos.length) * 100);
},
getVideoProgress(video) {
if (video.isCompleted) return '已完成';
if (video.duration > 0 && video.watchDuration > 0) {
const percent = Math.round((video.watchDuration / video.duration) * 100);
return `观看${percent}%`;
}
return '未开始';
},
formatDate(dateStr) {
if (!dateStr) return '未知';
const date = new Date(dateStr);
const now = new Date();
const diffTime = now - date;
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
if (diffDays === 0) {
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));
if (diffHours === 0) {
const diffMinutes = Math.floor(diffTime / (1000 * 60));
return diffMinutes <= 0 ? '刚刚' : `${diffMinutes}分钟前`;
}
return `${diffHours}小时前`;
} else if (diffDays === 1) {
return '昨天';
} else if (diffDays < 7) {
return `${diffDays}天前`;
} else {
return date.getFullYear() + '-' +
String(date.getMonth() + 1).padStart(2, '0') + '-' +
String(date.getDate()).padStart(2, '0');
}
},
formatDuration(seconds) {
if (!seconds) return '00:00';
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return String(minutes).padStart(2, '0') + ':' + String(remainingSeconds).padStart(2, '0');
}
}
});
</script>
<%}%>
@@ -0,0 +1,262 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="videoTitle" placeholder fixed />
<div class="video-container">
<div v-if="loading" class="loading-container">
<van-loading type="spinner" size="24px">加载中...</van-loading>
</div>
<div v-else-if="videoInfo" class="video-player">
<video
ref="videoElement"
class="video-element"
:src="videoInfo.url"
controls
preload="metadata"
@loadedmetadata="onVideoLoaded"
@play="onVideoPlay"
@pause="onVideoPause"
@timeupdate="onTimeUpdate"
@ended="onVideoEnded"
@error="onVideoError"
>
您的浏览器不支持视频播放
</video>
</div>
<van-empty v-else description="视频加载失败" />
</div>
<!-- 进度保存提示 -->
<van-toast v-model="showProgressToast" message="进度已保存" :duration="1000" />
<!-- 完成学习弹窗 -->
<van-dialog
v-model="showCompletionDialog"
title="恭喜完成学习!"
message="您已完成本视频的学习,学习记录已保存。"
show-cancel-button
cancel-button-text="继续观看"
confirm-button-text="返回课程"
@confirm="backToCourse"
/>
</div>
<style>
.video-container {
background: #000;
min-height: calc(100vh - 46px);
display: flex;
align-items: center;
justify-content: center;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
color: white;
}
.video-player {
width: 100%;
height: 100%;
}
.video-element {
width: 100%;
height: auto;
max-height: calc(100vh - 46px);
object-fit: contain;
}
</style>
<script>
const vue = new Vue({
el: "#app",
store,
data() {
return {
videoInfo: null,
loading: true,
videoId: null,
courseId: null,
videoTitle: '视频播放',
isPlaying: false,
progressSaveTimer: null,
showProgressToast: false,
showCompletionDialog: false
}
},
methods: {
historyBack,
// 加载视频信息
async loadVideoInfo() {
if (!this.videoId) {
this.$toast.fail('缺少视频ID参数');
this.loading = false;
return;
}
try {
const { code, data, msg } = await $.get('${base}/platform/h5/edu/video/detail', {
videoId: this.videoId
});
if (code === 0 && data) {
this.videoInfo = data;
this.$nextTick(() => {
this.loadWatchProgress();
});
} else {
this.$toast.fail(msg || '加载视频信息失败');
}
} catch (error) {
this.$toast.fail('网络错误');
}
this.loading = false;
},
// 视频加载完成
onVideoLoaded() {
console.log('视频加载完成');
},
// 视频开始播放
onVideoPlay() {
this.isPlaying = true;
this.startProgressSave();
},
// 视频暂停
onVideoPause() {
this.isPlaying = false;
this.saveProgress();
},
// 时间更新
onTimeUpdate() {
// 可以在这里添加进度更新逻辑
},
// 视频播放结束
onVideoEnded() {
this.isPlaying = false;
this.markVideoCompleted();
},
// 视频加载错误
onVideoError() {
this.$toast.fail('视频加载失败');
},
// 加载观看进度
async loadWatchProgress() {
if (!this.videoId || !this.courseId) return;
try {
const { code, data } = await $.get('${base}/platform/h5/edu/study/record', {
videoId: this.videoId
});
if (code === 0 && data && data.watchDuration > 0) {
const video = this.$refs.videoElement;
if (video) {
video.currentTime = data.watchDuration;
}
}
} catch (error) {
console.error('加载观看进度失败:', error);
}
},
// 保存观看进度
async saveProgress() {
const video = this.$refs.videoElement;
if (!video || !this.videoId || !this.courseId) return;
try {
await $.post('${base}/platform/h5/edu/study/progress/save', {
videoId: this.videoId,
courseId: this.courseId,
watchDuration: Math.floor(video.currentTime)
});
this.showProgressToast = true;
} catch (error) {
console.error('保存进度失败:', error);
}
},
// 开始定时保存进度
startProgressSave() {
this.clearProgressSave();
this.progressSaveTimer = setInterval(() => {
this.saveProgress();
}, 10000); // 每10秒保存一次
},
// 清除定时保存
clearProgressSave() {
if (this.progressSaveTimer) {
clearInterval(this.progressSaveTimer);
this.progressSaveTimer = null;
}
},
// 标记视频完成
async markVideoCompleted() {
if (!this.videoId || !this.courseId) return;
try {
const { code } = await $.post('${base}/platform/h5/edu/study/complete', {
videoId: this.videoId,
courseId: this.courseId
});
if (code === 0) {
this.showCompletionDialog = true;
}
} catch (error) {
console.error('标记完成失败:', error);
}
},
// 返回课程
backToCourse() {
if (this.courseId) {
this.$router.push(`/course/detail?courseId=${this.courseId}`);
} else {
this.historyBack();
}
}
},
mounted() {
// 获取URL参数
const urlParams = new URLSearchParams(window.location.search);
this.videoId = urlParams.get('videoId');
this.courseId = urlParams.get('courseId');
this.videoTitle = decodeURIComponent(urlParams.get('title') || '视频播放');
this.loadVideoInfo();
},
beforeDestroy() {
// 页面销毁前保存进度
this.saveProgress();
this.clearProgressSave();
}
});
</script>
<!--#
}
#-->