Ant Design Pro快速入门——后端集成
纯前端菜鸡,只有一些HTML、CSS和JavaScript的基础,最近配合的前端跑路了,想用Ant Design Pro来快速搭建一套中后台来管理数据,只能自己上了,主要自己也想试试水,从后端的视角看前端,整体偏向于实战。
后端集成
中后台最常用的就是数据处理和数据交互,与后端的CURD接口交互不可避免,那Ant Design Pro如何与后端交互呢?
openAPI
Pro中提供了openAPI的插件,用于快速生成API请求代码,其核心功能如下:
- 自动生成API请求代码:根据后端提供的OpenAPI文档,自动生成前端的
services/API.ts文件 - 集成请求工具:默认使用umi的request工具,可配置axios或其他请求工具
- Mock数据支持:可以直接使用OpenAPI 文档生成Mock数据,无需手动编写
- 类型安全:生成的API代码包含完整的类型定义,类型检查更 strict。
关于这个功能,怎么说吧,看起来是一件很cool的事情,但在实际的开发过程中,你很难要求开发按何种方式来管理文档,所以有兴趣可以自行了解openAPI,我个人认为还是手动写请求比较实在。当然,这并不是重点,重点是如果你在项目中配置了 @umijs/plugin-openapi 并运行生成命令(如 umi openapi)时,插件会:
- 从后端提供的 OpenAPI/Swagger 文档(如 https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json)读取接口定义。
- 根据接口规范,自动生成对应的 TypeScript 请求代码 和 类型定义,并保存到 services 目录下。
src/services/
├── ant-design-pro/ # 默认生成的 API 文件(通常对应业务接口)
│ ├── api.ts # 自动生成的 API 请求函数
│ └── typings.d.ts # 自动生成的类型定义
├── swagger/ # 从 Swagger 生成的 API 文件(可能用于基础服务)
│ ├── user.ts
│ └── ...
所以,不需要把自定义的请求放到这两个目录的文件中,如果后端 Swagger 文档更新后重新生成代码,手动修改的内容会被覆盖,我初学时就是把一些代码定义到了api.ts和typings.d.ts中,然后跑了npm run openapi自动生成文档,写的逻辑全被自动覆盖了……
网络请求
Pro总结出一套标准的接口结构规范,并提供统一的接口解析、错误处理的能力。
使用Request
通过import { request } from '@umijs/max';可是使用内置的request方法,该方法有两个参数,第一个是url,第二个是配置项,配置项中可以包含请求参数,请求方法,请求头等,示例如下:
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';
/** 获取规则列表 GET /api/rule */
export async function rule(
params: {
// query
/** 当前的页码 */
current?: number;
/** 页面的容量 */
pageSize?: number;
},
options?: { [key: string]: any },
) {
return request<WorkFlowAPI.RuleList>('/api/rule', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
使用useRequest
useRequest是一个Hooks,用于请求数据,它封装了request方法,提供了一些常用的功能,如loading、error、data等,示例如下:
import { useRequest } from '@umijs/max';
//自动管理请求状态
export default () => {
const { data, error, loading } = useRequest('/api/user');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Username: {data.name}</div>;
};
/******************************分隔线************************************/
//手动触发请求,适合表单提交等场景
const { run } = useRequest(
(params) => fetch('/api/search', { method: 'POST', body: params }),
{ manual: true } // 手动触发
);
// 点击按钮时发送请求
<button onClick={() => run({ keyword: 'test' })}>Search</button>
/******************************分隔线************************************/
// 监听依赖项变化
const [keyword, setKeyword] = useState('');
const { data } = useRequest('/api/search', {
refreshDeps: [keyword], // keyword 变化时重新请求
});
/******************************分隔线************************************/
// 轮询请求,每 3 秒请求一次
const { data } = useRequest('/api/stock-price', {
pollingInterval: 3000,
});
/******************************分隔线************************************/
// 缓存请求结果,5秒内直接读缓存,避免重复的相同请求
const { data } = useRequest('/api/config', {
cacheKey: 'global-config', // 缓存键
staleTime: 5000, // 5秒内直接读缓存
});
中间件
如果我们想在请求的过程中插入自定义逻辑,如:
- 修改请求参数(如添加全局 token)
- 统一处理错误(如接口报错时弹出通知)
- 日志记录(记录请求耗时) - Mock 数据拦截(开发环境下模拟响应)
全局中间件
在 src/app.tsx 中配置全局中间件:
// src/app.ts
export const request = {
middlewares: [
// 示例:添加请求头
async (ctx, next) => {
ctx.req.options.headers = {
...ctx.req.options.headers,
Authorization: 'Bearer xxx',
};
await next(); // 继续执行下一个中间件或发送请求
},
// 示例:统一错误处理
async (ctx, next) => {
try {
await next();
} catch (error) {
console.error('Request failed:', error);
throw error; // 继续抛出错误,由 useRequest 的 error 状态捕获
}
},
],
};
单次中间件
const { data } = useRequest('/api/data', {
middlewares: [
async (ctx, next) => {
console.log('请求参数:', ctx.params); // 打印请求参数
await next();
console.log('响应数据:', ctx.data); // 打印响应数据
},
],
});
拦截器
拦截器也可以达到同样的效果。
前置拦截
在网络请求的 .then 或 catch 处理前拦截,你可以在 src/app.tsx 网络请求配置内增加如下配置:
export const request: RequestConfig = {
// 新增自动添加AccessToken的请求前拦截器
requestInterceptors: [authHeaderInterceptor],
};
//拦截逻辑
const authHeaderInterceptor = (url: string, options: RequestConfig) => {
const authHeader = { Authorization: 'Bearer xxxxxx' };
return {
url: `${url}`,
options: { ...options, interceptors: true, headers: authHeader },
};
};
后置拦截
后置拦截一般用来处理异常,你可以在 src/app.tsx 网络请求配置内增加如下配置:
// src/app.tsx
const demoResponseInterceptors = (response: Response, options: RequestConfig) => {
response.headers.append('interceptors', 'yes yo');
return response;
};
export const request: RequestConfig = {
responseInterceptors: [demoResponseInterceptors],
};
跨域请求
跨域请求,相比大家并不陌生,比较常见的解决方案就是JsonP和CORS,我比较感兴趣的是这个proxy的解决方案,它是通过本地代理服务器将前端请求转发给后端,流程如下:
- 前端请求 → 发送到本地开发服务器(如 http://localhost:8000/api/users)。
- 代理服务器 → 将请求转发到真实后端(如 http://api.example.com/api/users)。
- 后端响应 → 代理服务器将结果返回给前端。
修改config/proxy.ts文件:
export default {
'/api': {
target: 'https://jsonplaceholder.typicode.com', // 免费测试 API
changeOrigin: true, // 修改请求头 Host
pathRewrite: { '^/api': '' }, // 去掉 /api 前缀
}
};
请求示例:
import { useRequest } from 'umi';
import { useState, useEffect } from 'react';
export default () => {
// 示例 1:获取用户列表(代理到 https://jsonplaceholder.typicode.com/users)
const { data, loading } = useRequest('/api/users');
const [content, setContent] = useState<string>('加载中...'); // 初始化内容
useEffect(() => {
const fetchData = async () => {
try {
const rep = await fetch('/api/users');
const text = await rep.text();
setContent(text);
} catch (error) {
console.error('获取日志失败:', error);
setContent('加载内容失败');
}
};
fetchData();
}, []);
return (
<div>
{content}
</div>
);
};
MOCK请求
在 Ant Design Pro 中,Mock 请求是一种在开发阶段模拟后端 API 返回数据的技术,无需依赖真实后端服务即可进行前端开发和测试:
- 独立开发:前端无需等待后端接口完成
- 快速原型:模拟各种响应(成功/失败/异常)
- 联测试:生成随机数据测试页面逻辑
- 无跨域问题:本地服务直接返回数据
在很多情况下前端是在后端还没有开发完成之前就开始开发的,这时候我们就需要用到 mock 数据了。Pro 中约定了两种 mock 的定义方式。
- 在根目录的 mock 中接入
- 在 src/page 中的 mock.ts 的文件中配置
// 代码中会兼容本地 service mock 以及部署站点的静态数据
export default {
// 支持值为 Object 和 Array
'GET /api/currentUser': (req: Request, res: Response) => {
if (!getAccess()) {
res.status(401).send({
data: {
isLogin: false,
},
errorCode: '401',
errorMessage: '请先登录!',
success: true,
});
return;
}
res.send({
success: true,
data: {
name: 'Serati Ma',
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
userid: '00000001',
email: 'antdesign@alipay.com',
signature: '海纳百川,有容乃大',
title: '交互专家',
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
tags: [
{
key: '0',
label: '很有想法的',
},
{
key: '1',
label: '专注设计',
},
{
key: '2',
label: '辣~',
},
{
key: '3',
label: '大长腿',
},
{
key: '4',
label: '川妹子',
},
{
key: '5',
label: '海纳百川',
},
],
notifyCount: 12,
unreadCount: 11,
country: 'China',
access: getAccess(),
geographic: {
province: {
label: '浙江省',
key: '330000',
},
city: {
label: '杭州市',
key: '330100',
},
},
address: '西湖区工专路 77 号',
phone: '0752-268888888',
},
});
},
// GET POST 可省略
'GET /api/users': [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
],
'POST /api/login/account': async (req: Request, res: Response) => {
const { password, username, type } = req.body;
await waitTime(2000);
if (password === '123456' && username === 'admin') {
res.send({
status: 'ok',
type,
currentAuthority: 'admin',
});
access = 'admin';
return;
}
if (password === '123456' && username === 'user') {
res.send({
status: 'ok',
type,
currentAuthority: 'user',
});
access = 'user';
return;
}
if (type === 'mobile') {
res.send({
status: 'ok',
type,
currentAuthority: 'admin',
});
access = 'admin';
return;
}
res.send({
status: 'error',
type,
currentAuthority: 'guest',
});
access = 'guest';
},
'POST /api/login/outLogin': (req: Request, res: Response) => {
access = '';
res.send({ data: {}, success: true });
},
'POST /api/register': (req: Request, res: Response) => {
res.send({ status: 'ok', currentAuthority: 'user', success: true });
},
'GET /api/500': (req: Request, res: Response) => {
res.status(500).send({
timestamp: 1513932555104,
status: 500,
error: 'error',
message: 'error',
path: '/base/category/list',
});
},
'GET /api/404': (req: Request, res: Response) => {
res.status(404).send({
timestamp: 1513932643431,
status: 404,
error: 'Not Found',
message: 'No message available',
path: '/base/category/list/2121212',
});
},
'GET /api/403': (req: Request, res: Response) => {
res.status(403).send({
timestamp: 1513932555104,
status: 403,
error: 'Forbidden',
message: 'Forbidden',
path: '/base/category/list',
});
},
'GET /api/401': (req: Request, res: Response) => {
res.status(401).send({
timestamp: 1513932555104,
status: 401,
error: 'Unauthorized',
message: 'Unauthorized',
path: '/base/category/list',
});
},
'GET /api/login/captcha': getFakeCaptcha,
};
有兴趣的可以看看语雀上的教程:Ant Design 实战教程(beta 版)

浙公网安备 33010602011771号