封装一个完整的HttpClient.ts

这是一个基于 Axios 封装的高质量 HttpClient。它包含了拦截器(自动加 Token、统一错误处理)、请求重试、文件上传/下载支持,并且是类型安全的。

后端接口返回的标准格式为:

javascript 复制代码
{
  "code": 200,
  "msg": "success",
  "data": { ... }
}

1. 准备工作

需要安装 axios:

javascript 复制代码
npm install axios

2. 封装代码 (src/utils/http.ts)

javascript 复制代码
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError, Method } from 'axios';

// 定义响应数据结构
interface ResponseData<T = any> {
  code: number;
  msg: string;
  data: T;
}

// 定义请求配置扩展(可选,用于添加自定义字段)
interface RequestConfig extends AxiosRequestConfig {
  noLoading?: boolean; // 是否不显示 loading
  noToast?: boolean;   // 是否不显示错误提示
}

// 自定义 HTTP 错误类
class HttpError extends Error {
  public code: number;
  public isShow?: boolean;

  constructor(message: string, code: number, isShow?: boolean) {
    super(message);
    this.name = 'HttpError';
    this.code = code;
    this.isShow = isShow;
  }
}

// 实例化
const service: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_BASE_API || '/api',
  timeout: 10000, // 10秒超时
});

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // 1. 添加 Token (示例,实际从 localStorage 或 pinia 获取)
    const token = localStorage.getItem('token');
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    // 2. 请求日志 (开发环境)
    if (import.meta.env.DEV) {
      console.log(`[Request] ${config.method?.toUpperCase()} ${config.url}`, config.data || config.params);
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse<ResponseData>) => {
    const res = response.data;

    // 业务状态码判断 (假设 200 为成功,500 为服务器错误,401 为未授权)
    if (res.code === 200) {
      return res.data; // 直接返回 data,方便使用
    } else {
      // 错误处理
      const errorMsg = res.msg || 'Error';
      // 判断是否需要提示用户(部分接口不需要 toast 提示)
      const isShow = response.config.isShow !== false;
      return Promise.reject(new HttpError(errorMsg, res.code, isShow));
    }
  },
  (error: AxiosError<ResponseData>) => {
    // 处理 HTTP 错误 (401, 403, 500 等)
    let message = 'Network Error';
    let code = -1;

    if (error.response) {
      const { status, data } = error.response;
      code = data?.code || status;
      switch (status) {
        case 400:
          message = data?.msg || 'Bad Request';
          break;
        case 401:
          message = 'Unauthorized, please login again';
          // 401 跳转逻辑可放此处
          break;
        case 403:
          message = 'Forbidden';
          break;
        case 404:
          message = 'Not Found';
          break;
        case 500:
          message = 'Internal Server Error';
          break;
        default:
          message = data?.msg || `Error ${status}`;
      }
    } else if (error.message.includes('timeout')) {
      message = 'Request Timeout';
    }

    return Promise.reject(new HttpError(message, code, true));
  }
);

// 核心:通用请求方法
const request = <T = any>(config: RequestConfig): Promise<T> => {
  return service.request(config);
};

// 扩展常用方法
export const http = {
  get: <T = any>(url: string, params?: any, config?: RequestConfig) => 
    request<T>({ ...config, method: 'GET' as Method, url, params }),
  
  post: <T = any>(url: string, data?: any, config?: RequestConfig) => 
    request<T>({ ...config, method: 'POST' as Method, url, data }),
  
  put: <T = any>(url: string, data?: any, config?: RequestConfig) => 
    request<T>({ ...config, method: 'PUT' as Method, url, data }),
  
  delete: <T = any>(url: string, params?: any, config?: RequestConfig) => 
    request<T>({ ...config, method: 'DELETE' as Method, url, params }),
  
  patch: <T = any>(url: string, data?: any, config?: RequestConfig) => 
    request<T>({ ...config, method: 'PATCH' as Method, url, data }),

  // 文件上传
  upload: <T = any>(url: string, file: File, onUploadProgress?: (progressEvent: any) => void, config?: RequestConfig) => {
    const formData = new FormData();
    formData.append('file', file);
    return request<T>({ ...config, method: 'POST' as Method, url, data: formData, headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress });
  },

  // 文件下载
  download: <T = any>(url: string, params?: any, filename?: string) => {
    return service.get(url, { params, responseType: 'blob' }).then((res: AxiosResponse) => {
      const blob = new Blob([res.data]);
      const link = document.createElement('a');
      const objectUrl = URL.createObjectURL(blob);
      link.href = objectUrl;
      link.download = filename || `download-${Date.now()}.xlsx`;
      link.click();
      URL.revokeObjectURL(objectUrl);
    });
  }
};

export default http;

3. 如何使用

页面或 API 模块中导入使用:

javascript 复制代码
import http from '@/utils/http';

// 1. GET 请求
http.get('/user/info', { id: 1 });

// 2. POST 请求
http.post('/user/login', { username: 'admin', password: '123456' });

// 3. PUT 请求
http.put('/user/update', { id: 1, name: 'newName' });

// 4. DELETE 请求
http.delete('/user/delete', { id: 1 });

// 5. 文件上传 (带进度条)
const handleUpload = () => {
  http.upload('/upload', file, (progressEvent) => {
    const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
    console.log(percentCompleted);
  });
};

// 6. 文件下载
http.download('/export', { type: 'excel' }, '用户列表.xlsx');

// 7. 处理错误
try {
  const res = await http.get('/user/info');
  console.log(res);
} catch (error: any) {
  console.error(error.message); // 拦截器里定义的错误信息
  // if (error.code === 401) router.push('/login');
}

封装亮点

  1. 类型安全:使用泛型 <T>,调用接口时可以精确指定返回数据类型。
  2. 统一响应处理:拦截器自动提取 data,无需每个接口都写 response.data.data
  3. 错误分类:区分了 HTTP 错误(如 404)和业务错误(如后端返回的 code: 500),并封装了 HttpError
  4. 便利方法:直接暴露 get, post, put, delete, upload, download,代码更简洁。
  5. 进度条支持:上传时支持 onUploadProgress 回调。
相关推荐
_阿南_6 小时前
项目中新增给AI制定的代码规范
前端·程序员
名字还没想好☜7 小时前
React 实现暗黑模式切换:localStorage 持久化、SSR 首屏闪烁与跟随系统主题
前端·javascript·react.js·ecmascript·react·next.js
自动化监测Learner8 小时前
主流 Web 端地图引擎对比:选型指南与优劣分析
javascript
console.log('npc')8 小时前
Git 冲突与 AI 协助指南
前端·人工智能·git·大模型
爱学堂IT分享9 小时前
Cesium可视化系统实战课程-Cesium教程学习
前端
糖墨夕9 小时前
理解大语言模型:Agent 的“大脑”
前端·agent
Rain的Java大神之路10 小时前
JavaWeb开发如何解决跨域问题
java·前端·后端·nginx·web安全·面试·运维开发
cpolar技术支持10 小时前
浏览器也能跑本地 AI:用 Transformers.js + WebGPU 做一个最小推理 Demo,cpolar 给同事远程体验
前端·ai·cpolar·webgpu·transformers.js
计算机魔术师11 小时前
OpenAI 发布 GPT-6 Astra:多项基准刷新纪录, cybersecurity 能力达 Critical 阈值
前端
xy345311 小时前
Axure9.0中继器遮罩实现方法
前端·ui·html·axure·原型·产品设计