feat: support edit people
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user