feat+fix:mock数据后端返回不匹配类型报错,讲师章节树状对接
This commit is contained in:
parent
0e1a73192f
commit
f4a5f6f782
@ -15,7 +15,7 @@ import type {
|
|||||||
CourseSection,
|
CourseSection,
|
||||||
CourseSectionListResponse,
|
CourseSectionListResponse,
|
||||||
BackendCourseSection,
|
BackendCourseSection,
|
||||||
BackendCourseSectionListResponse,
|
BackendInstructor,
|
||||||
Quiz,
|
Quiz,
|
||||||
LearningProgress,
|
LearningProgress,
|
||||||
SearchRequest,
|
SearchRequest,
|
||||||
@ -551,94 +551,65 @@ export class CourseApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取课程章节列表
|
// 获取课程章节列表
|
||||||
static async getCourseSections(lessonId: string): Promise<ApiResponse<CourseSectionListResponse>> {
|
static async getCourseSections(courseId: string): Promise<ApiResponse<CourseSectionListResponse>> {
|
||||||
try {
|
try {
|
||||||
console.log('尝试从API获取课程章节数据,课程ID:', lessonId)
|
console.log('🔍 获取课程章节数据,课程ID:', courseId)
|
||||||
console.log('API请求URL: /lesson/section/list')
|
console.log('🔍 API请求URL: /biz/course/' + courseId + '/section')
|
||||||
console.log('API请求参数:', { lesson_id: lessonId })
|
|
||||||
|
|
||||||
const backendResponse = await ApiRequest.get<BackendCourseSectionListResponse>('/lesson/section/list', { lesson_id: lessonId })
|
const response = await ApiRequest.get<any>(`/biz/course/${courseId}/section`)
|
||||||
console.log('章节API响应:', backendResponse)
|
console.log('🔍 章节API响应:', response)
|
||||||
|
|
||||||
// 检查是否是axios响应格式还是我们的ApiResponse格式
|
// 处理后端响应格式
|
||||||
let actualData: any
|
if (response.data && response.data.success && response.data.result) {
|
||||||
let actualCode: number
|
console.log('✅ 响应状态码:', response.data.code)
|
||||||
let actualMessage: string
|
console.log('✅ 响应消息:', response.data.message)
|
||||||
let actualTimestamp: string | undefined
|
console.log('✅ 原始章节数据:', response.data.result)
|
||||||
|
console.log('✅ 章节数据数量:', response.data.result.length || 0)
|
||||||
|
|
||||||
// 使用类型断言来处理不同的响应格式
|
// 适配数据格式
|
||||||
const responseAny = backendResponse as any
|
const adaptedSections: CourseSection[] = response.data.result.map((section: BackendCourseSection) => ({
|
||||||
|
id: section.id,
|
||||||
|
lessonId: section.courseId, // 使用courseId作为lessonId
|
||||||
|
outline: '', // 暂时为空,根据type可以设置不同的内容
|
||||||
|
name: section.name,
|
||||||
|
type: section.type,
|
||||||
|
parentId: section.parentId,
|
||||||
|
sort: section.sortOrder,
|
||||||
|
level: section.level,
|
||||||
|
revision: 1, // 默认版本号
|
||||||
|
createdAt: section.createTime ? new Date(section.createTime).getTime() : null,
|
||||||
|
updatedAt: section.updateTime ? new Date(section.updateTime).getTime() : null,
|
||||||
|
deletedAt: null,
|
||||||
|
completed: false,
|
||||||
|
duration: undefined
|
||||||
|
}))
|
||||||
|
|
||||||
if (responseAny.data && typeof responseAny.data === 'object' && 'data' in responseAny.data) {
|
console.log('✅ 适配后的章节数据:', adaptedSections)
|
||||||
// 这是我们期望的ApiResponse格式: { code, message, data: { list }, timestamp }
|
|
||||||
actualData = responseAny.data.data
|
|
||||||
actualCode = responseAny.data.code
|
|
||||||
actualMessage = responseAny.data.message
|
|
||||||
actualTimestamp = responseAny.data.timestamp?.toString()
|
|
||||||
console.log('检测到ApiResponse格式')
|
|
||||||
} else {
|
|
||||||
// 这可能是直接的axios响应格式: { list }
|
|
||||||
actualData = responseAny.data
|
|
||||||
actualCode = responseAny.status || 200
|
|
||||||
actualMessage = responseAny.statusText || 'OK'
|
|
||||||
actualTimestamp = undefined
|
|
||||||
console.log('检测到直接响应格式')
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('响应状态码:', actualCode)
|
|
||||||
console.log('响应消息:', actualMessage)
|
|
||||||
console.log('原始章节数据:', actualData?.list)
|
|
||||||
console.log('章节数据数量:', actualData?.list?.length || 0)
|
|
||||||
|
|
||||||
// 检查数据是否存在
|
|
||||||
if (!actualData || !Array.isArray(actualData.list)) {
|
|
||||||
console.warn('API返回的数据结构不正确:', actualData)
|
|
||||||
return {
|
return {
|
||||||
code: actualCode,
|
code: response.data.code,
|
||||||
message: actualMessage,
|
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: {
|
data: {
|
||||||
list: [],
|
list: [],
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
traceId: actualTimestamp || ''
|
traceId: ''
|
||||||
},
|
}
|
||||||
timestamp: actualTimestamp
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 适配数据格式
|
|
||||||
const adaptedSections: CourseSection[] = actualData.list.map((section: BackendCourseSection) => ({
|
|
||||||
id: section.id,
|
|
||||||
lessonId: section.lessonId,
|
|
||||||
outline: section.videoUrl, // 将videoUrl映射到outline
|
|
||||||
name: section.name,
|
|
||||||
parentId: section.parentId,
|
|
||||||
sort: section.sortOrder, // 将sortOrder映射到sort
|
|
||||||
level: section.level === 0 ? 1 : 0, // 转换level逻辑:API中0=子级,1=父级;前端中0=父级,1=子级
|
|
||||||
revision: section.revision,
|
|
||||||
createdAt: section.createdTime ? new Date(section.createdTime).getTime() : null,
|
|
||||||
updatedAt: section.updatedTime ? new Date(section.updatedTime).getTime() : null,
|
|
||||||
deletedAt: null,
|
|
||||||
completed: false,
|
|
||||||
duration: undefined
|
|
||||||
}))
|
|
||||||
|
|
||||||
console.log('适配后的章节数据:', adaptedSections)
|
|
||||||
|
|
||||||
const adaptedResponse: ApiResponse<CourseSectionListResponse> = {
|
|
||||||
code: actualCode,
|
|
||||||
message: actualMessage,
|
|
||||||
data: {
|
|
||||||
list: adaptedSections,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
traceId: actualTimestamp || ''
|
|
||||||
},
|
|
||||||
timestamp: actualTimestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
return adaptedResponse
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('章节API调用失败:', error)
|
console.error('❌ 章节API调用失败:', error)
|
||||||
console.error('错误详情:', {
|
console.error('❌ 错误详情:', {
|
||||||
message: (error as Error).message,
|
message: (error as Error).message,
|
||||||
stack: (error as Error).stack,
|
stack: (error as Error).stack,
|
||||||
response: (error as any).response?.data,
|
response: (error as any).response?.data,
|
||||||
@ -791,6 +762,66 @@ export class CourseApi {
|
|||||||
return ApiRequest.get(`/courses/${courseId}/access`)
|
return ApiRequest.get(`/courses/${courseId}/access`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取课程讲师列表
|
||||||
|
static async getCourseInstructors(courseId: string): Promise<ApiResponse<Instructor[]>> {
|
||||||
|
try {
|
||||||
|
console.log('🔍 获取课程讲师数据,课程ID:', courseId)
|
||||||
|
console.log('🔍 API请求URL: /biz/course/' + courseId + '/teachers')
|
||||||
|
|
||||||
|
const response = await ApiRequest.get<any>(`/biz/course/${courseId}/teachers`)
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 适配数据格式
|
||||||
|
const adaptedInstructors: Instructor[] = response.data.result.map((instructor: BackendInstructor) => ({
|
||||||
|
id: parseInt(instructor.id) || 0, // 转换为数字ID
|
||||||
|
name: instructor.name,
|
||||||
|
title: instructor.title,
|
||||||
|
bio: instructor.tag || '', // 使用tag作为bio
|
||||||
|
avatar: instructor.avatar,
|
||||||
|
rating: 4.8, // 默认评分
|
||||||
|
studentsCount: 1000, // 默认学生数
|
||||||
|
coursesCount: 10, // 默认课程数
|
||||||
|
experience: '5年教学经验', // 默认经验
|
||||||
|
education: ['计算机科学硕士'], // 默认教育背景
|
||||||
|
certifications: ['高级讲师认证'] // 默认认证
|
||||||
|
}))
|
||||||
|
|
||||||
|
console.log('✅ 适配后的讲师数据:', adaptedInstructors)
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: response.data.code,
|
||||||
|
message: response.data.message,
|
||||||
|
data: adaptedInstructors
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ API返回的数据结构不正确:', response.data)
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: '数据格式错误',
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,32 +368,32 @@ export interface LessonResource {
|
|||||||
|
|
||||||
// 后端API返回的章节数据结构
|
// 后端API返回的章节数据结构
|
||||||
export interface BackendCourseSection {
|
export interface BackendCourseSection {
|
||||||
id: number
|
id: string
|
||||||
lessonId: number
|
courseId: string
|
||||||
videoUrl: string // 视频链接
|
|
||||||
name: string // 章节名称
|
name: string // 章节名称
|
||||||
|
type: number // 章节类型:0=视频、1=资料、2=考试、3=作业
|
||||||
sortOrder: number // 排序
|
sortOrder: number // 排序
|
||||||
parentId: number // 父章节ID
|
parentId: string // 父章节ID
|
||||||
level: number // 层级:0=子级(课时),1=父级(章节)
|
level: number // 章节层级:0=一级章节、1=二级章节
|
||||||
revision: number // 版本号
|
createBy: string
|
||||||
createdBy: number
|
createTime: string
|
||||||
createdTime: string | null
|
updateBy: string
|
||||||
updatedBy: number
|
updateTime: string
|
||||||
updatedTime: string | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 前端使用的课程章节类型(适配后的数据结构)
|
// 前端使用的课程章节类型(适配后的数据结构)
|
||||||
export interface CourseSection {
|
export interface CourseSection {
|
||||||
id: string // 改为string类型,保持一致性
|
id: string // 改为string类型,保持一致性
|
||||||
lessonId: string // 改为string类型,与Course.id保持一致
|
lessonId: string // 改为string类型,与Course.id保持一致
|
||||||
outline: string // 章节大纲/内容链接(从videoUrl适配)
|
outline: string // 章节大纲/内容链接
|
||||||
name: string // 章节名称
|
name: string // 章节名称
|
||||||
parentId: number // 父章节ID
|
type: number // 章节类型:0=视频、1=资料、2=考试、3=作业
|
||||||
|
parentId: string // 父章节ID,改为string类型
|
||||||
sort: number // 排序(从sortOrder适配)
|
sort: number // 排序(从sortOrder适配)
|
||||||
level: number // 层级:0=父级(章节),1=子级(课时)- 已从后端数据转换
|
level: number // 层级:0=一级章节、1=二级章节
|
||||||
revision: number // 版本号
|
revision: number // 版本号
|
||||||
createdAt: number | null // 从createdTime适配
|
createdAt: number | null // 从createTime适配
|
||||||
updatedAt: number | null // 从updatedTime适配
|
updatedAt: number | null // 从updateTime适配
|
||||||
deletedAt: string | null
|
deletedAt: string | null
|
||||||
completed?: boolean // 是否已完成(前端状态)
|
completed?: boolean // 是否已完成(前端状态)
|
||||||
duration?: string // 课时时长(前端计算)
|
duration?: string // 课时时长(前端计算)
|
||||||
@ -401,8 +401,29 @@ export interface CourseSection {
|
|||||||
|
|
||||||
// 后端章节列表响应格式
|
// 后端章节列表响应格式
|
||||||
export interface BackendCourseSectionListResponse {
|
export interface BackendCourseSectionListResponse {
|
||||||
list: BackendCourseSection[]
|
success: boolean
|
||||||
total: number
|
message: string
|
||||||
|
code: number
|
||||||
|
result: BackendCourseSection[]
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后端讲师数据结构
|
||||||
|
export interface BackendInstructor {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
avatar: string
|
||||||
|
title: string
|
||||||
|
tag: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后端讲师列表响应格式
|
||||||
|
export interface BackendInstructorListResponse {
|
||||||
|
success: boolean
|
||||||
|
message: string
|
||||||
|
code: number
|
||||||
|
result: BackendInstructor[]
|
||||||
|
timestamp: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// 前端章节列表响应格式
|
// 前端章节列表响应格式
|
||||||
|
@ -140,7 +140,15 @@
|
|||||||
<!-- 讲师信息 -->
|
<!-- 讲师信息 -->
|
||||||
<div class="instructors-section">
|
<div class="instructors-section">
|
||||||
<h3 class="section-title">讲师</h3>
|
<h3 class="section-title">讲师</h3>
|
||||||
<div class="instructors-list">
|
|
||||||
|
<div v-if="instructorsLoading" class="instructors-loading">
|
||||||
|
<p>正在加载讲师信息...</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="instructorsError" class="instructors-error">
|
||||||
|
<p>{{ instructorsError }}</p>
|
||||||
|
<button @click="loadCourseInstructors" class="retry-btn">重试</button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="instructors-list">
|
||||||
<div class="instructor-item" v-for="instructor in instructors" :key="instructor.id">
|
<div class="instructor-item" v-for="instructor in instructors" :key="instructor.id">
|
||||||
<div class="instructor-avatar">
|
<div class="instructor-avatar">
|
||||||
<SafeAvatar :src="instructor.avatar" :name="instructor.name" :size="50" />
|
<SafeAvatar :src="instructor.avatar" :name="instructor.name" :size="50" />
|
||||||
@ -480,6 +488,10 @@ const courseSections = ref<CourseSection[]>([])
|
|||||||
const sectionsLoading = ref(false)
|
const sectionsLoading = ref(false)
|
||||||
const sectionsError = ref('')
|
const sectionsError = ref('')
|
||||||
|
|
||||||
|
// 讲师数据
|
||||||
|
const instructorsLoading = ref(false)
|
||||||
|
const instructorsError = ref('')
|
||||||
|
|
||||||
// 报名状态管理
|
// 报名状态管理
|
||||||
const isEnrolled = ref(false) // 用户是否已报名该课程
|
const isEnrolled = ref(false) // 用户是否已报名该课程
|
||||||
const enrollmentLoading = ref(false) // 报名加载状态
|
const enrollmentLoading = ref(false) // 报名加载状态
|
||||||
@ -525,46 +537,12 @@ interface ChapterGroup {
|
|||||||
|
|
||||||
const groupedSections = ref<ChapterGroup[]>([])
|
const groupedSections = ref<ChapterGroup[]>([])
|
||||||
|
|
||||||
// 生成模拟章节数据(用于演示)
|
// 生成模拟章节数据(暂时禁用,等待API修复)
|
||||||
const generateMockSections = (): CourseSection[] => {
|
// const generateMockSections = (): CourseSection[] => {
|
||||||
return [
|
// return []
|
||||||
// 第一章 - 课前准备 (4个)
|
// }
|
||||||
{ id: "1", lessonId: courseId.value, name: '开课彩蛋:新开始新征程', outline: 'https://example.com/video1.m3u8', parentId: 0, sort: 1, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '01:03:56' },
|
|
||||||
{ id: "2", lessonId: courseId.value, name: '课程定位与目标', outline: 'https://example.com/video2.m3u8', parentId: 0, sort: 2, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '00:44:05' },
|
|
||||||
{ id: "3", lessonId: courseId.value, name: '教学安排及学习建议', outline: 'https://example.com/video3.m3u8', parentId: 0, sort: 3, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '00:52:22' },
|
|
||||||
{ id: "4", lessonId: courseId.value, name: '课前准备PPT', outline: 'https://example.com/ppt1.ppt', parentId: 0, sort: 4, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
|
|
||||||
// 第二章 - 程序设计基础知识 (5个)
|
|
||||||
{ id: "5", lessonId: courseId.value, name: '第一课 程序设计入门', outline: 'https://example.com/video4.m3u8', parentId: 0, sort: 5, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '00:52:22' },
|
|
||||||
{ id: "6", lessonId: courseId.value, name: '操作PPT', outline: 'https://example.com/ppt2.ppt', parentId: 0, sort: 6, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "7", lessonId: courseId.value, name: '第二课 循环结构', outline: 'https://example.com/video5.m3u8', parentId: 0, sort: 7, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '01:03:56' },
|
|
||||||
{ id: "8", lessonId: courseId.value, name: '函数&循环', outline: 'https://example.com/video5.m3u8', parentId: 0, sort: 8, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "9", lessonId: courseId.value, name: '第三课 条件结构', outline: 'https://example.com/video6.m3u8', parentId: 0, sort: 9, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:45:30' },
|
|
||||||
|
|
||||||
// 第三章 - 实战项目 (6个)
|
|
||||||
{ id: "10", lessonId: courseId.value, name: '项目一:计算器开发', outline: 'https://example.com/video7.m3u8', parentId: 0, sort: 10, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:20:15' },
|
|
||||||
{ id: "11", lessonId: courseId.value, name: '项目源码下载', outline: 'https://example.com/source1.zip', parentId: 0, sort: 11, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "12", lessonId: courseId.value, name: '项目二:数据管理系统', outline: 'https://example.com/video8.m3u8', parentId: 0, sort: 12, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:45:20' },
|
|
||||||
{ id: "13", lessonId: courseId.value, name: '作业:完成个人项目', outline: '', parentId: 0, sort: 13, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "14", lessonId: courseId.value, name: '项目三:Web应用开发', outline: 'https://example.com/video9.m3u8', parentId: 0, sort: 14, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '02:10:45' },
|
|
||||||
{ id: "15", lessonId: courseId.value, name: '期末考试', outline: '', parentId: 0, sort: 15, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
|
|
||||||
// 第四章 - 高级应用 (4个)
|
|
||||||
{ id: "16", lessonId: courseId.value, name: '高级特性介绍', outline: 'https://example.com/video10.m3u8', parentId: 0, sort: 16, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:55:30' },
|
|
||||||
{ id: "17", lessonId: courseId.value, name: '性能优化技巧', outline: 'https://example.com/video11.m3u8', parentId: 0, sort: 17, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:15:20' },
|
|
||||||
{ id: "18", lessonId: courseId.value, name: '部署与发布', outline: 'https://example.com/video12.m3u8', parentId: 0, sort: 18, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:40:15' },
|
|
||||||
{ id: "19", lessonId: courseId.value, name: '课程总结', outline: 'https://example.com/video13.m3u8', parentId: 0, sort: 19, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:30:10' },
|
|
||||||
|
|
||||||
// 第五章 - 拓展学习 (3个)
|
|
||||||
{ id: "20", lessonId: courseId.value, name: '行业发展趋势', outline: 'https://example.com/video14.m3u8', parentId: 0, sort: 20, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:35:45' },
|
|
||||||
{ id: "21", lessonId: courseId.value, name: '学习资源推荐', outline: 'https://example.com/resources.pdf', parentId: 0, sort: 21, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "22", lessonId: courseId.value, name: '结业证书申请', outline: '', parentId: 0, sort: 22, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
|
|
||||||
// 第六章 - 答疑与交流 (2个)
|
|
||||||
{ id: "23", lessonId: courseId.value, name: '常见问题解答', outline: 'https://example.com/video15.m3u8', parentId: 0, sort: 23, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:25:30' },
|
|
||||||
{ id: "24", lessonId: courseId.value, name: '在线答疑直播', outline: 'https://example.com/live1.m3u8', parentId: 0, sort: 24, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:30:00' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将章节按章分组
|
// 将章节按章分组
|
||||||
const groupSectionsByChapter = (sections: CourseSection[]) => {
|
const groupSectionsByChapter = (sections: CourseSection[]) => {
|
||||||
@ -783,17 +761,17 @@ const loadCourseSections = async () => {
|
|||||||
console.log('章节API响应:', response)
|
console.log('章节API响应:', response)
|
||||||
|
|
||||||
if (response.code === 0 || response.code === 200) {
|
if (response.code === 0 || response.code === 200) {
|
||||||
if (response.data && Array.isArray(response.data)) {
|
if (response.data && response.data.list && Array.isArray(response.data.list)) {
|
||||||
courseSections.value = response.data
|
courseSections.value = response.data.list
|
||||||
groupedSections.value = groupSectionsByChapter(response.data)
|
groupedSections.value = groupSectionsByChapter(response.data.list)
|
||||||
console.log('章节数据设置成功:', courseSections.value)
|
console.log('✅ 章节数据设置成功:', courseSections.value)
|
||||||
console.log('分组数据:', groupedSections.value)
|
console.log('✅ 分组数据:', groupedSections.value)
|
||||||
} else {
|
} else {
|
||||||
console.log('API返回的章节数据为空,使用模拟数据')
|
console.log('⚠️ API返回的章节数据为空,使用模拟数据')
|
||||||
loadMockData()
|
loadMockData()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('API返回错误,使用模拟数据')
|
console.log('⚠️ API返回错误,使用模拟数据')
|
||||||
loadMockData()
|
loadMockData()
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -807,15 +785,47 @@ const loadCourseSections = async () => {
|
|||||||
|
|
||||||
// 加载模拟数据
|
// 加载模拟数据
|
||||||
const loadMockData = () => {
|
const loadMockData = () => {
|
||||||
console.log('加载模拟章节数据')
|
console.log('⚠️ API调用失败,暂不使用模拟数据')
|
||||||
const mockSections = generateMockSections()
|
// 暂时不加载模拟数据,等待API修复
|
||||||
courseSections.value = mockSections
|
courseSections.value = []
|
||||||
groupedSections.value = groupSectionsByChapter(mockSections)
|
groupedSections.value = []
|
||||||
|
}
|
||||||
|
|
||||||
// 计算学习进度
|
// 加载课程讲师列表
|
||||||
// const completed = mockSections.filter(section => section.completed).length
|
const loadCourseInstructors = async () => {
|
||||||
// completedLessons.value = completed
|
if (!courseId.value || courseId.value.trim() === '') {
|
||||||
// progress.value = Math.round((completed / mockSections.length) * 100)
|
instructorsError.value = '课程ID无效'
|
||||||
|
console.error('课程ID无效:', courseId.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
instructorsLoading.value = true
|
||||||
|
instructorsError.value = ''
|
||||||
|
|
||||||
|
console.log('调用API获取课程讲师...')
|
||||||
|
const response = await CourseApi.getCourseInstructors(courseId.value)
|
||||||
|
console.log('讲师API响应:', response)
|
||||||
|
|
||||||
|
if (response.code === 0 || response.code === 200) {
|
||||||
|
if (response.data && Array.isArray(response.data)) {
|
||||||
|
instructors.value = response.data
|
||||||
|
console.log('✅ 讲师数据设置成功:', instructors.value)
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ API返回的讲师数据为空,使用默认数据')
|
||||||
|
// 保持默认的mock数据
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ API返回错误,使用默认数据')
|
||||||
|
instructorsError.value = response.message || '获取讲师信息失败'
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('加载课程讲师失败:', err)
|
||||||
|
instructorsError.value = '获取讲师信息失败'
|
||||||
|
// 保持默认的mock数据
|
||||||
|
} finally {
|
||||||
|
instructorsLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换章节展开/折叠
|
// 切换章节展开/折叠
|
||||||
@ -1131,7 +1141,8 @@ onMounted(() => {
|
|||||||
console.log('课程详情页加载完成,课程ID:', courseId.value)
|
console.log('课程详情页加载完成,课程ID:', courseId.value)
|
||||||
initializeMockState() // 初始化模拟状态
|
initializeMockState() // 初始化模拟状态
|
||||||
loadCourseDetail()
|
loadCourseDetail()
|
||||||
// loadCourseSections() // 暂时禁用章节接口调用,因为接口不存在
|
loadCourseSections() // 启用章节接口调用
|
||||||
|
loadCourseInstructors() // 启用讲师接口调用
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -1821,6 +1832,34 @@ onMounted(() => {
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 讲师加载状态 */
|
||||||
|
.instructors-loading {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructors-error {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructors-error .retry-btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #1890ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructors-error .retry-btn:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
/* 分隔线样式 */
|
/* 分隔线样式 */
|
||||||
.course-info-divider {
|
.course-info-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
@ -534,45 +534,10 @@ const displayComments = ref([
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
// 生成模拟章节数据(用于演示)
|
// 生成模拟章节数据(暂时禁用)
|
||||||
const generateMockSections = (): CourseSection[] => {
|
const generateMockSections = (): CourseSection[] => {
|
||||||
return [
|
// 暂时返回空数组,等待API修复
|
||||||
// 第一章 - 课前准备 (4个)
|
return []
|
||||||
{ id: "1", lessonId: courseId.value, name: '开课彩蛋:新开始新征程', outline: 'https://example.com/video1.m3u8', parentId: 0, sort: 1, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '01:03:56' },
|
|
||||||
{ id: "2", lessonId: courseId.value, name: '课程定位与目标', outline: 'https://example.com/video2.m3u8', parentId: 0, sort: 2, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '00:44:05' },
|
|
||||||
{ id: "3", lessonId: courseId.value, name: '教学安排及学习建议', outline: 'https://example.com/video3.m3u8', parentId: 0, sort: 3, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '00:52:22' },
|
|
||||||
{ id: "4", lessonId: courseId.value, name: '课前准备PPT', outline: 'https://example.com/ppt1.ppt', parentId: 0, sort: 4, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
|
|
||||||
// 第二章 - 程序设计基础知识 (5个)
|
|
||||||
{ id: "5", lessonId: courseId.value, name: '第一课 程序设计入门', outline: 'https://example.com/video4.m3u8', parentId: 0, sort: 5, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '00:52:22' },
|
|
||||||
{ id: "6", lessonId: courseId.value, name: '操作PPT', outline: 'https://example.com/ppt2.ppt', parentId: 0, sort: 6, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "7", lessonId: courseId.value, name: '第二课 循环结构', outline: 'https://example.com/video5.m3u8', parentId: 0, sort: 7, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: true, duration: '01:03:56' },
|
|
||||||
{ id: "8", lessonId: courseId.value, name: '函数&循环', outline: 'https://example.com/video5.m3u8', parentId: 0, sort: 8, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "9", lessonId: courseId.value, name: '第三课 条件结构', outline: 'https://example.com/video6.m3u8', parentId: 0, sort: 9, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:45:30' },
|
|
||||||
|
|
||||||
// 第三章 - 实战项目 (6个)
|
|
||||||
{ id: "10", lessonId: courseId.value, name: '项目一:计算器开发', outline: 'https://example.com/video7.m3u8', parentId: 0, sort: 10, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:20:15' },
|
|
||||||
{ id: "11", lessonId: courseId.value, name: '项目源码下载', outline: 'https://example.com/source1.zip', parentId: 0, sort: 11, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "12", lessonId: courseId.value, name: '项目二:数据管理系统', outline: 'https://example.com/video8.m3u8', parentId: 0, sort: 12, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:45:20' },
|
|
||||||
{ id: "13", lessonId: courseId.value, name: '作业:完成个人项目', outline: '', parentId: 0, sort: 13, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "14", lessonId: courseId.value, name: '项目三:Web应用开发', outline: 'https://example.com/video9.m3u8', parentId: 0, sort: 14, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '02:10:45' },
|
|
||||||
{ id: "15", lessonId: courseId.value, name: '期末考试', outline: '', parentId: 0, sort: 15, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
|
|
||||||
// 第四章 - 高级应用 (4个)
|
|
||||||
{ id: "16", lessonId: courseId.value, name: '高级特性介绍', outline: 'https://example.com/video10.m3u8', parentId: 0, sort: 16, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:55:30' },
|
|
||||||
{ id: "17", lessonId: courseId.value, name: '性能优化技巧', outline: 'https://example.com/video11.m3u8', parentId: 0, sort: 17, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:15:20' },
|
|
||||||
{ id: "18", lessonId: courseId.value, name: '部署与发布', outline: 'https://example.com/video12.m3u8', parentId: 0, sort: 18, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:40:15' },
|
|
||||||
{ id: "19", lessonId: courseId.value, name: '课程总结', outline: 'https://example.com/video13.m3u8', parentId: 0, sort: 19, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:30:10' },
|
|
||||||
|
|
||||||
// 第五章 - 拓展学习 (3个)
|
|
||||||
{ id: "20", lessonId: courseId.value, name: '行业发展趋势', outline: 'https://example.com/video14.m3u8', parentId: 0, sort: 20, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:35:45' },
|
|
||||||
{ id: "21", lessonId: courseId.value, name: '学习资源推荐', outline: 'https://example.com/resources.pdf', parentId: 0, sort: 21, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
{ id: "22", lessonId: courseId.value, name: '结业证书申请', outline: '', parentId: 0, sort: 22, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: undefined },
|
|
||||||
|
|
||||||
// 第六章 - 答疑与交流 (2个)
|
|
||||||
{ id: "23", lessonId: courseId.value, name: '常见问题解答', outline: 'https://example.com/video15.m3u8', parentId: 0, sort: 23, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '00:25:30' },
|
|
||||||
{ id: "24", lessonId: courseId.value, name: '在线答疑直播', outline: 'https://example.com/live1.m3u8', parentId: 0, sort: 24, level: 1, revision: 1, createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, completed: false, duration: '01:30:00' }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将章节按章分组
|
// 将章节按章分组
|
||||||
|
@ -541,14 +541,15 @@ const generateChapterGroups = () => {
|
|||||||
lessonId: courseId.value,
|
lessonId: courseId.value,
|
||||||
outline: currentVideoUrl.value,
|
outline: currentVideoUrl.value,
|
||||||
name: currentVideoTitle.value || '开课彩蛋:新开始新征程',
|
name: currentVideoTitle.value || '开课彩蛋:新开始新征程',
|
||||||
parentId: 1,
|
parentId: "1",
|
||||||
sort: 0,
|
sort: 0,
|
||||||
level: 1,
|
level: 1,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
updatedAt: null,
|
updatedAt: null,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
completed: false
|
completed: false,
|
||||||
|
type: 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
expanded: true
|
expanded: true
|
||||||
@ -556,7 +557,7 @@ const generateChapterGroups = () => {
|
|||||||
} else {
|
} else {
|
||||||
parentSections.forEach((parentSection, index) => {
|
parentSections.forEach((parentSection, index) => {
|
||||||
const childSections = courseSections.value.filter(section =>
|
const childSections = courseSections.value.filter(section =>
|
||||||
section.level === 1 && section.parentId === parseInt(parentSection.id)
|
section.level === 1 && section.parentId === parentSection.id
|
||||||
)
|
)
|
||||||
|
|
||||||
groups.push({
|
groups.push({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user