13 KiB
13 KiB
API 调用模式和通用实现指南
核心 API 架构
ApiClient 核心类
位置:src/services/api.ts
class ApiClient {
private instance: any;
private isRefreshing = false;
private failedQueue: Array<{
resolve: (value: any) => void;
reject: (reason: any) => void;
}> = [];
// 特性:
// 1. 自动附加 Authorization 头
// 2. 401 错误时自动刷新 token
// 3. Token 刷新期间的请求入队
// 4. 自动处理 token 过期和重新登录
}
export const apiClient = new ApiClient();
API 服务层设计模式
模式 1:列表 API(用户等级配置)
文件:src/services/userLevelConfigApi.ts
import { apiClient } from './api';
import type { UserLevelConfig, UserLevelConfigListRequest, UserLevelConfigListResponse } from '../types/userLevelConfig';
// 模式:响应数据提取
// - apiClient 返回完整响应对象
// - 最后一层 API 函数负责提取数据或返回完整响应
export const getUserLevelConfigList = async (
params: UserLevelConfigListRequest
): Promise<UserLevelConfigListResponse> => {
const response = await apiClient.get('/admin/user-level-configs', { params });
return response.data; // 返回响应数据部分
};
export const getAllUserLevelConfigs = async (): Promise<UserLevelConfig[]> => {
const response = await apiClient.get('/admin/user-level-configs/all');
return response.data;
};
模式 2:列表 API(系统配置)
文件:src/services/systemConfigApi.ts
import { apiClient } from './api';
import type { SystemConfig, SystemConfigListRequest, SystemConfigListResponse } from '../types/systemConfig';
// 注意:不同的 API 可能返回不同的数据结构
// 系统配置返回完整响应对象(包含 code, message, data)
export const getSystemConfigList = async (
params: SystemConfigListRequest
): Promise<SystemConfigListResponse> => {
const response = await apiClient.get('/admin/system-configs', { params });
return response; // 返回完整响应对象
};
模式 3:CRUD 操作
// 创建
export const createUserLevelConfig = async (
data: UserLevelConfigCreateRequest
): Promise<UserLevelConfig> => {
const response = await apiClient.post('/admin/user-level-configs', data);
return response.data;
};
// 读取
export const getUserLevelConfigById = async (id: number): Promise<UserLevelConfig> => {
const response = await apiClient.get(`/admin/user-level-configs/${id}`);
return response.data;
};
// 更新
export const updateUserLevelConfig = async (
id: number,
data: UserLevelConfigUpdateRequest
): Promise<UserLevelConfig> => {
const response = await apiClient.put(`/admin/user-level-configs/${id}`, data);
return response.data;
};
// 删除
export const deleteUserLevelConfig = async (id: number): Promise<void> => {
await apiClient.delete(`/admin/user-level-configs/${id}`);
};
// 特殊操作:状态更新
export const updateUserLevelConfigStatus = async (
id: number,
data: UserLevelConfigStatusRequest
): Promise<void> => {
await apiClient.put(`/admin/user-level-configs/${id}/status`, data);
};
类型定义模式
完整类型定义示例
文件:src/types/userLevelConfig.ts
// 1. 主体数据类型
export interface UserLevelConfig {
id: number;
level_name: string;
level_code: string;
project_limit: number;
description: string;
sort_order: number;
status: number; // 1=启用, 0=禁用
created_at: string;
updated_at: string;
}
// 2. 请求类型
export interface UserLevelConfigListRequest {
level_name?: string;
status?: number;
page?: number;
size?: number;
}
export interface UserLevelConfigCreateRequest {
level_name: string;
level_code: string;
project_limit: number;
description?: string;
sort_order?: number;
}
export interface UserLevelConfigUpdateRequest {
level_name: string;
project_limit: number;
description?: string;
sort_order?: number;
}
export interface UserLevelConfigStatusRequest {
status: number;
}
// 3. 响应类型
export interface UserLevelConfigListResponse {
data: UserLevelConfig[];
total: number;
page: number;
size: number;
}
页面组件实现模式
CRUD 页面的标准结构
文件:src/pages/UserLevelConfigs.tsx
import React, { useEffect, useState } from 'react';
import { Table, Button, Modal, Form, Input, message } from 'antd';
import type { UserLevelConfig } from '../types/userLevelConfig';
import { getUserLevelConfigList, createUserLevelConfig, updateUserLevelConfig, deleteUserLevelConfig } from '../services/userLevelConfigApi';
export default function UserLevelConfigs() {
// ==================== 状态管理 ====================
const [loading, setLoading] = useState(false);
const [list, setList] = useState<UserLevelConfig[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [size, setSize] = useState(10);
// 编辑弹窗状态
const [editOpen, setEditOpen] = useState(false);
const [editing, setEditing] = useState<UserLevelConfig | null>(null);
const [form] = Form.useForm();
// 创建弹窗状态
const [createOpen, setCreateOpen] = useState(false);
const [createForm] = Form.useForm();
// ==================== 数据加载 ====================
const fetchList = async () => {
setLoading(true);
try {
const res = await getUserLevelConfigList({ page, size });
// 处理响应数据:兼容数组和对象两种格式
if (Array.isArray(res)) {
setList(res);
setTotal(res.length);
} else {
setList(res?.data ?? []);
setTotal(res?.total ?? 0);
}
} catch (e) {
console.error('获取列表失败:', e);
message.error('获取列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchList();
}, [page, size]);
// ==================== 创建操作 ====================
const openCreate = () => {
createForm.resetFields();
setCreateOpen(true);
};
const submitCreate = async () => {
try {
const values = await createForm.validateFields();
await createUserLevelConfig(values);
message.success('创建成功');
setCreateOpen(false);
fetchList();
} catch (error: any) {
message.error(error?.response?.data?.message || '创建失败');
}
};
// ==================== 编辑操作 ====================
const openEdit = (config: UserLevelConfig) => {
setEditing(config);
form.setFieldsValue({
level_name: config.level_name,
project_limit: config.project_limit,
description: config.description,
sort_order: config.sort_order,
});
setEditOpen(true);
};
const submitEdit = async () => {
try {
const values = await form.validateFields();
if (!editing) return;
await updateUserLevelConfig(editing.id, values);
message.success('更新成功');
setEditOpen(false);
fetchList();
} catch (error) {
message.error('更新失败');
}
};
// ==================== 删除操作 ====================
const handleDelete = async (config: UserLevelConfig) => {
try {
await deleteUserLevelConfig(config.id);
message.success('删除成功');
fetchList();
} catch (error) {
message.error('删除失败');
}
};
// ==================== 渲染 ====================
const columns = [
// 列定义...
];
return (
<div>
{/* 主表格 */}
<Table dataSource={list} columns={columns} loading={loading} />
{/* 创建弹窗 */}
<Modal title="新建" open={createOpen} onOk={submitCreate}>
<Form form={createForm} layout="vertical">
{/* 表单字段 */}
</Form>
</Modal>
{/* 编辑弹窗 */}
<Modal title="编辑" open={editOpen} onOk={submitEdit}>
<Form form={form} layout="vertical">
{/* 表单字段 */}
</Form>
</Modal>
</div>
);
}
权限和路由集成
添加新页面的完整检查清单
1. 创建类型定义
src/types/xxxConfig.ts- 定义数据模型和请求/响应类型
2. 创建 API 服务
src/services/xxxConfigApi.ts- 实现 CRUD API 方法
3. 创建页面组件
src/pages/XxxConfigs.tsx- 实现 CRUD UI
4. 注册路由
src/App.tsx- 添加路由定义<Route path="/system/xxx-configs" element={<XxxConfigs />} />
5. 添加菜单项
src/components/DynamicMenu.tsx- 添加菜单项定义{ key: 'system-xxx-configs', label: 'Xxx 配置', icon: <SettingOutlined />, path: '/xxx-configs', }
6. 更新 Layout 子导航
src/components/Layout.tsx- 添加子导航按钮(如果需要)<button className={activeSubTab === 'xxx-configs' ? 'active' : ''} onClick={() => navigate('/system/xxx-configs')} > Xxx 配置 </button>
错误处理最佳实践
API 错误处理
// 页面中的错误处理
const fetchList = async () => {
try {
const res = await getUserLevelConfigList({ page, size });
setList(res?.data ?? []);
} catch (error: any) {
// 1. 优先使用 API 返回的错误信息
const errorMsg = error?.response?.data?.message;
// 2. 其次使用通用错误信息
message.error(errorMsg || '获取列表失败');
// 3. 记录详细错误用于调试
console.error('获取列表失败:', error);
}
};
// 创建/更新时的错误处理
const submitCreate = async () => {
try {
const values = await createForm.validateFields();
await createUserLevelConfig(values);
message.success('创建成功');
} catch (error: any) {
// 区分表单验证错误和 API 错误
if (error?.response?.data?.message) {
message.error(error.response.data.message);
} else {
message.error('创建失败');
}
}
};
通用 CRUD 表格列定义
标准列定义模式
const columns = [
// 基本字段列
{
title: '名称',
dataIndex: 'level_name',
key: 'level_name',
},
{
title: '代码',
dataIndex: 'level_code',
key: 'level_code',
},
// 数值列(带格式化)
{
title: '数值',
dataIndex: 'value',
key: 'value',
render: (value: number) => (value === 0 ? '不限' : value),
},
// 状态列(带标签)
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (value: number) =>
value === 1 ? <Tag color="green">启用</Tag> : <Tag color="red">禁用</Tag>,
},
// 操作列(最后)
{
title: '操作',
key: 'action',
render: (_: any, record: T) => (
<Space>
<Button type="link" icon={<EditOutlined />} onClick={() => openEdit(record)}>
编辑
</Button>
<Button type="link" icon={<DeleteOutlined />} danger onClick={() => handleDelete(record)}>
删除
</Button>
</Space>
),
},
];
表单字段最佳实践
创建表单示例
<Form form={createForm} layout="vertical">
{/* 必填字段 */}
<Form.Item
name="level_name"
label="等级名称"
rules={[{ required: true, message: '请输入等级名称' }]}
>
<Input placeholder="请输入等级名称" />
</Form.Item>
{/* 代码字段(创建时必填,编辑时只读) */}
<Form.Item
name="level_code"
label="等级代码"
rules={[{ required: true, message: '请输入等级代码' }]}
>
<Input placeholder="请输入等级代码" />
</Form.Item>
{/* 数值字段 */}
<Form.Item
name="project_limit"
label="项目数限制(0表示不限)"
rules={[{ required: true, message: '请输入项目数限制' }]}
initialValue={0}
>
<InputNumber min={0} style={{ width: '100%' }} />
</Form.Item>
{/* 可选文本字段 */}
<Form.Item name="description" label="描述">
<Input.TextArea rows={3} placeholder="请输入描述" />
</Form.Item>
{/* 排序字段 */}
<Form.Item name="sort_order" label="排序顺序" initialValue={0}>
<InputNumber min={0} style={{ width: '100%' }} />
</Form.Item>
</Form>
分页实现
标准分页逻辑
// 状态
const [page, setPage] = useState(1);
const [size, setSize] = useState(10);
const [total, setTotal] = useState(0);
// 依赖项变化时重新加载
useEffect(() => {
fetchList();
}, [page, size]);
// 表格分页配置
<Table
dataSource={list}
columns={columns}
pagination={{
current: page,
pageSize: size,
total: total,
onChange: (p, s) => {
setPage(p);
setSize(s);
},
}}
/>
总结
API 调用流程
用户操作
↓
页面组件(使用 API 函数)
↓
API 服务函数(处理参数和响应)
↓
ApiClient(自动处理 token 和拦截器)
↓
Axios(HTTP 请求)
↓
后端 API
关键要点
- 分层设计:每一层职责清晰
- 类型安全:完整的 TypeScript 类型支持
- 错误处理:统一的错误处理机制
- 权限集成:无缝的权限管理
- 状态管理:简化的本地状态管理
- 自动化:自动 Token 管理和刷新