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/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..f85c3df 100644 --- a/src/api/modules/course.ts +++ b/src/api/modules/course.ts @@ -5,6 +5,19 @@ import type { PaginationResponse, Course, CourseCategory, + BackendCourseCategory, + BackendCourseCategoryListResponse, + CourseSubject, + BackendCourseSubject, + BackendCourseSubjectListResponse, + CourseDifficulty, + BackendCourseDifficulty, + BackendCourseDifficultyListResponse, + CourseListQueryParams, + CourseDetailQueryParams, + BackendCourseItem, + BackendCourseListResponse, + BackendCourseDetailResponse, Chapter, Lesson, LessonResource, @@ -17,7 +30,6 @@ import type { SearchRequest, Instructor, BackendCourse, - BackendCourseListResponse, CourseListRequest, } from '../types' @@ -76,128 +88,96 @@ export class CourseApi { 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) @@ -225,6 +205,95 @@ export class CourseApi { } } + // 获取课程详情 - 适配后端接口 + 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 + } + } + } + // 搜索课程 static searchCourses(params: SearchRequest): Promise>> { return ApiRequest.get('/courses/search', params) @@ -246,86 +315,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 +427,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: [] + } + } } // 获取分类下的课程 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..08f4c54 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,132 @@ 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 // 专题值 +} + +// 后端课程数据格式 +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 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/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 24be854..2db68c5 100644 --- a/src/views/CourseDetail.vue +++ b/src/views/CourseDetail.vue @@ -463,7 +463,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 暂时未使用,后续需要时再启用 @@ -715,7 +715,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 +768,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 @@ -1131,7 +1131,7 @@ onMounted(() => { console.log('课程详情页加载完成,课程ID:', courseId.value) initializeMockState() // 初始化模拟状态 loadCourseDetail() - loadCourseSections() + // loadCourseSections() // 暂时禁用章节接口调用,因为接口不存在 }) diff --git a/src/views/Courses.vue b/src/views/Courses.vue index dba942e..32855a0 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,9 @@ @@ -617,6 +642,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/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 } } }