feat: support edit people

This commit is contained in:
2025-11-12 12:19:46 +08:00
parent c61a106373
commit dda8e13587
2 changed files with 241 additions and 16 deletions

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } 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 type { FormInstance } from 'antd';
import './PeopleForm.css'; import './PeopleForm.css';
import KeyValueList from './KeyValueList.tsx' import KeyValueList from './KeyValueList.tsx'
import { createPeople, type People } from '../apis'; import { createPeople, type People } from '../apis';
@@ -8,9 +9,13 @@ const { TextArea } = Input;
interface PeopleFormProps { interface PeopleFormProps {
initialData?: any; initialData?: any;
// 编辑模式下由父组件控制提交,隐藏内部提交按钮
hideSubmitButton?: boolean;
// 暴露 AntD Form 实例给父组件,用于在外部触发校验与取值
onFormReady?: (form: FormInstance) => void;
} }
const PeopleForm: React.FC<PeopleFormProps> = ({ initialData }) => { const PeopleForm: React.FC<PeopleFormProps> = ({ initialData, hideSubmitButton = false, onFormReady }) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -36,10 +41,17 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData }) => {
form.setFieldsValue(formData); form.setFieldsValue(formData);
// 显示成功消息 // 显示成功消息
message.success('已自动填充表单,请检查并确认信息'); // message.success('已自动填充表单,请检查并确认信息');
} }
}, [initialData, form]); }, [initialData, form]);
// 将表单实例暴露给父组件
useEffect(() => {
onFormReady?.(form);
// 仅在首次挂载时调用一次
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onFinish = async (values: any) => { const onFinish = async (values: any) => {
setLoading(true); setLoading(true);
@@ -161,11 +173,13 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData }) => {
<TextArea autoSize={{ minRows: 3, maxRows: 6 }} placeholder="例如:性格开朗、三观一致等" /> <TextArea autoSize={{ minRows: 3, maxRows: 6 }} placeholder="例如:性格开朗、三观一致等" />
</Form.Item> </Form.Item>
{!hideSubmitButton && (
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit" loading={loading} block> <Button type="primary" htmlType="submit" loading={loading} block>
{loading ? '提交中...' : '提交'} {loading ? '提交中...' : '提交'}
</Button> </Button>
</Form.Item> </Form.Item>
)}
</Form> </Form>
</div> </div>
); );

View File

@@ -1,15 +1,17 @@
import React from 'react'; import React from 'react';
import { Layout, Typography, Table, Grid, InputNumber, Button, Space, Tag, message, Modal, Dropdown, Input } from 'antd'; import { Layout, Typography, Table, Grid, InputNumber, Button, Space, Tag, message, Modal, Dropdown, Input } from 'antd';
import type { FormInstance } 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, EllipsisOutlined, DeleteOutlined, ManOutlined, WomanOutlined, ExclamationCircleOutlined, PictureOutlined } from '@ant-design/icons'; import { SearchOutlined, EllipsisOutlined, DeleteOutlined, ManOutlined, WomanOutlined, ExclamationCircleOutlined, PictureOutlined, EditOutlined } from '@ant-design/icons';
import './MainContent.css'; import './MainContent.css';
import InputDrawer from './InputDrawer.tsx'; import InputDrawer from './InputDrawer.tsx';
import ImageModal from './ImageModal.tsx'; import ImageModal from './ImageModal.tsx';
import PeopleForm from './PeopleForm.tsx';
import { getPeoples } from '../apis'; import { getPeoples } from '../apis';
import type { People } from '../apis'; import type { People } from '../apis';
import { deletePeople } from '../apis/people'; import { deletePeople, updatePeople } from '../apis/people';
const { Content } = Layout; const { Content } = Layout;
@@ -522,6 +524,13 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
// 图片弹窗状态 // 图片弹窗状态
const [imageModalVisible, setImageModalVisible] = React.useState(false); const [imageModalVisible, setImageModalVisible] = React.useState(false);
const [currentImageUrl, setCurrentImageUrl] = React.useState(''); const [currentImageUrl, setCurrentImageUrl] = React.useState('');
// 编辑弹窗状态(仅桌面端)
const [editModalVisible, setEditModalVisible] = React.useState(false);
const [editingRecord, setEditingRecord] = React.useState<Resource | null>(null);
const editFormRef = React.useRef<FormInstance | null>(null);
// 移动端编辑模式状态
const [mobileEditing, setMobileEditing] = React.useState(false);
const handleTableChange: TableProps<Resource>['onChange'] = (pg) => { const handleTableChange: TableProps<Resource>['onChange'] = (pg) => {
setPagination({ current: pg?.current ?? 1, pageSize: pg?.pageSize ?? 10 }); setPagination({ current: pg?.current ?? 1, pageSize: pg?.pageSize ?? 10 });
@@ -692,13 +701,55 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ flex: 1 }}>{v ? v : '-'}</span> <span style={{ flex: 1 }}>{v ? v : '-'}</span>
{swipedRowId === record.id && ( {swipedRowId === record.id && (
<div style={{ display: 'inline-flex', gap: 8 }}>
<Button
type="primary"
size="small"
icon={
<span
style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 24,
height: 24,
borderRadius: 4,
backgroundColor: '#1677ff',
color: '#fff',
}}
>
<EditOutlined style={{ fontSize: 12 }} />
</span>
}
onClick={() => {
setEditingRecord(record);
setMobileEditing(true);
setSwipedRowId(null);
}}
/>
<Button <Button
type="primary" type="primary"
danger danger
size="small" size="small"
icon={<DeleteOutlined />} icon={
<span
style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 24,
height: 24,
borderRadius: 4,
backgroundColor: '#f5222d',
color: '#fff',
}}
>
<DeleteOutlined style={{ fontSize: 12 }} />
</span>
}
onClick={() => confirmDelete(record.id)} onClick={() => confirmDelete(record.id)}
/> />
</div>
)} )}
</div> </div>
); );
@@ -715,6 +766,30 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
trigger={["click"]} trigger={["click"]}
menu={{ menu={{
items: [ items: [
...(!isMobile
? [
{
key: 'edit',
label: '编辑',
icon: (
<span
style={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 18,
height: 18,
borderRadius: 4,
backgroundColor: '#1677ff',
color: '#fff',
}}
>
<EditOutlined style={{ fontSize: 12 }} />
</span>
),
},
]
: []),
{ {
key: 'delete', key: 'delete',
label: '删除', label: '删除',
@@ -738,6 +813,10 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
], ],
onClick: ({ key }) => { onClick: ({ key }) => {
if (key === 'delete') confirmDelete(record.id); if (key === 'delete') confirmDelete(record.id);
if (key === 'edit' && !isMobile) {
setEditingRecord(record);
setEditModalVisible(true);
}
}, },
}} }}
> >
@@ -755,11 +834,80 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
</Typography.Title> </Typography.Title>
{isMobile && mobileEditing && (
<div>
<Typography.Title level={4} style={{ color: 'var(--text-primary)' }}>
</Typography.Title>
<PeopleForm
initialData={editingRecord || undefined}
hideSubmitButton
onFormReady={(f) => (editFormRef.current = f)}
/>
<div style={{ display: 'flex', gap: 12, marginTop: 12 }}>
<Button
type="primary"
onClick={async () => {
try {
const form = editFormRef.current;
if (!form) {
message.error('表单未就绪');
return;
}
const values = await form.validateFields();
const peopleData: People = {
name: values.name,
contact: values.contact || undefined,
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,
cover: values.cover || undefined,
};
if (!editingRecord) {
message.error('缺少当前编辑的人员信息');
return;
}
const res = await updatePeople(editingRecord.id, peopleData);
if (res.error_code === 0) {
message.success('更新成功');
setMobileEditing(false);
setEditingRecord(null);
await reloadResources();
} else {
message.error(res.error_info || '更新失败');
}
} catch (err: any) {
if (err?.errorFields) {
message.error('请完善表单后再保存');
} else {
message.error('更新失败');
}
}
}}
>
</Button>
<Button
onClick={() => {
setMobileEditing(false);
setEditingRecord(null);
}}
>
</Button>
</div>
</div>
)}
<Table<Resource> <Table<Resource>
rowKey="id" rowKey="id"
loading={loading} loading={loading}
columns={columns} columns={columns}
dataSource={data} dataSource={data}
style={{ display: isMobile && mobileEditing ? 'none' : undefined }}
onRow={(record) => onRow={(record) =>
isMobile isMobile
? { ? {
@@ -863,6 +1011,69 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
setCurrentImageUrl(''); setCurrentImageUrl('');
}} }}
/> />
{/* 编辑弹窗,仅桌面端显示 */}
{!isMobile && (
<Modal
open={editModalVisible}
title="编辑"
width="50%"
style={{ minWidth: 768 }}
onCancel={() => {
setEditModalVisible(false);
setEditingRecord(null);
}}
onOk={async () => {
try {
const form = editFormRef.current;
if (!form) {
message.error('表单未就绪');
return;
}
const values = await form.validateFields();
const peopleData: People = {
name: values.name,
contact: values.contact || undefined,
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,
cover: values.cover || undefined,
};
if (!editingRecord) {
message.error('缺少当前编辑的人员信息');
return;
}
const res = await updatePeople(editingRecord.id, peopleData);
if (res.error_code === 0) {
message.success('更新成功');
setEditModalVisible(false);
setEditingRecord(null);
await reloadResources();
} else {
message.error(res.error_info || '更新失败');
}
} catch (err: any) {
if (err?.errorFields) {
message.error('请完善表单后再确认');
} else {
message.error('更新失败');
}
}
}}
destroyOnClose
okText="确认"
cancelText="取消"
>
<PeopleForm
initialData={editingRecord || undefined}
hideSubmitButton
onFormReady={(f) => (editFormRef.current = f)}
/>
</Modal>
)}
</Content> </Content>
); );
}; };