diff --git a/src/api/index.ts b/src/api/index.ts index a2a5407..e5eae4d 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -5,6 +5,7 @@ export * from './request' // 导出所有API模块 export { default as AuthApi } from './modules/auth' export { default as CourseApi } from './modules/course' +export { default as ChapterApi } from './modules/chapter' export { default as CommentApi } from './modules/comment' export { default as FavoriteApi } from './modules/favorite' export { default as OrderApi } from './modules/order' diff --git a/src/api/modules/chapter.ts b/src/api/modules/chapter.ts new file mode 100644 index 0000000..7f2ea51 --- /dev/null +++ b/src/api/modules/chapter.ts @@ -0,0 +1,314 @@ +// 课程章节相关API接口 +import { ApiRequest } from '../request' +import type { + ApiResponse, + CourseSection, + CourseSectionListResponse, + BackendCourseSection, +} from '../types' + +// 章节查询参数类型 +export interface ChapterQueryParams { + courseId: string + keyword?: string + page?: number + pageSize?: number + type?: number | null // 章节类型:0=视频、1=资料、2=考试、3=作业,null=全部 + parentId?: string // 父章节ID,用于查询子章节 + level?: number // 章节层级:0=一级章节、1=二级章节 +} + +/** + * 课程章节API模块 + */ +export class ChapterApi { + + /** + * 获取课程章节列表 + * @param params 查询参数 + * @returns 章节列表响应 + */ + static async getChapters(params: ChapterQueryParams): Promise> { + try { + console.log('🚀 调用课程章节列表API,参数:', params) + + // 构建查询参数 - courseId作为Path参数,其他作为Query参数 + const queryParams: any = {} + + if (params.keyword) queryParams.keyword = params.keyword + if (params.type !== undefined) queryParams.type = params.type + if (params.parentId) queryParams.parentId = params.parentId + if (params.level !== undefined) queryParams.level = params.level + if (params.page) queryParams.page = params.page + if (params.pageSize) queryParams.pageSize = params.pageSize + + console.log('🔍 Path参数 courseId (token):', params.courseId) + console.log('🔍 Query参数:', queryParams) + + // 调用后端API - courseId作为Path参数,其他作为Query参数 + const response = await ApiRequest.get(`/aiol/aiolCourse/${params.courseId}/section`, queryParams) + console.log('🔍 章节列表API响应:', response) + + // 处理后端响应格式 + let rawData = null; + if (response.data && response.data.success && response.data.result) { + rawData = response.data.result; + console.log('✅ 响应数据来源: result字段'); + } else if (response.data && response.data.list) { + rawData = response.data.list; + console.log('✅ 响应数据来源: list字段'); + } else if (Array.isArray(response.data)) { + rawData = response.data; + console.log('✅ 响应数据来源: 直接数组'); + } + + if (rawData && Array.isArray(rawData)) { + console.log('✅ 原始章节数据:', rawData) + console.log('✅ 章节数据数量:', rawData.length) + + // 适配数据格式 - 直接使用原始数据,因为字段名已经匹配 + const adaptedSections: CourseSection[] = rawData.map((section: any) => ({ + id: section.id, + lessonId: section.lessonId, // 直接使用原始字段 + outline: section.outline || '', // 直接使用原始字段 + name: section.name, + type: section.type, // 直接使用原始字段 + parentId: section.parentId || '', // 直接使用原始字段 + sort: section.sort, // 直接使用原始字段 + level: section.level, + revision: section.revision || 1, // 直接使用原始字段 + createdAt: section.createdAt, // 直接使用原始字段 + updatedAt: section.updatedAt, // 直接使用原始字段 + deletedAt: section.deletedAt, + completed: section.completed || false, + duration: section.duration + })) + + console.log('✅ 适配后的章节数据:', adaptedSections) + + return { + code: 200, + message: 'success', + data: { + list: adaptedSections, + timestamp: Date.now(), + traceId: '' + } + } + } else { + console.warn('⚠️ API返回的数据结构不正确:', response.data) + return { + code: 500, + message: '数据格式错误', + data: { + list: [], + timestamp: Date.now(), + traceId: '' + } + } + } + } catch (error) { + console.error('❌ 章节API调用失败:', error) + console.error('❌ 错误详情:', { + message: (error as Error).message, + stack: (error as Error).stack, + response: (error as any).response?.data, + status: (error as any).response?.status, + statusText: (error as any).response?.statusText + }) + + // 重新抛出错误,不使用模拟数据 + throw error + } + } + + /** + * 搜索课程章节 + * @param params 搜索参数 + * @returns 搜索结果 + */ + static async searchChapters(params: ChapterQueryParams): Promise> { + try { + console.log('🔍 搜索课程章节,参数:', params) + + // 构建搜索参数 - courseId作为Path参数,keyword等作为Query参数 + const searchParams: any = {} + + if (params.keyword) searchParams.keyword = params.keyword + if (params.type !== undefined) searchParams.type = params.type + if (params.parentId) searchParams.parentId = params.parentId + if (params.level !== undefined) searchParams.level = params.level + if (params.page) searchParams.page = params.page + if (params.pageSize) searchParams.pageSize = params.pageSize + + console.log('🔍 Path参数 courseId (token):', params.courseId) + console.log('🔍 Query参数 (包含keyword):', searchParams) + + // 调用后端API - courseId作为Path参数,keyword等作为Query参数 + const response = await ApiRequest.get(`/aiol/aiolCourse/${params.courseId}/section`, searchParams) + console.log('🔍 章节搜索API响应:', response) + + // 处理后端响应格式 + if (response.data && response.data.success && response.data.result) { + console.log('✅ 搜索响应状态码:', response.data.code) + console.log('✅ 搜索响应消息:', response.data.message) + console.log('✅ 搜索结果数据:', response.data.result) + console.log('✅ 搜索结果数量:', response.data.result.length || 0) + + // 适配数据格式 - 使用BackendCourseSection类型 + const adaptedSections: CourseSection[] = response.data.result.map((section: BackendCourseSection) => ({ + id: section.id, + lessonId: section.courseId, // 使用BackendCourseSection字段 + outline: '', + name: section.name, + type: section.type, + parentId: section.parentId || '', // 使用BackendCourseSection字段 + sort: section.sortOrder, // 使用BackendCourseSection字段 + level: section.level, + revision: 1, + createdAt: section.createTime ? new Date(section.createTime).getTime() : null, // 使用BackendCourseSection字段 + updatedAt: section.updateTime ? new Date(section.updateTime).getTime() : null, // 使用BackendCourseSection字段 + deletedAt: null, + completed: false, + duration: undefined + })) + + console.log('✅ 适配后的搜索结果:', adaptedSections) + + return { + code: response.data.code, + message: response.data.message, + data: { + list: adaptedSections, + timestamp: Date.now(), + traceId: response.data.timestamp?.toString() || '' + } + } + } else { + console.warn('⚠️ 搜索API返回的数据结构不正确:', response.data) + return { + code: 500, + message: '搜索数据格式错误', + data: { + list: [], + timestamp: Date.now(), + traceId: '' + } + } + } + } catch (error) { + console.error('❌ 章节搜索API调用失败:', error) + throw error + } + } + + /** + * 新建课程章节 + * @param sectionData 章节数据 + * @returns 创建结果 + */ + static async createChapter(sectionData: any): Promise> { + try { + console.log('🚀 调用新建章节API,数据:', sectionData) + + // 包装数据为aiolCourseSectionDTO格式 + const requestData = { + aiolCourseSectionDTO: sectionData + } + + // 调用后端API - 新建章节 + const response = await ApiRequest.post('/aiol/aiolCourseSection/add', requestData) + console.log('🔍 新建章节API响应:', response) + + return response + } catch (error) { + console.error('❌ 新建章节失败:', error) + throw error + } + } + + /** + * 编辑课程章节 + * @param sectionData 章节数据 + * @returns 编辑结果 + */ + static async editChapter(sectionData: any): Promise> { + try { + console.log('🚀 调用编辑章节API,数据:', sectionData) + + // 尝试不同的数据格式 + const requestData = { + ...sectionData, + // 确保所有必要字段都存在 + id: sectionData.id, + name: sectionData.name, + courseId: sectionData.courseId, + type: sectionData.type || 0, + sortOrder: sectionData.sortOrder || 10, + parentId: sectionData.parentId || '0', + level: sectionData.level || 1 + } + + console.log('🔍 发送给服务器的完整请求数据:', JSON.stringify(requestData, null, 2)) + console.log('🔍 章节ID:', sectionData.id) + console.log('🔍 章节名称:', sectionData.name) + console.log('🔍 课程ID:', sectionData.courseId) + + // 调用后端API - 编辑章节 + // 使用原来的edit路径 + const response = await ApiRequest.post('/aiol/aiolCourseSection/edit', requestData) + console.log('🔍 编辑章节API响应:', response) + + return response + } catch (error) { + console.error('❌ 编辑章节失败:', error) + throw error + } + } + + /** + * 删除课程章节 + * @param sectionId 章节ID + * @returns 删除结果 + */ + static async deleteChapter(sectionId: string): Promise> { + try { + console.log('🚀 调用删除章节API,章节ID:', sectionId) + + // 调用后端API - 删除章节 + const response = await ApiRequest.delete('/aiol/aiolCourseSection/delete', { + id: sectionId + }) + console.log('🔍 删除章节API响应:', response) + + return response + } catch (error) { + console.error('❌ 删除章节失败:', error) + throw error + } + } + + /** + * 批量删除课程章节 + * @param sectionIds 章节ID数组 + * @returns 删除结果 + */ + static async deleteChaptersBatch(sectionIds: string[]): Promise> { + try { + console.log('🚀 调用批量删除章节API,章节IDs:', sectionIds) + + // 调用后端API - 批量删除章节 + const response = await ApiRequest.delete('/aiol/aiolCourseSection/deleteBatch', { + ids: sectionIds.join(',') + }) + console.log('🔍 批量删除章节API响应:', response) + + return response + } catch (error) { + console.error('❌ 批量删除章节失败:', error) + throw error + } + } +} + +export default ChapterApi diff --git a/src/api/types.ts b/src/api/types.ts index cd0d1f4..ca85f3f 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -462,6 +462,17 @@ export interface BackendCourseSectionListResponse { timestamp: number } +// 章节查询参数类型 +export interface ChapterQueryParams { + courseId: string + keyword?: string + page?: number + pageSize?: number + type?: number | null // 章节类型:0=视频、1=资料、2=考试、3=作业,null=全部 + parentId?: string // 父章节ID,用于查询子章节 + level?: number // 章节层级:0=一级章节、1=二级章节 +} + // 后端讲师数据结构 export interface BackendInstructor { id: string diff --git a/src/components/admin/CourseComponents/CourseCategory.vue b/src/components/admin/CourseComponents/CourseCategory.vue index ce102d2..43f63fb 100644 --- a/src/components/admin/CourseComponents/CourseCategory.vue +++ b/src/components/admin/CourseComponents/CourseCategory.vue @@ -415,7 +415,10 @@ const handleOfflineCourse = (course: CourseDisplayItem) => { id: course.id!, name: course.name, description: course.description, - status: 2 // 2=已结束状态 + status: 2, // 2=已结束状态 + pause_exit: '0', // 默认值 + allow_speed: '0', // 默认值 + show_subtitle: '0' // 默认值 }; await TeachCourseApi.editCourse(updatedData); diff --git a/src/components/admin/CourseComponents/CourseCreate.vue b/src/components/admin/CourseComponents/CourseCreate.vue index 275edb7..6d94c84 100644 --- a/src/components/admin/CourseComponents/CourseCreate.vue +++ b/src/components/admin/CourseComponents/CourseCreate.vue @@ -188,7 +188,6 @@ import { import '@wangeditor/editor/dist/css/style.css' // @ts-ignore import { Editor, Toolbar } from '@wangeditor/editor-for-vue' -import TeachCourseApi from '@/api/modules/teachCourse' const router = useRouter() const route = useRoute() diff --git a/src/views/teacher/course/ChapterEditor.vue b/src/views/teacher/course/ChapterEditor.vue index 638ee4f..478a72b 100644 --- a/src/views/teacher/course/ChapterEditor.vue +++ b/src/views/teacher/course/ChapterEditor.vue @@ -1,233 +1,311 @@