需求:

在新增&编辑表单中,共分三个表单模块,第二个模块设计为一个可编辑表格组件,其中可选下拉列表依赖外层第一个模块的某条数据值,提供新增、编辑、删除、按规定条件去重等功能,并在第三个模块中自动计算列表数值总和

实现:

1.表单初始化接口的返回约定为三个数组,按模块对应:

  const [dataSource, setDataSource] = useState<{
base_info?: API.FormListType[];
detail_info?: API.FormListType[];
total_info?: API.FormListType[];
}>({});

  

2.表单初始化接口返回后,配置表单dataSource【其中第三个模块配置为2位小数只读,第二个模块配置自定义组件】:

setCreateForm({
base_info: createList?.base_info?.map((el: any) => {
if (el.id === 'attachment') {
return {
...el,
renderFormItem: () => (
<File
optionData={{
type: 'default',
value: '选择文件',
props: { is_approval_file: 1 },
api: 'onUploadGeneralUpload',
}}
/>
),
};
}
return el;
}),
detail_info: createList?.detail_info?.map((el: any) => {
if (el.id === 'detail_info') {
return {
...el,
renderFormItem: () => <Condition id={undefined} />,
};
}
return el;
}),
total_info: createList?.total_info?.map((el: any) => {
return {
...el,
readonly: true,
value: Number(el.value).toFixed(2),
};
}),
});

  

表单组件:

// 去掉接口信息等的部分代码
import { useState, useRef } from 'react';
import { Button, Spin, Typography, Modal } from 'antd';
import { DrawerForm } from '@ant-design/pro-form';
import SchemaForm from '@/components/SchemaForm';
import ModuleTitle from '@/components/ModuleTitle';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import type { FormInstance } from 'antd';
import type { ParamsType } from '@ant-design/pro-provider';
import Condition from './Condition'; type UpdateFormProps = {
onUpdate?: () => void;
record?: SettleApplicationParams;
createForm: {
base_info?: API.FormListType[];
detail_info?: API.FormListType[];
total_info?: API.FormListType[];
};
}; const { Text } = Typography; const UpdateForm: React.FC<UpdateFormProps> = ({ record, onUpdate, createForm }) => {
const formRef = useRef<FormInstance>();
const [dataSource, setDataSource] = useState<{
base_info?: API.FormListType[];
detail_info?: API.FormListType[];
total_info?: API.FormListType[];
}>({});
const [loading, setLoading] = useState<boolean>(false);
const [visible, setVisible] = useState<boolean>(false);
const [formValues, setFormValues] = useState<ParamsType>({}); const baseFormChange = (changedValues: ParamsType, allValues: ParamsType) => {
// 如果【detail_info】有值,修改【company_id 】需要弹窗提示,确认则清空第二个模块的数据,否则关闭弹窗,值不变
if (
allValues.detail_info.length > 0 &&
(changedValues.company_id || !allValues.company_id)
) {
Modal.confirm({
icon: <ExclamationCircleOutlined />,
content: <Text strong>切换xx将会清空以下xx信息,请确认是否切换</Text>,
okText: '确认',
cancelText: '取消',
onOk() {
setDataSource({
...dataSource,
detail_info: dataSource?.detail_info?.map((el: any) => {
if (el.id === 'detail_info') {
return {
...el,
value: [],
renderFormItem: () => (
<Condition
id={changedValues.company_id || allValues.company_id}
/>
),
};
}
return el;
}),
});
formRef.current?.setFieldsValue({
channel_company_id: changedValues.company_id || allValues.company_id,
detail_info: [],
});
setFormValues(formRef.current?.getFieldsValue(true));
},
onCancel() {
formRef.current?.setFieldsValue({
company_id: formValues.company_id,
});
setFormValues(formRef.current?.getFieldsValue(true));
},
});
}
// 如果【detail_info】无值,修改【company_id】,第二模块组件传参需要传最新的【company_id】
if (!allValues.detail_info.length && changedValues.company_id) {
setDataSource({
...dataSource,
detail_info: dataSource?.detail_info?.map((el: any) => {
if (el.id === 'detail_info') {
return {
...el,
value: [],
renderFormItem: () => <Condition id={changedValues.company_id} />,
};
}
return el;
}),
});
formRef.current?.setFieldsValue({ company_id: changedValues.company_id });
setFormValues(formRef.current?.getFieldsValue(true));
}
// 【总计】的数据根据第二模块列表的值计算
if (changedValues.detail_info) {
// 第二模块中的第一列数值关联
let bill_turnover_total = 0;
// 第二模块中的第二列数值关联
let bill_divide_turnover_total = 0;
changedValues.detail_info?.forEach(
(item: { bill_divide_turnover: number; bill_turnover: number }) => {
bill_turnover_total += Number(item.bill_turnover);
bill_divide_turnover_total += Number(item.bill_divide_turnover);
},
);
formRef.current?.setFieldsValue({
bill_turnover_total: Number(bill_turnover_total).toFixed(2),
bill_divide_turnover_total: Number(bill_divide_turnover_total).toFixed(2),
});
setFormValues(formRef.current?.getFieldsValue(true));
}
}; // 获取单条数据
const getInfo = async () => {
if (!record?.id) return;
try {
const { result } = await 接口(record?.id);
if (result) {
setDataSource({
base_info: createForm?.base_info?.map((el) => {
return { ...el, value: (el.id && result && result[el.id]) || el.value };
}),
detail_info: createForm?.detail_info?.map((el: any) => {
if (el.id === 'detail_info') {
return {
...el,
value: (el.id && result && result[el.id]) || el.value,
renderFormItem: () => <Condition id={result.company_id} />,
};
}
return { ...el, value: (el.id && result && result[el.id]) || el.value };
}),
total_info: createForm?.total_info?.map((el) => {
return { ...el, value: (el.id && result && result[el.id]) || el.value };
}),
});
setVisible(true);
}
} catch (error) {
//
} finally {
setLoading(false);
}
}; // 表单处理
async function showForm() {
setLoading(true);
if (record?.id) {
getInfo();
} else {
setDataSource(createForm);
setLoading(false);
setVisible(true);
}
} return (
<>
{record && record.id ? (
<Spin spinning={loading}>
<a key="edit" onClick={showForm}>
编辑
</a>
</Spin>
) : (
<Button type="primary" key="add" onClick={showForm}>
新增
</Button>
)}
<DrawerForm
formRef={formRef}
width={'70%'}
visible={visible}
title={`${record && record.id ? '编辑' : '新增'}xxx`}
drawerProps={{
bodyStyle: { paddingTop: 8 },
onClose: () => setVisible(false),
destroyOnClose: true,
}}
onValuesChange={baseFormChange}
onFinish={async (formData) => {
const { code } =
record && record.id
? await 编辑接口({
id: record && record.id,
...formData,
})
: await 新增接口({ ...formData });
if (code === 0 && onUpdate) {
formRef.current?.resetFields();
onUpdate();
setVisible(false);
return true;
}
return false;
}}
>
<ModuleTitle title="第一模块信息" />
<SchemaForm dataSource={dataSource?.base_info} layoutType="Embed" submitter={false} />
<ModuleTitle title="第二模块信息" />
<SchemaForm dataSource={dataSource?.detail_info} layoutType="Embed" submitter={false} />
<ModuleTitle title="第三模块总计" />
<SchemaForm dataSource={dataSource?.total_info} layoutType="Embed" submitter={false} />
</DrawerForm>
</>
);
}; export default UpdateForm;

  

3.表单组件定义完毕,表单项关联也进行了处理,下一步就是自定义组件的书写:

【组件内部需要判断前三列选项是否重复已有数据&进行接口请求进行后台数据重复判断】

【组件内部第一列下拉数据依赖于外层数据,第二列数据依赖于第一列数据的选项值】

/* 组件 */
import { useState, useEffect, useMemo } from 'react';
import { Form, message } from 'antd';
import moment from 'moment';
import { isEmpty } from 'lodash';
import { EditableProTable } from '@ant-design/pro-table';
import type { ProColumns } from '@ant-design/pro-table';
import type { FormInstance } from 'antd'; type ConditionProps = {
onChange?: (data: SettleApplicationLogsParams[]) => void;
value?: SettleApplicationLogsParams[];
id?: string;
}; const Condition: React.FC<ConditionProps> = (props) => {
const { value, id, onChange } = props;
const [form] = Form.useForm();
const [dataSource, setDataSource] = useState<SettleApplicationLogsParams[]>([]);
const [companyOpChannel, setCompanyOpChannel] = useState<Record<string, API.FormListType[]>>({});
const [companyGame, setCompanyGame] = useState<{ label: string; value: string }[]>([]);
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => []);
const [channelCompanyId, setChannelCompanyId] = useState<string | undefined>(undefined); // 获取第二列下拉数据【需要依赖第一列的选项值】
const getChannelCompanyOpChannel = async (game_id: string, company_id?: string) => {
if (!company_id || !game_id) return;
try {
const { result } = await 接口({ company_id, game_id });
if (result) {
const optionList = (result || []).map((itemO: any) => {
return {
label: itemO.value,
value: itemO.id,
};
});
setCompanyOpChannel({ ...companyOpChannel, [game_id]: optionList });
}
} catch (error) {
//
}
}; // 获取第一列下拉数据
const getFirstList = async (company_id: string) => {
if (!company_id) return;
try {
const { result } = await 接口({ company_id });
if (result) {
const optionList = (result || []).map((itemO: any) => {
return {
label: itemO.value,
value: itemO.id,
};
});
setCompanyGame(optionList);
}
} catch (error) {
//
}
}; const tableColumns: ProColumns[] = [
{
title: '第一列下拉',
dataIndex: 'game_id',
valueType: 'select',
render: (_, row) => row.game_name || '-',
fieldProps: (_form: FormInstance, { rowKey }: { rowKey: string }) => {
if (!channelCompanyId) {
return { disabled: true, options: [], placeholder: '请选择外层第一模块依赖值' };
}
if (companyGame.length === 0) {
return { allowClear: false, options: [] };
} return {
allowClear: false,
showSearch: true,
options: companyGame,
onChange: (val: string) => {
if (!rowKey) return;
// 重置运营渠道列表
getChannelCompanyOpChannel(val, channelCompanyId || undefined);
const fieldsValue = _form.getFieldsValue();
_form.setFieldsValue({
...fieldsValue,
[rowKey]: {
...fieldsValue[rowKey],
game_id: val,
game_name:
companyGame?.find((el: { value: string; label: string }) => el.value === val)
?.label || '-',
op_channel: null,
op_channel_name: null,
},
});
},
};
},
formItemProps: () => {
return {
rules: [{ required: true, message: '此项为必填项' }],
};
},
},
{
title: '第二列下拉',
dataIndex: 'op_channel',
valueType: 'select',
render: (_, row) => row.op_channel_name || '-',
fieldProps: (_form: FormInstance, { rowKey }: { rowKey: string }) => {
const rowValue = _form?.getFieldsValue(true) || {};
if (!rowKey || isEmpty(rowKey) || isEmpty(rowValue)) {
return { disabled: true, options: [], placeholder: '请选择第1列下拉' };
}
const key = rowKey[0];
const { game_id } = rowValue[key] || {};
if (!game_id || !companyOpChannel[game_id] || companyOpChannel[game_id].length === 0) {
return { allowClear: false, options: [] };
}
return {
allowClear: false,
showSearch: true,
options: companyOpChannel[game_id],
onChange: (val: string) => {
if (!rowKey) return;
const fieldsValue = _form.getFieldsValue();
_form.setFieldsValue({
...fieldsValue,
[rowKey]: {
...fieldsValue[rowKey],
op_channel: val,
op_channel_name:
companyOpChannel[game_id]?.find((el) => el.value === val)?.label || '-',
},
});
},
};
},
},
{
title: '选择月份',
dataIndex: 'settle_time',
valueType: 'dateMonth',
render: (_, row) => moment(row.settle_time).format('YYYY-MM'),
formItemProps: () => {
return {
rules: [{ required: true, message: '此项为必填项' }],
};
},
},
{
title: '数值1',
dataIndex: 'bill_turnover',
valueType: 'digit',
fieldProps: { precision: 2, min: 0 },
render: (_, row) => Number(row.bill_turnover).toFixed(2),
formItemProps: () => {
return {
rules: [{ required: true, message: '此项为必填项' }],
};
},
},
{
title: '数值2',
dataIndex: 'bill_divide_turnover',
valueType: 'digit',
fieldProps: { precision: 2, min: 0 },
render: (_, row) => Number(row.bill_divide_turnover).toFixed(2),
formItemProps: () => {
return {
rules: [{ required: true, message: '此项为必填项' }],
};
},
},
{
title: '操作',
valueType: 'option',
width: 200,
render: (text: any, record: any, _: any, action: any) => [
<a
key="editable"
onClick={() => {
action?.startEditable?.(record.id);
}}
>
编辑
</a>,
<a
key="delete"
onClick={() => {
setDataSource(dataSource.filter((item) => item.id !== record.id));
if (onChange) {
onChange(dataSource.filter((item) => item.id !== record.id));
}
}}
>
删除
</a>,
],
},
]; useEffect(() => {
if (value) {
if (value.length === 0 && dataSource.length === 0) {
return;
} else {
setDataSource(value);
if (onChange) {
onChange(value);
}
}
}
}, []); // 外层依赖值改变时,清空数据并请求新的第一列下拉列表
useEffect(() => {
setCompanyOpChannel({});
setCompanyGame([]);
setEditableRowKeys([]);
setChannelCompanyId(id);
if (dataSource.length > 0) {
setDataSource([]);
if (onChange) {
onChange([]);
}
}
if (id) getFirstList(id);
}, [id]); return useMemo(
() => (
<EditableProTable
recordCreatorProps={{
position: 'bottom',
disabled: dataSource.length >= 10 || !id,
creatorButtonText: '新增',
record: () => ({ id: (Math.random() * 1000000).toFixed(0) }),
}}
maxLength={10}
locale={{ emptyText: '暂无数据' }}
loading={false}
toolBarRender={false}
columns={tableColumns}
value={dataSource}
onChange={(values: SettleApplicationLogsParams[]) => {
setDataSource(values);
if (onChange) {
onChange(values);
}
}}
scroll={{ y: '235px' }}
editable={{
form,
type: 'multiple',
editableKeys,
onChange: setEditableRowKeys,
onSave: async (rowKey, data) => {
// 校验[当前前三列是否已存在记录]
const repeatData = dataSource?.filter(
(item) =>
item.game_id === data.game_id &&
item.op_channel === data.op_channel &&
item.settle_time === data.settle_time,
);
if (repeatData.length) {
message.error('已存在相同记录');
return Promise.reject();
}
try {
const { code, message: ResMessage } = await 接口({
...data,
company_id: id,
});
if (code === 0) {
return Promise.resolve();
} else {
message.error(ResMessage);
return Promise.reject();
}
} catch (error) {
return Promise.reject();
}
},
}}
rowKey="id"
/>
),
[tableColumns],
);
}; export default Condition;

  

基本代码官方文档都有,嘻嘻:https://procomponents.ant.design/components/editable-table/#editable-%E7%BC%96%E8%BE%91%E8%A1%8C%E9%85%8D%E7%BD%AE

最终效果:

最新文章

  1. APUE学习之多线程编程(二):线程同步
  2. bat获取所有的参数
  3. Nginx搭建https服务器
  4. Hibernate入门与简谈
  5. Linux学习笔记16--Linux扩展权限
  6. uva10870 矩阵
  7. javase基础笔记2——数据类型和面向对象
  8. lodash接触:string-capitalize
  9. Android 使用 ksoap2-android 访问WebService(C#)
  10. Effective Java 27 Favor generic methods
  11. android 开发进阶自定义控件 类似 TextView
  12. python之for学习
  13. When to Redis ? when to MongoDB?
  14. Spring 并发访问的线程安全性问题
  15. 15.javaweb XML详解教程
  16. MySQL之集合函数与分组查询
  17. Java 虚拟机的垃圾回收
  18. php7 数据导出Excel office 2011中文乱码问题
  19. mvc5总结(1)
  20. express返回html文件

热门文章

  1. 《Makefile常用函数》
  2. 夸克开发板 FaceDetectOnTft.py 测试
  3. golang yaml配置
  4. win10 校验MD5值
  5. Android 6.0动态添加权限(Finn_ZengYuan博客)
  6. JQuery的dataTable实现分页
  7. noi 1.5 42画矩形
  8. CSS渐变样色的字
  9. matlab画图之plot画折线图
  10. BeanUtils.copyProperties null覆盖问题