fix:优化上课备课路由死循环

This commit is contained in:
zhonghua 2026-03-20 10:07:17 +08:00
parent ed9371b21f
commit 8502d8b2d3
4 changed files with 59 additions and 126 deletions

View File

@ -340,7 +340,7 @@ const prepareCourse = (course: any) => {
message.warning('课程信息异常,无法进入备课'); message.warning('课程信息异常,无法进入备课');
return; return;
} }
router.push(`/teacher/courses/${id}/prepare`); router.push(`/teacher/courses/${id}`);
}; };
onMounted(() => { onMounted(() => {

View File

@ -4,7 +4,9 @@
<div class="course-header"> <div class="course-header">
<div class="header-left"> <div class="header-left">
<a-button type="text" @click="goBackToDetail" class="back-btn"> <a-button type="text" @click="goBackToDetail" class="back-btn">
<template #icon><LeftOutlined /></template> <template #icon>
<LeftOutlined />
</template>
返回 返回
</a-button> </a-button>
<div class="course-cover" v-if="course.coverImagePath"> <div class="course-cover" v-if="course.coverImagePath">
@ -33,15 +35,21 @@
</div> </div>
<div class="header-right"> <div class="header-right">
<a-button @click="showScheduleModal" :disabled="!selectedClassId" class="schedule-btn"> <a-button @click="showScheduleModal" :disabled="!selectedClassId" class="schedule-btn">
<template #icon><CalendarOutlined /></template> <template #icon>
<CalendarOutlined />
</template>
预约上课 预约上课
</a-button> </a-button>
<a-button type="primary" @click="startTeaching" :disabled="!selectedClassId" class="start-btn"> <a-button type="primary" @click="startTeaching" :disabled="!selectedClassId" class="start-btn">
<template #icon><PlayCircleOutlined /></template> <template #icon>
<PlayCircleOutlined />
</template>
开始上课 开始上课
</a-button> </a-button>
<a-button @click="handleExit" class="exit-btn"> <a-button @click="handleExit" class="exit-btn">
<template #icon><CloseOutlined /></template> <template #icon>
<CloseOutlined />
</template>
退出备课 退出备课
</a-button> </a-button>
</div> </div>
@ -52,40 +60,23 @@
<a-row :gutter="20"> <a-row :gutter="20">
<!-- 左侧课程导航 --> <!-- 左侧课程导航 -->
<a-col :span="6"> <a-col :span="6">
<PrepareNavigation <PrepareNavigation :course="course" :lessons="lessons" :selected-section="selectedSection"
:course="course" :selected-lesson-id="selectedLessonId" :selected-item="selectedItem" @select-section="handleSelectSection"
:lessons="lessons" @select-lesson="handleSelectLesson" @select-item="handleSelectItem" />
:selected-section="selectedSection"
:selected-lesson-id="selectedLessonId"
:selected-item="selectedItem"
@select-section="handleSelectSection"
@select-lesson="handleSelectLesson"
@select-item="handleSelectItem"
/>
</a-col> </a-col>
<!-- 右侧内容预览 --> <!-- 右侧内容预览 -->
<a-col :span="18"> <a-col :span="18">
<PreparePreview <PreparePreview :course="course" :selected-type="selectedSection" :selected-lesson="selectedLesson"
:course="course" :selected-item="selectedItem" :selected-step="selectedStep" @select-step="handleSelectStep"
:selected-type="selectedSection" @preview-resource="handlePreviewResource" />
:selected-lesson="selectedLesson"
:selected-item="selectedItem"
:selected-step="selectedStep"
@select-step="handleSelectStep"
@preview-resource="handlePreviewResource"
/>
</a-col> </a-col>
</a-row> </a-row>
</div> </div>
</a-spin> </a-spin>
<!-- 文件预览弹窗 --> <!-- 文件预览弹窗 -->
<FilePreviewModal <FilePreviewModal v-model:open="previewModalVisible" :file-url="previewFileUrl" :file-name="previewFileName" />
v-model:open="previewModalVisible"
:file-url="previewFileUrl"
:file-name="previewFileName"
/>
<!-- 预约上课弹窗四步流程与课表页一致 --> <!-- 预约上课弹窗四步流程与课表页一致 -->
<TeacherCreateScheduleModal ref="scheduleModalRef" @success="onScheduleSuccess" /> <TeacherCreateScheduleModal ref="scheduleModalRef" @success="onScheduleSuccess" />
@ -414,13 +405,7 @@ const handleExit = () => {
}; };
const goBackToDetail = () => { const goBackToDetail = () => {
// 使 ID courseId /courses/undefined router.back();
const id = route.params.id || courseId.value;
if (id && id !== 'undefined' && id !== 'null') {
router.push(`/teacher/courses/${id}`);
} else {
router.push('/teacher/courses');
}
}; };
onMounted(() => { onMounted(() => {

View File

@ -4,7 +4,9 @@
<div class="lesson-toolbar"> <div class="lesson-toolbar">
<div class="toolbar-left"> <div class="toolbar-left">
<a-button class="exit-btn" @click="exitLesson"> <a-button class="exit-btn" @click="exitLesson">
<template #icon><ArrowLeftOutlined /></template> <template #icon>
<ArrowLeftOutlined />
</template>
退出上课 退出上课
</a-button> </a-button>
<div class="course-info" v-if="course.name"> <div class="course-info" v-if="course.name">
@ -28,11 +30,14 @@
<StepBackwardOutlined /> 上一步 <StepBackwardOutlined /> 上一步
</a-button> </a-button>
<a-button type="primary" class="next-btn" @click="nextStep" :disabled="isLastStepOfLastLesson"> <a-button type="primary" class="next-btn" @click="nextStep" :disabled="isLastStepOfLastLesson">
下一步 <StepForwardOutlined /> 下一步
<StepForwardOutlined />
</a-button> </a-button>
</a-button-group> </a-button-group>
<a-button type="primary" class="broadcast-btn" @click="openBroadcastMode"> <a-button type="primary" class="broadcast-btn" @click="openBroadcastMode">
<template #icon><ExpandOutlined /></template> <template #icon>
<ExpandOutlined />
</template>
展播模式 展播模式
</a-button> </a-button>
<a-button class="toolbar-icon-btn" @click="showNotesDrawer = true"> <a-button class="toolbar-icon-btn" @click="showNotesDrawer = true">
@ -47,15 +52,9 @@
<!-- 课程进度条多课程时显示 --> <!-- 课程进度条多课程时显示 -->
<div v-if="lessons.length > 1" class="course-progress-bar"> <div v-if="lessons.length > 1" class="course-progress-bar">
<a-steps :current="currentLessonIndex" size="small" class="course-steps"> <a-steps :current="currentLessonIndex" size="small" class="course-steps">
<a-step <a-step v-for="(lesson, index) in lessons" :key="lesson.id" :title="getLessonShortName(lesson)"
v-for="(lesson, index) in lessons" :status="getLessonStatus(index)" :disabled="index > currentLessonIndex" @click="handleLessonClick(index)"
:key="lesson.id" class="clickable-step" />
:title="getLessonShortName(lesson)"
:status="getLessonStatus(index)"
:disabled="index > currentLessonIndex"
@click="handleLessonClick(index)"
class="clickable-step"
/>
</a-steps> </a-steps>
</div> </div>
@ -69,12 +68,7 @@
环节 {{ currentStepIndex + 1 }}/{{ currentLesson?.steps?.length || 0 }} 环节 {{ currentStepIndex + 1 }}/{{ currentLesson?.steps?.length || 0 }}
</span> </span>
</div> </div>
<a-progress <a-progress :percent="stepProgressPercent" :show-info="false" stroke-color="#FF8C42" class="step-progress" />
:percent="stepProgressPercent"
:show-info="false"
stroke-color="#FF8C42"
class="step-progress"
/>
</div> </div>
<!-- 主内容区 --> <!-- 主内容区 -->
@ -88,16 +82,10 @@
</div> </div>
<div class="step-list"> <div class="step-list">
<div <div v-for="(step, index) in currentLesson?.steps || []" :key="step.id" class="step-item" :class="{
v-for="(step, index) in currentLesson?.steps || []" active: currentStepIndex === index,
:key="step.id" completed: index < currentStepIndex
class="step-item" }" @click="handleStepClick(index)">
:class="{
active: currentStepIndex === index,
completed: index < currentStepIndex
}"
@click="handleStepClick(index)"
>
<div class="step-number"> <div class="step-number">
<CheckOutlined v-if="index < currentStepIndex" /> <CheckOutlined v-if="index < currentStepIndex" />
<span v-else>{{ index + 1 }}</span> <span v-else>{{ index + 1 }}</span>
@ -168,12 +156,8 @@
<div v-if="currentStep.images?.length" class="resource-group"> <div v-if="currentStep.images?.length" class="resource-group">
<span class="resource-type-label">图片</span> <span class="resource-type-label">图片</span>
<div class="resource-items"> <div class="resource-items">
<div <div v-for="(img, idx) in currentStep.images" :key="idx" class="resource-item"
v-for="(img, idx) in currentStep.images" @click="previewResource(img, 'image')">
:key="idx"
class="resource-item"
@click="previewResource(img, 'image')"
>
<PictureOutlined /> {{ img.name || `图片${idx + 1}` }} <PictureOutlined /> {{ img.name || `图片${idx + 1}` }}
</div> </div>
</div> </div>
@ -183,12 +167,8 @@
<div v-if="currentStep.videos?.length" class="resource-group"> <div v-if="currentStep.videos?.length" class="resource-group">
<span class="resource-type-label">视频</span> <span class="resource-type-label">视频</span>
<div class="resource-items"> <div class="resource-items">
<div <div v-for="(vid, idx) in currentStep.videos" :key="idx" class="resource-item"
v-for="(vid, idx) in currentStep.videos" @click="previewResource(vid, 'video')">
:key="idx"
class="resource-item"
@click="previewResource(vid, 'video')"
>
<VideoCameraOutlined /> {{ vid.name || `视频${idx + 1}` }} <VideoCameraOutlined /> {{ vid.name || `视频${idx + 1}` }}
</div> </div>
</div> </div>
@ -198,12 +178,8 @@
<div v-if="currentStep.audioList?.length" class="resource-group"> <div v-if="currentStep.audioList?.length" class="resource-group">
<span class="resource-type-label">音频</span> <span class="resource-type-label">音频</span>
<div class="resource-items"> <div class="resource-items">
<div <div v-for="(aud, idx) in currentStep.audioList" :key="idx" class="resource-item"
v-for="(aud, idx) in currentStep.audioList" @click="previewResource(aud, 'audio')">
:key="idx"
class="resource-item"
@click="previewResource(aud, 'audio')"
>
<AudioOutlined /> {{ aud.name || `音频${idx + 1}` }} <AudioOutlined /> {{ aud.name || `音频${idx + 1}` }}
</div> </div>
</div> </div>
@ -213,12 +189,8 @@
<div v-if="currentStep.pptFiles?.length" class="resource-group"> <div v-if="currentStep.pptFiles?.length" class="resource-group">
<span class="resource-type-label">课件</span> <span class="resource-type-label">课件</span>
<div class="resource-items"> <div class="resource-items">
<div <div v-for="(ppt, idx) in currentStep.pptFiles" :key="idx" class="resource-item"
v-for="(ppt, idx) in currentStep.pptFiles" @click="previewResource(ppt, 'ppt')">
:key="idx"
class="resource-item"
@click="previewResource(ppt, 'ppt')"
>
<FilePptOutlined /> {{ ppt.name || `课件${idx + 1}` }} <FilePptOutlined /> {{ ppt.name || `课件${idx + 1}` }}
</div> </div>
</div> </div>
@ -228,12 +200,8 @@
<div v-if="currentStep.documents?.length" class="resource-group"> <div v-if="currentStep.documents?.length" class="resource-group">
<span class="resource-type-label">文档</span> <span class="resource-type-label">文档</span>
<div class="resource-items"> <div class="resource-items">
<div <div v-for="(doc, idx) in currentStep.documents" :key="idx" class="resource-item"
v-for="(doc, idx) in currentStep.documents" @click="previewResource(doc, 'document')">
:key="idx"
class="resource-item"
@click="previewResource(doc, 'document')"
>
<FileTextOutlined /> {{ doc.name || `文档${idx + 1}` }} <FileTextOutlined /> {{ doc.name || `文档${idx + 1}` }}
</div> </div>
</div> </div>
@ -258,12 +226,8 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div v-if="courseResources.length > 0" class="materials-list"> <div v-if="courseResources.length > 0" class="materials-list">
<div <div v-for="item in courseResources" :key="item.id" class="material-item"
v-for="item in courseResources" @click="previewCourseResource(item)">
:key="item.id"
class="material-item"
@click="previewCourseResource(item)"
>
<div class="material-icon" :class="item.type"> <div class="material-icon" :class="item.type">
<VideoCameraOutlined v-if="item.type === 'video'" /> <VideoCameraOutlined v-if="item.type === 'video'" />
<AudioOutlined v-else-if="item.type === 'audio'" /> <AudioOutlined v-else-if="item.type === 'audio'" />
@ -315,12 +279,7 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div v-if="stepMaterials.length > 0" class="materials-list"> <div v-if="stepMaterials.length > 0" class="materials-list">
<div <div v-for="item in stepMaterials" :key="item.id" class="material-item" @click="previewMaterial(item)">
v-for="item in stepMaterials"
:key="item.id"
class="material-item"
@click="previewMaterial(item)"
>
<div class="material-icon" :class="item.type"> <div class="material-icon" :class="item.type">
<PlayCircleOutlined v-if="item.type === '视频'" /> <PlayCircleOutlined v-if="item.type === '视频'" />
<SoundOutlined v-else-if="item.type === '音频'" /> <SoundOutlined v-else-if="item.type === '音频'" />
@ -389,12 +348,7 @@
</a-modal> </a-modal>
<!-- 课堂记录抽屉 --> <!-- 课堂记录抽屉 -->
<a-drawer <a-drawer v-model:open="showNotesDrawer" title="课堂记录" placement="right" width="400">
v-model:open="showNotesDrawer"
title="课堂记录"
placement="right"
width="400"
>
<a-form layout="vertical"> <a-form layout="vertical">
<a-form-item label="完成情况"> <a-form-item label="完成情况">
<a-radio-group v-model:value="lessonRecord.completion"> <a-radio-group v-model:value="lessonRecord.completion">
@ -410,11 +364,8 @@
<a-rate v-model:value="lessonRecord.participationRating" /> <a-rate v-model:value="lessonRecord.participationRating" />
</a-form-item> </a-form-item>
<a-form-item label="完成备注"> <a-form-item label="完成备注">
<a-textarea <a-textarea v-model:value="lessonRecord.completionNote" placeholder="记录课程完成情况、学生表现等..."
v-model:value="lessonRecord.completionNote" :auto-size="{ minRows: 6, maxRows: 10 }" />
placeholder="记录课程完成情况、学生表现等..."
:auto-size="{ minRows: 6, maxRows: 10 }"
/>
</a-form-item> </a-form-item>
<a-button type="primary" block size="large" @click="saveLessonRecord"> <a-button type="primary" block size="large" @click="saveLessonRecord">
保存并结束 保存并结束
@ -423,11 +374,7 @@
</a-drawer> </a-drawer>
<!-- 文件预览弹窗 --> <!-- 文件预览弹窗 -->
<FilePreviewModal <FilePreviewModal v-model:open="previewModalVisible" :file-url="previewFileUrl" :file-name="previewFileName" />
v-model:open="previewModalVisible"
:file-url="previewFileUrl"
:file-name="previewFileName"
/>
</div> </div>
</template> </template>
@ -919,7 +866,7 @@ const exitLesson = () => {
okText: '确认退出', okText: '确认退出',
cancelText: '继续上课', cancelText: '继续上课',
onOk: () => { onOk: () => {
router.push(`/teacher/courses/${course.value.id}/prepare`); router.back();
}, },
}); });
}; };
@ -936,7 +883,7 @@ const saveLessonRecord = async () => {
await clearProgress(); await clearProgress();
message.success('课程记录已保存'); message.success('课程记录已保存');
showNotesDrawer.value = false; showNotesDrawer.value = false;
router.push(`/teacher/courses/${course.value.id}/prepare`); router.back();
} catch (error: any) { } catch (error: any) {
message.error(error.message || '保存记录失败'); message.error(error.message || '保存记录失败');
} }
@ -1212,6 +1159,7 @@ onUnmounted(() => {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
.course-steps { .course-steps {
:deep(.ant-steps-item-process), :deep(.ant-steps-item-process),
:deep(.ant-steps-item-finish) { :deep(.ant-steps-item-finish) {
cursor: pointer; cursor: pointer;

View File

@ -334,14 +334,14 @@ const handleStartLessonFromSchedule = async (schedule: TeacherSchedule) => {
try { try {
const lesson = await startLessonFromSchedule(schedule.id); const lesson = await startLessonFromSchedule(schedule.id);
message.success('课堂创建成功'); message.success('课堂创建成功');
router.push(`/teacher/lessons/${lesson.id}`); router.push({ path: `/teacher/lessons/${lesson.id}`, query: { from: 'schedule' } });
} catch (error) { } catch (error) {
message.error('创建课堂失败'); message.error('创建课堂失败');
} }
}; };
const goToLesson = (lessonId: number) => { const goToLesson = (lessonId: number) => {
router.push(`/teacher/lessons/${lessonId}`); router.push({ path: `/teacher/lessons/${lessonId}`, query: { from: 'schedule' } });
}; };
// //