Files
2026-01-25 15:05:03 +08:00

1071 lines
21 KiB
Markdown

# API服务封装文档
## 1. Axios基础配置
### 1.1 Axios实例创建
```javascript
// services/api.js
import axios from 'axios';
// 创建axios实例
const api = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api/v1',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
export default api;
```
### 1.2 环境变量配置
```env
# .env.development
VITE_API_BASE_URL=http://localhost:5000/api/v1
VITE_APP_TITLE=海洋项目管理系统
# .env.production
VITE_API_BASE_URL=https://api.example.com/api/v1
VITE_APP_TITLE=海洋项目管理系统
```
## 2. 请求拦截器
### 2.1 请求前拦截
```javascript
// services/api.js
// 请求拦截器
api.interceptors.request.use(
(config) => {
// 添加Token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加时间戳防止缓存
if (config.method === 'get') {
config.params = {
...config.params,
_t: Date.now(),
};
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
```
### 2.2 请求日志(开发环境)
```javascript
// services/api.js
if (import.meta.env.DEV) {
api.interceptors.request.use((config) => {
console.log('Request:', {
url: config.url,
method: config.method,
params: config.params,
data: config.data,
});
return config;
});
}
```
## 3. 响应拦截器
### 3.1 响应数据处理
```javascript
// services/api.js
import { message } from 'antd';
// 响应拦截器
api.interceptors.response.use(
(response) => {
const { data } = response;
// 检查业务状态码
if (data.success === false) {
message.error(data.message || '请求失败');
return Promise.reject(new Error(data.message || '请求失败'));
}
// 返回数据部分
return data.data || data;
},
(error) => {
// 错误处理
if (error.response) {
handleResponseError(error.response);
} else if (error.request) {
handleNetworkError(error);
} else {
handleOtherError(error);
}
return Promise.reject(error);
}
);
```
### 3.2 错误处理函数
```javascript
// services/api.js
/**
* 处理响应错误
*/
const handleResponseError = (response) => {
const { status, data } = response;
switch (status) {
case 400:
message.error(data.message || '请求参数错误');
break;
case 401:
message.error('登录已过期,请重新登录');
clearAuth();
window.location.href = '/login';
break;
case 403:
message.error('权限不足');
break;
case 404:
message.error('请求的资源不存在');
break;
case 409:
message.error(data.message || '资源已存在');
break;
case 500:
message.error('服务器错误,请稍后重试');
break;
default:
message.error(data.message || '请求失败');
}
};
/**
* 处理网络错误
*/
const handleNetworkError = (error) => {
if (error.code === 'ECONNABORTED') {
message.error('请求超时,请检查网络连接');
} else {
message.error('网络错误,请检查网络连接');
}
};
/**
* 处理其他错误
*/
const handleOtherError = (error) => {
message.error(error.message || '发生未知错误');
};
/**
* 清除认证信息
*/
const clearAuth = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
};
```
### 3.3 响应日志(开发环境)
```javascript
// services/api.js
if (import.meta.env.DEV) {
api.interceptors.response.use(
(response) => {
console.log('Response:', response);
return response;
},
(error) => {
console.error('Error:', error);
return Promise.reject(error);
}
);
}
```
## 4. API模块化设计
### 4.1 认证API (services/auth.js)
```javascript
// services/auth.js
import api from './api';
export const authAPI = {
/**
* 用户登录
* @param {string} username - 用户名
* @param {string} password - 密码
*/
login: (username, password) => {
return api.post('/auth/login', { username, password });
},
/**
* 获取当前用户信息
*/
getCurrentUser: () => {
return api.get('/auth/me');
},
/**
* 登出
*/
logout: () => {
return api.post('/auth/logout');
},
};
```
### 4.2 用户API (services/user.js)
```javascript
// services/user.js
import api from './api';
export const userAPI = {
/**
* 获取用户列表
* @param {Object} params - 查询参数
* @param {number} params.page - 页码
* @param {number} params.page_size - 每页数量
* @param {string} params.department - 部门筛选
* @param {string} params.role - 角色筛选
* @param {string} params.keyword - 关键词搜索
*/
getList: (params) => {
return api.get('/users', { params });
},
/**
* 获取用户详情
* @param {number} id - 用户ID
*/
getDetail: (id) => {
return api.get(`/users/${id}`);
},
/**
* 创建用户
* @param {Object} data - 用户数据
*/
create: (data) => {
return api.post('/users', data);
},
/**
* 更新用户
* @param {number} id - 用户ID
* @param {Object} data - 更新数据
*/
update: (id, data) => {
return api.put(`/users/${id}`, data);
},
/**
* 删除用户
* @param {number} id - 用户ID
*/
delete: (id) => {
return api.delete(`/users/${id}`);
},
/**
* 重置用户密码
* @param {number} id - 用户ID
* @param {string} new_password - 新密码
*/
resetPassword: (id, new_password) => {
return api.post(`/users/${id}/reset-password`, { new_password });
},
};
```
### 4.3 项目API (services/project.js)
```javascript
// services/project.js
import api from './api';
export const projectAPI = {
/**
* 获取项目列表
* @param {Object} params - 查询参数
*/
getList: (params) => {
return api.get('/projects', { params });
},
/**
* 获取项目详情
* @param {number} id - 项目ID
*/
getDetail: (id) => {
return api.get(`/projects/${id}`);
},
/**
* 创建项目
* @param {Object} data - 项目数据
*/
create: (data) => {
return api.post('/projects', data);
},
/**
* 更新项目
* @param {number} id - 项目ID
* @param {Object} data - 更新数据
*/
update: (id, data) => {
return api.put(`/projects/${id}`, data);
},
/**
* 删除项目
* @param {number} id - 项目ID
*/
delete: (id) => {
return api.delete(`/projects/${id}`);
},
/**
* 批量删除项目
* @param {Array<number>} ids - 项目ID数组
*/
batchDelete: (ids) => {
return api.post('/projects/batch-delete', { ids });
},
/**
* 获取基础统计
* @param {Object} params - 查询参数
*/
getStatistics: (params) => {
return api.get('/projects/statistics', { params });
},
/**
* 获取分组统计
* @param {Object} params - 查询参数
* @param {string} params.group_by - 分组字段
*/
getGroupStatistics: (params) => {
return api.get('/projects/statistics/group', { params });
},
/**
* 获取时间维度统计
* @param {Object} params - 查询参数
* @param {string} params.time_field - 时间字段
*/
getTimelineStatistics: (params) => {
return api.get('/projects/statistics/timeline', { params });
},
/**
* 导出项目数据
* @param {Object} params - 查询参数
*/
export: (params) => {
return api.get('/projects/export', {
params,
responseType: 'blob',
});
},
};
```
### 4.4 统计API (services/statistics.js)
```javascript
// services/statistics.js
import api from './api';
import { projectAPI } from './project';
// 重用projectAPI的统计方法
export const statisticsAPI = {
...projectAPI.getStatistics,
...projectAPI.getGroupStatistics,
...projectAPI.getTimelineStatistics,
/**
* 获取仪表盘数据
*/
getDashboard: () => {
return api.get('/statistics/dashboard');
},
/**
* 获取项目趋势
* @param {Object} params - 查询参数
*/
getTrend: (params) => {
return api.get('/statistics/trend', { params });
},
};
```
## 5. 自定义Hooks封装
### 5.1 useApi Hook
```javascript
// hooks/useApi.js
import { useState, useCallback } from 'react';
import { message } from 'antd';
/**
* API调用Hook
* @param {Function} apiFunc - API函数
*/
export const useApi = (apiFunc) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const execute = useCallback(
async (...args) => {
try {
setLoading(true);
setError(null);
const result = await apiFunc(...args);
setData(result);
return result;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
}
},
[apiFunc]
);
return { data, loading, error, execute };
};
```
### 5.2 useList Hook
```javascript
// hooks/useList.js
import { useState, useCallback } from 'react';
/**
* 列表数据Hook
* @param {Function} apiFunc - API函数
*/
export const useList = (apiFunc) => {
const [list, setList] = useState([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
});
const fetchList = useCallback(
async (params = {}) => {
try {
setLoading(true);
const result = await apiFunc({
page: pagination.current,
page_size: pagination.pageSize,
...params,
});
setList(result.items || []);
setTotal(result.total || 0);
} catch (error) {
console.error('Fetch list error:', error);
} finally {
setLoading(false);
}
},
[apiFunc, pagination]
);
const handleTableChange = useCallback(
(newPagination) => {
setPagination(newPagination);
},
[]
);
const refresh = useCallback(() => {
fetchList();
}, [fetchList]);
return {
list,
total,
loading,
pagination,
fetchList,
handleTableChange,
refresh,
};
};
```
### 5.3 useCreate Hook
```javascript
// hooks/useCreate.js
import { useState, useCallback } from 'react';
import { message } from 'antd';
/**
* 创建数据Hook
* @param {Function} apiFunc - API函数
* @param {Function} onSuccess - 成功回调
*/
export const useCreate = (apiFunc, onSuccess) => {
const [loading, setLoading] = useState(false);
const create = useCallback(
async (data) => {
try {
setLoading(true);
await apiFunc(data);
message.success('创建成功');
if (onSuccess) {
onSuccess();
}
} catch (error) {
console.error('Create error:', error);
} finally {
setLoading(false);
}
},
[apiFunc, onSuccess]
);
return { create, loading };
};
```
### 5.4 useUpdate Hook
```javascript
// hooks/useUpdate.js
import { useState, useCallback } from 'react';
import { message } from 'antd';
/**
* 更新数据Hook
* @param {Function} apiFunc - API函数
* @param {Function} onSuccess - 成功回调
*/
export const useUpdate = (apiFunc, onSuccess) => {
const [loading, setLoading] = useState(false);
const update = useCallback(
async (id, data) => {
try {
setLoading(true);
await apiFunc(id, data);
message.success('更新成功');
if (onSuccess) {
onSuccess();
}
} catch (error) {
console.error('Update error:', error);
} finally {
setLoading(false);
}
},
[apiFunc, onSuccess]
);
return { update, loading };
};
```
### 5.5 useDelete Hook
```javascript
// hooks/useDelete.js
import { useState, useCallback } from 'react';
import { Modal, message } from 'antd';
/**
* 删除数据Hook
* @param {Function} apiFunc - API函数
* @param {Function} onSuccess - 成功回调
*/
export const useDelete = (apiFunc, onSuccess) => {
const [loading, setLoading] = useState(false);
const handleDelete = useCallback(
async (id, options = {}) => {
const { title = '确认删除', content = '确定要删除吗?' } = options;
Modal.confirm({
title,
content,
onOk: async () => {
try {
setLoading(true);
await apiFunc(id);
message.success('删除成功');
if (onSuccess) {
onSuccess();
}
} catch (error) {
console.error('Delete error:', error);
} finally {
setLoading(false);
}
},
});
},
[apiFunc, onSuccess]
);
return { handleDelete, loading };
};
```
## 6. 请求工具函数
### 6.1 防抖函数
```javascript
// utils/debounce.js
/**
* 防抖函数
* @param {Function} func - 需要防抖的函数
* @param {number} delay - 延迟时间
*/
export const debounce = (func, delay = 300) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
};
```
### 6.2 节流函数
```javascript
// utils/throttle.js
/**
* 节流函数
* @param {Function} func - 需要节流的函数
* @param {number} delay - 延迟时间
*/
export const throttle = (func, delay = 300) => {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
func.apply(this, args);
lastCall = now;
}
};
};
```
### 6.3 取消重复请求
```javascript
// utils/request.js
const pendingRequests = new Map();
/**
* 生成请求key
*/
const generateRequestKey = (config) => {
const { method, url, params, data } = config;
return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&');
};
/**
* 取消重复请求
*/
export const cancelDuplicateRequest = (config) => {
const requestKey = generateRequestKey(config);
if (pendingRequests.has(requestKey)) {
const cancel = pendingRequests.get(requestKey);
cancel('取消重复请求');
pendingRequests.delete(requestKey);
}
};
/**
* 添加待处理请求
*/
export const addPendingRequest = (config, cancel) => {
const requestKey = generateRequestKey(config);
pendingRequests.set(requestKey, cancel);
};
/**
* 移除待处理请求
*/
export const removePendingRequest = (config) => {
const requestKey = generateRequestKey(config);
pendingRequests.delete(requestKey);
};
```
## 7. 文件上传和下载
### 7.1 文件上传
```javascript
// services/upload.js
import api from './api';
/**
* 上传文件
* @param {File} file - 文件对象
* @param {Function} onProgress - 进度回调
*/
export const uploadFile = (file, onProgress) => {
const formData = new FormData();
formData.append('file', file);
return api.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
if (onProgress) {
onProgress(percentCompleted);
}
},
});
};
/**
* 批量上传文件
* @param {File[]} files - 文件数组
*/
export const uploadFiles = async (files) => {
const uploadPromises = files.map((file) => uploadFile(file));
return Promise.all(uploadPromises);
};
```
### 7.2 文件下载
```javascript
// services/download.js
import api from './api';
/**
* 下载文件
* @param {string} url - 下载地址
* @param {string} filename - 文件名
*/
export const downloadFile = async (url, filename) => {
try {
const response = await api.get(url, {
responseType: 'blob',
});
const blob = new Blob([response]);
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Download error:', error);
}
};
/**
* 导出Excel
* @param {Object} params - 查询参数
*/
export const exportExcel = async (params) => {
try {
const response = await api.get('/export', {
params,
responseType: 'blob',
});
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = `export_${Date.now()}.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Export error:', error);
}
};
```
## 8. 错误处理和重试
### 8.1 请求重试
```javascript
// services/api.js
/**
* 请求重试配置
*/
const retryConfig = {
retries: 3,
retryDelay: 1000,
retryCondition: (error) => {
// 网络错误或5xx错误时重试
return !error.response || error.response.status >= 500;
},
};
api.interceptors.response.use(undefined, async (error) => {
const { config } = error;
if (!config || !config.retryCount) {
return Promise.reject(error);
}
const { retries, retryDelay, retryCondition } = retryConfig;
// 检查是否满足重试条件
if (!retryCondition(error)) {
return Promise.reject(error);
}
// 检查重试次数
config.retryCount = config.retryCount || 0;
if (config.retryCount >= retries) {
return Promise.reject(error);
}
// 增加重试计数
config.retryCount++;
// 延迟重试
await new Promise((resolve) => setTimeout(resolve, retryDelay));
// 重新请求
return api(config);
});
```
## 9. 缓存策略
### 9.1 内存缓存
```javascript
// utils/cache.js
const cache = new Map();
/**
* 设置缓存
*/
export const setCache = (key, value, ttl = 60000) => {
const expiry = Date.now() + ttl;
cache.set(key, { value, expiry });
};
/**
* 获取缓存
*/
export const getCache = (key) => {
const item = cache.get(key);
if (!item) {
return null;
}
if (Date.now() > item.expiry) {
cache.delete(key);
return null;
}
return item.value;
};
/**
* 清除缓存
*/
export const clearCache = (key) => {
if (key) {
cache.delete(key);
} else {
cache.clear();
}
};
```
### 9.2 带缓存的请求
```javascript
// hooks/useCachedApi.js
import { useState, useEffect } from 'react';
import { getCache, setCache } from '../utils/cache';
/**
* 带缓存的API调用Hook
*/
export const useCachedApi = (apiFunc, cacheKey, ttl = 60000) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
// 检查缓存
const cached = getCache(cacheKey);
if (cached) {
setData(cached);
return;
}
// 发起请求
try {
setLoading(true);
const result = await apiFunc();
setData(result);
setCache(cacheKey, result, ttl);
} catch (error) {
console.error('API error:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, [apiFunc, cacheKey, ttl]);
return { data, loading };
};
```
## 10. API使用示例
### 10.1 在组件中使用
```jsx
import React, { useEffect } from 'react';
import { useList } from '../hooks/useList';
import { projectAPI } from '../services/project';
import Table from '../components/Common/Table';
const ProjectList = () => {
const { list, total, loading, pagination, handleTableChange } = useList(
projectAPI.getList
);
const columns = [
{
title: '项目编号',
dataIndex: 'project_no',
key: 'project_no',
},
{
title: '项目名称',
dataIndex: 'name',
key: 'name',
},
// ...其他列
];
return (
<Table
columns={columns}
dataSource={list}
loading={loading}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total,
}}
onChange={handleTableChange}
/>
);
};
export default ProjectList;
```
### 10.2 表单提交
```jsx
import React from 'react';
import { Form, Input, Button } from 'antd';
import { useCreate } from '../hooks/useCreate';
import { projectAPI } from '../services/project';
const ProjectForm = ({ onSuccess }) => {
const [form] = Form.useForm();
const { create, loading } = useCreate(projectAPI.create, onSuccess);
const handleSubmit = async (values) => {
await create(values);
};
return (
<Form form={form} onFinish={handleSubmit} layout="vertical">
<Form.Item
name="name"
label="项目名称"
rules={[{ required: true, message: '请输入项目名称' }]}
>
<Input placeholder="请输入项目名称" />
</Form.Item>
{/* ...其他表单项 */}
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading}>
提交
</Button>
</Form.Item>
</Form>
);
};
export default ProjectForm;
```
---
**文档维护**: 前端程序员
**文档类型**: API服务封装文档
**最后更新**: 2026-01-25