feat: use api for manage people

- wrap the backend apis
- post people by api
- list people by api
- auto fill people form by post input api
- auto fill people form by post image api
This commit is contained in:
2025-10-25 15:50:29 +08:00
parent ddb04ff15e
commit 28de10061a
12 changed files with 968 additions and 50 deletions

200
src/apis/README.md Normal file
View File

@@ -0,0 +1,200 @@
# API 接口封装
本目录包含了对 FastAPI 后端接口的完整封装,提供了类型安全的 TypeScript 接口。
## 文件结构
```
src/apis/
├── index.ts # 统一导出文件
├── config.ts # API 配置
├── request.ts # 基础请求工具
├── types.ts # TypeScript 类型定义
├── input.ts # 文本输入接口
├── upload.ts # 图片上传接口
├── people.ts # 人员管理接口
└── README.md # 使用说明
```
## 使用方法
### 1. 导入方式
```typescript
// 方式一导入所有API
import api from '@/apis';
// 方式二:按需导入
import { postInput, getPeoples, postInputImage } from '@/apis';
// 方式三:分模块导入
import { api } from '@/apis';
const { input, people, upload } = api;
```
### 2. 文本输入接口
```typescript
import { postInput } from '@/apis';
// 提交文本
try {
const response = await postInput('这是一段文本');
console.log('提交成功:', response);
} catch (error) {
console.error('提交失败:', error);
}
```
### 3. 图片上传接口
```typescript
import { postInputImage, validateImageFile } from '@/apis';
// 上传图片
const handleFileUpload = async (file: File) => {
// 验证文件
const validation = validateImageFile(file);
if (!validation.valid) {
alert(validation.error);
return;
}
try {
const response = await postInputImage(file);
console.log('上传成功:', response);
} catch (error) {
console.error('上传失败:', error);
}
};
// 带进度的上传
import { postInputImageWithProgress } from '@/apis';
const handleFileUploadWithProgress = async (file: File) => {
try {
const response = await postInputImageWithProgress(file, (progress) => {
console.log(`上传进度: ${progress}%`);
});
console.log('上传成功:', response);
} catch (error) {
console.error('上传失败:', error);
}
};
```
### 4. 人员管理接口
```typescript
import {
createPeople,
getPeoples,
searchPeoples,
deletePeople,
getPeoplesPaginated
} from '@/apis';
// 创建人员
const createNewPeople = async () => {
const peopleData = {
name: '张三',
gender: '男',
age: 25,
height: 175,
marital_status: '未婚'
};
try {
const response = await createPeople(peopleData);
console.log('创建成功:', response);
} catch (error) {
console.error('创建失败:', error);
}
};
// 查询人员列表
const fetchPeoples = async () => {
try {
const response = await getPeoples({
limit: 20,
offset: 0
});
console.log('查询结果:', response.data);
} catch (error) {
console.error('查询失败:', error);
}
};
// 搜索人员
const searchForPeople = async (keyword: string) => {
try {
const response = await searchPeoples(keyword, 10);
console.log('搜索结果:', response.data);
} catch (error) {
console.error('搜索失败:', error);
}
};
// 分页查询
const fetchPeoplesPaginated = async (page: number) => {
try {
const response = await getPeoplesPaginated(page, 10);
console.log('分页结果:', response.data);
} catch (error) {
console.error('查询失败:', error);
}
};
// 删除人员
const removePeople = async (peopleId: string) => {
try {
const response = await deletePeople(peopleId);
console.log('删除成功:', response);
} catch (error) {
console.error('删除失败:', error);
}
};
```
## 错误处理
所有接口都会抛出 `ApiError` 类型的错误,包含以下信息:
```typescript
try {
const response = await postInput('test');
} catch (error) {
if (error instanceof ApiError) {
console.log('错误状态码:', error.status);
console.log('错误信息:', error.message);
console.log('错误详情:', error.data);
}
}
```
## 类型定义
所有接口都提供了完整的 TypeScript 类型支持:
```typescript
import type {
People,
GetPeoplesParams,
PostInputRequest,
ApiResponse
} from '@/apis';
```
## 配置
可以通过修改 `config.ts` 文件来调整 API 配置:
```typescript
export const API_CONFIG = {
BASE_URL: 'http://127.0.0.1:8099', // API 基础地址
TIMEOUT: 10000, // 请求超时时间
HEADERS: {
'Content-Type': 'application/json',
},
};
```

17
src/apis/config.ts Normal file
View File

@@ -0,0 +1,17 @@
// API 配置
export const API_CONFIG = {
BASE_URL: 'http://127.0.0.1:8099',
TIMEOUT: 10000,
HEADERS: {
'Content-Type': 'application/json',
},
};
// API 端点
export const API_ENDPOINTS = {
INPUT: '/input',
INPUT_IMAGE: '/input_image',
PEOPLES: '/peoples',
PEOPLE_BY_ID: (id: string) => `/peoples/${id}`,
} as const;

29
src/apis/index.ts Normal file
View File

@@ -0,0 +1,29 @@
// API 模块统一导出
// 配置和工具
export * from './config';
export * from './request';
export * from './types';
// 具体接口
export * from './input';
export * from './upload';
export * from './people';
// 默认导出所有API函数
import * as inputApi from './input';
import * as uploadApi from './upload';
import * as peopleApi from './people';
export const api = {
// 文本输入相关
input: inputApi,
// 图片上传相关
upload: uploadApi,
// 人员管理相关
people: peopleApi,
};
export default api;

26
src/apis/input.ts Normal file
View File

@@ -0,0 +1,26 @@
// 文本输入相关 API
import { post } from './request';
import { API_ENDPOINTS } from './config';
import type { PostInputRequest, ApiResponse } from './types';
/**
* 提交文本输入
* @param text 输入的文本内容
* @returns Promise<ApiResponse>
*/
export async function postInput(text: string): Promise<ApiResponse> {
const requestData: PostInputRequest = { text };
// 为 postInput 设置 30 秒超时时间
return post<ApiResponse>(API_ENDPOINTS.INPUT, requestData, { timeout: 30000 });
}
/**
* 提交文本输入(使用对象参数)
* @param data 包含文本的请求对象
* @returns Promise<ApiResponse>
*/
export async function postInputData(data: PostInputRequest): Promise<ApiResponse> {
// 为 postInputData 设置 30 秒超时时间
return post<ApiResponse>(API_ENDPOINTS.INPUT, data, { timeout: 30000 });
}

168
src/apis/people.ts Normal file
View File

@@ -0,0 +1,168 @@
// 人员管理相关 API
import { get, post, del } from './request';
import { API_ENDPOINTS } from './config';
import type {
PostPeopleRequest,
GetPeoplesParams,
People,
ApiResponse,
PaginatedResponse
} from './types';
/**
* 创建人员信息
* @param people 人员信息对象
* @returns Promise<ApiResponse>
*/
export async function createPeople(people: Record<string, any>): Promise<ApiResponse> {
const requestData: PostPeopleRequest = { people };
console.log('创建人员请求数据:', requestData);
return post<ApiResponse>(API_ENDPOINTS.PEOPLES, requestData);
}
/**
* 查询人员列表
* @param params 查询参数
* @returns Promise<ApiResponse<People[]>>
*/
export async function getPeoples(params?: GetPeoplesParams): Promise<ApiResponse<People[]>> {
return get<ApiResponse<People[]>>(API_ENDPOINTS.PEOPLES, params);
}
/**
* 搜索人员
* @param searchText 搜索关键词
* @param topK 返回结果数量默认5
* @returns Promise<ApiResponse<People[]>>
*/
export async function searchPeoples(
searchText: string,
topK = 5
): Promise<ApiResponse<People[]>> {
const params: GetPeoplesParams = {
search: searchText,
top_k: topK,
};
return get<ApiResponse<People[]>>(API_ENDPOINTS.PEOPLES, params);
}
/**
* 按条件筛选人员
* @param filters 筛选条件
* @returns Promise<ApiResponse<People[]>>
*/
export async function filterPeoples(filters: {
name?: string;
gender?: string;
age?: number;
height?: number;
marital_status?: string;
}): Promise<ApiResponse<People[]>> {
const params: GetPeoplesParams = {
...filters,
limit: 50, // 默认返回50条
};
return get<ApiResponse<People[]>>(API_ENDPOINTS.PEOPLES, params);
}
/**
* 分页获取人员列表
* @param page 页码从1开始
* @param pageSize 每页数量默认10
* @param filters 可选的筛选条件
* @returns Promise<ApiResponse<PaginatedResponse<People>>>
*/
export async function getPeoplesPaginated(
page = 1,
pageSize = 10,
filters?: Partial<GetPeoplesParams>
): Promise<ApiResponse<PaginatedResponse<People>>> {
const params: GetPeoplesParams = {
...filters,
limit: pageSize,
offset: (page - 1) * pageSize,
};
const response = await get<ApiResponse<People[]>>(API_ENDPOINTS.PEOPLES, params);
// 将响应转换为分页格式
const paginatedResponse: PaginatedResponse<People> = {
items: response.data || [],
total: response.data?.length || 0, // 注意:实际项目中应该从后端获取总数
limit: pageSize,
offset: (page - 1) * pageSize,
};
return {
...response,
data: paginatedResponse,
};
}
/**
* 删除人员信息
* @param peopleId 人员ID
* @returns Promise<ApiResponse>
*/
export async function deletePeople(peopleId: string): Promise<ApiResponse> {
return del<ApiResponse>(API_ENDPOINTS.PEOPLE_BY_ID(peopleId));
}
/**
* 批量创建人员信息
* @param peopleList 人员信息数组
* @returns Promise<ApiResponse[]>
*/
export async function createPeoplesBatch(
peopleList: Record<string, any>[]
): Promise<ApiResponse[]> {
const promises = peopleList.map(people => createPeople(people));
return Promise.all(promises);
}
/**
* 高级搜索人员
* @param options 搜索选项
* @returns Promise<ApiResponse<People[]>>
*/
export async function advancedSearchPeoples(options: {
searchText?: string;
filters?: {
name?: string;
gender?: string;
ageRange?: { min?: number; max?: number };
heightRange?: { min?: number; max?: number };
marital_status?: string;
};
pagination?: {
page?: number;
pageSize?: number;
};
topK?: number;
}): Promise<ApiResponse<People[]>> {
const { searchText, filters = {}, pagination = {}, topK = 10 } = options;
const { page = 1, pageSize = 10 } = pagination;
const params: GetPeoplesParams = {
search: searchText,
name: filters.name,
gender: filters.gender,
marital_status: filters.marital_status,
limit: pageSize,
offset: (page - 1) * pageSize,
top_k: topK,
};
// 处理年龄范围(这里简化处理,实际可能需要后端支持范围查询)
if (filters.ageRange?.min !== undefined) {
params.age = filters.ageRange.min;
}
// 处理身高范围(这里简化处理,实际可能需要后端支持范围查询)
if (filters.heightRange?.min !== undefined) {
params.height = filters.heightRange.min;
}
return get<ApiResponse<People[]>>(API_ENDPOINTS.PEOPLES, params);
}

163
src/apis/request.ts Normal file
View File

@@ -0,0 +1,163 @@
// 基础请求工具函数
import { API_CONFIG } from './config';
import type { HTTPValidationError } from './types';
// 请求选项接口
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
headers?: Record<string, string>;
body?: any;
timeout?: number;
}
// 自定义错误类
export class ApiError extends Error {
status?: number;
data?: any;
constructor(message: string, status?: number, data?: any) {
super(message);
this.name = 'ApiError';
this.status = status;
this.data = data;
}
}
// 基础请求函数
export async function request<T = any>(
url: string,
options: RequestOptions = {}
): Promise<T> {
const {
method = 'GET',
headers = {},
body,
timeout = API_CONFIG.TIMEOUT,
} = options;
const fullUrl = url.startsWith('http') ? url : `${API_CONFIG.BASE_URL}${url}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const requestHeaders: Record<string, string> = {
...API_CONFIG.HEADERS,
...headers,
};
let requestBody: string | FormData | undefined;
if (body instanceof FormData) {
// 对于 FormData不设置 Content-Type让浏览器自动设置
delete requestHeaders['Content-Type'];
requestBody = body;
} else if (body) {
requestBody = JSON.stringify(body);
}
const response = await fetch(fullUrl, {
method,
headers: requestHeaders,
body: requestBody,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
let errorData: any;
try {
errorData = await response.json();
} catch {
errorData = { message: response.statusText };
}
throw new ApiError(
errorData.message || `HTTP ${response.status}: ${response.statusText}`,
response.status,
errorData
);
}
// 检查响应是否有内容
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else {
// 如果没有 JSON 内容,返回空对象
return {} as T;
}
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof ApiError) {
throw error;
}
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new ApiError('请求超时', 408);
}
throw new ApiError(error.message);
}
throw new ApiError('未知错误');
}
}
// GET 请求
export function get<T = any>(url: string, params?: Record<string, any>): Promise<T> {
let fullUrl = url;
if (params) {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
searchParams.append(key, String(value));
}
});
const queryString = searchParams.toString();
if (queryString) {
fullUrl += (url.includes('?') ? '&' : '?') + queryString;
}
}
return request<T>(fullUrl, { method: 'GET' });
}
// POST 请求
export function post<T = any>(url: string, data?: any, options?: Partial<RequestOptions>): Promise<T> {
return request<T>(url, {
method: 'POST',
body: data,
...options,
});
}
// PUT 请求
export function put<T = any>(url: string, data?: any): Promise<T> {
return request<T>(url, {
method: 'PUT',
body: data,
});
}
// DELETE 请求
export function del<T = any>(url: string): Promise<T> {
return request<T>(url, { method: 'DELETE' });
}
// 文件上传请求
export function upload<T = any>(url: string, file: File, fieldName = 'file', options?: Partial<RequestOptions>): Promise<T> {
const formData = new FormData();
formData.append(fieldName, file);
return request<T>(url, {
method: 'POST',
body: formData,
...options,
});
}

61
src/apis/types.ts Normal file
View File

@@ -0,0 +1,61 @@
// API 请求和响应类型定义
// 基础响应类型
export interface ApiResponse<T = any> {
data?: T;
error_code: number;
error_info?: string;
}
// 验证错误类型
export interface ValidationError {
loc: (string | number)[];
msg: string;
type: string;
}
export interface HTTPValidationError {
detail: ValidationError[];
}
// 文本输入请求类型
export interface PostInputRequest {
text: string;
}
// 人员信息请求类型
export interface PostPeopleRequest {
people: Record<string, any>;
}
// 人员查询参数类型
export interface GetPeoplesParams {
name?: string;
gender?: string;
age?: number;
height?: number;
marital_status?: string;
limit?: number;
offset?: number;
search?: string;
top_k?: number;
}
// 人员信息类型
export interface People {
id?: string;
name?: string;
gender?: string;
age?: number;
height?: number;
marital_status?: string;
[key: string]: any;
}
// 分页响应类型
export interface PaginatedResponse<T> {
items: T[];
total: number;
limit: number;
offset: number;
}

108
src/apis/upload.ts Normal file
View File

@@ -0,0 +1,108 @@
// 图片上传相关 API
import { upload } from './request';
import { API_ENDPOINTS } from './config';
import type { ApiResponse } from './types';
/**
* 上传图片文件
* @param file 要上传的图片文件
* @returns Promise<ApiResponse>
*/
export async function postInputImage(file: File): Promise<ApiResponse> {
// 验证文件类型
if (!file.type.startsWith('image/')) {
throw new Error('只能上传图片文件');
}
return upload<ApiResponse>(API_ENDPOINTS.INPUT_IMAGE, file, 'image', { timeout: 30000 });
}
/**
* 上传图片文件(带进度回调)
* @param file 要上传的图片文件
* @param onProgress 上传进度回调函数
* @returns Promise<ApiResponse>
*/
export async function postInputImageWithProgress(
file: File,
onProgress?: (progress: number) => void
): Promise<ApiResponse> {
// 验证文件类型
if (!file.type.startsWith('image/')) {
throw new Error('只能上传图片文件');
}
const formData = new FormData();
formData.append('file', file);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 监听上传进度
if (onProgress) {
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const progress = Math.round((event.loaded / event.total) * 100);
onProgress(progress);
}
});
}
// 监听请求完成
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const response = xhr.responseText ? JSON.parse(xhr.responseText) : {};
resolve(response);
} catch (error) {
resolve({error_code: 1, error_info: '解析响应失败'});
}
} else {
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
});
// 监听请求错误
xhr.addEventListener('error', () => {
reject(new Error('网络错误'));
});
// 监听请求超时
xhr.addEventListener('timeout', () => {
reject(new Error('请求超时'));
});
// 发送请求
xhr.open('POST', `http://127.0.0.1:8099${API_ENDPOINTS.INPUT_IMAGE}`);
xhr.timeout = 30000; // 30秒超时
xhr.send(formData);
});
}
/**
* 验证图片文件
* @param file 文件对象
* @param maxSize 最大文件大小(字节),默认 10MB
* @returns 验证结果
*/
export function validateImageFile(file: File, maxSize = 10 * 1024 * 1024): { valid: boolean; error?: string } {
// 检查文件类型
if (!file.type.startsWith('image/')) {
return { valid: false, error: '只能上传图片文件' };
}
// 检查文件大小
if (file.size > maxSize) {
const maxSizeMB = Math.round(maxSize / (1024 * 1024));
return { valid: false, error: `文件大小不能超过 ${maxSizeMB}MB` };
}
// 检查支持的图片格式
const supportedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
if (!supportedTypes.includes(file.type)) {
return { valid: false, error: '支持的图片格式JPEG、PNG、GIF、WebP' };
}
return { valid: true };
}

View File

@@ -1,29 +1,72 @@
import React from 'react'; import React from 'react';
import { Input, Upload, message, Button } from 'antd'; import { Input, Upload, message, Button, Spin } from 'antd';
import { PictureOutlined, SendOutlined } from '@ant-design/icons'; import { PictureOutlined, SendOutlined, LoadingOutlined } from '@ant-design/icons';
import { postInput, postInputImage } from '../apis';
import './InputPanel.css'; import './InputPanel.css';
const { TextArea } = Input; const { TextArea } = Input;
const InputPanel: React.FC = () => { interface InputPanelProps {
onResult?: (data: any) => void;
}
const InputPanel: React.FC<InputPanelProps> = ({ onResult }) => {
const [value, setValue] = React.useState(''); const [value, setValue] = React.useState('');
const [fileList, setFileList] = React.useState<any[]>([]); const [fileList, setFileList] = React.useState<any[]>([]);
const [loading, setLoading] = React.useState(false);
const send = () => { const send = async () => {
const hasText = value.trim().length > 0; const hasText = value.trim().length > 0;
const hasImage = fileList.length > 0; const hasImage = fileList.length > 0;
if (!hasText && !hasImage) { if (!hasText && !hasImage) {
message.info('请输入内容或上传图片'); message.info('请输入内容或上传图片');
return; return;
} }
// 此处替换为真实发送逻辑
console.log('发送内容:', { text: value, files: fileList }); setLoading(true);
setValue(''); try {
setFileList([]); let response;
message.success('已发送');
// 如果有图片,优先处理图片上传
if (hasImage) {
const file = fileList[0].originFileObj || fileList[0];
if (!file) {
message.error('图片文件无效,请重新选择');
return;
}
console.log('上传图片:', file.name);
response = await postInputImage(file);
} else {
// 只有文本时,调用文本处理 API
console.log('处理文本:', value.trim());
response = await postInput(value.trim());
}
console.log('API响应:', response);
if (response.error_code === 0 && response.data) {
message.success('处理完成!已自动填充表单');
// 将结果传递给父组件
onResult?.(response.data);
message.info('输入已清空');
// 清空输入
setValue('');
setFileList([]);
} else {
message.error(response.error_info || '处理失败,请重试');
}
} catch (error) {
console.error('API调用失败:', error);
message.error('网络错误,请检查连接后重试');
} finally {
setLoading(false);
}
}; };
const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (loading) return; // 加载中时禁用快捷键
if (e.key === 'Enter') { if (e.key === 'Enter') {
if (e.shiftKey) { if (e.shiftKey) {
// Shift+Enter 换行(保持默认行为) // Shift+Enter 换行(保持默认行为)
@@ -37,13 +80,20 @@ const InputPanel: React.FC = () => {
return ( return (
<div className="input-panel"> <div className="input-panel">
<TextArea <Spin
value={value} spinning={loading}
onChange={(e) => setValue(e.target.value)} tip="正在处理中,请稍候..."
placeholder="请输入个人信息描述,或点击右侧上传图片…" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
autoSize={{ minRows: 3, maxRows: 6 }} >
onKeyDown={onKeyDown} <TextArea
/> value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="请输入个人信息描述,或点击右侧上传图片…"
autoSize={{ minRows: 3, maxRows: 6 }}
onKeyDown={onKeyDown}
disabled={loading}
/>
</Spin>
<div className="input-actions"> <div className="input-actions">
<Upload <Upload
accept="image/*" accept="image/*"
@@ -52,10 +102,17 @@ const InputPanel: React.FC = () => {
onChange={({ fileList }) => setFileList(fileList as any)} onChange={({ fileList }) => setFileList(fileList as any)}
maxCount={9} maxCount={9}
showUploadList={{ showPreviewIcon: false }} showUploadList={{ showPreviewIcon: false }}
disabled={loading}
> >
<Button type="text" icon={<PictureOutlined />} /> <Button type="text" icon={<PictureOutlined />} disabled={loading} />
</Upload> </Upload>
<Button type="primary" icon={<SendOutlined />} onClick={send} /> <Button
type="primary"
icon={loading ? <LoadingOutlined /> : <SendOutlined />}
onClick={send}
loading={loading}
disabled={loading}
/>
</div> </div>
</div> </div>
); );

View File

@@ -8,6 +8,12 @@ import './MainContent.css';
const { Content } = Layout; const { Content } = Layout;
const MainContent: React.FC = () => { const MainContent: React.FC = () => {
const [formData, setFormData] = React.useState<any>(null);
const handleInputResult = (data: any) => {
setFormData(data);
};
return ( return (
<Content className="main-content"> <Content className="main-content">
<div className="content-body"> <div className="content-body">
@@ -18,11 +24,11 @@ const MainContent: React.FC = () => {
</Typography.Paragraph> </Typography.Paragraph>
<PeopleForm /> <PeopleForm initialData={formData} />
</div> </div>
<div className="input-panel-wrapper"> <div className="input-panel-wrapper">
<InputPanel /> <InputPanel onResult={handleInputResult} />
<HintText /> <HintText />
</div> </div>
</Content> </Content>

View File

@@ -1,18 +1,84 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import { Form, Input, Select, InputNumber, Button, message, Row, Col } from 'antd'; import { Form, Input, Select, InputNumber, Button, message, Row, Col } from 'antd';
import './PeopleForm.css'; import './PeopleForm.css';
import KeyValueList from './KeyValueList.tsx' import KeyValueList from './KeyValueList.tsx'
import { createPeople } from '../apis';
const { TextArea } = Input; const { TextArea } = Input;
const PeopleForm: React.FC = () => { interface PeopleFormProps {
const [form] = Form.useForm(); initialData?: any;
}
const onFinish = (values: any) => { const PeopleForm: React.FC<PeopleFormProps> = ({ initialData }) => {
// 暂时打印内容,模拟提交 const [form] = Form.useForm();
console.log('People form submit:', values); const [loading, setLoading] = useState(false);
message.success('表单已提交');
form.resetFields(); // 当 initialData 变化时,自动填充表单
useEffect(() => {
if (initialData) {
console.log('收到API返回数据自动填充表单:', initialData);
// 处理返回的数据,将其转换为表单需要的格式
const formData: any = {};
if (initialData.name) formData.name = initialData.name;
if (initialData.gender) formData.gender = initialData.gender;
if (initialData.age) formData.age = initialData.age;
if (initialData.height) formData.height = initialData.height;
if (initialData.marital_status) formData.marital_status = initialData.marital_status;
if (initialData.match_requirement) formData.match_requirement = initialData.match_requirement;
if (initialData.introduction) formData.introduction = initialData.introduction;
// 设置表单字段值
form.setFieldsValue(formData);
// 显示成功消息
message.success('已自动填充表单,请检查并确认信息');
}
}, [initialData, form]);
const onFinish = async (values: any) => {
setLoading(true);
try {
const peopleData = {
name: values.name,
gender: values.gender,
age: values.age,
height: values.height || undefined,
marital_status: values.marital_status || undefined,
introduction: values.introduction || {},
match_requirement: values.match_requirement || undefined,
};
console.log('提交人员数据:', peopleData);
const response = await createPeople(peopleData);
console.log('API响应:', response);
if (response.error_code === 0) {
message.success('人员信息已成功提交到后端!');
form.resetFields();
} else {
message.error(response.error_info || '提交失败,请重试');
}
} catch (error: any) {
console.error('提交失败:', error);
// 根据错误类型显示不同的错误信息
if (error.status === 422) {
message.error('表单数据格式有误,请检查输入内容');
} else if (error.status >= 500) {
message.error('服务器错误,请稍后重试');
} else {
message.error(error.message || '提交失败,请重试');
}
} finally {
setLoading(false);
}
}; };
return ( return (
@@ -75,8 +141,8 @@ const PeopleForm: React.FC = () => {
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit" block> <Button type="primary" htmlType="submit" loading={loading} block>
{loading ? '提交中...' : '提交'}
</Button> </Button>
</Form.Item> </Form.Item>
</Form> </Form>

View File

@@ -1,33 +1,50 @@
import React from 'react'; import React from 'react';
import { Layout, Typography, Table, Grid, InputNumber, Button, Space, Tag } from 'antd'; import { Layout, Typography, Table, Grid, InputNumber, Button, Space, Tag, message } from 'antd';
import type { ColumnsType, ColumnType } from 'antd/es/table'; import type { ColumnsType, ColumnType } from 'antd/es/table';
import type { FilterDropdownProps } from 'antd/es/table/interface'; import type { FilterDropdownProps } from 'antd/es/table/interface';
import type { TableProps } from 'antd'; import type { TableProps } from 'antd';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import './MainContent.css'; import './MainContent.css';
import { getPeoples } from '../apis';
import type { People } from '../apis';
const { Content } = Layout; const { Content } = Layout;
// 数据类型定义 // 数据类型定义 - 使用 API 中的 People 类型
export type DictValue = Record<string, string>; export type DictValue = Record<string, string>;
export type Resource = { export type Resource = People;
id: string;
name: string;
gender: '男' | '女' | '其他/保密' | string;
age: number;
height?: number;
marital_status?: string;
introduction?: DictValue;
};
// 模拟从后端获取资源列表(真实环境替换为实际接口) // 获取人员列表数据
async function fetchResources(): Promise<Resource[]> { async function fetchResources(): Promise<Resource[]> {
try { try {
const res = await fetch('/api/resources'); const response = await getPeoples({
if (!res.ok) throw new Error('network'); limit: 1000, // 获取大量数据用于前端分页和筛选
const data = await res.json(); offset: 0
return data as Resource[]; });
} catch (e) {
// 检查响应是否成功
if (response.error_code !== 0) {
console.error('API错误:', response.error_info);
message.error(response.error_info || '获取数据失败');
// 返回空数组或使用 mock 数据作为后备
return [];
}
// 转换数据格式以匹配组件期望的结构
return response.data?.map((person: any) => ({
id: person.id || `person-${Date.now()}-${Math.random()}`,
name: person.name || '未知',
gender: person.gender || '其他/保密',
age: person.age || 0,
height: person.height,
marital_status: person.marital_status,
introduction: person.introduction || {},
})) || [];
} catch (error: any) {
console.error('获取人员列表失败:', error);
message.error('获取人员列表失败,使用模拟数据');
// 回退到 mock 数据,便于本地开发 // 回退到 mock 数据,便于本地开发
return [ return [
{ {
@@ -415,7 +432,7 @@ async function fetchResources(): Promise<Resource[]> {
} }
// 数字范围筛选下拉 // 数字范围筛选下拉
function buildNumberRangeFilter<T extends Resource>(dataIndex: keyof T, label: string): ColumnType<T> { function buildNumberRangeFilter(dataIndex: keyof Resource, label: string): ColumnType<Resource> {
return { return {
title: label, title: label,
dataIndex, dataIndex,
@@ -476,7 +493,7 @@ function buildNumberRangeFilter<T extends Resource>(dataIndex: keyof T, label: s
if (max !== undefined && val > max) return false; if (max !== undefined && val > max) return false;
return true; return true;
}, },
} as ColumnType<T>; } as ColumnType<Resource>;
} }
const ResourceList: React.FC = () => { const ResourceList: React.FC = () => {
@@ -592,7 +609,7 @@ const ResourceList: React.FC = () => {
}} }}
> >
<span style={{ color: '#9ca3af' }}>{k}</span> <span style={{ color: '#9ca3af' }}>{k}</span>
<span style={{ color: '#e5e7eb' }}>{v}</span> <span style={{ color: '#e5e7eb' }}>{String(v)}</span>
</div> </div>
))} ))}
</div> </div>