// HTTP 请求封装文件 import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosRequestConfig, AxiosResponse } from 'axios' import { useUserStore } from '@/stores/user' // import router from '@/router' import type { ApiResponse } from './types' // 网络状态检测 const checkNetworkStatus = (): boolean => { if (typeof navigator !== 'undefined' && 'onLine' in navigator) { return navigator.onLine } return true // 默认认为网络可用 } // 消息提示函数 - 使用window.alert作为fallback,实际项目中应该使用UI库的消息组件 const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') => { // 这里可以替换为你使用的UI库的消息组件 // 例如:naive-ui的 useMessage() console.log(`[${type.toUpperCase()}] ${message}`) // 临时使用alert,实际项目中应该替换为UI库的消息组件 if (type === 'error') { alert(`错误: ${message}`) } } // 创建axios实例 const request: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api', timeout: 30000, // 增加到30秒 headers: { 'Content-Type': 'application/json', }, }) // 请求拦截器 request.interceptors.request.use( (config: InternalAxiosRequestConfig) => { // 添加认证token const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } // 添加请求时间戳 config.headers['X-Request-Time'] = Date.now().toString() // 开发环境下打印请求信息 if (import.meta.env.DEV) { console.log('🚀 Request:', { url: config.url, method: config.method, params: config.params, data: config.data, }) } return config }, (error) => { console.error('❌ Request Error:', error) return Promise.reject(error) } ) // 响应拦截器 request.interceptors.response.use( (response: AxiosResponse) => { const { data } = response // 开发环境下打印响应信息 if (import.meta.env.DEV) { console.log('✅ Response:', { url: response.config.url, status: response.status, data: data, }) } // 处理不同的响应格式 let normalizedData: ApiResponse // 如果响应已经是标准格式 if (data && typeof data === 'object' && 'code' in data && 'message' in data) { normalizedData = data } else { // 如果响应不是标准格式,包装成标准格式 normalizedData = { code: 200, message: '请求成功', data: data } } // 检查业务状态码 if (normalizedData.code === 200 || normalizedData.code === 0) { // 返回标准化后的响应 response.data = normalizedData return response } // 处理业务错误 const errorMessage = normalizedData.message || '请求失败' // 不在这里显示错误消息,让组件自己处理 // showMessage(errorMessage, 'error') // 创建一个包含完整响应信息的错误对象 const error = new Error(errorMessage) ;(error as any).response = { data: normalizedData, status: 200 // HTTP状态码是200,但业务状态码不是成功 } return Promise.reject(error) }, (error) => { console.error('❌ Response Error:', error) // 处理HTTP状态码错误 const { response } = error let errorMessage = '网络错误,请稍后重试' if (response) { switch (response.status) { case 400: errorMessage = '请求参数错误' break case 401: errorMessage = '登录已过期,请重新登录' // 清除用户信息,不跳转页面(使用模态框登录) const userStore = useUserStore() userStore.logout() break case 403: errorMessage = '没有权限访问' break case 404: // 对于登出接口的404错误,不显示错误消息 if (error.config?.url?.includes('/logout')) { console.log('登出接口不存在,这是正常的') return Promise.resolve({ data: { code: 200, message: '登出成功', data: null } }) } errorMessage = '请求的资源不存在' break case 422: errorMessage = '数据验证失败' break case 429: errorMessage = '请求过于频繁,请稍后重试' break case 500: errorMessage = '服务器内部错误' break case 502: errorMessage = '网关错误' break case 503: errorMessage = '服务暂时不可用' break case 504: errorMessage = '网关超时' break default: errorMessage = `请求失败 (${response.status})` } } else if (error.code === 'ECONNABORTED') { errorMessage = '请求超时,请检查网络连接' } else if (error.message === 'Network Error') { errorMessage = '网络连接失败,请检查网络设置' } showMessage(errorMessage, 'error') return Promise.reject(error) } ) // 导入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 ( requestFn: () => Promise, maxRetries: number = 2, delay: number = 1000 ): Promise => { let lastError: any for (let i = 0; i <= maxRetries; i++) { try { // 检查网络状态 if (!checkNetworkStatus()) { throw new Error('网络连接不可用,请检查网络设置') } return await requestFn() } catch (error: any) { lastError = error // 如果是最后一次重试,或者不是网络错误,直接抛出 if (i === maxRetries || (error.code !== 'ECONNABORTED' && error.message !== 'Network Error' && !error.message?.includes('网络'))) { throw error } console.log(`请求失败,${delay}ms后进行第${i + 1}次重试...`) console.log('错误详情:', error.message) await new Promise(resolve => setTimeout(resolve, delay)) delay *= 2 // 指数退避 } } throw lastError } // 请求方法封装 export class ApiRequest { // GET 请求 static async get( url: string, params?: any, config?: AxiosRequestConfig ): Promise> { // 检查是否启用Mock if (import.meta.env.VITE_ENABLE_MOCK === 'true') { 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) } } // POST 请求 static async post( url: string, data?: any, config?: AxiosRequestConfig ): Promise> { // 检查是否启用Mock if (import.meta.env.VITE_ENABLE_MOCK === 'true') { return handleMockRequest(url, 'POST', data) } try { return await retryRequest(() => request.post(url, data, config)) } catch (error) { console.warn('API请求失败,降级到Mock数据:', error) // 如果真实API失败,降级到Mock数据 return handleMockRequest(url, 'POST', data) } } // PUT 请求 static put( url: string, data?: any, config?: AxiosRequestConfig ): Promise> { return request.put(url, data, config) } // PATCH 请求 static patch( url: string, data?: any, config?: AxiosRequestConfig ): Promise> { return request.patch(url, data, config) } // DELETE 请求 static delete( url: string, params?: any, config?: AxiosRequestConfig ): Promise> { return request.delete(url, { params, ...config }) } // 文件上传 static upload( url: string, file: File, onProgress?: (progress: number) => void, config?: AxiosRequestConfig ): Promise> { const formData = new FormData() formData.append('file', file) return request.post(url, formData, { headers: { 'Content-Type': 'multipart/form-data', }, onUploadProgress: (progressEvent) => { if (onProgress && progressEvent.total) { const progress = Math.round( (progressEvent.loaded * 100) / progressEvent.total ) onProgress(progress) } }, ...config, }) } // 文件下载 static download( url: string, filename?: string, params?: any, config?: AxiosRequestConfig ): Promise { return request .get(url, { params, responseType: 'blob', ...config, }) .then((response: any) => { const blob = new Blob([response.data]) const downloadUrl = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = downloadUrl link.download = filename || 'download' document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(downloadUrl) }) } // API健康检查 static async healthCheck(): Promise { try { const response = await request.get('/health', { timeout: 5000 }) return response.status === 200 } catch (error) { console.warn('API健康检查失败:', error) return false } } } export default request