签入版本
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
# API 层开发
|
||||
|
||||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||||
|
||||
:::tip 说明
|
||||
前端 API 层位于 `src/api/` 目录,封装所有与后端的 HTTP 请求。底层基于 Axios 封装了 `VAxios` 类(位于 `src/utils/http/axios/`),提供请求拦截、响应解包、错误处理、重复请求取消等能力。
|
||||
:::
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
src/api/
|
||||
├── common/
|
||||
│ ├── user.ts # 登录、登出、获取用户信息、验证码
|
||||
│ └── menu.ts # 获取菜单(adminMenus)
|
||||
└── tool/
|
||||
└── example.ts # 案例管理接口
|
||||
```
|
||||
|
||||
## Axios 封装架构
|
||||
|
||||
HTTP 层封装体系:
|
||||
|
||||
```
|
||||
src/utils/http/axios/
|
||||
├── index.ts # 入口:创建 VAxios 实例,定义 transform 处理逻辑
|
||||
├── Axios.ts # VAxios 类:封装 AxiosInstance,管理拦截器
|
||||
├── axiosTransform.ts # AxiosTransform 抽象类:定义拦截器钩子接口
|
||||
├── axiosCancel.ts # AxiosCanceler:重复请求取消(基于 CancelToken Map)
|
||||
├── checkStatus.ts # HTTP 状态码错误提示
|
||||
├── helper.ts # 工具函数(时间戳追加、日期格式化)
|
||||
└── types.ts # 类型定义(RequestOptions、Result、CreateAxiosOptions)
|
||||
```
|
||||
|
||||
### 全局实例
|
||||
|
||||
`index.ts` 导出全局单例 `http`,所有 API 函数使用此实例:
|
||||
|
||||
```typescript
|
||||
// src/utils/http/axios/index.ts
|
||||
export const http = createAxios();
|
||||
```
|
||||
|
||||
创建时配置了关键参数:
|
||||
|
||||
```typescript
|
||||
{
|
||||
timeout: 10 * 1000, // 10秒超时
|
||||
authenticationScheme: 'Bearer', // JWT 认证方案
|
||||
prefixUrl: urlPrefix, // 接口前缀 /api
|
||||
headers: { 'Content-Type': ContentTypeEnum.JSON },
|
||||
requestOptions: {
|
||||
joinPrefix: true,
|
||||
isTransformResponse: true, // 自动解包响应
|
||||
urlPrefix: urlPrefix, // /api
|
||||
withToken: true, // 自动携带 Token
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## API 定义示例
|
||||
|
||||
以案例管理为例(`src/api/tool/example.ts`),实际代码如下:
|
||||
|
||||
```typescript
|
||||
import { http } from '@/utils/http/axios';
|
||||
|
||||
// 分页查询案例列表
|
||||
export function getExamplePage(params?) {
|
||||
return http.request({
|
||||
url: '/example/page',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取全部案例列表(无分页)
|
||||
export function getExampleList(params?) {
|
||||
return http.request({
|
||||
url: '/example/list',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 根据 ID 获取详情
|
||||
export function getExampleDetail(id) {
|
||||
return http.request({
|
||||
url: '/example/detail/' + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
// 添加案例
|
||||
export function exampleAdd(data: any) {
|
||||
return http.request({
|
||||
url: '/example/add',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 更新案例
|
||||
export function exampleUpdate(data: any) {
|
||||
return http.request({
|
||||
url: '/example/update',
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除案例
|
||||
export function exampleDelete(id) {
|
||||
return http.request({
|
||||
url: '/example/delete/' + id,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除案例
|
||||
export function exampleBatchDelete(data: any) {
|
||||
return http.request({
|
||||
url: '/example/batchDelete',
|
||||
method: 'DELETE',
|
||||
data,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
:::tip API 路径说明
|
||||
API 函数中的 `url` 不需要写 `/api` 前缀。`VAxios` 创建时配置了 `prefixUrl`,拦截器的 `beforeRequestHook` 会自动拼接前缀。最终请求路径为 `/api/example/page`。Vite 开发服务器通过代理将 `/api` 转发到 Django 后端。
|
||||
:::
|
||||
|
||||
## 请求拦截器
|
||||
|
||||
请求拦截器自动注入 Token:
|
||||
|
||||
```typescript
|
||||
requestInterceptors: (config, options) => {
|
||||
const token = storage.get('ACCESS-TOKEN', '');
|
||||
if (token && config.requestOptions?.withToken !== false) {
|
||||
(config as Recordable).headers.Authorization = options.authenticationScheme
|
||||
? `${options.authenticationScheme} ${token}`
|
||||
: token;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
```
|
||||
|
||||
Token 存储在 `localStorage` 中,键名为 `ACCESS-TOKEN`,通过 `storage` 工具类管理(支持 7 天过期)。
|
||||
|
||||
## 响应拦截器
|
||||
|
||||
响应解包逻辑在 `transformRequestData` 中处理:
|
||||
|
||||
```typescript
|
||||
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
|
||||
const { code, data, msg } = res.data;
|
||||
|
||||
// 业务成功(code === 0)
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
return data; // 直接返回 data 字段,调用方拿到的是业务数据
|
||||
}
|
||||
|
||||
// 业务失败(code === 1)
|
||||
if (code === ResultEnum.ERROR) {
|
||||
ElMessage.error(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// 登录超时(code === 401)
|
||||
if (code === ResultEnum.TIMEOUT) {
|
||||
ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', { ... })
|
||||
.then(() => {
|
||||
storage.clear();
|
||||
window.location.href = '/login';
|
||||
});
|
||||
throw new Error(msg);
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
解包后的返回值约定:
|
||||
|
||||
| 接口类型 | 返回值结构 | 说明 |
|
||||
|----------|-----------|------|
|
||||
| 普通接口 | `data`(直接返回业务数据) | 拦截器已解包 `res.data.data` |
|
||||
| 分页接口 | `{ records, total, size, current, pages }` | 后端通过 `R.ok(data=page_data)` 返回 |
|
||||
| 登录接口 | `{ access_token: "..." }` | 使用 `isTransformResponse: false`,自行处理 |
|
||||
|
||||
## 接口调用方式
|
||||
|
||||
在 Vue 组件中调用 API:
|
||||
|
||||
```typescript
|
||||
import { getExamplePage, exampleAdd } from '@/api/tool/example'
|
||||
|
||||
// 查询列表(loadDataTable 传给 BasicTable 的 request 属性)
|
||||
const loadDataTable = async (params: any) => {
|
||||
const result = await getExamplePage({ ...formParams, ...params });
|
||||
return result; // 拦截器已解包,result 即为 { records, total, size, current, pages }
|
||||
};
|
||||
|
||||
// 添加数据
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
await exampleAdd(formData);
|
||||
message('操作成功');
|
||||
emit('success');
|
||||
};
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
错误处理分两层:
|
||||
|
||||
1. **业务错误**(code !== 0):拦截器统一弹出 `ElMessage.error(msg)`,并 `throw new Error`
|
||||
2. **HTTP 错误**(网络异常、超时等):`responseInterceptorsCatch` 处理
|
||||
|
||||
```typescript
|
||||
responseInterceptorsCatch: (error: any) => {
|
||||
const { response, code } = error || {};
|
||||
if (code === 'ECONNABORTED') {
|
||||
ElMessage.error('接口请求超时,请刷新页面重试!');
|
||||
return;
|
||||
}
|
||||
if (error.toString().includes('Network Error')) {
|
||||
ElMessageBox.confirm('请检查您的网络连接是否正常', '网络异常', { ... });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
checkStatus(error.response?.status, response?.data?.msg);
|
||||
},
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
所有 API 函数返回的是已解包的响应数据(经过拦截器处理),分页接口直接返回 `{ records, total, size, current, pages }` 结构,调用方无需再 `.data` 解包。
|
||||
:::
|
||||
|
||||
## 总结
|
||||
|
||||
API 层通过 `VAxios` 类封装 Axios,提供请求拦截(自动注入 Token)、响应解包(`code===0` 返回 `data`)、重复请求取消(`AxiosCanceler`)、错误处理(业务错误 + HTTP 状态码)等能力。各模块按目录组织接口函数,调用方拿到的是已解包的业务数据。
|
||||
Reference in New Issue
Block a user