1071 lines
21 KiB
Markdown
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
|