From d9d80c833785b013f05cda05bfec86bff44d6e2e Mon Sep 17 00:00:00 2001 From: yuk255 Date: Sat, 13 Sep 2025 16:20:00 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=AF=B9=E6=8E=A5=E8=AF=BE?= =?UTF-8?q?=E7=A8=8B=E4=B8=8B=E6=95=99=E5=B8=88=E7=9A=84=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=9B=E4=BF=AE=E6=94=B9=E7=AB=A0=E8=8A=82=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/modules/teachCourse.ts | 57 +- .../admin/CourseComponents/CourseCategory.vue | 56 +- .../admin/CourseComponents/CourseCreate.vue | 192 ++- src/views/teacher/course/ChapterEditor.vue | 1512 ++++++----------- .../teacher/course/ChapterManagement.vue | 8 +- 5 files changed, 782 insertions(+), 1043 deletions(-) diff --git a/src/api/modules/teachCourse.ts b/src/api/modules/teachCourse.ts index 3de2d28..9f82156 100644 --- a/src/api/modules/teachCourse.ts +++ b/src/api/modules/teachCourse.ts @@ -48,17 +48,17 @@ export interface CreateCourseRequest { end_time?: string | null enroll_count?: number | null max_enroll?: number | null - status?: number | null + status?: string | number | null question?: string | null - pauseExit: string - allowSpeed: string - showSubtitle: string + pauseExit?: string + allowSpeed?: string + showSubtitle?: string categoryId?: number | string | null // 支持单个ID(number)或多个ID的逗号分隔字符串 } // 编辑课程请求参数 export interface EditCourseRequest extends CreateCourseRequest { - id: string + id: string, } // 查询教师课程列表参数 @@ -131,9 +131,9 @@ export class TeachCourseApi { */ static async createCourse(data: CreateCourseRequest): Promise> { try { - console.log('🚀 发送新建课程请求:', { url: '/aiol/aiolCourse/add', data }) + console.log('🚀 发送新建课程请求:', { url: '/aiol/aiolCourse/teacher_add', data }) - const response = await ApiRequest.post('/aiol/aiolCourse/add', data) + const response = await ApiRequest.post('/aiol/aiolCourse/teacher_add', data) console.log('📝 新建课程响应:', response) return response @@ -205,6 +205,49 @@ export class TeachCourseApi { } } + /** + * 查询课程下的教师 + */ + static async getTeacherListInCourse(courseId:string): Promise> { + try { + const response = await ApiRequest.get<{ result: any[] }>(`/aiol/aiolCourse/${courseId}/teachers`) + return response + } catch (error) { + console.error('❌ 查询教师失败:', error) + throw error + } + } + + + /** + * 课程添加老师 + */ + static async addTeacher(data: { courseId: string, userId: string }): Promise> { + try { + const response = await ApiRequest.post(`/aiol/aiolCourse/${data.courseId}/bind_teacher?userId=${data.userId}`) + + return response + } catch (error) { + console.error('❌ 课程添加老师失败:', error) + throw error + } + } + + /** + * 课程移除老师 + */ + static async unbindTeacher(data: { courseId: string, userId: string }): Promise> { + try { + const response = await ApiRequest.delete(`/aiol/aiolCourse/${data.courseId}/unbind_teacher?userId=${data.userId}`) + + return response + } catch (error) { + console.error('❌ 课程移除老师失败:', error) + throw error + } + } + + /** * 课程视频上架 */ diff --git a/src/components/admin/CourseComponents/CourseCategory.vue b/src/components/admin/CourseComponents/CourseCategory.vue index e377b8b..7cfdc01 100644 --- a/src/components/admin/CourseComponents/CourseCategory.vue +++ b/src/components/admin/CourseComponents/CourseCategory.vue @@ -301,7 +301,7 @@ const getOptionsForCourse = (course: CourseDisplayItem) => { ]; } else if (course.status === 2) { // 已结束 return [ - { label: '查看', value: 'view', icon: '/images/teacher/查看.png' }, + // { label: '查看', value: 'view', icon: '/images/teacher/小编辑.png' }, { label: '删除', value: 'delete', icon: '/images/teacher/删除.png' } ]; } @@ -430,29 +430,12 @@ const handleOfflineCourse = (course: CourseDisplayItem) => { // 通过API下架课程 - 设置状态为已结束 const updatedData = { id: course.id!, - name: course.name, - description: course.description, - status: 2, // 2=已结束状态 - // 添加必需的字段 - pauseExit: '1', - allowSpeed: '1', - showSubtitle: '1' + status: '0', }; await TeachCourseApi.editCourse(updatedData); - // 更新本地数据 - const targetCourse = courseList.value.find(c => c.id === course.id); - if (targetCourse) { - targetCourse.status = 2; - targetCourse.statusText = '已结束'; - } - - const originalCourse = originalCourseList.value.find(c => c.id === course.id); - if (originalCourse) { - originalCourse.status = 2; - originalCourse.statusText = '已结束'; - } + getCourseList(); message.success(`课程"${course.name}"已下架,现在可以删除了`); } catch (error) { @@ -465,12 +448,35 @@ const handleOfflineCourse = (course: CourseDisplayItem) => { // 发布课程 const handlePublishCourse = (course: CourseDisplayItem) => { - const targetCourse = courseList.value.find(c => c.id === course.id); - if (targetCourse) { - targetCourse.status = 1; // 设置为进行中状态 - targetCourse.statusText = '进行中'; - message.success(`课程"${course.name}"已发布`); + if (!course.id) { + message.error('课程ID不存在,无法下架'); + return; } + + dialog.info({ + title: '确认发布', + content: `确定要发布课程"${course.name}"吗?发布后课程不能删除。`, + positiveText: '确定发布', + negativeText: '取消', + onPositiveClick: async () => { + try { + // 通过API发布课程 - 设置状态为进行中 + const updatedData = { + id: course.id!, + status: '1', + }; + + await TeachCourseApi.editCourse(updatedData); + + getCourseList(); + + message.success(`课程"${course.name}"已下架,现在可以删除了`); + } catch (error) { + console.error('下架课程失败:', error); + message.error('下架课程失败,请稍后重试'); + } + } + }); }; // 移动课程 diff --git a/src/components/admin/CourseComponents/CourseCreate.vue b/src/components/admin/CourseComponents/CourseCreate.vue index f82dd1a..f5c220a 100644 --- a/src/components/admin/CourseComponents/CourseCreate.vue +++ b/src/components/admin/CourseComponents/CourseCreate.vue @@ -32,8 +32,8 @@
- - 授课老师: +
@@ -187,7 +187,7 @@ import { import '@wangeditor/editor/dist/css/style.css' // @ts-ignore import { Editor, Toolbar } from '@wangeditor/editor-for-vue' -import TeachCourseApi from '@/api/modules/teachCourse' +import TeachCourseApi, { ClassApi } from '@/api/modules/teachCourse' import UploadApi from '@/api/modules/upload' import CourseApi from '@/api/modules/course'; import { useCourseStore } from '@/stores/course' @@ -258,6 +258,42 @@ const formData = reactive({ requiredPoints: 60 }) +// 保存原始的老师列表,用于编辑模式下比较变更 +const originalInstructors = ref([]) + +// 加载当前课程的老师列表 +const loadCourseTeachers = async (courseId: string) => { + try { + console.log('🔄 开始获取课程老师列表,课程ID:', courseId) + + const response = await TeachCourseApi.getTeacherListInCourse(courseId) + + console.log('🔍 课程老师API响应:', response) + + if (response.data && response.data.result) { + const teachers = response.data.result + console.log('✅ 获取到课程老师列表:', teachers) + + // 提取老师ID列表 + const teacherIds = teachers.map((teacher: any) => teacher.id || teacher.userId) + + formData.instructors = teacherIds + originalInstructors.value = [...teacherIds] + + console.log('📝 已设置课程老师ID列表:', teacherIds) + } else { + console.log('⚠️ 课程暂无绑定老师') + formData.instructors = [] + originalInstructors.value = [] + } + } catch (error) { + console.error('❌ 获取课程老师列表失败:', error) + // 发生错误时设置为空数组 + formData.instructors = [] + originalInstructors.value = [] + } +} + // 加载课程数据 @@ -278,7 +314,15 @@ const loadCourseData = async () => { } else { formData.courseCategory = []; } - formData.instructors = storeCourseData.instructors || []; + + // 如果是编辑模式且有课程ID,调用API获取当前课程的老师 + if (isEditMode.value && courseId.value) { + await loadCourseTeachers(courseId.value); + } else { + // 如果不是编辑模式,使用store中的数据 + formData.instructors = storeCourseData.instructors || []; + originalInstructors.value = [...(storeCourseData.instructors || [])]; + } formData.startTime = storeCourseData.start_time || storeCourseData.startTime || null; formData.endTime = storeCourseData.end_time || storeCourseData.endTime || null; formData.studentType = (storeCourseData.type === 1 || storeCourseData.studentType === 'partial') ? 'partial' : 'all'; @@ -333,7 +377,15 @@ const loadCourseData = async () => { } else { formData.courseCategory = []; } - formData.instructors = courseData.instructors || []; + + // 如果是编辑模式且有课程ID,调用API获取当前课程的老师 + if (isEditMode.value && courseId.value) { + await loadCourseTeachers(courseId.value); + } else { + // 如果不是编辑模式,使用路由数据中的老师信息 + formData.instructors = courseData.instructors || []; + originalInstructors.value = [...(courseData.instructors || [])]; + } formData.startTime = courseData.start_time || courseData.startTime || null; formData.endTime = courseData.end_time || courseData.endTime || null; formData.studentType = (courseData.type === 1 || courseData.studentType === 'partial') ? 'partial' : 'all'; @@ -384,17 +436,6 @@ const loadCourseData = async () => { } } -// 组件挂载时处理 -onMounted(() => { - // 如果是编辑模式(有courseId)、有通过路由传递的课程数据,或者store中有数据 - if (isEditMode.value && courseId.value) { - loadCourseData() - } else if (route.query.courseData || courseStore.courseEditData) { - // 即使没有ID,也尝试从路由数据或store加载(用于从列表页面直接编辑的情况) - loadCourseData() - } -}) - // 课程分类选项 const categoryOptions: Ref<{ label: string; value: number }[]> = ref([]) @@ -402,12 +443,7 @@ const categoryOptions: Ref<{ label: string; value: number }[]> = ref([]) const instructorOptions = ref([] as { label: string; value: string }[]) // 班级选项 -const classOptions = [ - { label: '前端开发班', value: 'frontend-class' }, - { label: '后端开发班', value: 'backend-class' }, - { label: 'AI算法班', value: 'ai-class' }, - { label: '全栈开发班', value: 'fullstack-class' } -] +const classOptions = ref([] as { label: string; value: string }[]) // 文件上传相关方法 const triggerFileUpload = () => { @@ -475,6 +511,78 @@ const formatDateTime = (timestamp: number): string => { return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` } +// 处理老师变更(绑定和解绑) +const handleTeacherChanges = async (courseId: string, isCreateMode: boolean = false) => { + try { + console.log('🔄 开始处理老师变更...') + console.log('课程ID:', courseId) + console.log('当前老师列表:', formData.instructors) + console.log('原始老师列表:', originalInstructors.value) + + if (isCreateMode) { + // 创建模式:绑定所有选中的老师 + console.log('📝 创建模式:绑定所有老师') + for (const instructorId of formData.instructors) { + try { + console.log('➕ 正在绑定老师:', instructorId) + await TeachCourseApi.addTeacher({ + courseId: courseId, + userId: instructorId + }) + console.log('✅ 老师绑定成功:', instructorId) + } catch (error) { + console.error('❌ 老师绑定失败:', instructorId, error) + // 不阻止其他老师的绑定,继续执行 + } + } + } else { + // 编辑模式:比较变更并处理 + console.log('✏️ 编辑模式:比较老师变更') + + // 需要添加的老师(在新列表中但不在原列表中) + const teachersToAdd = formData.instructors.filter(id => !originalInstructors.value.includes(id)) + console.log('需要添加的老师:', teachersToAdd) + + // 需要移除的老师(在原列表中但不在新列表中) + const teachersToRemove = originalInstructors.value.filter(id => !formData.instructors.includes(id)) + console.log('需要移除的老师:', teachersToRemove) + + // 添加新老师 + for (const instructorId of teachersToAdd) { + try { + console.log('➕ 正在添加老师:', instructorId) + await TeachCourseApi.addTeacher({ + courseId: courseId, + userId: instructorId + }) + console.log('✅ 老师添加成功:', instructorId) + } catch (error) { + console.error('❌ 老师添加失败:', instructorId, error) + } + } + + // 移除老师 + for (const instructorId of teachersToRemove) { + try { + console.log('➖ 正在移除老师:', instructorId) + await TeachCourseApi.unbindTeacher({ + courseId: courseId, + userId: instructorId + }) + console.log('✅ 老师移除成功:', instructorId) + } catch (error) { + console.error('❌ 老师移除失败:', instructorId, error) + } + } + } + + console.log('✅ 老师变更处理完成') + } catch (error) { + console.error('❌ 处理老师变更失败:', error) + throw error + } +} + // 保存 const handleSubmit = async () => { try { @@ -546,9 +654,10 @@ const handleSubmit = async () => { description: formData.courseDescription, type: formData.studentType === 'all' ? 0 : 1, categoryId: Array.isArray(formData.courseCategory) ? formData.courseCategory.join(',') : formData.courseCategory, - target: formData.studentType === 'all' ? '' : formData.selectedClasses.join(','), // target 字段处理 - start_time: formData.startTime ? formatDateTime(formData.startTime) : null, - end_time: formData.endTime ? formatDateTime(formData.endTime) : null, + target: '', // target 字段处理 + classId: formData.studentType === 'all' ? '' : formData.selectedClasses.join(','), // classId + startTime: formData.startTime ? formatDateTime(formData.startTime) : null, + endTime: formData.endTime ? formatDateTime(formData.endTime) : null, pauseExit: formData.stopOnLeave ? '1' : '0', // 离开页面停止播放 allowSpeed: formData.videoSpeedControl ? '1' : '0', // 视频倍数播放 showSubtitle: formData.showVideoText ? '1' : '0', // 显示视频文本 @@ -576,6 +685,9 @@ const handleSubmit = async () => { const response = await TeachCourseApi.editCourse(editData) if (response.data.code === 200) { + // 课程更新成功后,处理老师的绑定和解绑 + await handleTeacherChanges(courseId.value); + message.success('课程更新成功!') // 清除缓存数据 courseStore.clearCourseEditData(); @@ -589,6 +701,12 @@ const handleSubmit = async () => { const response = await TeachCourseApi.createCourse(createCourseData) if (response.data.code === 200) { + // 课程创建成功后,获取新创建的课程ID并绑定老师 + const newCourseId = response.data.data?.id || response.data.id; + if (newCourseId && formData.instructors.length > 0) { + await handleTeacherChanges(newCourseId.toString(), true); + } + message.success('课程创建成功!') // 清除缓存数据 courseStore.clearCourseEditData(); @@ -639,9 +757,33 @@ const getTeacherList = () => { }) } +// 获取班级 +const getClassList = () => { + ClassApi.queryClassList({course_id:null}).then(response => { + console.log('班级列表:', response.data.result); + + response.data.result.forEach((cls: any) => { + classOptions.value.push({ + label: cls.name, + value: cls.id + }) + }) + }).catch(error => { + console.error('获取班级列表失败:', error) + }) +} + onMounted(() => { + // 如果是编辑模式(有courseId)、有通过路由传递的课程数据,或者store中有数据 + if (isEditMode.value && courseId.value) { + loadCourseData() + } else if (route.query.courseData || courseStore.courseEditData) { + // 即使没有ID,也尝试从路由数据或store加载(用于从列表页面直接编辑的情况) + loadCourseData() + } getCourseList() getTeacherList() + getClassList() }) diff --git a/src/views/teacher/course/ChapterEditor.vue b/src/views/teacher/course/ChapterEditor.vue index 012c8d3..91281a7 100644 --- a/src/views/teacher/course/ChapterEditor.vue +++ b/src/views/teacher/course/ChapterEditor.vue @@ -27,38 +27,24 @@ - - - 添加章节 - - - + + + + 保存章节 + @@ -68,14 +54,9 @@ 'tablet-content': isTablet, 'sidebar-collapsed-content': sidebarCollapsed && isMobile }"> -
+
- 第{{ currentChapterIndex + 1 }}章 - - {{ chapters[currentChapterIndex].expanded ? '收起' : '展开' }} - - + {{ currentChapter.name ? currentChapter.name : '暂未设置章节名' }}
@@ -83,13 +64,12 @@ 本章名称:
-
-
@@ -150,56 +130,31 @@
添加考试/练习: -
- - - -
-
- - -
- 已上传文件: -
-
- {{ file.name }} - -
+
+ + 从考试/练习选择 +
添加作业: -
- - - +
+ + 从作业库选择 +
- 添加小节 - - - - 保存章节 -
import { ref, computed, onMounted, onUnmounted } from 'vue'; import CustomDropdown from '@/components/CustomDropdown.vue'; -import HomeworkDropdown from '@/components/HomeworkDropdown.vue'; import ExamPaperLibraryModal from '@/components/ExamPaperLibraryModal.vue'; import HomeworkLibraryModal from '@/components/HomeworkLibraryModal.vue'; import ResourceLibraryModal from '@/components/ResourceLibraryModal.vue'; import { ArrowBackOutline } from '@vicons/ionicons5'; import { useRouter, useRoute } from 'vue-router'; -import { ChapterApi } from '@/api'; +import TeachCourseApi from '@/api/modules/teachCourse'; import { useUserStore } from '@/stores/user'; import { useMessage } from 'naive-ui'; @@ -288,10 +242,10 @@ const userStore = useUserStore(); const message = useMessage(); // 控制菜单显示状态的响应式变量 -const isMenuVisible = ref(false); +// const isMenuVisible = ref(false); // 已删除,不再使用 // 加载状态 -const loading = ref(false); +// const loading = ref(false); // 已删除,不再使用 const saving = ref(false); // 控制删除确认模态框的显示状态 @@ -325,92 +279,28 @@ const sidebarCollapsed = ref(false); interface Chapter { id: number; name: string; - expanded: boolean; sections: any[]; } -// 章节数据结构 -const chapters = ref([ - { - id: 1, - name: '课前准备', - expanded: true, - sections: [ - { - id: 1, - lessonName: '开课彩蛋新开始', - coursewareName: '课件准备PPT', - coursewareUploadOption: '', - coursewareFiles: [], - selectedExamOption: '', - homeworkName: '请添加作业', - uploadedFiles: [], - homeworkFiles: [], - contentTitle: '1.开课彩蛋:新开始', - contentDescription: '第一节课程定位程定位与目标' - }, - { - id: 2, - lessonName: '开课彩蛋新开始', - coursewareName: '课件准备PPT', - coursewareUploadOption: '', - coursewareFiles: [], - selectedExamOption: '', - homeworkName: '请添加作业', - uploadedFiles: [], - homeworkFiles: [], - contentTitle: '2.课程内容:扩展知识', - contentDescription: '第二节课程扩展内容' - } - ] - }, - { - id: 2, - name: '课前准备', - expanded: false, - sections: [ - { - id: 1, - lessonName: '课程导入基础知识', - coursewareName: '课件准备PPT', - coursewareUploadOption: '', - coursewareFiles: [], - selectedExamOption: '', - homeworkName: '请添加作业', - uploadedFiles: [], - homeworkFiles: [], - contentTitle: '2.课程导入:基础知识', - contentDescription: '第二节课程基础知识讲解' - } - ] - }, - { - id: 3, - name: '课前准备', - expanded: false, - sections: [ - { - id: 1, - lessonName: '实践操作技能训练', - coursewareName: '课件准备PPT', - coursewareUploadOption: '', - coursewareFiles: [], - selectedExamOption: '', - homeworkName: '请添加作业', - uploadedFiles: [], - homeworkFiles: [], - contentTitle: '3.实践操作:技能训练', - contentDescription: '第三节实践操作技能训练' - } - ] - } -]); +// 章节数据结构 - 简化为单个章节 +const chapter = ref({ + id: 1, + name: '课前准备', + sections: [ + { + id: 1, + lessonName: '开课彩蛋新开始', + coursewareName: '课件准备PPT', + coursewareUploadOption: '', + coursewareFiles: [], + contentTitle: '1.开课彩蛋:新开始', + contentDescription: '第一节课程定位程定位与目标' + } + ] +}); -// 当前选中的章节索引 -const currentChapterIndex = ref(0); - -// 下一个章节的ID -const nextChapterId = ref(4); +// 当前章节的计算属性 +const currentChapter = computed(() => chapter.value); // 课件选项 const coursewareOptions = [ @@ -424,117 +314,19 @@ const coursewareUploadOptions = [ { label: '从资源库选择', value: '从资源库选择' } ]; -// 作业选项 -const homeworkOptions = [ - { label: '从作业库选择', value: '从作业库选择' }, - { label: '本地上传', value: '本地上传' } -]; - // 输入框引用 const chapterInputRef = ref(); -// 当前章节名称的计算属性 -const currentChapterName = computed({ - get: () => { - if (chapters.value.length > 0 && currentChapterIndex.value >= 0 && currentChapterIndex.value < chapters.value.length) { - return chapters.value[currentChapterIndex.value]?.name || ''; - } - return ''; - }, - set: (value: string) => { - console.log('🔍 计算属性setter被调用,新值:', value); - console.log('🔍 当前章节索引:', currentChapterIndex.value); - console.log('🔍 章节数组长度:', chapters.value.length); - - if (chapters.value.length > 0 && currentChapterIndex.value >= 0 && currentChapterIndex.value < chapters.value.length) { - if (chapters.value[currentChapterIndex.value]) { - console.log('📝 更新章节名称:', chapters.value[currentChapterIndex.value].name, '->', value); - chapters.value[currentChapterIndex.value].name = value; - console.log('✅ 章节名称已更新'); - } else { - console.log('⚠️ 当前章节不存在'); - } - } else { - console.log('⚠️ 章节索引超出范围,无法更新'); - } - } -}); - - -// 章节菜单选项 -const chapterMenuOptions = [ - { - label: '添加节', - key: 'addSection' - }, - { - label: '重命名', - key: 'rename' - }, - { - label: '删除', - key: 'delete' - } -]; - -// 考试选项 -const examOptions = [ - { - label: '从考试/练习选择', - value: '从考试/练习选择' - }, - { - label: '本地上传', - value: '本地上传' - } -]; - -// 处理章节菜单选择 -const handleChapterMenuSelect = (key: string, chapterIndex?: number) => { - const targetIndex = chapterIndex !== undefined ? chapterIndex : currentChapterIndex.value; - if (key === 'delete') { - openDeleteModal(targetIndex); - } else if (key === 'rename') { - // 处理重命名逻辑 - console.log('重命名章节', targetIndex); - // 切换到目标章节并聚焦到输入框 - if (targetIndex !== currentChapterIndex.value) { - currentChapterIndex.value = targetIndex; - } - // 确保章节是展开状态 - chapters.value[targetIndex].expanded = true; - // 聚焦到输入框 - setTimeout(() => { - if (chapterInputRef.value) { - chapterInputRef.value.focus(); - } - }, 100); - } else if (key === 'addSection') { - // 处理添加节逻辑 - addSectionToChapter(targetIndex); - } -}; - // 处理章节名称失去焦点 const handleChapterNameBlur = async () => { console.log('🔍 章节名称失去焦点事件触发'); - console.log('🔍 当前章节索引:', currentChapterIndex.value); - console.log('🔍 章节数组长度:', chapters.value.length); + console.log('🔍 当前章节:', currentChapter.value); - if (chapters.value.length > 0 && currentChapterIndex.value >= 0 && currentChapterIndex.value < chapters.value.length) { - const currentChapter = chapters.value[currentChapterIndex.value]; - console.log('🔍 当前章节:', currentChapter); - - if (currentChapter && currentChapter.name.trim()) { - console.log('📝 章节名称已更新:', currentChapter.name); - console.log('💾 开始自动保存章节...'); - // 自动保存章节名称 - await saveChapter(); - } else { - console.log('⚠️ 章节名称为空,跳过保存'); - } + if (currentChapter.value && currentChapter.value.name.trim()) { + console.log('📝 章节名称已更新:', currentChapter.value.name); + // 移除自动保存,只有点击保存按钮才保存 } else { - console.log('⚠️ 章节索引超出范围,跳过保存'); + console.log('⚠️ 章节名称为空'); } }; @@ -544,143 +336,11 @@ const handleChapterNameFocus = () => { console.log('开始编辑章节名称'); }; -// 添加节到指定章节 -const addSectionToChapter = async (chapterIndex: number) => { - try { - if (!userStore.user?.id) { - message.error('用户未登录,无法创建节'); - return; - } - - loading.value = true; - - // 构建节数据 - const now = new Date(); - const formatDateTime = (date: Date) => { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - } - - const chapter = chapters.value[chapterIndex]; - const sectionCount = chapter.sections.length + 1; - - const sectionData = { - // 不传递id字段,让服务器自动生成 - courseId: route.params.id || userStore.user.id, // 使用课程ID - name: `${chapterIndex + 1}.${sectionCount} 新节`, - type: 0, // 视频类型 - sortOrder: sectionCount * 10, // 步长10 - parentId: chapter.id.toString(), // 父章节ID(章) - level: 2, // 2=节 - createBy: userStore.user.id, - createTime: formatDateTime(now), - updateBy: userStore.user.id, - updateTime: formatDateTime(now), - targetId: '', // 资源ID,暂时为空 - targetType: '' // 资源类型,暂时为空 - }; - - console.log('📝 创建节数据:', sectionData); - - const response = await ChapterApi.createChapter(sectionData); - - if (response.data && response.data.success) { - message.success('节创建成功!'); - - // 重新加载章节列表以获取最新数据 - await loadChapters(); - - console.log('✅ 节创建成功,已重新加载章节列表'); - } else { - message.error('节创建失败,请重试'); - console.error('❌ 节创建失败:', response); - } - } catch (error) { - console.error('❌ 创建节时出错:', error); - message.error('创建节失败,请重试'); - } finally { - loading.value = false; - } -}; - -// 添加章节 -const addChapter = async () => { - try { - if (!userStore.user?.id) { - message.error('用户未登录,无法创建章节'); - return; - } - - loading.value = true; - - // 构建章节数据 - const now = new Date(); - const formatDateTime = (date: Date) => { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - } - - const chapterData = { - // 不传递id字段,让服务器自动生成 - courseId: route.params.id || userStore.user.id, // 使用课程ID - name: `第${nextChapterId.value}章 新章节`, - type: 0, // 视频类型 - sortOrder: nextChapterId.value * 10, // 步长10,方便后续插入 - parentId: '0', // 章使用"0" - level: 1, // 1=章 - createBy: userStore.user.id, - createTime: formatDateTime(now), - updateBy: userStore.user.id, - updateTime: formatDateTime(now), - targetId: '', // 资源ID,暂时为空 - targetType: '' // 资源类型,暂时为空 - }; - - console.log('📝 创建章节数据:', chapterData); - - const response = await ChapterApi.createChapter(chapterData); - - console.log('🔍 创建章节API完整响应:', response); - console.log('🔍 响应状态:', response.code); - console.log('🔍 响应数据:', response.data); - console.log('🔍 响应成功标志:', response.data?.success); - console.log('🔍 响应消息:', response.data?.message); - - if (response.data && response.data.success) { - message.success('章节创建成功!'); - - // 重新加载章节列表以获取最新数据 - await loadChapters(); - - console.log('✅ 章节创建成功,已重新加载章节列表'); - } else { - console.error('❌ 章节创建失败,响应数据:', response.data); - message.error('章节创建失败:' + (response.data?.message || '未知错误')); - } - } catch (error: any) { - console.error('❌ 创建章节失败:', error); - message.error('创建章节失败:' + (error.message || '网络错误')); - } finally { - loading.value = false; - } -}; - // 保存章节 const saveChapter = async () => { console.log('💾 保存章节函数被调用'); console.log('🔍 用户ID:', userStore.user?.id); - console.log('🔍 章节数量:', chapters.value.length); - console.log('🔍 当前章节索引:', currentChapterIndex.value); + console.log('🔍 当前章节:', currentChapter.value); try { if (!userStore.user?.id) { @@ -689,80 +349,68 @@ const saveChapter = async () => { return; } - if (chapters.value.length === 0) { + if (!currentChapter.value) { console.log('⚠️ 没有章节需要保存'); message.warning('没有章节需要保存'); return; } + // 验证章节名称 + if (!currentChapter.value.name || !currentChapter.value.name.trim()) { + message.error('章节名称不能为空'); + return; + } + console.log('🔄 开始保存章节...'); saving.value = true; - // 构建保存数据 - const now = new Date(); - const formatDateTime = (date: Date) => { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - } - - // 保存当前选中的章节 - const currentChapter = chapters.value[currentChapterIndex.value]; - if (!currentChapter) { - message.error('章节不存在'); + const courseId = route.params.courseId as string; + if (!courseId) { + console.error('❌ 课程ID不存在'); + message.error('课程ID不存在'); return; } + + // 检测模式:通过路由查询参数判断 + const isAddMode = route.query.mode === 'add'; + const editChapterId = route.query.chapterId as string; + console.log('📋 课程ID:', courseId); + console.log('🔍 模式检测:', { isAddMode, editChapterId, currentChapterId: currentChapter.value.id }); + + // 构建保存数据 - 使用新API的数据结构 const chapterData = { - id: currentChapter.id ? currentChapter.id.toString() : '', - courseId: route.params.id || userStore.user.id, // 使用课程ID - name: currentChapter.name.trim(), + course_id: courseId, + name: currentChapter.value.name.trim(), type: 0, // 视频类型 - sortOrder: (currentChapterIndex.value + 1) * 10, // 步长10 - parentId: '0', // 章使用"0" - level: 1, // 1=章 - createBy: userStore.user.id, - createTime: formatDateTime(now), - updateBy: userStore.user.id, - updateTime: formatDateTime(now), - targetId: '', // 资源ID,暂时为空 - targetType: '' // 资源类型,暂时为空 + sort_order: 10, // 固定排序 + parent_id: '0', // 章的父ID为0 + level: 1 // 1=章 }; - console.log('🔍 当前章节数据:', currentChapter); - console.log('🔍 章节ID:', currentChapter.id); - console.log('🔍 章节ID类型:', typeof currentChapter.id); - - // 验证章节名称 - if (!chapterData.name) { - message.error('章节名称不能为空'); - return; + console.log('� 保存章节数据:', chapterData); + let response; + if (isAddMode || currentChapter.value.id === 0) { + // 新增章节 + console.log('🆕 创建新章节'); + response = await TeachCourseApi.createCourseSection(chapterData); + } else { + // 编辑现有章节 + console.log('✏️ 编辑现有章节,ID:', currentChapter.value.id); + response = await TeachCourseApi.editCourseSection({ + id: currentChapter.value.id.toString(), + ...chapterData + }); } - console.log('💾 保存章节数据:', chapterData); - - // 编辑现有章节 - console.log('✏️ 编辑现有章节'); - const response = await ChapterApi.editChapter(chapterData); - - console.log('🔍 编辑章节完整响应:', response); - console.log('🔍 响应状态:', response.code); - console.log('🔍 响应数据:', response.data); - console.log('🔍 响应成功标志:', response.data?.success); - console.log('🔍 响应消息:', response.data?.message); + console.log('🔍 保存章节完整响应:', response); // 检查不同的成功响应格式 if (response.data && (response.data.success === true || response.data.code === 200 || response.data.code === 0)) { message.success('章节保存成功!'); - // 重新加载章节列表以获取最新数据 - await loadChapters(); - - console.log('✅ 章节保存成功,已重新加载章节列表'); + // 移除自动返回,让用户可以继续编辑 + console.log('✅ 章节保存成功'); } else { console.error('❌ 章节保存失败,响应数据:', response.data); message.error('章节保存失败:' + (response.data?.message || '未知错误')); @@ -775,167 +423,6 @@ const saveChapter = async () => { } }; -// 保存节 -const saveSection = async (section: any) => { - console.log('💾 保存节函数被调用'); - console.log('🔍 节数据:', section); - - try { - if (!userStore.user?.id) { - console.log('❌ 用户未登录'); - message.error('用户未登录,无法保存节'); - return; - } - - if (!section.lessonName || !section.lessonName.trim()) { - console.log('❌ 节名称不能为空'); - message.error('节名称不能为空'); - return; - } - - console.log('🔄 开始保存节...'); - - // 构建保存数据 - const now = new Date(); - const formatDateTime = (date: Date) => { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - const seconds = String(date.getSeconds()).padStart(2, '0'); - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - } - - const currentChapter = chapters.value[currentChapterIndex.value]; - if (!currentChapter) { - message.error('章节不存在'); - return; - } - - // 计算节的排序值,使用节在章节中的索引位置 - const sectionIndex = currentChapter.sections.findIndex((s: any) => s.id === section.id); - const sortOrder = (sectionIndex >= 0 ? sectionIndex + 1 : 1) * 10; - - const sectionData = { - id: section.id ? section.id.toString() : '', - courseId: route.params.id || userStore.user.id, - name: section.lessonName.trim(), - type: 0, // 视频类型 - sortOrder: sortOrder, // 使用节在章节中的位置作为排序 - parentId: currentChapter.id ? currentChapter.id.toString() : '0', // 节的父ID是章的ID - level: 2, // 2=节 - createBy: userStore.user.id, - createTime: formatDateTime(now), - updateBy: userStore.user.id, - updateTime: formatDateTime(now), - targetId: '', - targetType: '' - }; - - console.log('💾 保存节数据:', sectionData); - - // 调用编辑API保存节 - const response = await ChapterApi.editChapter(sectionData); - console.log('🔍 编辑节完整响应:', response); - console.log('🔍 响应状态:', response.code); - console.log('🔍 响应数据:', response.data); - console.log('🔍 响应成功标志:', response.data?.success); - console.log('🔍 响应消息:', response.data?.message); - - if (response.data && (response.data.success === true || response.data.code === 200 || response.data.code === 0)) { - message.success('节保存成功!'); - - // 重新加载章节列表以获取最新数据 - await loadChapters(); - - console.log('✅ 节保存成功,已重新加载章节列表'); - } else { - console.error('❌ 节保存失败,响应数据:', response.data); - message.error('节保存失败:' + (response.data?.message || '未知错误')); - } - } catch (error: any) { - console.error('❌ 保存节失败:', error); - message.error('保存节失败:' + (error.message || '网络错误')); - } -}; - -// 删除章节(本地删除,不调用API) -const deleteChapterLocal = (chapterIndex: number) => { - if (chapters.value.length > 1) { - chapters.value.splice(chapterIndex, 1); - - // 调整当前章节索引 - if (currentChapterIndex.value >= chapters.value.length) { - currentChapterIndex.value = chapters.value.length - 1; - } else if (currentChapterIndex.value > chapterIndex) { - currentChapterIndex.value--; - } - - // 确保至少有一个章节是展开的 - const hasExpandedChapter = chapters.value.some(chapter => chapter.expanded); - if (!hasExpandedChapter && chapters.value.length > 0) { - chapters.value[currentChapterIndex.value].expanded = true; - } - } -}; - -// 删除章节(调用API) -const deleteChapter = async (chapterIndex: number) => { - try { - if (!userStore.user?.id) { - message.error('用户未登录,无法删除章节'); - return; - } - - if (chapters.value.length <= 1) { - message.warning('至少需要保留一个章节'); - return; - } - - const chapter = chapters.value[chapterIndex]; - if (!chapter) { - message.error('章节不存在'); - return; - } - - deleting.value = true; - - // 调用删除API - const response = await ChapterApi.deleteChapter(chapter.id.toString()); - - if (response.data && response.data.success) { - message.success('章节删除成功!'); - - // 本地删除章节 - deleteChapterLocal(chapterIndex); - } else { - message.error('章节删除失败:' + (response.data?.message || '未知错误')); - } - } catch (error: any) { - console.error('❌ 删除章节失败:', error); - message.error('删除章节失败:' + (error.message || '网络错误')); - } finally { - deleting.value = false; - } -}; - -// 打开删除确认模态框 -const openDeleteModal = (chapterIndex: number) => { - const chapter = chapters.value[chapterIndex]; - if (!chapter) { - message.error('章节不存在'); - return; - } - - chapterToDelete.value = { - index: chapterIndex, - chapter: chapter - }; - showDeleteModal.value = true; - isMenuVisible.value = false; // 关闭操作菜单 -}; - // 处理删除确认 const handleDeleteConfirm = async () => { if (!chapterToDelete.value) { @@ -943,9 +430,10 @@ const handleDeleteConfirm = async () => { return; } - await deleteChapter(chapterToDelete.value.index); + // 由于简化后只有一个章节,删除确认只是关闭模态框 showDeleteModal.value = false; chapterToDelete.value = null; + message.warning('无法删除唯一的章节'); }; // 处理删除取消 @@ -974,37 +462,6 @@ const toggleSidebar = () => { sidebarCollapsed.value = !sidebarCollapsed.value; }; -// 切换章节展开/收起状态 -const toggleChapterExpansion = (chapterIndex?: number) => { - if (chapterIndex !== undefined) { - // 如果点击的章节已经展开,则关闭它 - if (chapters.value[chapterIndex].expanded) { - chapters.value[chapterIndex].expanded = false; - } else { - // 关闭所有章节 - chapters.value.forEach((chapter) => { - chapter.expanded = false; - }); - // 展开点击的章节 - chapters.value[chapterIndex].expanded = true; - currentChapterIndex.value = chapterIndex; - } - } else { - // 如果没有传递索引,则切换当前选中章节的状态 - const currentExpanded = chapters.value[currentChapterIndex.value].expanded; - if (currentExpanded) { - chapters.value[currentChapterIndex.value].expanded = false; - } else { - // 关闭所有章节 - chapters.value.forEach((chapter) => { - chapter.expanded = false; - }); - // 展开当前章节 - chapters.value[currentChapterIndex.value].expanded = true; - } - } -}; - // 加载章节列表 const loadChapters = async () => { try { @@ -1015,32 +472,56 @@ const loadChapters = async () => { console.log('🔄 开始加载章节列表...'); - const courseId = route.params.id || userStore.user.id; - const params = { - courseId: courseId.toString(), - keyword: '', - type: undefined, - parentId: '', - level: undefined, - page: 1, - pageSize: 100 - }; + const courseId = route.params.courseId as string; + if (!courseId) { + console.error('❌ 课程ID不存在'); + message.error('课程ID不存在'); + return; + } - console.log('📋 查询参数:', params); + console.log('📋 课程ID:', courseId); - const response = await ChapterApi.getChapters(params); + // 检查是否为新增模式 + const isAddMode = route.query.mode === 'add'; + if (isAddMode) { + console.log('🆕 新增模式:创建空白章节'); + // 创建空的章节用于编辑 + const defaultChapter: Chapter = { + id: 0, + name: '', + sections: [{ + id: 0, + lessonName: '', + coursewareName: '', + coursewareUploadOption: '', + coursewareFiles: [], + contentTitle: '', + contentDescription: '' + }] + }; + chapter.value = defaultChapter; + return; + } + + // 编辑模式:加载现有章节数据 + const editChapterId = route.query.chapterId as string; + if (editChapterId) { + console.log('✏️ 编辑特定章节模式,章节ID:', editChapterId); + } else { + console.log('✏️ 编辑模式:加载第一个章节数据'); + } + + // 使用新的章节接口 + const response = await TeachCourseApi.getCourseSections(courseId); console.log('🔍 完整API响应:', response); console.log('🔍 响应数据:', response.data); - console.log('🔍 响应状态:', response.code); - console.log('🔍 响应数据类型:', typeof response.data); - console.log('🔍 响应数据是否为数组:', Array.isArray(response.data)); // 处理API响应结构 let chapterData = null; - if (response.data && response.data.list) { - chapterData = response.data.list; - console.log('✅ 从服务器加载的章节数据 (list):', chapterData); + if (response.data && response.data.result) { + chapterData = response.data.result; + console.log('✅ 从服务器加载的章节数据 (result):', chapterData); } else if (Array.isArray(response.data)) { chapterData = response.data; console.log('✅ 从服务器加载的章节数据 (array):', chapterData); @@ -1049,9 +530,6 @@ const loadChapters = async () => { if (chapterData && Array.isArray(chapterData)) { console.log('✅ 章节数据数量:', chapterData.length); - // 清空现有章节 - chapters.value = []; - // 按level分组:章(level=1)和节(level=2) const chaptersData = chapterData.filter((item: any) => item.level === 1); const sectionsData = chapterData.filter((item: any) => item.level === 2); @@ -1059,64 +537,85 @@ const loadChapters = async () => { console.log('📚 章数据:', chaptersData); console.log('📖 节数据:', sectionsData); - // 构建章节结构 - chaptersData.forEach((chapterData: any) => { - const chapter: Chapter = { - id: chapterData.id, - name: chapterData.name, - expanded: false, + // 构建章节结构 - 根据是否指定章节ID来处理 + if (chaptersData.length > 0) { + let targetChapterData = null; + + if (editChapterId) { + // 查找指定ID的章节 + targetChapterData = chaptersData.find((ch: any) => ch.id === editChapterId); + if (!targetChapterData) { + console.error('❌ 未找到指定ID的章节:', editChapterId); + message.error('未找到指定的章节'); + return; + } + console.log('🎯 找到目标章节:', targetChapterData); + } else { + // 默认加载第一个章节 + targetChapterData = chaptersData[0]; + console.log('📚 加载第一个章节:', targetChapterData); + } + + const newChapter: Chapter = { + id: parseInt(targetChapterData.id || '0'), + name: targetChapterData.name || '未命名章节', sections: [] }; // 找到属于这个章的节 - const chapterSections = sectionsData.filter((section: any) => section.parentId === chapterData.id); + const chapterSections = sectionsData.filter((section: any) => section.parent_id === targetChapterData.id); chapterSections.forEach((sectionData: any) => { - chapter.sections.push({ - id: sectionData.id, - lessonName: sectionData.name, + newChapter.sections.push({ + id: parseInt(sectionData.id || '0'), + lessonName: sectionData.name || '未命名小节', coursewareName: '课件准备PPT', coursewareUploadOption: '', coursewareFiles: [], - selectedExamOption: '', - homeworkName: '请添加作业', - uploadedFiles: [], - homeworkFiles: [], - contentTitle: sectionData.name, + contentTitle: sectionData.name || '未命名小节', contentDescription: '节描述' }); }); - chapters.value.push(chapter); - }); - - console.log('📚 处理后的章节数据:', chapters.value); - - console.log('✅ 构建完成的章节结构:', chapters.value); - console.log('🔍 重新加载后的章节名称:', chapters.value.map(ch => ({ id: ch.id, name: ch.name }))); - - // 如果有章节,默认选中第一个 - if (chapters.value.length > 0) { - currentChapterIndex.value = 0; - chapters.value[0].expanded = true; - console.log('✅ 默认选中第一个章节:', chapters.value[0]); - console.log('🔍 第一个章节的名称:', chapters.value[0].name); + // 更新单个章节 + chapter.value = newChapter; + message.success(`成功加载章节:${chapter.value.name}`); } else { - // 如果没有章节,重置索引 - currentChapterIndex.value = 0; - console.log('⚠️ 没有找到章节数据'); + // 如果没有找到章节数据,可能是新课程,创建默认空章节 + console.log('📝 没有找到章节数据,创建默认章节'); + const defaultChapter: Chapter = { + id: 0, + name: '', + sections: [] + }; + chapter.value = defaultChapter; + message.info('当前课程还没有章节,请添加第一个章节'); } - - message.success(`成功加载 ${chapters.value.length} 个章节`); } else { console.warn('⚠️ 没有获取到章节数据'); console.warn('⚠️ 响应结构:', response); console.warn('⚠️ 响应数据:', response.data); - message.warning('没有找到章节数据'); + + // 创建默认空章节 + const defaultChapter: Chapter = { + id: 0, + name: '', + sections: [] + }; + chapter.value = defaultChapter; + message.info('当前课程还没有章节,请添加第一个章节'); } } catch (error) { console.error('❌ 加载章节失败:', error); console.error('❌ 错误详情:', error); - message.error('加载章节失败,请重试'); + + // 出错时也创建默认空章节,允许用户继续操作 + const defaultChapter: Chapter = { + id: 0, + name: '', + sections: [] + }; + chapter.value = defaultChapter; + message.error('加载章节失败,已创建新章节'); } }; @@ -1138,7 +637,9 @@ onUnmounted(() => { const handleLessonBlur = async (section: any) => { console.log('课节名称已更新:', section.lessonName); if (section.lessonName && section.lessonName.trim()) { - await saveSection(section); + // 同步更新 contentTitle,使侧边栏显示的名称与编辑的名称一致 + section.contentTitle = section.lessonName; + // 移除自动保存,只有点击保存按钮才保存 } }; @@ -1148,42 +649,32 @@ const handleLessonFocus = () => { // 添加小节 const addSection = () => { - const currentChapter = chapters.value[currentChapterIndex.value]; - const newSectionId = Math.max(...currentChapter.sections.map((s: any) => s.id), 0) + 1; + // 使用小节数量来生成简单的名称 + const sectionCount = currentChapter.value.sections.length + 1; + const lessonName = `新小节${sectionCount}`; const newSection: any = { - id: newSectionId, - lessonName: '新小节', + id: 0, // 新增小节时ID设为0,这样保存时会调用创建API + lessonName: lessonName, coursewareName: '课件准备PPT', coursewareUploadOption: '', coursewareFiles: [], - selectedExamOption: '', - homeworkName: '请添加作业', - uploadedFiles: [], - homeworkFiles: [], - contentTitle: `${newSectionId}.新小节`, + contentTitle: lessonName, // 与 lessonName 保持一致 contentDescription: '新小节描述' }; - currentChapter.sections.push(newSection); + currentChapter.value.sections.push(newSection); }; -// 处理考试选项变化 -const handleExamOptionChange = (section: any, value: any) => { - console.log('选项变化:', value, 'section id:', section.id); - // 如果选择的是"本地上传",直接触发文件选择 - if (value === '本地上传') { - console.log('触发文件选择'); - const fileInput = document.getElementById(`file-upload-${section.id}`) as HTMLInputElement; - if (fileInput) { - fileInput.click(); - } else { - console.error('找不到文件输入框:', `file-upload-${section.id}`); - } - } else if (value === '从考试/练习选择') { - // 如果选择的是"从考试/练习选择",显示试卷库模态框 - console.log('准备显示试卷库模态框'); - showExamLibraryModal.value = true; - console.log('showExamLibraryModal.value:', showExamLibraryModal.value); - } +// 新增的按钮点击方法 +// 打开考试库 +const openExamLibrary = (section: any) => { + console.log('打开考试库,section:', section); + showExamLibraryModal.value = true; +}; + +// 打开作业库 +const openHomeworkLibrary = (section: any) => { + console.log('打开作业库,section:', section); + showHomeworkLibraryModal.value = true; }; // 处理课件上传选项变化 @@ -1206,38 +697,6 @@ const handleCoursewareUploadOptionChange = (section: any, value: any) => { } }; -// 处理作业选项变化 -const handleHomeworkOptionChange = (section: any, value: any) => { - console.log('作业选项变化:', value, 'section id:', section.id); - // 如果选择的是"本地上传",直接触发文件选择 - if (value === '本地上传') { - console.log('触发作业文件选择'); - const fileInput = document.getElementById(`homework-file-upload-${section.id}`) as HTMLInputElement; - if (fileInput) { - fileInput.click(); - } else { - console.error('找不到作业文件输入框:', `homework-file-upload-${section.id}`); - } - } else if (value === '从作业库选择') { - // 如果选择的是"从作业库选择",显示作业库模态框 - console.log('准备显示作业库模态框'); - showHomeworkLibraryModal.value = true; - console.log('showHomeworkLibraryModal.value:', showHomeworkLibraryModal.value); - showHomeworkLibraryModal.value = true; - console.log('showHomeworkLibraryModal.value:', showHomeworkLibraryModal.value); - } -}; - -// 处理文件上传 -const handleFileUpload = (event: Event, section: any) => { - const target = event.target as HTMLInputElement; - if (target.files) { - const files = Array.from(target.files); - section.uploadedFiles = files; - console.log('文件已上传:', files); - } -}; - // 处理课件文件上传 const handleCoursewareFileUpload = (event: Event, section: any) => { const target = event.target as HTMLInputElement; @@ -1248,21 +707,6 @@ const handleCoursewareFileUpload = (event: Event, section: any) => { } }; -// 处理作业文件上传 -const handleHomeworkFileUpload = (event: any, section: any) => { - const target = event.target as HTMLInputElement; - if (target.files) { - const files = Array.from(target.files); - section.homeworkFiles = files; - console.log('作业文件已上传:', files); - } -}; - -// 删除文件 -const removeFile = (section: any, fileIndex: number) => { - section.uploadedFiles.splice(fileIndex, 1); -}; - // 删除课件文件 const removeCoursewareFile = (section: any, fileIndex: number) => { section.coursewareFiles.splice(fileIndex, 1); @@ -1293,12 +737,10 @@ const handleResourceLibraryConfirm = (selectedResources: any[]) => { }; // 删除课节 -const removeLessonSection = (sectionId: number, chapterIndex?: number) => { - const targetChapterIndex = chapterIndex !== undefined ? chapterIndex : currentChapterIndex.value; - const chapter = chapters.value[targetChapterIndex]; - const index = chapter.sections.findIndex((section: any) => section.id === sectionId); - if (index > -1 && chapter.sections.length > 1) { - chapter.sections.splice(index, 1); +const removeLessonSection = (sectionId: number) => { + const index = currentChapter.value.sections.findIndex((section: any) => section.id === sectionId); + if (index > -1 && currentChapter.value.sections.length > 1) { + currentChapter.value.sections.splice(index, 1); } }; @@ -1762,77 +1204,36 @@ const goBack = () => { line-height: 18px; } +.single-chapter-container { + margin-top: 20px; +} + .chapter-item { width: 240px; height: 54px; background: #F5F8FB 100% no-repeat; background-size: 100% 100%; margin: 23px 0 0 16px; - cursor: pointer; - transition: all 0.3s ease; - justify-content: space-around; + justify-content: center; align-items: center; + border-radius: 6px; } -.chapter-item.collapsed { - background: transparent; -} - -.chapter-item:hover { - background: rgba(2, 136, 209, 0.05); -} - -.chapter-arrow-icon { - margin-left: 10px; - width: 9px; - height: 11px; - transform: rotate(180deg); - transition: transform 0.3s ease; - /* 旋转图标180度 */ -} - -.chapter-arrow-icon.rotated { - width: 11px; - height: 9px; - transform: rotate(0deg); -} - -.chapter-title { - width: 131px; +.chapter-item .chapter-title { + width: 180px; height: 21px; overflow-wrap: break-word; color: rgba(2, 136, 209, 1); font-size: 18px; font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; - font-weight: normal; + font-weight: 600; text-align: left; white-space: nowrap; line-height: 21px; - transition: color 0.3s ease; - margin-right: 40px; overflow: hidden; text-overflow: ellipsis; } -.chapter-item.collapsed .chapter-title { - color: rgba(51, 51, 51, 1); -} - -.chapter-options-icon { - width: 6px; - height: 20px; - margin-right: 5px; - transition: opacity 0.3s ease; -} - -.chapter-options-icon.transparent { - opacity: 0 !important; -} - -.chapter-item:hover .chapter-options-icon.transparent { - opacity: 0.8 !important; -} - .chapter-content-item { /* width: 234px; */ height: auto; @@ -1899,54 +1300,7 @@ const goBack = () => { word-break: break-word; } -.action-menu { - width: 102px; - height: 82px; - background-color: #FFFFFF; - border: 1px solid #E6E6E6; - border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - margin-left: -13px; - padding: 10px; - box-sizing: border-box; - display: none; - /* 默认隐藏菜单 */ -} - -/* 当菜单可见时显示 */ -.action-menu.visible { - display: flex; -} - -.action-rename { - width: 42px; - height: 17px; - overflow-wrap: break-word; - color: rgba(51, 51, 51, 1); - font-size: 14px; - font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; - font-weight: normal; - text-align: left; - white-space: nowrap; - line-height: 17px; - margin: 3px 0 0 20px; -} - -.action-delete { - width: 28px; - height: 17px; - overflow-wrap: break-word; - color: rgba(51, 51, 51, 1); - font-size: 14px; - font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; - font-weight: normal; - text-align: left; - white-space: nowrap; - line-height: 17px; - margin: 14px 0 0 20px; -} - - +/* 删除未使用的样式 */ .chapter-detail-container { position: relative; @@ -1977,27 +1331,6 @@ const goBack = () => { line-height: 21px; } -.collapse-button { - width: 54px; - height: 22px; - position: relative; - right: 20px; -} - -.collapse-text { - width: 32px; - height: 18px; - overflow-wrap: break-word; - color: rgba(102, 102, 102, 1); - font-size: 16px; - font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; - font-weight: normal; - text-align: center; - white-space: nowrap; - line-height: 18px; -} - - .chapter-name-section { width: 100%; max-width: 540px; @@ -2055,15 +1388,10 @@ const goBack = () => { height: 42px; } -/* 删除旧的章节名称输入框样式,使用 Naive UI 样式 */ - .chapter-name-input:focus { background: rgba(240, 248, 255, 0.5); } -/* 编辑图标容器 */ -/* 删除未使用的编辑图标相关样式 */ - /* 课件输入框容器 */ .courseware-input-container { width: 400px; @@ -2129,93 +1457,327 @@ const goBack = () => { margin-top: 12px; } -/* 删除旧的课件输入框样式,使用 Naive UI 样式 */ - -.courseware-dropdown-icon { - position: absolute; - right: 2px; - top: 50%; - transform: translateY(-50%); - width: 16px; - height: 16px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; -} - -/* 删除未使用的 svg 样式 */ - -/* 考试/练习下拉框容器 */ -.exam-dropdown-container { +/* 考试/练习按钮容器 */ +.exam-button-container { width: 400px; height: 42px; margin: 1px 0 0 5px; - position: relative; } -/* 删除多余的下拉框样式,使用 Naive UI 原生样式 */ +.exam-section { + width: 100%; + max-width: 540px; + height: 42px; + margin: 0 0 0 20px; + display: flex; + justify-content: flex-end; + align-items: center; +} -.exam-dropdown-text { - color: rgba(102, 102, 102, 1); +.exam-label { + width: 120px; + height: 18px; + overflow-wrap: break-word; + color: rgba(51, 51, 51, 1); + font-size: 16px; + font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; + font-weight: normal; + text-align: right; + white-space: nowrap; + line-height: 18px; + flex-shrink: 0; +} + +.courseware-section { + width: 100%; + max-width: 540px; + height: 43px; + margin: 0 0 0 20px; + display: flex; + justify-content: flex-end; + align-items: center; +} + +.courseware-label { + width: 120px; + height: 18px; + overflow-wrap: break-word; + color: rgba(51, 51, 51, 1); + font-size: 16px; + font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; + font-weight: normal; + text-align: right; + white-space: nowrap; + line-height: 18px; + flex-shrink: 0; +} + +.homework-section { + width: 100%; + max-width: 540px; + height: 42px; + margin: 0 0 0 20px; + margin-bottom: -30px; + display: flex; + justify-content: flex-end; + align-items: center; +} + +.homework-label { + width: 120px; + height: 18px; + overflow-wrap: break-word; + color: rgba(51, 51, 51, 1); + font-size: 16px; + font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; + font-weight: normal; + text-align: right; + white-space: nowrap; + line-height: 18px; + flex-shrink: 0; +} + +.lesson-section { + position: relative; + width: 100%; + max-width: 540px; + height: 42px; + margin: 0 0 0 20px; + display: flex; + justify-content: flex-end; + align-items: center; +} + +.lesson-label-wrapper { + width: 120px; + height: 18px; + overflow-wrap: break-word; + font-size: 0; + font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; + font-weight: normal; + text-align: right; + white-space: nowrap; + line-height: 18px; + flex-shrink: 0; +} + +.lesson-required-mark { + width: 120px; + height: 18px; + overflow-wrap: break-word; + color: rgba(255, 77, 79, 1); + font-size: 16px; + font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; + font-weight: normal; + text-align: right; + white-space: nowrap; + line-height: 18px; + flex-shrink: 0; +} + +.lesson-label-text { + width: 120px; + height: 18px; + overflow-wrap: break-word; + color: rgba(51, 51, 51, 1); + font-size: 16px; + font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; + font-weight: normal; + text-align: right; + white-space: nowrap; + line-height: 18px; + flex-shrink: 0; +} + +/* 课节输入框容器 */ +.lesson-input-container { + margin-left: 4px; + width: 400px; + height: 42px; +} + +/* 作业按钮容器 */ +.homework-button-container { + width: 400px; + height: 42px; + margin: 1px 0 0 5px; +} + +/* 作业输入框容器 */ +.homework-input-container { + width: 400px; + height: 42px; + margin: 1px 0 0 5px; +} + +.chapter-container { + margin-top: 20px; + border: 1px solid #EDEDED; + padding-bottom: 20px; +} + +.collapse-icon { + width: 13px; + height: 8px; + transition: transform 0.3s ease; + flex-shrink: 0; + object-fit: contain; + transform: rotate(180deg); +} + +.collapse-icon.rotated { + transform: rotate(0deg); +} + +.chapter-content-container { + position: relative; + width: 100%; + margin-bottom: 40px; + padding: 0; + display: flex; + flex-direction: column; + gap: 30px; + transition: all 0.3s ease; + animation: fadeInUp 0.3s ease-out; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.chapter-divider { + margin: 31px auto 0; + width: 100%; + max-width: 540px; + height: 1px; + background: #CCCCCC; + display: block; + opacity: 1; +} + +/* 文件上传相关样式 */ +.file-input { + display: none; +} + +.uploaded-files-section { + width: 100%; + max-width: 540px; + height: auto; + margin: 10px 0 0 20px; + display: flex; + justify-content: flex-end; + align-items: flex-start; +} + +.uploaded-files-label { + width: 120px; + height: 18px; + overflow-wrap: break-word; + color: rgba(51, 51, 51, 1); + font-size: 16px; + font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; + font-weight: normal; + text-align: right; + white-space: nowrap; + line-height: 18px; + flex-shrink: 0; + margin-top: 12px; +} + +.uploaded-files-container { + width: 400px; + margin: 1px 0 0 5px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.file-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + background: #f5f5f5; + border-radius: 4px; + border: 1px solid #e0e0e0; +} + +.file-name { + color: #333; + font-size: 14px; + font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; + flex: 1; + margin-right: 10px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.remove-file-btn { + background: none; + border: none; + cursor: pointer; + padding: 2px; + border-radius: 2px; + transition: background-color 0.3s ease; +} + +.remove-file-btn:hover { + background: rgba(255, 77, 79, 0.1); +} + +.add-section-btn { + width: 94px; + height: 23px; + margin: 49px 0 342px 65px; +} + +.add-section-icon { + width: 22px; + height: 22px; + margin-top: 1px; +} + +.add-section-text { + width: 64px; + height: 18px; + overflow-wrap: break-word; + color: rgba(2, 136, 209, 1); font-size: 16px; font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; font-weight: normal; text-align: left; + white-space: nowrap; line-height: 18px; - flex: 1; } -.exam-dropdown-icon { - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.3s; +/* 保存按钮样式 */ +.save-chapter-btn { + margin: 20px; } -.exam-dropdown-icon.rotated { - transform: rotate(180deg); -} - -/* 删除未使用的 svg 样式 */ - -.exam-dropdown-menu { - position: absolute; - top: 100%; - left: 0; - right: 0; - background: #ffffff; - border: 1px solid #e0e0e0; - border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - z-index: 1000; - margin-top: 2px; -} - -.exam-dropdown-option { - padding: 12px; - color: rgba(102, 102, 102, 1); - font-size: 16px; +.save-text { + width: 80px; + height: 18px; + overflow-wrap: break-word; + color: rgba(255, 255, 255, 1); + font-size: 14px; font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif; font-weight: normal; - cursor: pointer; - transition: all 0.3s; - border-bottom: 1px solid #f0f0f0; + text-align: center; + white-space: nowrap; + line-height: 18px; } -.exam-dropdown-option:last-child { - border-bottom: none; -} - -.exam-dropdown-option:hover { - background: rgba(24, 144, 255, 0.1); - color: #1890ff; -} - -/* 删除未使用的 text_26 和 thumbnail_16 样式 */ - .collapse-icon { width: 13px; height: 8px; @@ -2557,20 +2119,6 @@ const goBack = () => { white-space: nowrap; line-height: 18px; } -/* 保存按钮样式 */ -.save-chapter-btn { - width: 120px; - height: 32px; - margin: 20px 0 0 20px; - background-color: #0288D1 !important; - border: 1px solid #0288D1 !important; - border-radius: 2px !important; - display: flex; - justify-content: center; - gap: 8px; - align-items: center; - margin-bottom: 30px; -} .save-icon { width: 18px; diff --git a/src/views/teacher/course/ChapterManagement.vue b/src/views/teacher/course/ChapterManagement.vue index 14ab4a8..7dd1172 100644 --- a/src/views/teacher/course/ChapterManagement.vue +++ b/src/views/teacher/course/ChapterManagement.vue @@ -236,8 +236,8 @@ const toggleChapter = (chapter: Chapter) => { // 章节操作方法 const addChapter = () => { - // 跳转到当前课程下的章节编辑器 - router.push(`/teacher/chapter-editor-teacher/${courseId.value}`) + // 跳转到当前课程下的章节编辑器,传递mode=add参数表示新增模式 + router.push(`/teacher/chapter-editor-teacher/${courseId.value}?mode=add`) } const importChapters = () => { @@ -261,10 +261,10 @@ const searchChapters = async () => { const editChapter = (chapter: Chapter) => { console.log('编辑章节:', chapter) - // 跳转到章节编辑器页面 + // 跳转到章节编辑器页面,传递章节ID参数表示编辑模式 const courseId = route.params.id if (courseId) { - router.push(`/teacher/chapter-editor-teacher/${courseId}`) + router.push(`/teacher/chapter-editor-teacher/${courseId}?chapterId=${chapter.id}`) } else { message.error('课程ID不存在') }