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:
@@ -1,29 +1,72 @@
|
||||
import React from 'react';
|
||||
import { Input, Upload, message, Button } from 'antd';
|
||||
import { PictureOutlined, SendOutlined } from '@ant-design/icons';
|
||||
import { Input, Upload, message, Button, Spin } from 'antd';
|
||||
import { PictureOutlined, SendOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||
import { postInput, postInputImage } from '../apis';
|
||||
import './InputPanel.css';
|
||||
|
||||
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 [fileList, setFileList] = React.useState<any[]>([]);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const send = () => {
|
||||
const send = async () => {
|
||||
const hasText = value.trim().length > 0;
|
||||
const hasImage = fileList.length > 0;
|
||||
if (!hasText && !hasImage) {
|
||||
message.info('请输入内容或上传图片');
|
||||
return;
|
||||
}
|
||||
// 此处替换为真实发送逻辑
|
||||
console.log('发送内容:', { text: value, files: fileList });
|
||||
setValue('');
|
||||
setFileList([]);
|
||||
message.success('已发送');
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
let response;
|
||||
|
||||
// 如果有图片,优先处理图片上传
|
||||
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>) => {
|
||||
if (loading) return; // 加载中时禁用快捷键
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
if (e.shiftKey) {
|
||||
// Shift+Enter 换行(保持默认行为)
|
||||
@@ -37,13 +80,20 @@ const InputPanel: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="input-panel">
|
||||
<TextArea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="请输入个人信息描述,或点击右侧上传图片…"
|
||||
autoSize={{ minRows: 3, maxRows: 6 }}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<Spin
|
||||
spinning={loading}
|
||||
tip="正在处理中,请稍候..."
|
||||
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
|
||||
>
|
||||
<TextArea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="请输入个人信息描述,或点击右侧上传图片…"
|
||||
autoSize={{ minRows: 3, maxRows: 6 }}
|
||||
onKeyDown={onKeyDown}
|
||||
disabled={loading}
|
||||
/>
|
||||
</Spin>
|
||||
<div className="input-actions">
|
||||
<Upload
|
||||
accept="image/*"
|
||||
@@ -52,10 +102,17 @@ const InputPanel: React.FC = () => {
|
||||
onChange={({ fileList }) => setFileList(fileList as any)}
|
||||
maxCount={9}
|
||||
showUploadList={{ showPreviewIcon: false }}
|
||||
disabled={loading}
|
||||
>
|
||||
<Button type="text" icon={<PictureOutlined />} />
|
||||
<Button type="text" icon={<PictureOutlined />} disabled={loading} />
|
||||
</Upload>
|
||||
<Button type="primary" icon={<SendOutlined />} onClick={send} />
|
||||
<Button
|
||||
type="primary"
|
||||
icon={loading ? <LoadingOutlined /> : <SendOutlined />}
|
||||
onClick={send}
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,12 @@ import './MainContent.css';
|
||||
const { Content } = Layout;
|
||||
|
||||
const MainContent: React.FC = () => {
|
||||
const [formData, setFormData] = React.useState<any>(null);
|
||||
|
||||
const handleInputResult = (data: any) => {
|
||||
setFormData(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<Content className="main-content">
|
||||
<div className="content-body">
|
||||
@@ -18,11 +24,11 @@ const MainContent: React.FC = () => {
|
||||
输入个人信息描述,上传图片,我将自动整理资源信息
|
||||
</Typography.Paragraph>
|
||||
|
||||
<PeopleForm />
|
||||
<PeopleForm initialData={formData} />
|
||||
</div>
|
||||
|
||||
<div className="input-panel-wrapper">
|
||||
<InputPanel />
|
||||
<InputPanel onResult={handleInputResult} />
|
||||
<HintText />
|
||||
</div>
|
||||
</Content>
|
||||
|
||||
@@ -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 './PeopleForm.css';
|
||||
import KeyValueList from './KeyValueList.tsx'
|
||||
import { createPeople } from '../apis';
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
const PeopleForm: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
interface PeopleFormProps {
|
||||
initialData?: any;
|
||||
}
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
// 暂时打印内容,模拟提交
|
||||
console.log('People form submit:', values);
|
||||
message.success('表单已提交');
|
||||
form.resetFields();
|
||||
const PeopleForm: React.FC<PeopleFormProps> = ({ initialData }) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 当 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 (
|
||||
@@ -75,8 +141,8 @@ const PeopleForm: React.FC = () => {
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" block>
|
||||
提交
|
||||
<Button type="primary" htmlType="submit" loading={loading} block>
|
||||
{loading ? '提交中...' : '提交'}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
@@ -1,33 +1,50 @@
|
||||
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 { FilterDropdownProps } from 'antd/es/table/interface';
|
||||
import type { TableProps } from 'antd';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import './MainContent.css';
|
||||
import { getPeoples } from '../apis';
|
||||
import type { People } from '../apis';
|
||||
|
||||
const { Content } = Layout;
|
||||
|
||||
// 数据类型定义
|
||||
// 数据类型定义 - 使用 API 中的 People 类型
|
||||
export type DictValue = Record<string, string>;
|
||||
export type Resource = {
|
||||
id: string;
|
||||
name: string;
|
||||
gender: '男' | '女' | '其他/保密' | string;
|
||||
age: number;
|
||||
height?: number;
|
||||
marital_status?: string;
|
||||
introduction?: DictValue;
|
||||
};
|
||||
export type Resource = People;
|
||||
|
||||
// 模拟从后端获取资源列表(真实环境替换为实际接口)
|
||||
// 获取人员列表数据
|
||||
async function fetchResources(): Promise<Resource[]> {
|
||||
try {
|
||||
const res = await fetch('/api/resources');
|
||||
if (!res.ok) throw new Error('network');
|
||||
const data = await res.json();
|
||||
return data as Resource[];
|
||||
} catch (e) {
|
||||
const response = await getPeoples({
|
||||
limit: 1000, // 获取大量数据用于前端分页和筛选
|
||||
offset: 0
|
||||
});
|
||||
|
||||
// 检查响应是否成功
|
||||
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 数据,便于本地开发
|
||||
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 {
|
||||
title: label,
|
||||
dataIndex,
|
||||
@@ -476,7 +493,7 @@ function buildNumberRangeFilter<T extends Resource>(dataIndex: keyof T, label: s
|
||||
if (max !== undefined && val > max) return false;
|
||||
return true;
|
||||
},
|
||||
} as ColumnType<T>;
|
||||
} as ColumnType<Resource>;
|
||||
}
|
||||
|
||||
const ResourceList: React.FC = () => {
|
||||
@@ -592,7 +609,7 @@ const ResourceList: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<span style={{ color: '#9ca3af' }}>{k}</span>
|
||||
<span style={{ color: '#e5e7eb' }}>{v}</span>
|
||||
<span style={{ color: '#e5e7eb' }}>{String(v)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user