Files
djangoadmin-wiki/DjangoAdmin_Django_EleVue/zh/volume4/frontend/api.md
T
2026-09-12 23:12:15 +08:00

242 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 状态码)等能力。各模块按目录组织接口函数,调用方拿到的是已解包的业务数据。