修改课程播放视频代码
This commit is contained in:
parent
c7453da854
commit
ab31374e1a
@ -7,10 +7,7 @@
|
|||||||
<title>在线学习平台</title>
|
<title>在线学习平台</title>
|
||||||
<meta name="description" content="专业的在线学习平台,提供优质的编程和技术课程">
|
<meta name="description" content="专业的在线学习平台,提供优质的编程和技术课程">
|
||||||
<meta name="keywords" content="在线学习,编程课程,技术培训,Vue.js,React,Node.js">
|
<meta name="keywords" content="在线学习,编程课程,技术培训,Vue.js,React,Node.js">
|
||||||
<!-- CKPlayer CSS -->
|
<!-- CKPlayer 将通过本地包引入,移除CDN引用 -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ckplayer@8.3.5/dist/ckplayer.css">
|
|
||||||
<!-- CKPlayer JS -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/ckplayer@8.3.5/dist/ckplayer.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
@ -93,9 +93,55 @@ export class CourseApi {
|
|||||||
// 调用后端API
|
// 调用后端API
|
||||||
const response = await ApiRequest.get<BackendCourseListResponse>('/lesson/list', queryParams)
|
const response = await ApiRequest.get<BackendCourseListResponse>('/lesson/list', queryParams)
|
||||||
console.log('课程列表API响应:', response)
|
console.log('课程列表API响应:', response)
|
||||||
|
console.log('响应数据结构:', response.data)
|
||||||
|
console.log('响应数据类型:', typeof response.data)
|
||||||
|
|
||||||
|
// 检查是否是axios响应格式还是我们的ApiResponse格式
|
||||||
|
let actualData: any
|
||||||
|
let actualCode: number
|
||||||
|
let actualMessage: string
|
||||||
|
|
||||||
|
// 使用类型断言来处理不同的响应格式
|
||||||
|
const responseAny = response as any
|
||||||
|
|
||||||
|
if (responseAny.data && typeof responseAny.data === 'object' && 'data' in responseAny.data) {
|
||||||
|
// 这是我们期望的ApiResponse格式: { code, message, data: { list, total } }
|
||||||
|
actualData = responseAny.data.data
|
||||||
|
actualCode = responseAny.data.code
|
||||||
|
actualMessage = responseAny.data.message
|
||||||
|
console.log('检测到ApiResponse格式')
|
||||||
|
} else {
|
||||||
|
// 这可能是直接的axios响应格式: { list, total }
|
||||||
|
actualData = responseAny.data
|
||||||
|
actualCode = responseAny.status || 200
|
||||||
|
actualMessage = responseAny.statusText || 'OK'
|
||||||
|
console.log('检测到直接响应格式')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('实际数据:', actualData)
|
||||||
|
console.log('实际数据的list字段:', actualData?.list)
|
||||||
|
console.log('list是否为数组:', Array.isArray(actualData?.list))
|
||||||
|
|
||||||
|
// 检查响应数据的有效性
|
||||||
|
if (!actualData || !Array.isArray(actualData.list)) {
|
||||||
|
console.warn('API响应数据格式异常')
|
||||||
|
console.warn('期望的格式: { list: [], total: number }')
|
||||||
|
console.warn('实际收到的格式:', actualData)
|
||||||
|
return {
|
||||||
|
code: actualCode || 0,
|
||||||
|
message: actualMessage || '数据格式异常',
|
||||||
|
data: {
|
||||||
|
list: [],
|
||||||
|
total: 0,
|
||||||
|
page: params?.page || 1,
|
||||||
|
pageSize: params?.pageSize || 10,
|
||||||
|
totalPages: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 适配后端响应格式为前端期望的格式
|
// 适配后端响应格式为前端期望的格式
|
||||||
const adaptedCourses: Course[] = response.data.list.map((backendCourse: BackendCourse) => ({
|
const adaptedCourses: Course[] = actualData.list.map((backendCourse: BackendCourse) => ({
|
||||||
id: backendCourse.id,
|
id: backendCourse.id,
|
||||||
title: backendCourse.name,
|
title: backendCourse.name,
|
||||||
description: backendCourse.description,
|
description: backendCourse.description,
|
||||||
@ -140,20 +186,33 @@ export class CourseApi {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const adaptedResponse: ApiResponse<PaginationResponse<Course>> = {
|
const adaptedResponse: ApiResponse<PaginationResponse<Course>> = {
|
||||||
code: response.code,
|
code: actualCode,
|
||||||
message: response.message,
|
message: actualMessage,
|
||||||
data: {
|
data: {
|
||||||
list: adaptedCourses,
|
list: adaptedCourses,
|
||||||
total: response.data.total,
|
total: actualData.total || 0,
|
||||||
page: params?.page || 1,
|
page: params?.page || 1,
|
||||||
pageSize: params?.pageSize || 10,
|
pageSize: params?.pageSize || 10,
|
||||||
totalPages: Math.ceil(response.data.total / (params?.pageSize || 10))
|
totalPages: Math.ceil((actualData.total || 0) / (params?.pageSize || 10))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return adaptedResponse
|
return adaptedResponse
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
console.error('课程API调用失败:', error)
|
||||||
|
|
||||||
|
// 返回空数据而不是抛出错误,确保应用不会崩溃
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: '获取课程列表失败',
|
||||||
|
data: {
|
||||||
|
list: [],
|
||||||
|
total: 0,
|
||||||
|
page: params?.page || 1,
|
||||||
|
pageSize: params?.pageSize || 10,
|
||||||
|
totalPages: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,55 +242,77 @@ export class CourseApi {
|
|||||||
// 调用后端课程详情接口
|
// 调用后端课程详情接口
|
||||||
const response = await ApiRequest.get<BackendCourse>('/lesson/detail', { id })
|
const response = await ApiRequest.get<BackendCourse>('/lesson/detail', { id })
|
||||||
|
|
||||||
|
// 检查是否是axios响应格式还是我们的ApiResponse格式
|
||||||
|
let actualData: any
|
||||||
|
let actualCode: number
|
||||||
|
let actualMessage: string
|
||||||
|
|
||||||
|
// 使用类型断言来处理不同的响应格式
|
||||||
|
const responseAny = response as any
|
||||||
|
|
||||||
|
if (responseAny.data && typeof responseAny.data === 'object' && 'data' in responseAny.data) {
|
||||||
|
// 这是我们期望的ApiResponse格式: { code, message, data: BackendCourse }
|
||||||
|
actualData = responseAny.data.data
|
||||||
|
actualCode = responseAny.data.code
|
||||||
|
actualMessage = responseAny.data.message
|
||||||
|
console.log('检测到ApiResponse格式')
|
||||||
|
} else {
|
||||||
|
// 这可能是直接的axios响应格式: BackendCourse
|
||||||
|
actualData = responseAny.data
|
||||||
|
actualCode = responseAny.status || 200
|
||||||
|
actualMessage = responseAny.statusText || 'OK'
|
||||||
|
console.log('检测到直接响应格式')
|
||||||
|
}
|
||||||
|
|
||||||
// 适配数据格式
|
// 适配数据格式
|
||||||
const adaptedCourse: Course = {
|
const adaptedCourse: Course = {
|
||||||
id: response.data.id,
|
id: actualData.id,
|
||||||
title: response.data.name,
|
title: actualData.name,
|
||||||
description: response.data.description,
|
description: actualData.description,
|
||||||
content: response.data.outline, // 使用 outline 作为课程内容
|
content: actualData.outline, // 使用 outline 作为课程内容
|
||||||
thumbnail: response.data.cover,
|
thumbnail: actualData.cover,
|
||||||
coverImage: response.data.cover,
|
coverImage: actualData.cover,
|
||||||
price: parseFloat(response.data.price || '0'),
|
price: parseFloat(actualData.price || '0'),
|
||||||
originalPrice: parseFloat(response.data.price || '0'),
|
originalPrice: parseFloat(actualData.price || '0'),
|
||||||
currency: 'CNY',
|
currency: 'CNY',
|
||||||
rating: 4.5,
|
rating: 4.5,
|
||||||
ratingCount: 0,
|
ratingCount: 0,
|
||||||
studentsCount: 0,
|
studentsCount: 0,
|
||||||
duration: this.calculateDuration(response.data.startTime, response.data.endTime),
|
duration: this.calculateDuration(actualData.startTime, actualData.endTime),
|
||||||
totalLessons: 0,
|
totalLessons: 0,
|
||||||
level: 'beginner' as const,
|
level: 'beginner' as const,
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
category: {
|
category: {
|
||||||
id: response.data.categoryId,
|
id: actualData.categoryId,
|
||||||
name: '未分类',
|
name: '未分类',
|
||||||
slug: 'uncategorized'
|
slug: 'uncategorized'
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: [],
|
||||||
skills: [],
|
skills: [],
|
||||||
requirements: response.data.prerequisite ? [response.data.prerequisite] : [],
|
requirements: actualData.prerequisite ? [actualData.prerequisite] : [],
|
||||||
objectives: response.data.target ? [response.data.target] : [],
|
objectives: actualData.target ? [actualData.target] : [],
|
||||||
instructor: {
|
instructor: {
|
||||||
id: response.data.teacherId || 0,
|
id: actualData.teacherId || 0,
|
||||||
name: response.data.school || '未知讲师',
|
name: actualData.school || '未知讲师',
|
||||||
title: '讲师',
|
title: '讲师',
|
||||||
bio: response.data.position || '',
|
bio: actualData.position || '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
rating: 4.5,
|
rating: 4.5,
|
||||||
studentsCount: 0,
|
studentsCount: 0,
|
||||||
coursesCount: 0,
|
coursesCount: 0,
|
||||||
experience: response.data.arrangement || '',
|
experience: actualData.arrangement || '',
|
||||||
education: [],
|
education: [],
|
||||||
certifications: []
|
certifications: []
|
||||||
},
|
},
|
||||||
status: 'published' as const,
|
status: 'published' as const,
|
||||||
createdAt: this.formatTimestamp(response.data.createdTime),
|
createdAt: this.formatTimestamp(actualData.createdTime),
|
||||||
updatedAt: this.formatTimestamp(response.data.updatedTime),
|
updatedAt: this.formatTimestamp(actualData.updatedTime),
|
||||||
publishedAt: response.data.startTime
|
publishedAt: actualData.startTime
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: response.code,
|
code: actualCode,
|
||||||
message: response.message,
|
message: actualMessage,
|
||||||
data: adaptedCourse
|
data: adaptedCourse
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -300,28 +381,54 @@ export class CourseApi {
|
|||||||
|
|
||||||
const backendResponse = await ApiRequest.get<BackendCourseSectionListResponse>('/lesson/section/list', { lesson_id: lessonId.toString() })
|
const backendResponse = await ApiRequest.get<BackendCourseSectionListResponse>('/lesson/section/list', { lesson_id: lessonId.toString() })
|
||||||
console.log('章节API响应:', backendResponse)
|
console.log('章节API响应:', backendResponse)
|
||||||
console.log('响应状态码:', backendResponse.code)
|
|
||||||
console.log('响应消息:', backendResponse.message)
|
// 检查是否是axios响应格式还是我们的ApiResponse格式
|
||||||
console.log('原始章节数据:', backendResponse.data?.list)
|
let actualData: any
|
||||||
console.log('章节数据数量:', backendResponse.data?.list?.length || 0)
|
let actualCode: number
|
||||||
|
let actualMessage: string
|
||||||
|
let actualTimestamp: string | undefined
|
||||||
|
|
||||||
|
// 使用类型断言来处理不同的响应格式
|
||||||
|
const responseAny = backendResponse as any
|
||||||
|
|
||||||
|
if (responseAny.data && typeof responseAny.data === 'object' && 'data' in responseAny.data) {
|
||||||
|
// 这是我们期望的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 (!backendResponse.data || !backendResponse.data.list) {
|
if (!actualData || !Array.isArray(actualData.list)) {
|
||||||
console.warn('API返回的数据结构不正确:', backendResponse.data)
|
console.warn('API返回的数据结构不正确:', actualData)
|
||||||
return {
|
return {
|
||||||
code: backendResponse.code,
|
code: actualCode,
|
||||||
message: backendResponse.message,
|
message: actualMessage,
|
||||||
data: {
|
data: {
|
||||||
list: [],
|
list: [],
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
traceId: backendResponse.timestamp?.toString() || ''
|
traceId: actualTimestamp || ''
|
||||||
},
|
},
|
||||||
timestamp: backendResponse.timestamp?.toString()
|
timestamp: actualTimestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 适配数据格式
|
// 适配数据格式
|
||||||
const adaptedSections: CourseSection[] = backendResponse.data.list.map((section: BackendCourseSection) => ({
|
const adaptedSections: CourseSection[] = actualData.list.map((section: BackendCourseSection) => ({
|
||||||
id: section.id,
|
id: section.id,
|
||||||
lessonId: section.lessonId,
|
lessonId: section.lessonId,
|
||||||
outline: section.videoUrl, // 将videoUrl映射到outline
|
outline: section.videoUrl, // 将videoUrl映射到outline
|
||||||
@ -340,14 +447,14 @@ export class CourseApi {
|
|||||||
console.log('适配后的章节数据:', adaptedSections)
|
console.log('适配后的章节数据:', adaptedSections)
|
||||||
|
|
||||||
const adaptedResponse: ApiResponse<CourseSectionListResponse> = {
|
const adaptedResponse: ApiResponse<CourseSectionListResponse> = {
|
||||||
code: backendResponse.code,
|
code: actualCode,
|
||||||
message: backendResponse.message,
|
message: actualMessage,
|
||||||
data: {
|
data: {
|
||||||
list: adaptedSections,
|
list: adaptedSections,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
traceId: backendResponse.timestamp?.toString() || ''
|
traceId: actualTimestamp || ''
|
||||||
},
|
},
|
||||||
timestamp: backendResponse.timestamp?.toString()
|
timestamp: actualTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
return adaptedResponse
|
return adaptedResponse
|
||||||
|
@ -355,16 +355,30 @@ const loadCourses = async () => {
|
|||||||
keyword: selectedMajor.value !== '全部' ? selectedMajor.value : undefined
|
keyword: selectedMajor.value !== '全部' ? selectedMajor.value : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('加载课程参数:', params)
|
||||||
const response = await CourseApi.getCourses(params)
|
const response = await CourseApi.getCourses(params)
|
||||||
|
console.log('课程API响应:', response)
|
||||||
|
|
||||||
|
if (response && response.data) {
|
||||||
if (response.code === 0 || response.code === 200) {
|
if (response.code === 0 || response.code === 200) {
|
||||||
courses.value = response.data.list
|
courses.value = response.data.list || []
|
||||||
total.value = response.data.total
|
total.value = response.data.total || 0
|
||||||
|
console.log('课程加载成功:', courses.value.length, '条课程')
|
||||||
} else {
|
} else {
|
||||||
console.error('获取课程列表失败:', response.message)
|
console.error('获取课程列表失败:', response.message)
|
||||||
|
courses.value = []
|
||||||
|
total.value = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('API响应格式异常:', response)
|
||||||
|
courses.value = []
|
||||||
|
total.value = 0
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载课程失败:', error)
|
console.error('加载课程失败:', error)
|
||||||
|
courses.value = []
|
||||||
|
total.value = 0
|
||||||
|
// 可以在这里添加用户友好的错误提示
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
@ -106,14 +106,14 @@ const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '')
|
|||||||
|
|
||||||
// 筛选标签数据
|
// 筛选标签数据
|
||||||
const filterTabs = ref([
|
const filterTabs = ref([
|
||||||
{ id: 'all', name: '全部讲师' },
|
{ id: 'field', name: '课长领域' },
|
||||||
{ id: 'main', name: '主讲' },
|
{ id: 'all', name: '全部' },
|
||||||
{ id: 'international', name: '注册国际讲师' },
|
{ id: 'chinese-promotion', name: '汉语国际推广' },
|
||||||
{ id: 'consultant', name: '咨询师' },
|
{ id: 'chinese-language', name: '汉语言' },
|
||||||
{ id: 'expert', name: '专家顾问' },
|
{ id: 'chinese-education', name: '华文教育' },
|
||||||
{ id: 'senior', name: '资深讲师' },
|
{ id: 'e-commerce', name: '电子商务' },
|
||||||
{ id: 'featured', name: '金牌讲师' },
|
{ id: 'new-energy', name: '新能源' },
|
||||||
{ id: 'enterprise', name: '企业导师' }
|
{ id: 'smart-education', name: '智慧教育' }
|
||||||
])
|
])
|
||||||
|
|
||||||
const activeTab = ref('all')
|
const activeTab = ref('all')
|
||||||
@ -328,32 +328,35 @@ const goToPage = (page: number) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
background: white;
|
background: transparent;
|
||||||
border-radius: 8px;
|
border-bottom: 1px solid #e5e5e5;
|
||||||
padding: 8px;
|
padding: 0;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-tab {
|
.filter-tab {
|
||||||
padding: 12px 24px;
|
padding: 12px 20px;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 6px;
|
border-radius: 0;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-tab:hover {
|
.filter-tab:hover {
|
||||||
background: #f8f9fa;
|
color: #4A90E2;
|
||||||
color: #333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-tab.active {
|
.filter-tab.active {
|
||||||
background: #4A90E2;
|
background: transparent;
|
||||||
color: white;
|
color: #4A90E2;
|
||||||
|
border-bottom-color: #4A90E2;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 师资卡片网格 */
|
/* 师资卡片网格 */
|
||||||
@ -546,12 +549,15 @@ const goToPage = (page: number) => {
|
|||||||
|
|
||||||
.filter-tabs {
|
.filter-tabs {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-tab {
|
.filter-tab {
|
||||||
padding: 8px 16px;
|
padding: 10px 16px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-icon {
|
.placeholder-icon {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user