// HTTP 请求封装文件 import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import { useUserStore } from '@/stores/user' import router from '@/router' import type { ApiResponse } from './types' // 消息提示函数 - 使用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: 10000, headers: { 'Content-Type': 'application/json', }, }) // 请求拦截器 request.interceptors.request.use( (config: AxiosRequestConfig) => { // 添加认证token const userStore = useUserStore() if (userStore.token) { config.headers = { ...config.headers, Authorization: `Bearer ${userStore.token}`, } } // 添加请求时间戳 config.headers = { ...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, }) } // 检查业务状态码 if (data.code === 200 || data.code === 0) { return data } // 处理业务错误 const errorMessage = data.message || '请求失败' // 不在这里显示错误消息,让组件自己处理 // showMessage(errorMessage, 'error') // 创建一个包含完整响应信息的错误对象 const error = new Error(errorMessage) ;(error as any).response = { data: data, 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: 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数据处理 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 === '/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') { // 模拟课程列表数据 const mockCourses = [ { id: 1, name: "暑期名师领学,提高班级教学质量!高效冲分指南", cover: "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", categoryId: 1, price: "99.00", school: "名师工作室", description: "本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。", teacherId: 1, outline: "课程大纲详细内容...", prerequisite: "具备基本的计算机操作能力", target: "掌握核心技能,能够在实际工作中熟练应用", arrangement: "理论与实践相结合,循序渐进的学习方式", startTime: "2025-01-26 10:13:17", endTime: "2025-03-26 10:13:17", revision: 1, position: "高级讲师", createdAt: 1737944724, updatedAt: 1737944724, updatedTime: null }, { id: 2, name: "计算机二级考前冲刺班", cover: "https://images.unsplash.com/photo-1517077304055-6e89abbf09b0?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", categoryId: 2, price: "199.00", school: "计算机学院", description: "备考计算机二级,名师带你高效复习,掌握考试重点,轻松通过考试。", teacherId: 2, outline: "考试大纲详细解析...", prerequisite: "具备基本的计算机基础知识", target: "顺利通过计算机二级考试", arrangement: "考点精讲+真题演练+模拟考试", startTime: "2025-02-01 09:00:00", endTime: "2025-02-28 18:00:00", revision: 1, position: "副教授", createdAt: 1737944724, updatedAt: 1737944724, updatedTime: null }, { id: 3, name: "摆脱哑巴英语,流利口语训练营", cover: "https://images.unsplash.com/photo-1434030216411-0b793f4b4173?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", categoryId: 3, price: "299.00", school: "外语学院", description: "专业外教授课,情景式教学,让你在短时间内突破口语障碍,自信开口说英语。", teacherId: 3, outline: "口语训练系统课程...", prerequisite: "具备基本的英语基础", target: "能够流利进行日常英语对话", arrangement: "外教一对一+小班练习+实战演练", startTime: "2025-02-15 19:00:00", endTime: "2025-04-15 21:00:00", revision: 1, position: "外籍教师", createdAt: 1737944724, updatedAt: 1737944724, updatedTime: null } ] return { code: 0, message: '查询课程列表成功', data: { list: mockCourses, total: mockCourses.length } } as ApiResponse } // 默认404响应 return { code: 404, message: '接口不存在', data: null } as ApiResponse } // 请求方法封装 export class ApiRequest { // GET 请求 static get( url: string, params?: any, config?: AxiosRequestConfig ): Promise> { // 检查是否启用Mock if (import.meta.env.VITE_ENABLE_MOCK === 'true') { return handleMockRequest(url, 'GET', params) } return request.get(url, { params, ...config }) } // POST 请求 static post( url: string, data?: any, config?: AxiosRequestConfig ): Promise> { // 检查是否启用Mock if (import.meta.env.VITE_ENABLE_MOCK === 'true') { return handleMockRequest(url, 'POST', data) } return request.post(url, data, config) } // 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) }) } } export default request