foreword
Recently, the front-end masters @alifd/next
used to write the background management system, and assigned me some pages similar to the following pages
, in which the branch office, storage address, and storage area are linked at three levels. Every item in the table can be deleted. The page of the pop-up box for adding a button looks like the following.
Click the row of the pop-up box, multiple selection, all selection can be updated to the page of the first picture
problems encountered
- How to control this table change, how to proceed, the input box can be operated alone, and the drop-down box can be operated alone. If a data element is changed back and forth, then the consumption of the page is very large. Wait a minute, here are a few examples
accomplish
I looked at the official field
examples of similar solutions before , and found that the writing is relatively concise. It is also not easy to operate. So I tried form
+ table
combined writing magic changed a bit
1. Use the form to wrap the table, because the form is embedded field
, so it should be more convenient.
const fieldTable = Field.useField(); //初始化fieldTable
<Form field={
fieldTable}>
<Table.StickyLock
size="medium"
primaryKey="id" // 识别的唯一值
fixedHeader
maxBodyHeight={
400} //和fixedHeader结合,设置table盒子的高度
dataSource={
dataSource} //数据源
columns={
columns} // 列表
rowSelection={
rowSelection} // 选择每一行的操作,默认多选
/>
</Form>
select rowSelection
settings
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);
const rowSelection = {
selectedRowKeys,
onChange: (_selectedRowKeys, _selectedRows) => {
// 单选时的操作
setSelectedRowKeys(_selectedRowKeys);
setSelectedRows(_selectedRows);
},
onSelectAll: (selected, _selectedRows) => {
//全选时的操作
setSelectedRowKeys(_selectedRows.map((item) => item.cid));
},
getProps: (record) => {
// 禁用的☑️的操作
return {
disabled: mode === 'view',
};
},
};
2. Encapsulate the columns, (delete several repeated types, roughly understandable)
const columns = [
// 新增/编辑用到的id,itemId,不显示但是可以区分(用是否有itemId来区分是点击编辑时拿到的数据,还是编辑时新增还是新增时新增的数据)
{
width: '0px', // 不会显示在页面
dataIndex: 'id',
align: 'center',
lock: 'left',
cell: (value, index, record) => {
return <div style={
{
display: 'none' }}>{
FormChildItem(value, index, record, 'id')}</div>;
},
},
{
width: '0px',// 不会显示在页面
dataIndex: 'itemId',
align: 'center',
lock: 'left',
cell: (value, index, record) => {
return <div style={
{
display: 'none' }}>{
FormChildItem(value, index, record, 'itemId')}</div>;
},
},
{
title: '类型',
dataIndex: 'wirType',
align: 'center',
lock: 'left',
cell: (value, index, record) => {
return value ? (
<Tag size="small" type="normal" color={
wirTypeColor[value]}>
{
wirTypeName[value] || 'BTS备货'}
</Tag>
) : (
<Tag size="small" type="normal" color="turquoise">
BTS备货
</Tag>
);
},
},
// 下面是formItem进行嵌套的
mode === 'add' ? isNull() : isRequireNo(), // 根据是编辑还是新增,动态显示table的列
{
title: '物料名称',
dataIndex: 'itemName',
align: 'center',
lock: 'left',
cell: (value, index, record) => {
return FormChildItem(value, index, record, 'itemName');
},
},
{
title: '键号',
dataIndex: 'itemCode',
align: 'center',
cell: (value, index, record) => {
return FormChildItem(value, index, record, 'itemCode');
},
},
{
title: '申请数量',
dataIndex: 'applyQuantity',
align: 'center',
cell: (value, index, record) => {
return FormChildItemRequire(value, index, record, 'applyQuantity');
},
},
{
title: '存储区',
dataIndex: 'areaId',
align: 'center',
cell: (value, index, record) => {
return FormChildItemSelect(value, index, record, 'areaId');
},
},
];
isNull
Used to operate on ternary expressions, otherwise an error will be reported when writing a table
const isNull = () => {
return {
align: 'center',
width: '0px',
};
};
const isRequireNo = () => {
return {
title: '订单编号',
dataIndex: 'requireNo',
align: 'center',
lock: 'left',
cell: (value, index, record) => {
return <div>{
value ? `${
value}⚠️` : ''}</div>;
},
};
};
3 Encapsulated FormChildItem
common form display, FormChildItemRequire
verifiable Input
, FormChildItemSelect
verifiable select
. Why do you want to FormChildItem
wrap it in a form for ordinary people, mainly because the back-end students need to use the data inside. To deal with, the following reason should be clear.
id
The following is used to distinguish each row , ⚠️Do not use the index index instead, because there will be problems when deleting
const FormChildItem = (value, index, record, _name) => {
return (
<FormItem
isPreview // 查看的时候不能修改的一个属性
name={
`${
_name}-${
record.id}`}
labelAlign="left"
>
<Input defaultValue={
value} />
</FormItem>
);
};
const FormChildItemSelect = (value, index, record, _name) => {
return (
<FormItem
isPreview={
mode === 'view'}
asterisk
name={
`${
_name}-${
record.id}`}
required
requiredMessage="请选择存储区"
labelAlign="left"
>
<Select style={
{
width: '110px' }} defaultValue={
value} dataSource={
storageType} />
</FormItem>
);
};
const FormChildItemRequire = (value, index, record, _name) => {
return (
<FormItem
isPreview={
mode === 'view'}.
asterisk
name={
`${
_name}-${
record.id}`}
required
requiredMessage="请输入申请数量"
labelAlign="left"
>
<NumberPicker defaultValue={
value} min={
0} />
</FormItem>
);
};
4. Now that we talked about deletion, let’s write the method of deletion (I won’t write new ones, it’s relatively simple, and the subcomponent of the pop-up box uses setDatasource to set the passed value)
// 点击删除按钮删除
const clickDeleteDataObj = () => {
if (!selectedRowKeys?.length) {
Message.error('请选择物料明细进行删除');
} else {
const newArr = removeDuplicates(dataSource, selectedRows); //dataSource:新增时通过从弹框选中拿到的数据数组;selectedRows:第一个页面删除时拿到的行数数组
setDataSource(newArr); // 重新设置table的源数据
// 重置key
setSelectedRowKeys([]); //清空被第一个页面被选中的框
}
};
// removeDuplicates删除的方法, 删除table数据,通过id来删除,简单有效。
const removeDuplicates = (arr1, arr2) => {
const ids = new Set(arr2.map((item) => item.id));
return arr1.filter((item) => !ids.has(item.id));
};
5. How to verify when saving?
Two form verifications are done here. It can be observed through page 1 that there is also a form at the top to be verified, and its field
name isfield
// 保存草稿| 提交申请
const handleOk = (type: number) => {
// type 1为保存草稿,2为提交申请
field.validate(field.getNames(), async (error) => {
if (error === null) {
const paramsValue: Object = field.getValues(); // 获取顶部的form数据
if (!dataSource?.length) {
return Message.error('请添加物料信息');
}
// 对table进行校验
fieldTable.validate(fieldTable.getNames(), async (errorIn) => {
const paramsValueIn: Object = fieldTable.getValues(); // 获取table的数据,这里获取的数据就是被FormItem包裹的数据,
//但是拿到的是对象,而且是所有`name-id`的对象,所以下面的方法将对象变为数组
const detailVoList = objChangeArr(paramsValueIn); //
if (errorIn === null) {
let TCode = 0;
let TMsg = '';
if (mode === 'add') {
// 新增,submitImmediately:保存草稿(false)和提交申请(true)
const {
code = 0, msg = '' } = await services.fetchAddReplenishmentDraftAndApply({
...paramsValue,
detailVoList,
submitImmediately: type === 2,
});
TCode = code;
TMsg = msg;
} else {
// 编辑
// 只需要editDataSource被标记删除的数据
const _editDetailVoList = editDataSource.filter((v: any) => v.delete);
console.log(_editDetailVoList, '_editDetaVoList');
const {
code = 0, msg = '' } = await services.fetchEditReplenishmentDraftAndApply({
...paramsValue,
detailVoList: [...detailVoList, ...(_editDetailVoList || [])],
submitImmediately: type === 2,
});
TCode = code;
TMsg = msg;
}
if (TCode === 101) {
Message.success(TMsg);
onOk?.(); // 父组件传入的值,不用管
field.resetToDefault();
}
} else {
// console.log(error);
}
});
} else {
// console.log(error);
}
});
};
- Objects to arrays
Objects like this
are converted to arrays
// 将对象转换成数组
const objChangeArr = (obj) => {
const _detailVoList = Object.keys(obj)
.reduce((acc: any, key: string) => {
const [prefix, id] = key.split('-');
if (prefix === 'id') {
acc.push({
// 编辑时,在有名为itemId的前提下,拿到id,否则(编辑时新增的,新增时新增的)就不传id,
id: obj[`itemId-${
id}`] ? obj[`id-${
id}`] : undefined, // 这个属于业务逻辑,不注重
itemId: obj[`itemId-${
id}`] || obj[`id-${
id}`],
itemCode: obj[`itemCode-${
id}`],
applyQuantity: obj[`applyQuantity-${
id}`],
areaId: obj[`areaId-${
id}`],
});
}
return acc;
}, [])
.filter(Boolean);
return _detailVoList;
};
there are other questions
- 1. Save, echo the deleted data when editing, and mark delete. You
can set another datasource data source to save when the page is initialized, and then merge it into it - 2. For the three-level linkage problem, refer to the first page and re-select the branch or storage address. The data in the table must be cleared, so I wrote it like this
// 清空所有存储区 // 这里利用dataSource来进行遍历。通过field的setValue,重新设置为空字符串来清空
const clearAreaId = () => {
dataSource?.map((itemClear: any) => {
const areaIdNum = `areaId-${
itemClear.id}`;
fieldTable.setValue(areaIdNum, '');
return itemClear;
});
};
postscript
After writing in this way, there is no problem with my function here, but the boss said that what I wrote is too complicated and difficult to maintain. I need to change it to field writing, but I don’t want to write field writing. I don't know if other big guys have any suggestions.