diff --git a/.env b/.env index 73add56..de6687f 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ # API配置 -VITE_API_BASE_URL=http://110.42.96.65:55510/api +VITE_API_BASE_URL=http://103.40.14.23:25526/jeecgboot # Mock配置 - 禁用Mock,使用真实API VITE_ENABLE_MOCK=false diff --git a/.env.development b/.env.development index 26903be..ed003aa 100644 --- a/.env.development +++ b/.env.development @@ -1,7 +1,7 @@ # 开发环境配置 # API配置 -VITE_API_BASE_URL=http://110.42.96.65:55510/api +VITE_API_BASE_URL=http://103.40.14.23:25526/jeecgboot # Mock配置 # 设置为 true 使用Mock数据,false 使用真实API diff --git a/.env.production b/.env.production index 13bea44..f0974f9 100644 --- a/.env.production +++ b/.env.production @@ -1,7 +1,7 @@ # 生产环境配置 # API配置 -VITE_API_BASE_URL=http://110.42.96.65:55510/api +VITE_API_BASE_URL=http://103.40.14.23:25526/jeecgboot # Mock配置 - 生产环境禁用Mock,使用真实API VITE_ENABLE_MOCK=false diff --git a/src/api/examples/usage.ts b/src/api/examples/usage.ts index d74cda2..172ecfc 100644 --- a/src/api/examples/usage.ts +++ b/src/api/examples/usage.ts @@ -73,15 +73,14 @@ export const getCoursesExample = async () => { const response = await CourseApi.getCourses({ page: 1, pageSize: 20, - categoryId: 1, - difficulty: 1, - sortBy: 'createdAt' + categoryId: '1', + difficulty: '1' }) if (response.code === 200) { - const { list, total } = response.data - console.log('课程列表:', list) - console.log('总数:', total) + const courses = response.data + console.log('课程列表:', courses) + console.log('总数:', courses.length) return response.data } } catch (error) { @@ -113,7 +112,7 @@ export const searchCoursesExample = async () => { } // 获取课程详情示例 -export const getCourseDetailExample = async (courseId: number) => { +export const getCourseDetailExample = async (courseId: string) => { try { const response = await CourseApi.getCourseById(courseId) if (response.code === 200) { diff --git a/src/api/index.ts b/src/api/index.ts index cb4d166..41502a8 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -12,13 +12,13 @@ export { default as UploadApi } from './modules/upload' export { default as StatisticsApi } from './modules/statistics' // API 基础配置 -export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api' +export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/jeecgboot' // API 端点配置 export const API_ENDPOINTS = { // 认证相关 AUTH: { - LOGIN: '/auth/login', + LOGIN: '/biz/user/login', REGISTER: '/auth/register', LOGOUT: '/auth/logout', REFRESH: '/auth/refresh', @@ -34,12 +34,12 @@ export const API_ENDPOINTS = { // 课程相关 COURSES: { - LIST: '/courses', + LIST: '/biz/course/list', SEARCH: '/courses/search', POPULAR: '/courses/popular', LATEST: '/courses/latest', RECOMMENDED: '/courses/recommended', - DETAIL: '/courses/:id', + DETAIL: '/biz/course/detail', CHAPTERS: '/courses/:id/chapters', LESSONS: '/courses/:id/lessons', ENROLL: '/courses/:id/enroll', @@ -52,9 +52,19 @@ export const API_ENDPOINTS = { // 分类相关 CATEGORIES: { - LIST: '/categories', + LIST: '/biz/course/category/list', COURSES: '/categories/:id/courses', }, + + // 专题相关 + SUBJECTS: { + LIST: '/biz/course/subject/list', + }, + + // 难度相关 + DIFFICULTIES: { + LIST: '/biz/course/difficulty/list', + }, // 章节课时相关 CHAPTERS: { diff --git a/src/api/modules/auth.ts b/src/api/modules/auth.ts index a76ecf3..8c16a8b 100644 --- a/src/api/modules/auth.ts +++ b/src/api/modules/auth.ts @@ -17,10 +17,16 @@ export class AuthApi { // 用户登录 static async login(data: LoginRequest): Promise> { try { - console.log('🚀 发送登录请求:', { url: '/users/login', data: { ...data, password: '***' } }) + // 转换参数格式:将email/phone转换为username + const loginData = { + username: data.email || data.phone || '', + password: data.password + } + + console.log('🚀 发送登录请求:', { url: '/biz/user/login', data: { ...loginData, password: '***' } }) // 调用后端API - const response = await ApiRequest.post('/users/login', data) + const response = await ApiRequest.post('/biz/user/login', loginData) console.log('🔍 Login API Response:', response) console.log('🔍 Response Code:', response.code) @@ -34,12 +40,19 @@ export class AuthApi { // 如果response.code是undefined,检查response.data是否包含完整的API响应 if (actualCode === undefined && actualData && typeof actualData === 'object') { - if ('code' in actualData && 'message' in actualData && 'data' in actualData) { - // 这种情况下,真正的API响应被包装在了response.data中 + // 检查是否是标准的jeecg-boot响应格式 (success, code, message, result) + if ('success' in actualData && 'code' in actualData && 'message' in actualData && 'result' in actualData) { + actualCode = actualData.code + actualMessage = actualData.message + actualData = actualData.result + console.log('🔧 修正后的响应 (jeecg-boot格式):', { code: actualCode, message: actualMessage, data: actualData }) + } + // 检查是否是其他格式 (code, message, data) + else if ('code' in actualData && 'message' in actualData && 'data' in actualData) { actualCode = actualData.code actualMessage = actualData.message actualData = actualData.data - console.log('🔧 修正后的响应:', { code: actualCode, message: actualMessage, data: actualData }) + console.log('🔧 修正后的响应 (标准格式):', { code: actualCode, message: actualMessage, data: actualData }) } } @@ -54,8 +67,8 @@ export class AuthApi { } as ApiResponse } - // 如果后端返回的是真实API格式(包含token, timestamp, expires) - if (actualData && actualData.token && actualData.timestamp) { + // 如果后端返回的是jeecg-boot格式(只包含token) + if (actualData && actualData.token) { const adaptedResponse: ApiResponse = { code: actualCode, message: actualMessage || '登录成功', @@ -64,7 +77,7 @@ export class AuthApi { id: 1, // 真实API没有返回用户ID,使用默认值 email: data.email || '', phone: data.phone || '', - username: data.phone || data.email?.split('@')[0] || 'user', + username: data.email || data.phone || '', nickname: '用户', avatar: '', role: 'student', @@ -216,7 +229,7 @@ export class AuthApi { // 获取当前用户信息 static getCurrentUser(): Promise> { - return ApiRequest.get('/auth/me') + return ApiRequest.get('/users/info') } // 更新用户资料 diff --git a/src/api/modules/course.ts b/src/api/modules/course.ts index b488f74..efc2396 100644 --- a/src/api/modules/course.ts +++ b/src/api/modules/course.ts @@ -5,20 +5,24 @@ import type { PaginationResponse, Course, CourseCategory, + CourseSubject, + CourseDifficulty, + CourseListQueryParams, + BackendCourseItem, Chapter, Lesson, LessonResource, CourseSection, CourseSectionListResponse, BackendCourseSection, - BackendCourseSectionListResponse, + BackendInstructor, + BackendSectionVideo, + SectionVideo, + VideoQuality, Quiz, LearningProgress, SearchRequest, Instructor, - BackendCourse, - BackendCourseListResponse, - CourseListRequest, } from '../types' /** @@ -62,142 +66,97 @@ export class CourseApi { } } + + /** - * 计算课程时长 + * 映射后端难度值到前端级别 */ - private static calculateDuration(startTime: string, endTime: string): string { - try { - const start = new Date(startTime) - const end = new Date(endTime) - const diffMs = end.getTime() - start.getTime() - const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24)) - return `${diffDays}天` - } catch (error) { - return '待定' + private static mapDifficultyToLevel(difficulty: number): string { + switch (difficulty) { + case 0: return '零基础' + case 1: return '初级' + case 2: return '进阶' + case 3: return '高阶' + default: return '未知' + } + } + + /** + * 映射后端难度值到标准级别 + */ + private static mapDifficultyToStandardLevel(difficulty: number): 'beginner' | 'intermediate' | 'advanced' { + switch (difficulty) { + case 0: return 'beginner' + case 1: return 'beginner' + case 2: return 'intermediate' + case 3: return 'advanced' + default: return 'beginner' } } // 获取课程列表 - 适配后端接口 - static async getCourses(params?: CourseListRequest): Promise>> { + static async getCourses(params?: CourseListQueryParams): Promise> { try { - console.log('调用课程列表API,参数:', params) + console.log('🚀 调用课程列表API,参数:', params) // 构建查询参数,根据API文档的参数名称 const queryParams: any = {} if (params?.categoryId) queryParams.categoryId = params.categoryId - if (params?.difficulty !== undefined) queryParams.difficulty = params.difficulty + if (params?.difficulty) queryParams.difficulty = params.difficulty if (params?.subject) queryParams.subject = params.subject - if (params?.page) queryParams.page = params.page - if (params?.pageSize) queryParams.pageSize = params.pageSize - if (params?.keyword) queryParams.keyword = params.keyword + + console.log('🔍 查询参数:', queryParams) // 调用后端API - const response = await ApiRequest.get('/lesson/list', queryParams) - console.log('课程列表API响应:', response) - console.log('响应数据结构:', response.data) - console.log('响应数据类型:', typeof response.data) + const response = await ApiRequest.get('/biz/course/list', queryParams) + console.log('🔍 课程列表API响应:', response) - // 检查是否是axios响应格式还是我们的ApiResponse格式 - let actualData: any - let actualCode: number - let actualMessage: string + // 处理后端响应格式 + if (response.data && response.data.success && response.data.result) { + // 转换后端数据格式为前端格式 + const courses: Course[] = response.data.result.map((item: BackendCourseItem) => ({ + id: item.id, // 保持字符串格式,不转换为数字 + title: item.name || '', + description: item.description || '', + instructor: item.school || '未知讲师', + duration: item.arrangement || '待定', + level: this.mapDifficultyToLevel(item.difficulty), + category: item.subject || '其他', + thumbnail: item.cover || '', + price: 0, // 后端没有价格字段,设为0 + rating: 0, // 后端没有评分字段,设为0 + studentsCount: item.enrollCount || 0, + lessonsCount: 0, // 后端没有课程数量字段,设为0 + tags: [], + isEnrolled: false, + progress: 0, + createdAt: this.formatTimestamp(item.createTime), + updatedAt: this.formatTimestamp(item.updateTime), + status: item.status === 1 ? 'published' : 'draft', + enrollmentCount: item.enrollCount || 0, + maxEnrollment: item.maxEnroll || 0, + startDate: item.startTime || '', + endDate: item.endTime || '', + outline: item.outline || '', + prerequisite: item.prerequisite || '', + reference: item.reference || '', + target: item.target || '', + question: item.question || '', + video: item.video || '' + })) - // 使用类型断言来处理不同的响应格式 - 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 - } + code: 200, + message: '获取成功', + data: courses + } + } else { + console.warn('⚠️ 课程列表API返回格式异常:', response) + return { + code: 500, + message: response.data?.message || '获取课程列表失败', + data: [] } } - - // 适配后端响应格式为前端期望的格式 - const adaptedCourses: Course[] = actualData.list.map((backendCourse: BackendCourse) => ({ - id: backendCourse.id, - title: backendCourse.name, - description: backendCourse.description, - thumbnail: backendCourse.cover, - coverImage: backendCourse.cover, - price: parseFloat(backendCourse.price || '0'), - originalPrice: parseFloat(backendCourse.price || '0'), - currency: 'CNY', - rating: 4.5, // 默认评分,后端没有返回 - ratingCount: 0, // 默认评分数量 - studentsCount: 0, // 默认学生数量 - duration: '待定', // 默认时长 - totalLessons: 0, // 默认课程数量 - level: this.mapDifficulty(backendCourse.difficulty || 0), - language: 'zh-CN', - category: { - id: backendCourse.categoryId, - name: this.getCategoryName(backendCourse.categoryId), - slug: 'category-' + backendCourse.categoryId - }, - tags: backendCourse.subject ? [backendCourse.subject] : [], - skills: [], - requirements: backendCourse.prerequisite ? [backendCourse.prerequisite] : [], - objectives: backendCourse.target ? [backendCourse.target] : [], - instructor: { - id: backendCourse.teacherId || 0, - name: backendCourse.school || '未知讲师', - title: '讲师', - bio: '', - avatar: '', - rating: 4.5, - studentsCount: 0, - coursesCount: 0, - experience: '', - education: [], - certifications: [] - }, - status: 'published' as const, - createdAt: this.formatTimestamp(backendCourse.createdTime), - updatedAt: this.formatTimestamp(backendCourse.updatedTime), - publishedAt: this.formatTimestamp(backendCourse.createdTime) - })) - - const adaptedResponse: ApiResponse> = { - code: actualCode, - message: actualMessage, - data: { - list: adaptedCourses, - total: actualData.total || 0, - page: params?.page || 1, - pageSize: params?.pageSize || 10, - totalPages: Math.ceil((actualData.total || 0) / (params?.pageSize || 10)) - } - } - - return adaptedResponse } catch (error: any) { console.error('课程API调用失败:', error) @@ -214,13 +173,96 @@ export class CourseApi { return { code: 500, message: errorMessage, - data: { - list: [], - total: 0, - page: params?.page || 1, - pageSize: params?.pageSize || 10, - totalPages: 0 + data: [] + } + } + } + + // 获取课程详情 - 适配后端接口 + static async getCourseDetail(id: string): Promise> { + try { + console.log('🚀 调用课程详情API,课程ID:', id) + + // 调用后端API + const response = await ApiRequest.get('/biz/course/detail', { id }) + + console.log('🔍 课程详情API响应:', response) + + // 处理后端响应格式 + if (response.data && response.data.success) { + // 检查result是否为null或空 + if (!response.data.result) { + console.warn('⚠️ 课程详情为空,可能课程不存在或已删除') + return { + code: 404, + message: '课程不存在或已删除', + data: {} as Course + } } + // 转换后端数据格式为前端格式 + const item: BackendCourseItem = response.data.result + const course: Course = { + id: item.id, // 保持字符串格式,不转换为数字 + title: item.name || '', + description: item.description || '', + thumbnail: item.cover || '', + price: 0, // 后端没有价格字段,设为0 + currency: 'CNY', + rating: 0, // 后端没有评分字段,设为0 + ratingCount: 0, + studentsCount: item.enrollCount || 0, + duration: item.arrangement || '待定', + totalLessons: 0, + level: this.mapDifficultyToStandardLevel(item.difficulty), + language: 'zh-CN', + category: { + id: 1, + name: item.subject || '其他', + slug: 'other' + }, + tags: [], + skills: [], + requirements: item.prerequisite ? [item.prerequisite] : [], + objectives: item.target ? [item.target] : [], + instructor: { + id: 1, + name: item.school || '未知讲师', + title: '讲师', + bio: '', + avatar: '', + rating: 4.5, + studentsCount: 0, + coursesCount: 0, + experience: '', + education: [], + certifications: [] + }, + status: item.status === 1 ? 'published' : 'draft', + isEnrolled: false, + progress: 0, + createdAt: this.formatTimestamp(item.createTime), + updatedAt: this.formatTimestamp(item.updateTime) + } + + return { + code: 200, + message: '获取成功', + data: course + } + } else { + console.warn('⚠️ 课程详情API返回格式异常:', response) + return { + code: 500, + message: response.data?.message || '获取课程详情失败', + data: {} as Course + } + } + } catch (error: any) { + console.error('❌ 获取课程详情失败:', error) + return { + code: 500, + message: error.message || '获取课程详情失败', + data: {} as Course } } } @@ -246,86 +288,89 @@ export class CourseApi { } // 获取课程详情 - 适配后端接口 - static async getCourseById(id: number): Promise> { + static async getCourseById(id: string): Promise> { try { // 调用后端课程详情接口 - const response = await ApiRequest.get('/lesson/detail', { id }) + const response = await ApiRequest.get('/biz/course/detail', { id }) - // 检查是否是axios响应格式还是我们的ApiResponse格式 - let actualData: any - let actualCode: number - let actualMessage: string + console.log('🔍 课程详情API响应:', response) - // 使用类型断言来处理不同的响应格式 - const responseAny = response as any + // 处理后端响应格式 + if (response.data && response.data.success) { + // 检查result是否为null或空 + if (!response.data.result) { + console.warn('⚠️ 课程详情为空,可能课程不存在或已删除') + return { + code: 404, + message: '课程不存在或已删除', + data: {} as Course + } + } + // 转换后端数据格式为前端格式 + const item: BackendCourseItem = response.data.result + const course: Course = { + id: item.id, // 保持字符串格式,不转换为数字 + title: item.name || '', + description: item.description || '', + thumbnail: item.cover || '', + price: 0, // 后端没有价格字段,设为0 + currency: 'CNY', + rating: 0, // 后端没有评分字段,设为0 + ratingCount: 0, + studentsCount: item.enrollCount || 0, + duration: item.arrangement || '待定', + totalLessons: 0, + level: this.mapDifficultyToStandardLevel(item.difficulty), + language: 'zh-CN', + category: { + id: 1, + name: item.subject || '其他', + slug: 'other' + }, + tags: [], + skills: [], + requirements: item.prerequisite ? [item.prerequisite] : [], + objectives: item.target ? [item.target] : [], + instructor: { + id: 1, + name: item.school || '未知讲师', + title: '讲师', + bio: '', + avatar: '', + rating: 4.5, + studentsCount: 0, + coursesCount: 0, + experience: '', + education: [], + certifications: [] + }, + status: item.status === 1 ? 'published' : 'draft', + isEnrolled: false, + progress: 0, + createdAt: this.formatTimestamp(item.createTime), + updatedAt: this.formatTimestamp(item.updateTime) + } - 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格式') + return { + code: 200, + message: '获取成功', + data: course + } } else { - // 这可能是直接的axios响应格式: BackendCourse - actualData = responseAny.data - actualCode = responseAny.status || 200 - actualMessage = responseAny.statusText || 'OK' - console.log('检测到直接响应格式') + console.warn('⚠️ 课程详情API返回格式异常:', response) + return { + code: 500, + message: response.data?.message || '获取课程详情失败', + data: {} as Course + } } - - // 适配数据格式 - const adaptedCourse: Course = { - id: actualData.id, - title: actualData.name, - description: actualData.description, - content: actualData.outline, // 使用 outline 作为课程内容 - thumbnail: actualData.cover, - coverImage: actualData.cover, - price: parseFloat(actualData.price || '0'), - originalPrice: parseFloat(actualData.price || '0'), - currency: 'CNY', - rating: 4.5, - ratingCount: 0, - studentsCount: 0, - duration: this.calculateDuration(actualData.startTime, actualData.endTime), - totalLessons: 0, - level: 'beginner' as const, - language: 'zh-CN', - category: { - id: actualData.categoryId, - name: '未分类', - slug: 'uncategorized' - }, - tags: [], - skills: [], - requirements: actualData.prerequisite ? [actualData.prerequisite] : [], - objectives: actualData.target ? [actualData.target] : [], - instructor: { - id: actualData.teacherId || 0, - name: actualData.school || '未知讲师', - title: '讲师', - bio: actualData.position || '', - avatar: '', - rating: 4.5, - studentsCount: 0, - coursesCount: 0, - experience: actualData.arrangement || '', - education: [], - certifications: [] - }, - status: 'published' as const, - createdAt: this.formatTimestamp(actualData.createdTime), - updatedAt: this.formatTimestamp(actualData.updatedTime), - publishedAt: actualData.startTime - } - + } catch (error: any) { + console.error('❌ 获取课程详情失败:', error) return { - code: actualCode, - message: actualMessage, - data: adaptedCourse + code: 500, + message: error.message || '获取课程详情失败', + data: {} as Course } - } catch (error) { - throw error } } @@ -355,8 +400,135 @@ export class CourseApi { } // 获取课程分类 - static getCategories(): Promise> { - return ApiRequest.get('/categories') + static async getCategories(): Promise> { + try { + console.log('🚀 获取课程分类列表') + + // 调用后端API(不需要token) + const response = await ApiRequest.get('/biz/course/category/list') + + console.log('🔍 分类API响应:', response) + + // 处理后端响应格式 + if (response.data && response.data.success && response.data.result) { + // 转换后端数据格式为前端格式 + const categories: CourseCategory[] = response.data.result.map((item: any) => ({ + id: parseInt(item.id) || 0, + name: item.name || '', + slug: item.name?.toLowerCase().replace(/\s+/g, '-') || '', + description: '', + sortOrder: item.sortOrder || 0 + })) + + return { + code: 200, + message: '获取成功', + data: categories + } + } else { + console.warn('⚠️ 分类API返回格式异常:', response) + return { + code: 500, + message: response.data?.message || '获取分类失败', + data: [] + } + } + } catch (error: any) { + console.error('❌ 获取课程分类失败:', error) + return { + code: 500, + message: error.message || '获取分类失败', + data: [] + } + } + } + + // 获取课程专题列表 + static async getSubjects(): Promise> { + try { + console.log('🚀 获取课程专题列表') + + // 调用后端API(不需要token) + const response = await ApiRequest.get('/biz/course/subject/list') + + console.log('🔍 专题API响应:', response) + + // 处理后端响应格式 + if (response.data && response.data.success && response.data.result) { + // 转换后端数据格式为前端格式(专题接口返回的是value和label字段) + const subjects: CourseSubject[] = response.data.result.map((item: any, index: number) => ({ + id: item.value || '0', // 保持原始的value值(字符串格式) + name: item.label || '', + slug: item.label?.toLowerCase().replace(/\s+/g, '-') || '', + description: '', + sortOrder: index + })) + + return { + code: 200, + message: '获取成功', + data: subjects + } + } else { + console.warn('⚠️ 专题API返回格式异常:', response) + return { + code: 500, + message: response.data?.message || '获取专题失败', + data: [] + } + } + } catch (error: any) { + console.error('❌ 获取课程专题失败:', error) + return { + code: 500, + message: error.message || '获取专题失败', + data: [] + } + } + } + + // 获取课程难度列表 + static async getDifficulties(): Promise> { + try { + console.log('🚀 获取课程难度列表') + + // 调用后端API(不需要token) + const response = await ApiRequest.get('/biz/course/difficulty/list') + + console.log('🔍 难度API响应:', response) + + // 处理后端响应格式 + if (response.data && response.data.success && response.data.result) { + // 转换后端数据格式为前端格式(难度接口返回的是value和label字段) + const difficulties: CourseDifficulty[] = response.data.result.map((item: any, index: number) => ({ + id: item.value || '0', // 保持原始的value值(字符串格式) + name: item.label || '', + slug: item.label?.toLowerCase().replace(/\s+/g, '-') || '', + description: '', + sortOrder: index + })) + + return { + code: 200, + message: '获取成功', + data: difficulties + } + } else { + console.warn('⚠️ 难度API返回格式异常:', response) + return { + code: 500, + message: response.data?.message || '获取难度失败', + data: [] + } + } + } catch (error: any) { + console.error('❌ 获取课程难度失败:', error) + return { + code: 500, + message: error.message || '获取难度失败', + data: [] + } + } } // 获取分类下的课程 @@ -382,94 +554,65 @@ export class CourseApi { } // 获取课程章节列表 - static async getCourseSections(lessonId: number): Promise> { + static async getCourseSections(courseId: string): Promise> { try { - console.log('尝试从API获取课程章节数据,课程ID:', lessonId) - console.log('API请求URL: /lesson/section/list') - console.log('API请求参数:', { lesson_id: lessonId.toString() }) + console.log('🔍 获取课程章节数据,课程ID:', courseId) + console.log('🔍 API请求URL: /biz/course/' + courseId + '/section') - const backendResponse = await ApiRequest.get('/lesson/section/list', { lesson_id: lessonId.toString() }) - console.log('章节API响应:', backendResponse) + const response = await ApiRequest.get(`/biz/course/${courseId}/section`) + console.log('🔍 章节API响应:', response) - // 检查是否是axios响应格式还是我们的ApiResponse格式 - let actualData: any - let actualCode: number - let actualMessage: string - let actualTimestamp: string | undefined + // 处理后端响应格式 + 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 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, // 保持原值,可能为null + parentId: section.parentId || '', // 如果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) { - // 这是我们期望的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('✅ 适配后的章节数据:', adaptedSections) - 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 { - code: actualCode, - message: actualMessage, + 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: actualTimestamp || '' - }, - timestamp: actualTimestamp + traceId: '' + } } } - - // 适配数据格式 - 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 = { - code: actualCode, - message: actualMessage, - data: { - list: adaptedSections, - timestamp: Date.now(), - traceId: actualTimestamp || '' - }, - timestamp: actualTimestamp - } - - return adaptedResponse } catch (error) { - console.error('章节API调用失败:', error) - console.error('错误详情:', { + console.error('❌ 章节API调用失败:', error) + console.error('❌ 错误详情:', { message: (error as Error).message, stack: (error as Error).stack, response: (error as any).response?.data, @@ -622,33 +765,189 @@ export class CourseApi { return ApiRequest.get(`/courses/${courseId}/access`) } - // 辅助方法:映射难度等级 - private static mapDifficulty(difficulty: number): 'beginner' | 'intermediate' | 'advanced' { - switch (difficulty) { - case 0: - return 'beginner' - case 1: - return 'intermediate' - case 2: - return 'advanced' - default: - return 'beginner' + // 获取课程讲师列表 + static async getCourseInstructors(courseId: string): Promise> { + try { + console.log('🔍 获取课程讲师数据,课程ID:', courseId) + console.log('🔍 API请求URL: /biz/course/' + courseId + '/teachers') + + const response = await ApiRequest.get(`/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 } } - // 辅助方法:获取分类名称 - private static getCategoryName(categoryId: number): string { - // 这里可以根据categoryId返回对应的分类名称 - // 暂时返回默认值,后续可以通过分类API获取 - const categoryMap: { [key: number]: string } = { - 1: '信息技术', - 2: '数学', - 3: '物理', - 4: '化学', - 5: '生物' + // 获取章节视频列表 + static async getSectionVideos(courseId: string, sectionId: string): Promise> { + try { + console.log('🔍 获取章节视频数据,课程ID:', courseId, '章节ID:', sectionId) + console.log('🔍 API请求URL: /biz/course/' + courseId + '/section_video/' + sectionId) + + const response = await ApiRequest.get(`/biz/course/${courseId}/section_video/${sectionId}`) + 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 adaptedVideos: SectionVideo[] = response.data.result.map((video: BackendSectionVideo) => { + // 解析fileUrl中的多个清晰度URL + const qualities = this.parseVideoQualities(video.fileUrl) + + return { + id: video.id, + name: video.name, + description: video.description, + type: video.type, + thumbnailUrl: video.thumbnailUrl, + duration: video.duration, + fileSize: video.fileSize, + qualities: qualities, + defaultQuality: '360', // 默认360p + currentQuality: '360' // 当前选中360p + } + }) + + console.log('✅ 适配后的视频数据:', adaptedVideos) + + return { + code: response.data.code, + message: response.data.message, + data: adaptedVideos + } + } 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 } - return categoryMap[categoryId] || '其他' } + + // 解析视频URL(逗号分隔的多个清晰度URL) + private static parseVideoQualities(fileUrl: string): VideoQuality[] { + const qualities: VideoQuality[] = [] + + try { + if (!fileUrl || fileUrl.trim() === '') { + console.warn('视频URL为空') + return qualities + } + + // 按逗号分割URL + const urls = fileUrl.split(',').map(url => url.trim()).filter(url => url.length > 0) + console.log('🔍 分割后的视频URL:', urls) + + // 支持的清晰度列表(按优先级排序) + const supportedQualities = [ + { value: '1080', label: '1080p' }, + { value: '720', label: '720p' }, + { value: '480', label: '480p' }, + { value: '360', label: '360p' } + ] + + // 根据URL数量分配清晰度 + // 假设URL按清晰度从高到低排列:1080p, 720p, 480p, 360p + urls.forEach((url, index) => { + if (index < supportedQualities.length) { + qualities.push({ + label: supportedQualities[index].label, + value: supportedQualities[index].value, + url: url + }) + } + }) + + // 如果没有解析到任何清晰度,使用第一个URL作为360p + if (qualities.length === 0 && urls.length > 0) { + qualities.push({ + label: '360p', + value: '360', + url: urls[0] + }) + } + + console.log('✅ 解析后的视频清晰度:', qualities) + } catch (error) { + console.warn('解析视频清晰度失败,使用原始URL:', error) + // 如果解析失败,将整个fileUrl作为360p使用 + qualities.push({ + label: '360p', + value: '360', + url: fileUrl + }) + } + + return qualities + } + } export default CourseApi diff --git a/src/api/request.ts b/src/api/request.ts index 3fa02ab..851dd1e 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -12,9 +12,6 @@ const checkNetworkStatus = (): boolean => { return true // 默认认为网络可用 } -// 全局开关:强制使用Mock数据(当前需求:全部使用Mock,不访问后端) -const FORCE_ENABLE_MOCK = true - // 消息提示函数 - 使用window.alert作为fallback,实际项目中应该使用UI库的消息组件 const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') => { // 这里可以替换为你使用的UI库的消息组件 @@ -30,21 +27,36 @@ const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'i // 创建axios实例 const request: AxiosInstance = axios.create({ - // 统一走 /api,由 Vite 代理到后端,避免CORS;若启用Mock,则只会走本地Mock - baseURL: '/api', + baseURL: import.meta.env.VITE_API_BASE_URL || '/jeecgboot', timeout: 30000, // 增加到30秒 headers: { 'Content-Type': 'application/json', }, }) +// 不需要token的接口列表 +const NO_TOKEN_URLS = [ + '/biz/course/category/list', + '/biz/course/subject/list', + '/biz/course/difficulty/list', + '/biz/course/list', + '/biz/course/detail', + // 可以在这里添加其他不需要token的接口 +] + // 请求拦截器 request.interceptors.request.use( (config: InternalAxiosRequestConfig) => { - // 添加认证token - const userStore = useUserStore() - if (userStore.token) { - config.headers.Authorization = `Bearer ${userStore.token}` + // 检查是否需要添加token + const needToken = !NO_TOKEN_URLS.some(url => config.url?.includes(url)) + + if (needToken) { + // 添加认证token(直接传token,不需要Bearer前缀) + const userStore = useUserStore() + const token = userStore.token || localStorage.getItem('X-Access-Token') || '' + if (token) { + config.headers['X-Access-Token'] = token + } } // 添加请求时间戳 @@ -180,242 +192,7 @@ request.interceptors.response.use( } ) -// 导入Mock数据 -import { mockCourses, getMockCourseSections } from './mock/courses' -// getMockCourseDetail 暂时注释,后续需要时再启用 -// Mock数据处理 -const handleMockRequest = async (url: string, method: string, data?: any): Promise> => { - console.log('🚀 Mock Request:', { url, method, data }) - - // 模拟网络延迟 - await new Promise(resolve => setTimeout(resolve, 500)) - - // 登录Mock - if (url === '/users/login' && method === 'POST') { - const { email, phone, password } = data || {} - const loginField = phone || email - - // 模拟登录验证 - if (loginField && password) { - return { - code: 200, - message: '登录成功', - data: { - user: { - id: 1, - email: email || `${phone}@example.com`, - phone: phone || '123456789', - username: phone || email?.split('@')[0] || 'user', - nickname: '测试用户', - avatar: 'https://via.placeholder.com/100', - role: 'student', - createdAt: '2024-01-01T00:00:00Z', - updatedAt: '2024-01-01T00:00:00Z' - }, - token: 'mock_jwt_token_' + Date.now(), - refreshToken: 'mock_refresh_token_' + Date.now(), - expiresIn: 3600 - } - } as ApiResponse - } else { - return { - code: 400, - message: '手机号/邮箱或密码不能为空', - data: null - } as ApiResponse - } - } - - // 注册Mock - if (url === '/auth/register' && method === 'POST') { - const { email, password, verificationCode } = data || {} - - if (!email || !password) { - return { - code: 400, - message: '邮箱和密码不能为空', - data: null - } as ApiResponse - } - - if (!verificationCode) { - return { - code: 400, - message: '验证码不能为空', - data: null - } as ApiResponse - } - - return { - code: 200, - message: '注册成功', - data: { - id: 2, - email: email, - username: email.split('@')[0], - nickname: '新用户', - avatar: '', - role: 'student', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() - } - } as ApiResponse - } - - // 发送验证码Mock - if (url === '/auth/send-verification' && method === 'POST') { - return { - code: 200, - message: '验证码已发送', - data: null - } as ApiResponse - } - - // 获取当前用户信息Mock - if (url === '/auth/me' && method === 'GET') { - return { - code: 200, - message: '获取成功', - data: { - id: 1, - email: 'test@example.com', - username: 'test', - nickname: '测试用户', - avatar: '', - role: 'student', - createdAt: '2024-01-01T00:00:00Z', - updatedAt: '2024-01-01T00:00:00Z' - } - } as ApiResponse - } - - // 用户登出Mock - if (url === '/auth/logout' && method === 'POST') { - return { - code: 200, - message: '登出成功', - data: null - } as ApiResponse - } - - // 课程详情Mock - if (url === '/lesson/detail' && method === 'GET') { - // 对于GET请求,参数直接在data中(data就是params对象) - const id = data?.id - console.log('课程详情Mock - 获取到的ID:', id, '原始data:', data) - - if (!id) { - return { - code: 400, - message: '课程ID必填', - data: null - } as ApiResponse - } - - // 根据课程ID提供不同的模拟数据 - const courseData = { - 1: { - name: 'DeepSeek大语言模型实战应用', - cover: 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=300&fit=crop', - price: '299.00', - school: 'DeepSeek技术学院', - description: '本课程深度聚焦DeepSeek大语言模型的实际应用,让每一位学员了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。', - position: 'AI技术专家 / 高级讲师' - }, - 2: { - name: 'Python编程基础与实战', - cover: 'https://images.unsplash.com/photo-1526379095098-d400fd0bf935?w=400&h=300&fit=crop', - price: '199.00', - school: '编程技术学院', - description: '从零开始学习Python编程,涵盖基础语法、数据结构、面向对象编程等核心概念,通过实际项目练习掌握Python开发技能。', - position: 'Python开发专家 / 资深讲师' - }, - 3: { - name: 'Web前端开发全栈课程', - cover: 'https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=400&h=300&fit=crop', - price: '399.00', - school: '前端技术学院', - description: '全面学习现代Web前端开发技术,包括HTML5、CSS3、JavaScript、Vue.js、React等主流框架,培养全栈开发能力。', - position: '前端架构师 / 技术总监' - } - } - - const currentCourse = courseData[id as keyof typeof courseData] || courseData[1] - - // 模拟课程详情数据 - return { - code: 0, - message: '查询课程详情成功', - data: { - id: parseInt(id), - name: currentCourse.name, - cover: currentCourse.cover, - categoryId: 1, - price: currentCourse.price, - school: currentCourse.school, - description: currentCourse.description, - teacherId: 1, - outline: '

课程大纲:

  • 第一章:基础入门
    - 环境搭建与配置
    - 基本概念理解
    - 实践操作演示
  • 第二章:核心技能
    - 核心功能详解
    - 实际应用场景
    - 案例分析讲解
  • 第三章:高级应用
    - 进阶技巧掌握
    - 项目实战演练
    - 问题解决方案
', - prerequisite: '具备基本的计算机操作能力', - target: '掌握核心技能,能够在实际工作中熟练应用', - arrangement: '理论与实践相结合,循序渐进的学习方式', - startTime: '2025-01-26 10:13:17', - endTime: '2025-03-26 10:13:17', - revision: 1, - position: currentCourse.position, - createdAt: 1737944724, - updatedAt: 1737944724, - updatedTime: null - } - } as ApiResponse - } - - // 课程列表Mock - if (url === '/lesson/list' && method === 'GET') { - return { - code: 0, - message: '查询课程列表成功', - data: { - list: mockCourses, - total: mockCourses.length - } - } as ApiResponse - } - - // 课程章节列表Mock - if (url === '/lesson/section/list' && method === 'GET') { - const lessonId = data?.lesson_id - console.log('课程章节Mock - 获取到的lesson_id:', lessonId, '原始data:', data) - - if (!lessonId) { - return { - code: 400, - message: '课程ID必填', - data: null - } as ApiResponse - } - - const mockSections = getMockCourseSections(parseInt(lessonId)) - - return { - code: 200, - message: '获取成功', - data: { - list: mockSections, - total: mockSections.length - }, - timestamp: new Date().toISOString() - } as ApiResponse - } - - // 默认404响应 - return { - code: 404, - message: '接口不存在', - data: null - } as ApiResponse -} // 重试机制 const retryRequest = async ( @@ -459,19 +236,7 @@ export class ApiRequest { params?: any, config?: AxiosRequestConfig ): Promise> { - const enableMock = FORCE_ENABLE_MOCK || ((import.meta as any).env?.VITE_ENABLE_MOCK === 'true') - // 优先:若显式启用Mock,直接使用Mock - if (enableMock) { - return handleMockRequest(url, 'GET', params) - } - - try { - return await retryRequest(() => request.get(url, { params, ...config })) - } catch (error) { - console.warn('API请求失败,降级到Mock数据:', error) - // 后备:真实API失败时,仍回落到Mock,保障页面可用 - return handleMockRequest(url, 'GET', params) - } + return await retryRequest(() => request.get(url, { params, ...config })) } // POST 请求 @@ -480,17 +245,7 @@ export class ApiRequest { data?: any, config?: AxiosRequestConfig ): Promise> { - const enableMock = FORCE_ENABLE_MOCK || ((import.meta as any).env?.VITE_ENABLE_MOCK === 'true') - if (enableMock) { - return handleMockRequest(url, 'POST', data) - } - - try { - return await retryRequest(() => request.post(url, data, config)) - } catch (error) { - console.warn('API请求失败,降级到Mock数据:', error) - return handleMockRequest(url, 'POST', data) - } + return await retryRequest(() => request.post(url, data, config)) } // PUT 请求 diff --git a/src/api/types.ts b/src/api/types.ts index 05628d9..7f4de62 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -50,6 +50,7 @@ export interface UserProfile { export interface LoginRequest { email?: string phone?: string + username?: string // 新增username字段,用于适配后端API password: string captcha?: string } @@ -81,7 +82,7 @@ export interface RegisterRequest { // 课程相关类型 export interface Course { - id: number + id: string // 改为string类型,保持后端ID的原始格式 title: string subtitle?: string description: string @@ -175,6 +176,134 @@ export interface CourseCategory { children?: CourseCategory[] } +// 后端分类数据格式 +export interface BackendCourseCategory { + id: string + name: string + sortOrder: number + createBy: string + createTime: string + updateBy: string + updateTime: string +} + +// 后端分类列表响应格式 +export interface BackendCourseCategoryListResponse { + success: boolean + message: string + code: number + result: BackendCourseCategory[] + timestamp: number +} + +// 后端专题数据格式(字典值格式) +export interface BackendCourseSubject { + value: string + label: string +} + +// 前端专题数据格式 +export interface CourseSubject { + id: string // 改为string类型,保持后端value字段的原始格式 + name: string + slug: string + description?: string + sortOrder: number +} + +// 后端专题列表响应格式 +export interface BackendCourseSubjectListResponse { + success: boolean + message: string + code: number + result: BackendCourseSubject[] + timestamp: number +} + +// 后端难度数据格式(字典值格式) +export interface BackendCourseDifficulty { + value: string + label: string +} + +// 前端难度数据格式 +export interface CourseDifficulty { + id: string // 改为string类型,保持后端value字段的原始格式 + name: string + slug: string + description?: string + sortOrder: number +} + +// 后端难度列表响应格式 +export interface BackendCourseDifficultyListResponse { + success: boolean + message: string + code: number + result: BackendCourseDifficulty[] + timestamp: number +} + +// 课程列表查询参数 +export interface CourseListQueryParams { + categoryId?: string // 分类ID + difficulty?: string // 难度值 + subject?: string // 专题值 + page?: number // 页码 + pageSize?: number // 每页数量 +} + +// 后端课程数据格式 +export interface BackendCourseItem { + id: string + name: string + cover: string + video: string + school: string + description: string + type: number + target: string + difficulty: number + subject: string + outline: string + prerequisite: string + reference: string + arrangement: string + startTime: string + endTime: string + enrollCount: number + maxEnroll: number + status: number + question: string + createBy: string + createTime: string + updateBy: string + updateTime: string +} + +// 后端课程列表响应格式 +export interface BackendCourseListResponse { + success: boolean + message: string + code: number + result: BackendCourseItem[] + timestamp: number +} + +// 课程详情查询参数 +export interface CourseDetailQueryParams { + id: string +} + +// 后端课程详情响应格式 +export interface BackendCourseDetailResponse { + success: boolean + message: string + code: number + result: BackendCourseItem + timestamp: number +} + export interface Instructor { id: number name: string @@ -239,32 +368,32 @@ export interface LessonResource { // 后端API返回的章节数据结构 export interface BackendCourseSection { - id: number - lessonId: number - videoUrl: string // 视频链接 + id: string + courseId: string name: string // 章节名称 + type: number | null // 章节类型:0=视频、1=资料、2=考试、3=作业,null=未设置 sortOrder: number // 排序 - parentId: number // 父章节ID - level: number // 层级:0=子级(课时),1=父级(章节) - revision: number // 版本号 - createdBy: number - createdTime: string | null - updatedBy: number - updatedTime: string | null + parentId: string // 父章节ID + level: number // 章节层级:0=一级章节、1=二级章节 + createBy: string + createTime: string + updateBy: string + updateTime: string } // 前端使用的课程章节类型(适配后的数据结构) export interface CourseSection { - id: number - lessonId: number - outline: string // 章节大纲/内容链接(从videoUrl适配) + id: string // 改为string类型,保持一致性 + lessonId: string // 改为string类型,与Course.id保持一致 + outline: string // 章节大纲/内容链接 name: string // 章节名称 - parentId: number // 父章节ID + type: number | null // 章节类型:0=视频、1=资料、2=考试、3=作业,null=未设置 + parentId: string // 父章节ID,改为string类型 sort: number // 排序(从sortOrder适配) - level: number // 层级:0=父级(章节),1=子级(课时)- 已从后端数据转换 + level: number // 层级:0=一级章节、1=二级章节 revision: number // 版本号 - createdAt: number | null // 从createdTime适配 - updatedAt: number | null // 从updatedTime适配 + createdAt: number | null // 从createTime适配 + updatedAt: number | null // 从updateTime适配 deletedAt: string | null completed?: boolean // 是否已完成(前端状态) duration?: string // 课时时长(前端计算) @@ -272,8 +401,78 @@ export interface CourseSection { // 后端章节列表响应格式 export interface BackendCourseSectionListResponse { - list: BackendCourseSection[] - total: number + success: boolean + 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 +} + +// 后端章节视频数据结构 +export interface BackendSectionVideo { + id: string + name: string + description: string + type: number + fileUrl: string // 视频文件URL,包含多个清晰度 + thumbnailUrl: string + duration: number + fileSize: number + metadata: string + izFeatured: number + status: number + createdBy: string + createdTime: string + updatedBy: string + updatedTime: string +} + +// 后端章节视频列表响应格式 +export interface BackendSectionVideoListResponse { + success: boolean + message: string + code: number + result: BackendSectionVideo[] + timestamp: number +} + +// 前端视频质量选项 +export interface VideoQuality { + label: string // 显示名称:1080p, 720p, 480p, 360p + value: string // 实际值:1080, 720, 480, 360 + url: string // 对应的视频URL +} + +// 前端章节视频类型 +export interface SectionVideo { + id: string + name: string + description: string + type: number + thumbnailUrl: string + duration: number + fileSize: number + qualities: VideoQuality[] // 可用的视频质量选项 + defaultQuality: string // 默认质量(360p) + currentQuality: string // 当前选中的质量 } // 前端章节列表响应格式 diff --git a/src/components/auth/LoginModal.vue b/src/components/auth/LoginModal.vue index 79cf7ac..e0a91e4 100644 --- a/src/components/auth/LoginModal.vue +++ b/src/components/auth/LoginModal.vue @@ -100,15 +100,21 @@ const handleLogin = async () => { isLoading.value = true try { console.log('🚀 开始登录:', { account: loginForm.account, password: '***' }) + console.log('🔍 表单密码长度:', loginForm.password?.length) + console.log('🔍 表单密码内容:', loginForm.password) // 判断输入的是手机号还是邮箱 const isPhone = /^[0-9]+$/.test(loginForm.account) - // 调用登录API - const response = await AuthApi.login({ + const loginParams = { ...(isPhone ? { phone: loginForm.account } : { email: loginForm.account }), password: loginForm.password - }) + } + + console.log('🔍 准备发送的登录参数:', loginParams) + + // 调用登录API + const response = await AuthApi.login(loginParams) console.log('✅ 登录响应:', response) @@ -120,6 +126,7 @@ const handleLogin = async () => { userStore.token = token // 保存到本地存储 + localStorage.setItem('X-Access-Token', token) localStorage.setItem('token', token) localStorage.setItem('refreshToken', refreshToken || '') localStorage.setItem('user', JSON.stringify(user)) diff --git a/src/data/mockCourses.ts b/src/data/mockCourses.ts deleted file mode 100644 index 8a7a541..0000000 --- a/src/data/mockCourses.ts +++ /dev/null @@ -1,661 +0,0 @@ -// 课程模拟数据 -import type { Course } from '@/api/types' - -export const mockCourses: Course[] = [ - { - "id": 1, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities5.png", - - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 2, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities4.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 3, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities3.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 4, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course5.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 5, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities1.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 6, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities5.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 7, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities4.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 8, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities4.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 9, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities5.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 10, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities4.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 11, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities5.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 12, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities4.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 13, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities4.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 14, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities5.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - }, - { - "id": 15, - "title": "暑期名师领学,提高班级教学质量!高效冲分指南", - "description": "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", - "thumbnail": "/images/courses/course-activities4.png", - "price": 99, - "originalPrice": 199, - "currency": "CNY", - "rating": 4.8, - "ratingCount": 324, - "studentsCount": 324, - "duration": "12小时43分钟", - "totalLessons": 54, - "level": "beginner", - "language": "zh-CN", - "category": { - "id": 1, - "name": "教育培训", - "slug": "education" - }, - "tags": ["教学质量", "高效学习", "名师指导"], - "skills": ["教学方法", "课堂管理", "学习指导"], - "requirements": ["具备基本的计算机操作能力"], - "objectives": ["掌握核心技能,能够在实际工作中熟练应用"], - "instructor": { - "id": 1, - "name": "刘莹", - "avatar": "https://via.placeholder.com/100", - "bio": "资深教育专家", - "title": "高级讲师", - "rating": 4.8, - "studentsCount": 1200, - "coursesCount": 15, - "experience": "10年教学经验", - "education": ["教育学博士", "计算机科学硕士"], - "certifications": ["高级讲师认证", "教学质量奖"] - }, - "status": "published", - "createdAt": "2025-01-26T10:13:17Z", - "updatedAt": "2025-01-26T10:13:17Z" - } -] - -// 根据ID获取课程 -export const getCourseById = (id: number): Course | undefined => { - return mockCourses.find(course => course.id === id) -} - -// 获取热门课程(前5个) -export const getPopularCourses = (): Course[] => { - return mockCourses.slice(0, 4) -} - -// 根据分类筛选课程 -export const getCoursesByCategory = (categoryName: string): Course[] => { - if (categoryName === '全部') return mockCourses - return mockCourses.filter(course => course.category.name === categoryName) -} - -// 根据难度筛选课程 -export const getCoursesByLevel = (level: string): Course[] => { - const levelMap: { [key: string]: string } = { - '初级': 'beginner', - '中级': 'intermediate', - '高级': 'advanced' - } - const targetLevel = levelMap[level] - if (!targetLevel) return mockCourses - return mockCourses.filter(course => course.level === targetLevel) -} - -// 搜索课程 -export const searchCourses = (keyword: string): Course[] => { - if (!keyword) return mockCourses - const lowerKeyword = keyword.toLowerCase() - return mockCourses.filter(course => - course.title.toLowerCase().includes(lowerKeyword) || - course.description.toLowerCase().includes(lowerKeyword) || - course.tags.some(tag => tag.toLowerCase().includes(lowerKeyword)) || - course.skills.some(skill => skill.toLowerCase().includes(lowerKeyword)) - ) -} diff --git a/src/stores/course.ts b/src/stores/course.ts index 11580ff..852a837 100644 --- a/src/stores/course.ts +++ b/src/stores/course.ts @@ -7,7 +7,7 @@ export type Course = ApiCourse export interface Lesson { id: number - courseId: number + courseId: string // 改为string类型,与Course.id保持一致 title: string description: string videoUrl?: string @@ -63,13 +63,13 @@ export const useCourseStore = defineStore('course', () => { console.log('尝试从API获取课程数据...') const response = await CourseApi.getCourses() console.log('API响应:', response) - courses.value = response.data.list + courses.value = response.data } catch (error) { console.error('API调用失败,使用模拟数据:', error) // 如果API调用失败,使用模拟数据作为后备 const mockCourses: Course[] = [ { - id: 1, + id: "1", title: 'Vue.js 3 完整教程', description: '从零开始学习Vue.js 3,包括Composition API、TypeScript集成等现代开发技术', content: '详细的Vue.js 3课程内容', @@ -114,7 +114,7 @@ export const useCourseStore = defineStore('course', () => { publishedAt: '2024-01-01' }, { - id: 2, + id: "2", title: 'React 18 实战开发', description: '掌握React 18的新特性,包括并发渲染、Suspense等高级功能', content: '详细的React 18课程内容', @@ -159,7 +159,7 @@ export const useCourseStore = defineStore('course', () => { publishedAt: '2024-01-05' }, { - id: 3, + id: "3", title: 'Node.js 后端开发', description: '学习Node.js后端开发,包括Express、数据库操作、API设计等', content: '详细的Node.js课程内容', @@ -210,7 +210,7 @@ export const useCourseStore = defineStore('course', () => { } } - const fetchCourseById = async (id: number) => { + const fetchCourseById = async (id: string) => { isLoading.value = true try { const response = await CourseApi.getCourseById(id) @@ -227,7 +227,7 @@ export const useCourseStore = defineStore('course', () => { } } - const fetchLessons = async (courseId: number) => { + const fetchLessons = async (courseId: string) => { isLoading.value = true try { // 模拟API调用 @@ -271,7 +271,7 @@ export const useCourseStore = defineStore('course', () => { } } - const enrollCourse = async (courseId: number) => { + const enrollCourse = async (courseId: string) => { isLoading.value = true try { // 模拟API调用 @@ -292,7 +292,7 @@ export const useCourseStore = defineStore('course', () => { } } - const updateProgress = async (courseId: number, progress: number) => { + const updateProgress = async (courseId: string, progress: number) => { const course = enrolledCourses.value.find(c => c.id === courseId) if (course) { course.progress = progress diff --git a/src/stores/user.ts b/src/stores/user.ts index 64ab0aa..076caf6 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -8,7 +8,7 @@ export interface User extends ApiUser {} export const useUserStore = defineStore('user', () => { // 状态 const user = ref(null) - const token = ref(localStorage.getItem('token')) + const token = ref(localStorage.getItem('X-Access-Token')) const isLoading = ref(false) // 计算属性 @@ -49,7 +49,7 @@ export const useUserStore = defineStore('user', () => { // 无论API调用是否成功,都清除本地数据 user.value = null token.value = null - localStorage.removeItem('token') + localStorage.removeItem('X-Access-Token') localStorage.removeItem('refreshToken') localStorage.removeItem('user') localStorage.removeItem('rememberMe') @@ -136,7 +136,7 @@ export const useUserStore = defineStore('user', () => { const initializeAuth = async () => { const savedUser = localStorage.getItem('user') - const savedToken = localStorage.getItem('token') + const savedToken = localStorage.getItem('X-Access-Token') if (savedUser && savedToken) { try { diff --git a/src/views/CourseDetail.vue b/src/views/CourseDetail.vue index 3d5a0ee..bac4820 100644 --- a/src/views/CourseDetail.vue +++ b/src/views/CourseDetail.vue @@ -140,7 +140,15 @@

讲师

-
+ +
+

正在加载讲师信息...

+
+
+

{{ instructorsError }}

+ +
+
@@ -463,7 +471,7 @@ import RegisterModal from '@/components/auth/RegisterModal.vue' const route = useRoute() const router = useRouter() const userStore = useUserStore() -const courseId = ref(Number(route.params.id)) +const courseId = ref(route.params.id as string) const { loginModalVisible, registerModalVisible, handleAuthSuccess, showLoginModal } = useAuth() // enrollCourse 暂时未使用,后续需要时再启用 @@ -480,6 +488,10 @@ const courseSections = ref([]) const sectionsLoading = ref(false) const sectionsError = ref('') +// 讲师数据 +const instructorsLoading = ref(false) +const instructorsError = ref('') + // 报名状态管理 const isEnrolled = ref(false) // 用户是否已报名该课程 const enrollmentLoading = ref(false) // 报名加载状态 @@ -525,46 +537,12 @@ interface ChapterGroup { const groupedSections = ref([]) -// 生成模拟章节数据(用于演示) -const generateMockSections = (): CourseSection[] => { - 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 }, +// 生成模拟章节数据(暂时禁用,等待API修复) +// const generateMockSections = (): CourseSection[] => { +// return [] +// } - // 第二章 - 程序设计基础知识 (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[]) => { @@ -603,10 +581,10 @@ const groupSectionsByChapter = (sections: CourseSection[]) => { // console.log('没有章节数据,生成模拟数据') // courseSections.value = generateMockSections() // } - +// // console.log('开始生成章节分组,原始数据:', courseSections.value) // console.log('章节数据数量:', courseSections.value.length) - +// // // 使用统一的分组函数 // groupedSections.value = groupSectionsByChapter(courseSections.value) // console.log('生成的章节分组:', groupedSections.value) @@ -715,7 +693,7 @@ const displayComments = ref([ const loadCourseDetail = async () => { console.log('开始加载课程详情,课程ID:', courseId.value) - if (!courseId.value || isNaN(courseId.value)) { + if (!courseId.value || courseId.value.trim() === '') { error.value = '课程ID无效' console.error('课程ID无效:', courseId.value) return @@ -768,7 +746,7 @@ const loadCourseDetail = async () => { // 加载课程章节列表 const loadCourseSections = async () => { - if (!courseId.value || isNaN(courseId.value)) { + if (!courseId.value || courseId.value.trim() === '') { sectionsError.value = '课程ID无效' console.error('课程ID无效:', courseId.value) return @@ -783,17 +761,17 @@ const loadCourseSections = async () => { console.log('章节API响应:', response) if (response.code === 0 || response.code === 200) { - if (response.data && Array.isArray(response.data)) { - courseSections.value = response.data - groupedSections.value = groupSectionsByChapter(response.data) - console.log('章节数据设置成功:', courseSections.value) - console.log('分组数据:', groupedSections.value) + if (response.data && response.data.list && Array.isArray(response.data.list)) { + courseSections.value = response.data.list + groupedSections.value = groupSectionsByChapter(response.data.list) + console.log('✅ 章节数据设置成功:', courseSections.value) + console.log('✅ 分组数据:', groupedSections.value) } else { - console.log('API返回的章节数据为空,使用模拟数据') + console.log('⚠️ API返回的章节数据为空,使用模拟数据') loadMockData() } } else { - console.log('API返回错误,使用模拟数据') + console.log('⚠️ API返回错误,使用模拟数据') loadMockData() } } catch (err) { @@ -807,15 +785,47 @@ const loadCourseSections = async () => { // 加载模拟数据 const loadMockData = () => { - console.log('加载模拟章节数据') - const mockSections = generateMockSections() - courseSections.value = mockSections - groupedSections.value = groupSectionsByChapter(mockSections) + console.log('⚠️ API调用失败,暂不使用模拟数据') + // 暂时不加载模拟数据,等待API修复 + courseSections.value = [] + groupedSections.value = [] +} - // 计算学习进度 - // const completed = mockSections.filter(section => section.completed).length - // completedLessons.value = completed - // progress.value = Math.round((completed / mockSections.length) * 100) +// 加载课程讲师列表 +const loadCourseInstructors = async () => { + if (!courseId.value || courseId.value.trim() === '') { + 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 + } } // 切换章节展开/折叠 @@ -859,7 +869,7 @@ const formatLessonDuration = (section: CourseSection): string => { ] // 根据section.id获取对应时长 - const durationIndex = section.id - 1 + const durationIndex = parseInt(section.id) - 1 if (durationIndex >= 0 && durationIndex < durations.length) { return durations[durationIndex] || '' } @@ -931,19 +941,8 @@ const handleDownload = (section: CourseSection) => { // 处理作业操作 const handleHomework = (section: CourseSection) => { console.log('打开作业:', section) - - // 跳转到练习页面 - router.push({ - name: 'Practice', - params: { - courseId: courseId.value, - sectionId: section.id - }, - query: { - courseName: course.value?.title || '课程名称', - practiceName: section.name - } - }) + // 这里可以跳转到作业页面 + alert(`打开作业: ${section.name}`) } // 处理考试操作 @@ -1142,7 +1141,8 @@ onMounted(() => { console.log('课程详情页加载完成,课程ID:', courseId.value) initializeMockState() // 初始化模拟状态 loadCourseDetail() - loadCourseSections() + loadCourseSections() // 启用章节接口调用 + loadCourseInstructors() // 启用讲师接口调用 }) @@ -1832,6 +1832,34 @@ onMounted(() => { 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 { height: 1px; diff --git a/src/views/CourseDetailEnrolled.vue b/src/views/CourseDetailEnrolled.vue index a1b98b4..5463a22 100644 --- a/src/views/CourseDetailEnrolled.vue +++ b/src/views/CourseDetailEnrolled.vue @@ -49,6 +49,29 @@

请选择要播放的视频课程

+ + +
+
+ +
+
+ {{ quality.label }} +
+
+
+
@@ -377,7 +400,7 @@ import { ref, onMounted, onUnmounted, computed, nextTick } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useUserStore } from '@/stores/user' import { CourseApi } from '@/api/modules/course' -import type { Course, CourseSection } from '@/api/types' +import type { Course, CourseSection, SectionVideo, VideoQuality } from '@/api/types' import SafeAvatar from '@/components/common/SafeAvatar.vue' import LearningProgressStats from '@/components/common/LearningProgressStats.vue' import NotesModal from '@/components/common/NotesModal.vue' @@ -395,7 +418,7 @@ declare global { const route = useRoute() const router = useRouter() const userStore = useUserStore() -const courseId = ref(Number(route.params.id)) +const courseId = ref(route.params.id as string) // 强制仅播放本地视频(如需关闭,置为 false) const FORCE_LOCAL_VIDEO = true @@ -405,6 +428,13 @@ const currentSection = ref(null) const currentVideoUrl = ref('') const ckplayer = ref(null) +// 视频相关状态 +const currentVideo = ref(null) +const videoQualities = ref([]) +const currentQuality = ref('360') // 默认360p +const videoLoading = ref(false) +const showQualityMenu = ref(false) + // 视频源配置 const VIDEO_CONFIG = { // 本地视频(当前使用) @@ -534,74 +564,50 @@ const displayComments = ref([ } ]) -// 生成模拟章节数据(用于演示) +// 生成模拟章节数据(暂时禁用) const generateMockSections = (): CourseSection[] => { - 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' } - ] + // 暂时返回空数组,等待API修复 + return [] } -// 将章节按章分组 +// 将章节按章分组 - 根据后端数据结构重新实现 const groupSectionsByChapter = (sections: CourseSection[]) => { - const chapterTitles = [ - '课前准备', - '程序设计基础知识', - '实战项目', - '高级应用', - '拓展学习', - '答疑与交流' - ] + console.log('🔍 开始分组章节数据:', sections) const groups: ChapterGroup[] = [] - let sectionsPerChapter = [4, 5, 6, 4, 3, 2] // 每章的课程数量 - let sectionIndex = 0 - for (let i = 0; i < chapterTitles.length; i++) { - const chapterSections = sections.slice(sectionIndex, sectionIndex + sectionsPerChapter[i]) - if (chapterSections.length > 0) { - groups.push({ - title: chapterTitles[i], - sections: chapterSections, - expanded: i === 0 // 默认展开第一章 - }) - } - sectionIndex += sectionsPerChapter[i] + // 找出所有一级章节(level=1,这些是父章节) + const parentChapters = sections.filter(section => section.level === 1) + console.log('🔍 找到一级章节:', parentChapters) + + // 为每个一级章节创建分组 + parentChapters.forEach((parentChapter, index) => { + // 找出该章节下的所有子章节(level=2,parentId匹配) + const childSections = sections.filter(section => + section.level === 2 && section.parentId === parentChapter.id + ) + + console.log(`🔍 章节"${parentChapter.name}"的子章节:`, childSections) + + // 创建章节分组 + groups.push({ + title: parentChapter.name, // 使用后端返回的章节名称 + sections: childSections.length > 0 ? childSections : [parentChapter], // 如果有子章节就用子章节,否则用父章节本身 + expanded: index === 0 // 默认展开第一章 + }) + }) + + // 如果没有找到一级章节,可能所有章节都是同级的,直接作为一个组 + if (groups.length === 0 && sections.length > 0) { + console.log('🔍 没有找到层级结构,将所有章节作为一组') + groups.push({ + title: '课程章节', + sections: sections, + expanded: true + }) } + console.log('✅ 章节分组完成:', groups) return groups } @@ -609,7 +615,7 @@ const groupSectionsByChapter = (sections: CourseSection[]) => { const loadCourseDetail = async () => { console.log('开始加载课程详情,课程ID:', courseId.value) - if (!courseId.value || isNaN(courseId.value)) { + if (!courseId.value || courseId.value.trim() === '') { error.value = '课程ID无效' console.error('课程ID无效:', courseId.value) return @@ -640,7 +646,7 @@ const loadCourseDetail = async () => { // 加载课程章节列表 const loadCourseSections = async () => { - if (!courseId.value || isNaN(courseId.value)) { + if (!courseId.value || courseId.value.trim() === '') { sectionsError.value = '课程ID无效' console.error('课程ID无效:', courseId.value) return @@ -655,11 +661,11 @@ const loadCourseSections = async () => { console.log('章节API响应:', response) if (response.code === 0 || response.code === 200) { - if (response.data && Array.isArray(response.data)) { - courseSections.value = response.data - groupedSections.value = groupSectionsByChapter(response.data) - console.log('章节数据设置成功:', courseSections.value) - console.log('分组数据:', groupedSections.value) + if (response.data && response.data.list && Array.isArray(response.data.list)) { + courseSections.value = response.data.list + groupedSections.value = groupSectionsByChapter(response.data.list) + console.log('✅ 章节数据设置成功:', courseSections.value) + console.log('✅ 分组数据:', groupedSections.value) // 默认播放右侧第一个视频章节(当未强制使用本地视频时) if (!FORCE_LOCAL_VIDEO) { const firstVideo = courseSections.value.find(s => s.outline && (s.outline.includes('.m3u8') || s.outline.includes('.mp4'))) @@ -721,6 +727,103 @@ const toggleChapter = (chapterIndex: number) => { groupedSections.value[chapterIndex].expanded = !groupedSections.value[chapterIndex].expanded } +// 加载章节视频 +const loadSectionVideo = async (section: CourseSection) => { + try { + videoLoading.value = true + console.log('🔍 加载章节视频,章节ID:', section.id) + + const response = await CourseApi.getSectionVideos(courseId.value, section.id) + console.log('🔍 视频API响应:', response) + + if (response.code === 0 || response.code === 200) { + if (response.data && response.data.length > 0) { + const video = response.data[0] // 取第一个视频 + currentVideo.value = video + videoQualities.value = video.qualities + currentQuality.value = video.defaultQuality + + // 获取默认清晰度的URL + const defaultQualityVideo = video.qualities.find(q => q.value === video.defaultQuality) + if (defaultQualityVideo) { + currentVideoUrl.value = defaultQualityVideo.url + console.log('✅ 设置视频URL:', currentVideoUrl.value) + + // 更新播放器 + await updateVideoPlayer() + } + } else { + console.warn('⚠️ 没有找到视频数据') + } + } else { + console.error('❌ 获取视频失败:', response.message) + } + } catch (error) { + console.error('❌ 加载章节视频失败:', error) + } finally { + videoLoading.value = false + } +} + +// 切换视频清晰度 +const changeVideoQuality = async (quality: string) => { + if (!currentVideo.value) return + + const qualityVideo = currentVideo.value.qualities.find(q => q.value === quality) + if (qualityVideo) { + currentQuality.value = quality + currentVideoUrl.value = qualityVideo.url + console.log('🔍 切换清晰度到:', quality, 'URL:', qualityVideo.url) + + // 更新播放器 + await updateVideoPlayer() + } +} + +// 更新视频播放器 +const updateVideoPlayer = async () => { + if (!currentVideoUrl.value) { + console.warn('⚠️ 视频URL为空,无法更新播放器') + return + } + + try { + console.log('🔍 更新播放器视频源:', currentVideoUrl.value) + + if (ckplayer.value) { + // 尝试不同的CKPlayer API方法 + if (typeof ckplayer.value.newVideo === 'function') { + console.log('✅ 使用newVideo方法更新视频源') + ckplayer.value.newVideo(currentVideoUrl.value) + } else if (typeof ckplayer.value.changeVideo === 'function') { + console.log('✅ 使用changeVideo方法更新视频源') + ckplayer.value.changeVideo(currentVideoUrl.value) + } else if (typeof ckplayer.value.videoSrc === 'function') { + console.log('✅ 使用videoSrc方法更新视频源') + ckplayer.value.videoSrc(currentVideoUrl.value) + } else { + console.log('⚠️ 未找到合适的更新方法,重新初始化播放器') + // 如果没有找到合适的方法,重新初始化播放器 + await nextTick() + initCKPlayer(currentVideoUrl.value) + } + } else { + console.log('🔍 播放器未初始化,开始初始化') + await nextTick() + initCKPlayer(currentVideoUrl.value) + } + } catch (error) { + console.error('❌ 更新播放器失败:', error) + // 如果更新失败,尝试重新初始化 + try { + await nextTick() + initCKPlayer(currentVideoUrl.value) + } catch (initError) { + console.error('❌ 重新初始化播放器也失败:', initError) + } + } +} + // 获取章节编号 const getChapterNumber = (num: number) => { const numbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] @@ -729,19 +832,39 @@ const getChapterNumber = (num: number) => { // 课程类型判断函数 const isVideoLesson = (section: CourseSection) => { - console.log(section.outline) + console.log('检查章节类型:', section.name, 'type:', section.type, 'outline:', section.outline) + // 优先根据type字段判断:0=视频 + if (section.type === 0) { + return true + } + // 如果type为null,则根据outline判断 return section.outline && (section.outline.includes('.m3u8') || section.outline.includes('.mp4')) } const isResourceLesson = (section: CourseSection) => { + // 优先根据type字段判断:1=资料 + if (section.type === 1) { + return true + } + // 如果type为null,则根据outline或名称判断 return section.outline && (section.outline.includes('.pdf') || section.outline.includes('.ppt') || section.outline.includes('.zip')) } const isHomeworkLesson = (section: CourseSection) => { + // 优先根据type字段判断:3=作业 + if (section.type === 3) { + return true + } + // 如果type为null,则根据名称判断 return section.name.includes('作业') || section.name.includes('练习') } const isExamLesson = (section: CourseSection) => { + // 优先根据type字段判断:2=考试 + if (section.type === 2) { + return true + } + // 如果type为null,则根据名称判断 return section.name.includes('考试') || section.name.includes('测试') } @@ -771,18 +894,39 @@ const formatLessonDuration = (section: CourseSection) => { // 处理章节点击 - 已报名状态,可以正常点击 const handleSectionClick = (section: CourseSection) => { - console.log('点击课程章节:', section.name) + console.log('🔍 点击课程章节:', section.name, section) currentSection.value = section - // 如果是视频课程,直接播放 - if (isVideoLesson(section)) { - handleVideoPlay(section) - } else if (isResourceLesson(section)) { + // 检查章节类型 + const isVideo = isVideoLesson(section) + const isResource = isResourceLesson(section) + const isHomework = isHomeworkLesson(section) + const isExam = isExamLesson(section) + + console.log('🔍 章节类型判断结果:', { + isVideo, + isResource, + isHomework, + isExam, + type: section.type + }) + + // 如果是视频课程,加载视频数据 + if (isVideo) { + console.log('✅ 识别为视频课程,开始加载视频数据') + loadSectionVideo(section) + } else if (isResource) { + console.log('✅ 识别为资料课程') handleDownload(section) - } else if (isHomeworkLesson(section)) { + } else if (isHomework) { + console.log('✅ 识别为作业课程') handleHomework(section) - } else if (isExamLesson(section)) { + } else if (isExam) { + console.log('✅ 识别为考试课程') handleExam(section) + } else { + console.log('⚠️ 未识别的课程类型,默认当作视频处理') + loadSectionVideo(section) } } @@ -1158,6 +1302,73 @@ onUnmounted(() => { opacity: 0.9; } +/* 清晰度选择器 */ +.video-quality-selector { + position: absolute; + top: 15px; + right: 15px; + z-index: 10; +} + +.quality-dropdown { + position: relative; +} + +.quality-btn { + display: flex; + align-items: center; + gap: 4px; + padding: 6px 12px; + background: rgba(0, 0, 0, 0.6); + color: white; + border: none; + border-radius: 4px; + font-size: 12px; + cursor: pointer; + transition: background-color 0.2s; +} + +.quality-btn:hover { + background: rgba(0, 0, 0, 0.8); +} + +.dropdown-icon { + transition: transform 0.2s; +} + +.quality-btn:hover .dropdown-icon { + transform: rotate(180deg); +} + +.quality-menu { + position: absolute; + top: 100%; + right: 0; + margin-top: 4px; + background: rgba(0, 0, 0, 0.9); + border-radius: 4px; + overflow: hidden; + min-width: 80px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.quality-option { + padding: 8px 12px; + color: white; + font-size: 12px; + cursor: pointer; + transition: background-color 0.2s; +} + +.quality-option:hover { + background: rgba(255, 255, 255, 0.1); +} + +.quality-option.active { + background: #1890ff; + color: white; +} + /* 课程信息区域 */ .course-info-section { /* padding: 24px 0; */ diff --git a/src/views/CourseStudy.vue b/src/views/CourseStudy.vue index 1d79c39..3dd6260 100644 --- a/src/views/CourseStudy.vue +++ b/src/views/CourseStudy.vue @@ -368,7 +368,7 @@ import type { Course, CourseSection } from '@/api/types' const route = useRoute() const router = useRouter() -const courseId = ref(Number(route.params.id)) +const courseId = ref(route.params.id as string) // 从URL查询参数获取视频信息 const currentVideoUrl = ref(route.query.videoUrl ? decodeURIComponent(route.query.videoUrl as string) : '') @@ -385,7 +385,7 @@ console.log('- 路由查询参数:', route.query) // 课程数据 const course = ref(null) const courseSections = ref([]) -const currentLessonId = ref(currentSectionId.value || 1) +const currentLessonId = ref(currentSectionId.value ? currentSectionId.value.toString() : "1") // 视频播放状态 const isPlaying = ref(false) @@ -476,12 +476,12 @@ const sortedChapters = computed(() => { const hasPrevious = computed(() => { // 检查是否有上一节课 - return currentLessonId.value > 1 + return parseInt(currentLessonId.value) > 1 }) const hasNext = computed(() => { // 检查是否有下一节课 - return currentLessonId.value < totalLessons.value + return parseInt(currentLessonId.value) < totalLessons.value }) const isCurrentLessonCompleted = computed(() => { @@ -498,7 +498,7 @@ watch(() => route.query, (newQuery) => { currentVideoTitle.value = decodeURIComponent(newQuery.sectionName as string) } if (newQuery.sectionId) { - currentLessonId.value = Number(newQuery.sectionId) + currentLessonId.value = newQuery.sectionId.toString() } }) @@ -537,18 +537,19 @@ const generateChapterGroups = () => { title: '课前准备', lessons: [ { - id: 1, + id: "1", lessonId: courseId.value, outline: currentVideoUrl.value, name: currentVideoTitle.value || '开课彩蛋:新开始新征程', - parentId: 1, + parentId: "1", sort: 0, level: 1, revision: 0, createdAt: Date.now(), updatedAt: null, deletedAt: null, - completed: false + completed: false, + type: 0 } ], expanded: true diff --git a/src/views/Courses.vue b/src/views/Courses.vue index 4df8f87..04fbe4d 100644 --- a/src/views/Courses.vue +++ b/src/views/Courses.vue @@ -17,70 +17,38 @@
类型:
- 全部 - 必修课 - 高分课 - 名师课堂 - 训练营 - 无考试 - 专题讲座 + 全部 + + {{ category.name }} + + + 加载中...
- +
专题:
- 全部 - 学科教研 - 班级管理 - 通识技能 - 信息素养 - 师风师德 - 专题教育 - 综合实践 - 个人成长 - 教学案例 - 教育技术 - 心理健康 - 家校沟通 - 课程设计 - 教育政策 - 教学评估 - 创新教育 - STEAM教育 - 教育心理学 - 差异化教学 - 教育领导力 - 在线教学 + 全部 + + {{ subject.name }} + + + 加载中...
@@ -91,14 +59,17 @@
全部 - 零基础 - 初级 - 中级 - 高级 + + {{ difficulty.name }} + + + 加载中...
@@ -118,9 +89,9 @@
- 最新 - 最热 - 推荐 + 最新 + 最热 + 推荐
@@ -203,8 +174,8 @@ @@ -617,6 +641,13 @@ onMounted(() => { font-weight: 500; } +.filter-tag.loading { + background: #f0f0f0; + color: #999; + cursor: not-allowed; + opacity: 0.7; +} + .sort-tabs { display: flex; gap: 20px; diff --git a/src/views/Home.vue b/src/views/Home.vue index 240ca16..d9e46aa 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -528,7 +528,7 @@ import { useCourseStore } from '@/stores/course' import { useAuth } from '@/composables/useAuth' import LoginModal from '@/components/auth/LoginModal.vue' import RegisterModal from '@/components/auth/RegisterModal.vue' -import { getPopularCourses } from '@/data/mockCourses' +// import { getPopularCourses } from '@/data/mockCourses' const { t, locale } = useI18n() const router = useRouter() @@ -554,8 +554,8 @@ const bannerAlt = computed(() => { // 热门课程数据 const popularCourses = computed(() => { - const courses = getPopularCourses() - return courses.map(course => ({ + const courses = courseStore.courses.slice(0, 4) // 取前4个课程作为热门课程 + return courses.map((course: any) => ({ id: course.id, title: course.title, thumbnail: course.thumbnail, @@ -719,12 +719,12 @@ const partners = computed(() => [ // ]) // 跳转到课程详情页面 -const goToCourseDetail = (courseId: number) => { +const goToCourseDetail = (courseId: string) => { router.push(`/course/${courseId}`) } // 处理课程报名 - 跳转到课程详情页面 -const handleEnrollCourse = (courseId: number) => { +const handleEnrollCourse = (courseId: string) => { // 跳转到课程详情页面,在那里进行登录状态判断和报名 router.push(`/course/${courseId}`) } diff --git a/src/views/Learning.vue b/src/views/Learning.vue index 5efa314..14436fb 100644 --- a/src/views/Learning.vue +++ b/src/views/Learning.vue @@ -155,7 +155,7 @@ const isPlaying = ref(false) const noteContent = ref('') const progress = ref(0) -const courseId = computed(() => Number(route.params.id)) +const courseId = computed(() => route.params.id as string) const course = computed(() => courseStore.currentCourse) const lessons = computed(() => courseStore.lessons) diff --git a/src/views/TeacherDetail.vue b/src/views/TeacherDetail.vue index 0435199..a11172d 100644 --- a/src/views/TeacherDetail.vue +++ b/src/views/TeacherDetail.vue @@ -128,7 +128,7 @@ const toggleFollow = () => { } import { useRouter } from 'vue-router' import type { Course } from '@/api/types' -import { mockCourses } from '@/data/mockCourses' +// import { mockCourses } from '@/data/mockCourses' const router = useRouter() @@ -201,7 +201,7 @@ const loadCourses = async () => { await new Promise(resolve => setTimeout(resolve, 500)) // 筛选逻辑 - let filteredCourses = [...mockCourses] + let filteredCourses: Course[] = [] // 暂时使用空数组,后续可以从API获取 // 按学科筛选 if (selectedSubject.value !== '全部') { @@ -234,7 +234,7 @@ const loadCourses = async () => { }); } else { // 默认排序(按课程ID) - filteredCourses.sort((a, b) => a.id - b.id); + filteredCourses.sort((a, b) => a.id.localeCompare(b.id)); } // 按专业筛选 @@ -242,7 +242,7 @@ const loadCourses = async () => { filteredCourses = filteredCourses.filter(course => course.title.includes(selectedMajor.value) || course.description.includes(selectedMajor.value) || - course.tags.some(tag => tag.includes(selectedMajor.value)) + course.tags?.some((tag: string) => tag.includes(selectedMajor.value)) ) } diff --git a/src/views/TestSections.vue b/src/views/TestSections.vue index 6f07d9a..12b8f6a 100644 --- a/src/views/TestSections.vue +++ b/src/views/TestSections.vue @@ -52,7 +52,7 @@ import { ref } from 'vue' import { CourseApi } from '@/api/modules/course' import type { CourseSection } from '@/api/types' -const testLessonId = ref(1) +const testLessonId = ref("1") const loading = ref(false) const error = ref('') const sections = ref([]) diff --git a/vite.config.ts b/vite.config.ts index 66a9fa7..ddc96ef 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,35 +1,27 @@ import { fileURLToPath, URL } from 'node:url' -import { defineConfig, loadEnv } from 'vite' +import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueDevTools from 'vite-plugin-vue-devtools' // https://vite.dev/config/ -export default defineConfig(({ mode }) => { - const env = loadEnv(mode, process.cwd(), '') - const proxyTarget = env.VITE_PROXY_TARGET || 'http://110.42.96.65:55510' - - return { - plugins: [ - vue(), - vueDevTools(), - ], - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) - }, +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) }, - server: { - port: 3000, - open: true, - proxy: { - // 将以 /api 开头的请求代理到后端,避免浏览器CORS限制 - '/api': { - target: proxyTarget, - changeOrigin: true, - // 如果后端接口不是以 /api 开头,可在这里改写路径 - // rewrite: (path) => path.replace(/^\/api/, '') - } + }, + server: { + port: 3000, + open: true, + proxy: { + '/jeecgboot': { + target: 'http://103.40.14.23:25526', + changeOrigin: true } } }