From f90037dd17a402f08a6b465e8c8d8b9cb4077588 Mon Sep 17 00:00:00 2001 From: zhonghua Date: Fri, 20 Mar 2026 10:56:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A0=B9=E6=8D=AE=E6=8E=92=E8=AF=BEles?= =?UTF-8?q?sonType=E7=9B=B4=E6=8E=A5=E8=BF=9B=E5=85=A5=E5=AD=90=E8=AF=BE?= =?UTF-8?q?=E7=A8=8B=EF=BC=8C=E5=AD=90=E8=AF=BE=E7=A8=8B=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E5=8D=B3=E4=B8=8A=E8=AF=BE=E7=BB=93=E6=9D=9F=EF=BC=9B=E5=8F=B3?= =?UTF-8?q?=E4=BE=A7=E6=B7=BB=E5=8A=A0=E8=AF=BE=E7=A8=8B=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端:LessonDetailResponse 新增 lessonType,从 SchedulePlan 读取 - 前端:根据 lessonType 直接进入对应子课程,子课程结束即上课结束 - 前端:右侧面板课程资源上方添加课程类型标签展示 Made-with: Cursor --- .../generated/model/lessonDetailResponse.ts | 2 + .../src/views/teacher/lessons/LessonView.vue | 143 +++++++++++++----- .../views/teacher/schedule/ScheduleView.vue | 108 +++++-------- .../teacher/TeacherLessonController.java | 12 ++ .../dto/response/LessonDetailResponse.java | 3 + 5 files changed, 164 insertions(+), 104 deletions(-) diff --git a/reading-platform-frontend/src/api/generated/model/lessonDetailResponse.ts b/reading-platform-frontend/src/api/generated/model/lessonDetailResponse.ts index 3ff466e..881bb8b 100644 --- a/reading-platform-frontend/src/api/generated/model/lessonDetailResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/lessonDetailResponse.ts @@ -16,4 +16,6 @@ export interface LessonDetailResponse { lesson?: LessonResponse; course?: CourseResponse; class?: ClassResponse; + /** 排课选择的课程类型(子课程模式时用于直接进入对应子课程) */ + lessonType?: string; } diff --git a/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue b/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue index 334244a..70b5847 100644 --- a/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue +++ b/reading-platform-frontend/src/views/teacher/lessons/LessonView.vue @@ -49,10 +49,10 @@ - -
+ +
- @@ -218,6 +218,19 @@
+ +
+
+ + 课程类型 +
+
+ + {{ getLessonTypeName(currentLesson.lessonType) }} + +
+
+
@@ -412,6 +425,7 @@ import { import { message, Modal } from 'ant-design-vue'; import * as teacherApi from '@/api/teacher'; import FilePreviewModal from '@/components/FilePreviewModal.vue'; +import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps'; const router = useRouter(); const route = useRoute(); @@ -434,6 +448,8 @@ let timerInterval: number | null = null; const course = ref({}); const classInfo = ref({}); const lessons = ref([]); +/** 排课选择的课程类型(子课程模式:仅展示该子课程,子课程结束即上课结束) */ +const scheduleLessonType = ref(undefined); const studentEvaluation = ref({ overall: 0, @@ -449,8 +465,37 @@ const lessonRecord = ref({ completionNote: '', }); +/** 判断排课 lessonType 与课程 lessonType 是否匹配(兼容 INTRODUCTION/INTRO、LANGUAGE/DOMAIN_LANGUAGE 等变体) */ +const lessonTypeMatches = (scheduleType: string, lessonType: string): boolean => { + if (!scheduleType || !lessonType) return false; + const s = scheduleType.toUpperCase(); + const l = lessonType.toUpperCase(); + if (s === l) return true; + const pairs: [string, string][] = [ + ['INTRODUCTION', 'INTRO'], + ['LANGUAGE', 'DOMAIN_LANGUAGE'], + ['HEALTH', 'DOMAIN_HEALTH'], + ['SCIENCE', 'DOMAIN_SCIENCE'], + ['SOCIAL', 'DOMAIN_SOCIAL'], + ['SOCIETY', 'DOMAIN_SOCIAL'], + ['ART', 'DOMAIN_ART'], + ]; + for (const [a, b] of pairs) { + if ((s === a || s === b) && (l === a || l === b)) return true; + } + return false; +}; + +/** 展示的课程列表:子课程模式时仅包含排课选中的子课程,否则为全部 */ +const displayLessons = computed(() => { + const type = scheduleLessonType.value; + if (!type || lessons.value.length === 0) return lessons.value; + const matched = lessons.value.filter((l) => lessonTypeMatches(type, l.lessonType || '')); + return matched.length > 0 ? matched : lessons.value; +}); + // 当前课程 -const currentLesson = computed(() => lessons.value[currentLessonIndex.value] || null); +const currentLesson = computed(() => displayLessons.value[currentLessonIndex.value] || null); // 当前环节 const currentStep = computed(() => { @@ -467,9 +512,9 @@ const stepProgressPercent = computed(() => { // 是否有上一个课程 const hasPreviousLesson = computed(() => currentLessonIndex.value > 0); -// 是否是最后一个课程的最后一个环节 +// 是否是最后一个课程的最后一个环节(子课程模式下,当前子课程最后一环节即视为最后) const isLastStepOfLastLesson = computed(() => { - if (currentLessonIndex.value < lessons.value.length - 1) return false; + if (currentLessonIndex.value < displayLessons.value.length - 1) return false; const totalSteps = currentLesson.value?.steps?.length || 1; return currentStepIndex.value >= totalSteps - 1; }); @@ -648,6 +693,9 @@ const loadLessonData = async () => { course.value = data.course || {}; classInfo.value = data.class || {}; + // 排课选择的课程类型(子课程模式:直接进入该子课程,子课程结束即上课结束) + scheduleLessonType.value = data.lessonType || undefined; + // 获取课程列表 // 如果授课记录包含多个课程,使用该列表;否则使用课程包的所有课程 if (data.lessonCourses && data.lessonCourses.length > 0) { @@ -701,50 +749,61 @@ const loadLessonData = async () => { }]; } + // 子课程模式:根据排课 lessonType 直接进入对应子课程(优先于进度恢复和 URL 参数) + const matchedLessons = scheduleLessonType.value + ? lessons.value.filter((l) => lessonTypeMatches(scheduleLessonType.value!, l.lessonType || '')) + : []; + if (matchedLessons.length > 0) { + currentLessonIndex.value = 0; + currentStepIndex.value = 0; + } + // 尝试恢复进度 try { const progress = await teacherApi.getLessonProgress(lessonId.value); - if (progress && (progress.currentLessonId || progress.currentStepId)) { - // 有保存的进度,询问用户是否恢复 + if (progress && (progress.currentLessonId !== undefined || progress.currentStepId !== undefined)) { + const isSub = matchedLessons.length > 0; + const matchedLesson = matchedLessons[0]; + const progressIsForMatched = isSub && progress.currentLessonId !== undefined + && matchedLesson && progress.currentLessonId === matchedLesson.id; + Modal.confirm({ title: '检测到上次上课进度', content: `上次上课到:${getProgressDescription(progress)},是否继续?`, okText: '继续上课', cancelText: '重新开始', onOk: () => { - // 恢复进度 - if (progress.currentLessonId !== undefined) { - const lessonIndex = lessons.value.findIndex((l) => l.id === progress.currentLessonId); - if (lessonIndex >= 0) { - currentLessonIndex.value = lessonIndex; - } - } - if (progress.currentStepId !== undefined) { + if (isSub && progressIsForMatched && progress.currentStepId !== undefined) { + // 子课程模式:仅恢复环节进度 + currentLessonIndex.value = 0; currentStepIndex.value = progress.currentStepId; + } else if (!isSub) { + // 非子课程模式:恢复课程和环节进度 + if (progress.currentLessonId !== undefined) { + const lessonIndex = lessons.value.findIndex((l) => l.id === progress.currentLessonId); + if (lessonIndex >= 0) currentLessonIndex.value = lessonIndex; + } + if (progress.currentStepId !== undefined) currentStepIndex.value = progress.currentStepId; } }, - onCancel: () => { - // 清除进度,从头开始 - clearProgress(); - }, + onCancel: () => clearProgress(), }); } } catch (progressError) { - // 没有保存的进度或获取失败,忽略 console.log('No saved progress found'); } - // 如果URL指定了课程索引,跳转到该课程(优先级高于恢复的进度) - const queryLessonIndex = route.query.lessonIndex ? parseInt(route.query.lessonIndex as string) : 0; - if (queryLessonIndex >= 0 && queryLessonIndex < lessons.value.length) { - currentLessonIndex.value = queryLessonIndex; - } - - // 如果URL指定了环节索引,跳转到该环节(优先级高于恢复的进度) - const queryStepIndex = route.query.stepIndex ? parseInt(route.query.stepIndex as string) : 0; - const totalSteps = lessons.value[currentLessonIndex.value]?.steps?.length || 0; - if (queryStepIndex >= 0 && queryStepIndex < totalSteps) { - currentStepIndex.value = queryStepIndex; + // 非子课程模式时,URL 参数可覆盖 + if (matchedLessons.length === 0) { + const queryLessonIndex = route.query.lessonIndex ? parseInt(route.query.lessonIndex as string) : 0; + if (queryLessonIndex >= 0 && queryLessonIndex < lessons.value.length) { + currentLessonIndex.value = queryLessonIndex; + } + const queryStepIndex = route.query.stepIndex ? parseInt(route.query.stepIndex as string) : 0; + const totalSteps = lessons.value[currentLessonIndex.value]?.steps?.length || 0; + if (queryStepIndex >= 0 && queryStepIndex < totalSteps) { + currentStepIndex.value = queryStepIndex; + } } // 启动计时器 @@ -770,10 +829,11 @@ const getProgressDescription = (progress: any): string => { // 保存进度 const saveProgress = async () => { try { + const list = displayLessons.value; await teacherApi.saveLessonProgress(lessonId.value, { - lessonIds: lessons.value.map((l) => l.id), - completedLessonIds: lessons.value.slice(0, currentLessonIndex.value).map((l) => l.id), - currentLessonId: lessons.value[currentLessonIndex.value]?.id, + lessonIds: list.map((l) => l.id), + completedLessonIds: list.slice(0, currentLessonIndex.value).map((l) => l.id), + currentLessonId: list[currentLessonIndex.value]?.id, currentStepId: currentStepIndex.value, progressData: { timerSeconds: timerSeconds.value, @@ -1559,6 +1619,19 @@ onUnmounted(() => { } } +.lesson-type-card { + .panel-header { + background: #F5F5F5; + color: #666; + } + + .lesson-type-tag { + font-size: 14px; + padding: 6px 14px; + border-radius: 8px; + } +} + .materials-card { .panel-header { background: #FFF5EB; diff --git a/reading-platform-frontend/src/views/teacher/schedule/ScheduleView.vue b/reading-platform-frontend/src/views/teacher/schedule/ScheduleView.vue index 6345344..50a5c08 100644 --- a/reading-platform-frontend/src/views/teacher/schedule/ScheduleView.vue +++ b/reading-platform-frontend/src/views/teacher/schedule/ScheduleView.vue @@ -4,7 +4,9 @@

我的课表

- + 预约上课 @@ -12,7 +14,9 @@
-

今日课程

+

+ 今日课程 +

@@ -21,31 +25,20 @@
{{ schedule.courseName || schedule.coursePackageName || '-' }}
{{ schedule.className || '-' }}
- + {{ getLessonTypeName(schedule.lessonType) }}
- + 开始上课 - + 继续上课 - + 创建课堂
@@ -58,13 +51,17 @@
- + 上一周 本周 下一周 - + {{ weekRangeText }} @@ -74,12 +71,7 @@
-
+
{{ day.dayName }}
{{ day.dateDisplay }}
@@ -87,27 +79,17 @@
-
-
+
+
{{ schedule.scheduledTime || '待定' }}
{{ schedule.courseName || schedule.coursePackageName || '-' }}
{{ schedule.className || '-' }}
- + {{ getLessonTypeName(schedule.lessonType) }}
@@ -133,18 +115,15 @@ - +