Files
goalfylearning-admin-web/API_PATTERNS.md

558 lines
13 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 调用模式和通用实现指南
## 核心 API 架构
### ApiClient 核心类
**位置**`src/services/api.ts`
```typescript
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`
```typescript
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`
```typescript
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; // 返回完整响应对象
};
```
### 模式 3CRUD 操作
```typescript
// 创建
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`
```typescript
// 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`
```typescript
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` - 添加路由定义
```typescript
<Route path="/system/xxx-configs" element={<XxxConfigs />} />
```
#### 5. 添加菜单项
- [ ] `src/components/DynamicMenu.tsx` - 添加菜单项定义
```typescript
{
key: 'system-xxx-configs',
label: 'Xxx 配置',
icon: <SettingOutlined />,
path: '/xxx-configs',
}
```
#### 6. 更新 Layout 子导航
- [ ] `src/components/Layout.tsx` - 添加子导航按钮(如果需要)
```typescript
<button
className={activeSubTab === 'xxx-configs' ? 'active' : ''}
onClick={() => navigate('/system/xxx-configs')}
>
Xxx 配置
</button>
```
---
## 错误处理最佳实践
### API 错误处理
```typescript
// 页面中的错误处理
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 表格列定义
### 标准列定义模式
```typescript
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>
),
},
];
```
---
## 表单字段最佳实践
### 创建表单示例
```typescript
<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>
```
---
## 分页实现
### 标准分页逻辑
```typescript
// 状态
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 和拦截器)
AxiosHTTP 请求)
后端 API
```
### 关键要点
1. **分层设计**:每一层职责清晰
2. **类型安全**:完整的 TypeScript 类型支持
3. **错误处理**:统一的错误处理机制
4. **权限集成**:无缝的权限管理
5. **状态管理**:简化的本地状态管理
6. **自动化**:自动 Token 管理和刷新