feat():learning后台管理前端页面初始化
This commit is contained in:
557
API_PATTERNS.md
Normal file
557
API_PATTERNS.md
Normal file
@@ -0,0 +1,557 @@
|
||||
# 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; // 返回完整响应对象
|
||||
};
|
||||
```
|
||||
|
||||
### 模式 3:CRUD 操作
|
||||
|
||||
```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 和拦截器)
|
||||
↓
|
||||
Axios(HTTP 请求)
|
||||
↓
|
||||
后端 API
|
||||
```
|
||||
|
||||
### 关键要点
|
||||
|
||||
1. **分层设计**:每一层职责清晰
|
||||
2. **类型安全**:完整的 TypeScript 类型支持
|
||||
3. **错误处理**:统一的错误处理机制
|
||||
4. **权限集成**:无缝的权限管理
|
||||
5. **状态管理**:简化的本地状态管理
|
||||
6. **自动化**:自动 Token 管理和刷新
|
||||
|
||||
Reference in New Issue
Block a user