OL-LearnPlatform/src/api/request.ts

528 lines
16 KiB
TypeScript
Raw Normal View History

2025-07-28 09:51:21 +08:00
// 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<ApiResponse>) => {
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 <T = any>(url: string, method: string, data?: any): Promise<ApiResponse<T>> => {
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<T>
} else {
return {
code: 400,
message: '手机号/邮箱或密码不能为空',
data: null
} as ApiResponse<T>
}
}
// 注册Mock
if (url === '/auth/register' && method === 'POST') {
const { email, password, verificationCode } = data || {}
if (!email || !password) {
return {
code: 400,
message: '邮箱和密码不能为空',
data: null
} as ApiResponse<T>
}
if (!verificationCode) {
return {
code: 400,
message: '验证码不能为空',
data: null
} as ApiResponse<T>
}
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<T>
}
// 发送验证码Mock
if (url === '/auth/send-verification' && method === 'POST') {
return {
code: 200,
message: '验证码已发送',
data: null
} as ApiResponse<T>
}
// 获取当前用户信息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<T>
}
// 课程详情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<T>
}
// 根据课程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: '<div><h4>课程大纲:</h4><ul><li><strong>第一章:基础入门</strong><br/>- 环境搭建与配置<br/>- 基本概念理解<br/>- 实践操作演示</li><li><strong>第二章:核心技能</strong><br/>- 核心功能详解<br/>- 实际应用场景<br/>- 案例分析讲解</li><li><strong>第三章:高级应用</strong><br/>- 进阶技巧掌握<br/>- 项目实战演练<br/>- 问题解决方案</li></ul></div>',
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<T>
}
// 课程列表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<T>
}
// 默认404响应
return {
code: 404,
message: '接口不存在',
data: null
} as ApiResponse<T>
}
// 请求方法封装
export class ApiRequest {
// GET 请求
static get<T = any>(
url: string,
params?: any,
config?: AxiosRequestConfig
): Promise<ApiResponse<T>> {
// 检查是否启用Mock
if (import.meta.env.VITE_ENABLE_MOCK === 'true') {
return handleMockRequest<T>(url, 'GET', params)
}
return request.get(url, { params, ...config })
}
// POST 请求
static post<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<ApiResponse<T>> {
// 检查是否启用Mock
if (import.meta.env.VITE_ENABLE_MOCK === 'true') {
return handleMockRequest<T>(url, 'POST', data)
}
return request.post(url, data, config)
}
// PUT 请求
static put<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<ApiResponse<T>> {
return request.put(url, data, config)
}
// PATCH 请求
static patch<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<ApiResponse<T>> {
return request.patch(url, data, config)
}
// DELETE 请求
static delete<T = any>(
url: string,
params?: any,
config?: AxiosRequestConfig
): Promise<ApiResponse<T>> {
return request.delete(url, { params, ...config })
}
// 文件上传
static upload<T = any>(
url: string,
file: File,
onProgress?: (progress: number) => void,
config?: AxiosRequestConfig
): Promise<ApiResponse<T>> {
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<void> {
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