学习课程记录

This commit is contained in:
2025-11-17 16:35:18 +08:00
parent d2e554c715
commit ba0f14489e
6 changed files with 73 additions and 14 deletions

View File

@@ -443,7 +443,15 @@ public class SCCourseServiceImpl implements SCCourseService {
@Override @Override
public ResultDomain<TbCourse> incrementLearnCount(String courseID) { public ResultDomain<TbCourse> incrementLearnCount(String courseID) {
// TODO: 实现增加课程学习人数 // TODO: 实现增加课程学习人数
return null; ResultDomain<TbCourse> resultDomain = new ResultDomain<>();
int i = courseMapper.incrementLearnCount(courseID,1);
if (i > 0) {
TbCourse course = courseMapper.selectByCourseId(courseID);
resultDomain.success("开始学习", course);
return resultDomain;
}
resultDomain.fail("报名失败");
return resultDomain;
} }
@Override @Override

View File

@@ -69,8 +69,8 @@ public class SCLearningRecordServiceImpl implements LearningRecordService {
resultDomain.fail("资源ID不能为空"); resultDomain.fail("资源ID不能为空");
return resultDomain; return resultDomain;
} }
if (learningRecord.getResourceType() == 2 && (learningRecord.getCourseID() == null || learningRecord.getChapterID() == null || learningRecord.getNodeID() == null)) { if (learningRecord.getResourceType() == 2 && learningRecord.getCourseID() == null) {
resultDomain.fail("课程信息不能为空"); resultDomain.fail("课程ID不能为空");
return resultDomain; return resultDomain;
} }
List<TbLearningRecord> records = learningRecordMapper.selectLearningRecords(learningRecord); List<TbLearningRecord> records = learningRecordMapper.selectLearningRecords(learningRecord);

View File

@@ -273,6 +273,7 @@ function getRankClass(index: number): string {
.rankings-section { .rankings-section {
.ranking-card { .ranking-card {
height: 100%;
.card-header { .card-header {
font-weight: 600; font-weight: 600;
font-size: 16px; font-size: 16px;

View File

@@ -484,8 +484,9 @@ function initVideoListeners() {
const videos = articleContent.querySelectorAll('video'); const videos = articleContent.querySelectorAll('video');
if (videos.length === 0) { if (videos.length === 0) {
// 没有视频,默认阅读即完成 // 没有视频,默认阅读即完成不emit事件依赖父组件的滚动检测
hasVideoCompleted.value = true; hasVideoCompleted.value = true;
console.log(' 文章中没有视频,完成条件:滚动到底部');
return; return;
} }

View File

@@ -312,11 +312,11 @@ async function handleCollect() {
try { try {
if (isCollected.value) { if (isCollected.value) {
// 取消收藏 // 取消收藏
const res = await userCollectionApi.removeCollection( const res = await userCollectionApi.removeCollection({
userInfo.value.id, userID: userInfo.value.id,
CollectionType.COURSE, collectionType: CollectionType.COURSE,
props.courseId collectionID: props.courseId
); });
if (res.success) { if (res.success) {
isCollected.value = false; isCollected.value = false;
ElMessage.success('已取消收藏'); ElMessage.success('已取消收藏');
@@ -360,7 +360,8 @@ async function handleStartLearning() {
await learningRecordApi.createRecord({ await learningRecordApi.createRecord({
userID: userInfo.value.id, userID: userInfo.value.id,
resourceType: 2, // 课程 resourceType: 2, // 课程
resourceID: props.courseId, courseID: props.courseId, // 使用courseID而不是resourceID
resourceID: props.courseId, // 保留resourceID以兼容
progress: 0, progress: 0,
duration: 0, duration: 0,
isComplete: false isComplete: false

View File

@@ -98,10 +98,10 @@
<!-- 文章资源 --> <!-- 文章资源 -->
<div v-if="currentNode.nodeType === 0 && articleData" class="article-content"> <div v-if="currentNode.nodeType === 0 && articleData" class="article-content">
<ArticleShowView <ArticleShow
:as-dialog="false" :as-dialog="false"
:article-data="articleData" :article-data="articleData"
:category-list="[]" :show-back-button="false"
@videos-completed="handleArticleVideosCompleted" @videos-completed="handleArticleVideosCompleted"
/> />
</div> </div>
@@ -195,7 +195,7 @@ import {
Download, Download,
InfoFilled InfoFilled
} from '@element-plus/icons-vue'; } from '@element-plus/icons-vue';
import { ArticleShowView } from '@/views/public/article'; import { ArticleShow } from '@/views/public/article';
import { courseApi } from '@/apis/study'; import { courseApi } from '@/apis/study';
import { learningRecordApi, learningHistoryApi } from '@/apis/study'; import { learningRecordApi, learningHistoryApi } from '@/apis/study';
import { resourceApi } from '@/apis/resource'; import { resourceApi } from '@/apis/resource';
@@ -516,6 +516,12 @@ async function loadNodeContent() {
if (!activeChapters.value.includes(currentChapterIndex.value)) { if (!activeChapters.value.includes(currentChapterIndex.value)) {
activeChapters.value.push(currentChapterIndex.value); activeChapters.value.push(currentChapterIndex.value);
} }
// 等待DOM更新后检查是否需要自动标记完成针对内容太短没有滚动条的情况
await nextTick();
setTimeout(() => {
checkAutoComplete();
}, 500); // 延迟500ms等待ArticleShow初始化完成
} }
// 选择节点 // 选择节点
@@ -706,6 +712,48 @@ function hasScrollbar(): boolean {
return contentAreaRef.value.scrollHeight > contentAreaRef.value.clientHeight; return contentAreaRef.value.scrollHeight > contentAreaRef.value.clientHeight;
} }
// 检查是否需要自动标记完成(针对内容太短没有滚动条的情况)
async function checkAutoComplete() {
// 如果当前节点已完成,跳过
if (isCurrentNodeCompleted.value) {
return;
}
if (!currentNode.value) return;
// 对富文本类型nodeType === 1立即检查
if (currentNode.value.nodeType === 1) {
if (!hasScrollbar()) {
console.log('📄 富文本内容较短无需滚动,自动标记为完成');
const nodeKey = `${currentChapterIndex.value}-${currentNodeIndex.value}`;
await markNodeComplete(nodeKey);
}
}
// 对文章类型nodeType === 0延迟检查等待ArticleShow初始化视频监听器
// 如果文章有视频会在视频播放完成时emit事件不会走到这里
// 如果文章没有视频且没有滚动条,需要自动标记完成
if (currentNode.value.nodeType === 0) {
// 保存当前节点信息,避免延迟后节点已改变
const chapterIdx = currentChapterIndex.value;
const nodeIdx = currentNodeIndex.value;
const nodeKey = `${chapterIdx}-${nodeIdx}`;
// 延迟1秒再检查给ArticleShow足够时间初始化
setTimeout(async () => {
// 再次检查是否已完成(可能已通过视频完成事件标记)
// 且确认当前还在同一个节点
if (!completedNodes.value.has(nodeKey) &&
currentChapterIndex.value === chapterIdx &&
currentNodeIndex.value === nodeIdx &&
!hasScrollbar()) {
console.log('📄 文章内容较短无需滚动且无视频,自动标记为完成');
await markNodeComplete(nodeKey);
}
}, 1000);
}
}
// 处理内容区域滚动 // 处理内容区域滚动
function handleContentScroll(event: Event) { function handleContentScroll(event: Event) {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;