ProComponent is another integration and encapsulation of antd, which reduces the front-end processing of details and linkage. In short, it is very cool to use.
Then I won't introduce too much about ProComponent here. Let's go straight to the topic. How to use, or how to use components and write code more elegantly and conveniently has always been the core pursuit of any programmer. I am also PiuPiuPiu~!
configuration of columns
treeSelect
request can be used to call the interface to get the value
labelInValue: The object that is set to get the value of this form item, not only includes value, the following example gets the fieldNames object
{
title: '区域',
dataIndex: 'areaIds',
hideInTable: true,
valueType: 'treeSelect',
request: async () => {
let res = await areaSituation();
if (res?.status == '00') {
return res.data;
}
},
fieldProps: {
showArrow: false,
filterTreeNode: true,
showSearch: true,
dropdownMatchSelectWidth: false,
autoClearSearchValue: true,
treeNodeFilterProp: 'title',
labelInValue: true,
fieldNames: {
label: 'title',
lvl: 'lvl',
value: 'nodeId',
children: 'childrenList',
},
onSelect: (_, dataObj) => {
setAreaIds([]);
let arr = [];
let getData = (data) => {
arr = [...arr, data?.nodeId];
if (data?.childrenList.length > 0) {
data?.childrenList?.forEach((item) => {
getData(item);
});
}
};
getData(dataObj);
setAreaIds(arr);
},
},
},
dateTimeRange
ProFormDateTimePicker This is a value that can take the year, month, day, hour, minute and second
ProFormDatePicker supports setting the initial value of the available moment to the year, month, day,
This default value is an array. If the time field required by the search backend is not an array but two other fields; the following writing method can be used
{
title: '操作时间',
dataIndex: 'actTime',
hideInTable: true,
valueType: 'dateRange',
// initialValue: [moment().subtract(30, 'days'), moment()],
initialValue: [
moment().subtract(1, 'month').format('YYYY-MM-DD'),
moment().format('YYYY-MM-DD'),
], //给时间搜索框设置默认开始时间一个月前--到现在的年月
fieldProps: {
placeholder: ['入库开始时间', '入库结束时间'],
},
search: {
transform: (value) => {
return {
startTime: value[0],
endTime: value[1],
};
},
},
},
radio
status can set the status of rendering to table
Sort an item in the sorter table
{
title: '提成方式',
dataIndex: 'commissionMode',
sorter: true,
formItemProps:{
initialValue:1,
},
valueType: 'radio',
valueEnum: { 0: '固定', 1: '比例' },
},
{
title: '状态',
dataIndex: 'taskState',
key: 'taskState',
valueEnum: {
false: { text: '失败', status: 'error' },
true: { text: '成功', status: 'success' },
},
hideInSearch: true,
},
renderFormItem
You can use SelectAccount to directly render components in a single list box, such as in EditableProTable,
Of course, this is used in the search box (the method of using valueType is still recommended, but if it is used in multiple places, it is also the best choice to encapsulate it into a component)
{
title: '冷藏费记账账户',
dataIndex: 'coldStorageFeeAccountId',
formItemProps: { rules: [{ required: true }] },
renderFormItem: (_, { type, ...rest }) => (
<SelectAccount labelInValue params={{ projectId: rest.recordKey }} />
),
},
digit
Render into a search box that can only enter numbers
{
title: '件数',
dataIndex: 'quantity',
valueType: 'digit',
fieldProps: { precision: 0, min: 1 },
formItemProps: { rules: [{ required: true }] },
},
Or you can customize the title text in search and table to be different
fieldProps: can set the state of the search box
{
title: (_, type) => (type === 'table' ? '操作账号' : ''),
dataIndex: 'operatorMobile',
key: 'operatorMobile',
//fieldProps: { readOnly: true, placeholder: '请先选择杂费' },
fieldProps: {
placeholder: '操作人账号',
},
},
ProForm form item
ProFormDependency
Can be used to monitor the operation after a change of a form item,
Why is name an array?
- It's because the contractType is submitted as an attribute value of the inboundAppointment object when the form is submitted.
- The advantage of this writing is that the front end does not need to reassemble the submission parameters in the submitted function.
<ProFormSelect
width="sm"
name={['inboundAppointment', 'contractType']}
label="合同类型"
rules={[{ required: true, message: '请选择合同类型' }]}
initialValue={'2'}
options={[
// { value: '0', label: '零仓' },
{ value: '1', label: '包仓' },
{ value: '2', label: '无合同' },
]}
placeholder="包仓/零仓"
/>
<ProFormDependency name={[['inboundAppointment', 'contractType']]}>
{({ inboundAppointment }) => {
if (
inboundAppointment?.contractType == '1' ||
inboundAppointment?.contractType == '0'
) {
return (
<ProFormSelect
width="sm"
name={['inboundAppointment', 'contractNo']}
rules={[{ required: true, message: '请选择合同' }]}
label="合同"
options={[
{ value: '>', label: '大于' },
{ value: '<', label: '小于' },
]}
placeholder="请选择合同"
/>
);
}
}}
</ProFormDependency>
ProFormFieldSet
We mentioned above that the name is set in the form of an array, which can help us omit the operation of assembling parameters.
That is even more powerful, it can help us assemble or split the parameters of the submission form in advance.
Then let's take a look at what the official said here: "ProFormFieldSet can combine and store the values of multiple internal children in ProForm, and can be transformed at the time of submission through transform",
In short, that's what I just said, right?
The following example is to split the obtained SelectSubject object value and save it in ProForm.
<ProFormFieldSet
width="md"
name={['subject']}
transform={(v) => ({ subjectName: v[0]?.label, subjectId: v[0]?.value })}
label="费用类型"
rules={[{ required: true }]}
>
<SelectSubject className="pro-field-md" labelInValue />
</ProFormFieldSet>
ProFormText ProForm.Group ProFormDateTimePicker
ProFormText a simple form item
ProFormDateTimePicker: time picker year month day can be accurate to hours, minutes and seconds
ProForm.Group: The form items can be displayed in a row if the space allows
const [time, setTime] = useState([moment().startOf('year'), moment().endOf('year')])
<ProForm.Group title="运输车辆">
<ProFormText
width="sm"
name={['outboundAppointment', 'plateNum']}
label="车牌号"
placeholder="请输入车牌号"
/>
<ProFormDateTimePicker
width="sm"
name={['outboundAppointment', 'expectArriveTime']}
label="预计到场时间"
/>
</ProForm.Group>
ProFormDigit
A form item that can only enter numbers
<ProFormDigit
label="预收天数"
name="unitTotal"
min={1}
max={10}
placeholder="请输入预收天数"
fieldProps={{ precision: 0 }}
/>
ProFormGroup
List item classification
<ProFormGroup label="甲方信息">
<ProFormText
width="sm"
name={'namejia'}
label="甲方名称"
placeholder="请输入甲方名称"
/>
<ProFormText width="sm" name={'namejia'} label="手机号" placeholder="请输入手机号" />
<ProFormText
width="sm"
name={'namejia'}
label="身份证号"
placeholder="请输入身份证号"
/>
</ProFormGroup>
DrawerForm
const [form] = ProForm.useForm();
<DrawerForm
submitter={{
render: () => {
return [
<Button key="export" type="primary" onClick={async () => {
await exportColdFeeList(exportData)
}}>
导出
</Button>,
];
},
}}
width={'70%'}
layout="horizontal"
form={form}
title="客户账单明细"
visible={visible}
onVisibleChange={setVisible}
drawerProps={{
destroyOnClose: true,
}}
/>
ProDescriptions
Official statement: Advanced description list component, which provides a more convenient and fast solution to build description lists.
I use it in the details of the list item, and the usage of the table is similar.
<ProDescriptions
columns={[
{ title: '客户', dataIndex: '' },
{ title: '合同类型', dataIndex: '' },
{ title: '移位日期', dataIndex: '' },
{ title: '操作人', dataIndex: 'operator' },
{ title: '操作时间', dataIndex: '' },
{ title: '确认人', dataIndex: '' },
{ title: '确认时间', dataIndex: '', span: 3 },
{
span: 3,
render: () => (
<ProTable
headerTitle="移位商品"
style={{ width: '100%' }}
rowKey="id"
dataSource={[{ id: 0 }]}
options={false}
search={false}
columns={[
{ title: '批次号', dataIndex: '' },
{ title: '商品', dataIndex: '', sorter: true },
{ title: '移出件数', dataIndex: '' },
{ title: '移出重量', dataIndex: '', sorter: true },
{ title: '移出板数', dataIndex: '', sorter: true },
{ title: '移出库位', dataIndex: '' },
{ title: '移入库位', dataIndex: '', sorter: true },
{ title: '移入板数', dataIndex: '', sorter: true },
]}
/>
),
},
{
span: 3,
render: () => (
<ProTable
headerTitle="杂费项目"
style={{ width: '100%' }}
rowKey="id"
dataSource={[{ id: 0 }]}
options={false}
search={false}
columns={[
{ title: '杂费名称', dataIndex: '' },
{ title: '收费依据', dataIndex: '', sorter: true },
{ title: '单价', dataIndex: '' },
{ title: '件数', dataIndex: '', sorter: true },
{ title: '小计', dataIndex: '', sorter: true },
]}
/>
),
},
{
span: 3,
render: () => (
<ProTable
headerTitle="作业人员"
style={{ width: '100%' }}
rowKey="id"
dataSource={[{ id: 0 }]}
options={false}
search={false}
columns={[
{ title: '姓名', dataIndex: '' },
{ title: '所属团队', dataIndex: '' },
{ title: '作业类型', dataIndex: '' },
{ title: '杂费名称', dataIndex: '' },
{ title: '作业数量', dataIndex: '' },
{ title: '作业费用', dataIndex: '' },
]}
/>
),
},
{
title: '相关附件',
dataIndex: 'filePath',
span: 3,
render: (text) => {
// let dataArr = text?.includes(',') ? text?.split(',') : [text];
// return (
// <>
// {dataArr?.map((item, index) => {
// return (
// <a key={index} href={item.url}>
// {item.name}
// </a>
// );
// })}
// </>
// );
},
},
{ title: '备注', dataIndex: 'remark', span: 3 },
]}
/>
ProTable EditableProTable
There are two kinds of tables, one is the most commonly used common table, and the other is the editable table with high similarity.
ProTable
There are three interesting things about the configuration of this ProTable:
- params comes with pageSize, pageNumber
- rowSelection can do batch configuration operations
- When there is only a single conditional search, you can set search to false, and the search of options can be set separately. If you need to search for multiple conditions, you can make some customized configurations for the search box separately. Of course, as an advanced customized component, there are only so many Integration is definitely not enough,
Advanced configuration
tableExtraRender
This is the middle area of the upper part of the configuration table list and the lower part of the search area. Of course, if you have the need to move this to the top, you can use the css style to cover it and write it down
<ProTable
tableExtraRender={() => (
<Row gutter={16}>
<Col span={6}>
<Card>
<Statistic title="入库单总数" value={topData?.all?.totalCount} />
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic title="入库吨重" value={topData?.all?.weight} />
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic title="今日入库单数" value={topData?.today?.totalCount} />
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic title="今日入库吨重" value={topData?.today?.weight} />
</Card>
</Col>
</Row>
)}
//search={{
//labelWidth: 0,
// collapsed: false,
// collapseRender: false,
// }}
rowKey="id"
scroll={{ x: 960 }}
columns={[
{ title: '商品名称', dataIndex: 'goodsName' },
{ title: '所属分类', dataIndex: 'category' },
]}
params={{ status: 0 }}
request={async (params = {}) => {
const json = await getGlobalCfgGoodsPageList(params);
return {
data: json.records,
page: params.current,
success: true,
total: json.total,
};
}}
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
}}
search={false}
toolBarRender={false}
options={{
search: {
name: 'goodsName',
placeholder: '请输入商品名称',
},
}}
/>
//css样式
.inListContent {
.ant-pro-table {
display: flex;
flex-direction: column;
}
.ant-pro-table-search {
order: 2;
}
.ant-pro-table-extra {
order: 1;
}
.ant-pro-card {
order: 3;
}
}
EditableProTable
The usage scenario of this component is generally used in the modified or added pop-up box. Don't ask me how I know it, because I often use it~
1. trigger="onValuesChange": It ensures that my current form components such as DrawerForm can get the value of this EditableProTable in real time. For example, what I get in onfinsh is the value of inboundGoodsList.
2. recordCreatorProps: some configurations that manually fill in the list
3.editable: is some configuration for editing
<ProForm.Item label="" name="inboundGoodsList" initialValue={[]} trigger="onValuesChange">
<EditableProTable
headerTitle="入库商品"
rowKey="id"
columns={[
{
title: '商品',
dataIndex: 'goodsId',
formItemProps: { rules: [{ required: true }] },
valueType: 'select',
request: async () => {
let res = await getBasicGoodsPageList({
current: 1,
pageSize: 999,
projectId: initialState?.project?.projectId,
});
if (res.status == '00') {
return res?.records?.map((item) => {
return {
label: item.goodsName,
value: item.id,
};
});
}
},
fieldProps: () => {
return {
labelInValue: true,
};
},
},
{
title: '件重(件/kg)',
dataIndex: 'packageUnit',
formItemProps: { rules: [{ required: true }] },
valueType: 'digit',
fieldProps: { precision: 0, min: 1 },
},
{
title: '重量(kg)',
dataIndex: 'weight',
editable: false,
valueType: 'digit',
renderText: (_, record, index) => {
if (record.quantity && record.packageUnit) {
return Math.round(record.quantity * record.packageUnit);
}
},
},
{ title: '生产日期', dataIndex: 'productionDate', valueType: 'date', width: 200 },
{ title: '操作', width: 50, valueType: 'option' },
]}
recordCreatorProps={{
newRecordType: 'dataSource',
record: () => ({
id: Date.now(),
}),
}}
editable={{
type: 'multiple',
form: tableForm,
editableKeys,
onChange: setEditableRowKeys,
actionRender: (row, _, dom) => {
return [dom.delete];
},
}}
/>
</ProForm.Item>
recordCreatorProps={ref?.current?.getRowsData()[ref?.current?.getRowsData().length-1]?.weightEnd !=='∞'?{
newRecordType: 'dataSource',
record: (index, dataSource) => {
const id = Date.now();
debugger
let weightStart;
let weightEnd;
if (index === 0) {
weightStart = 0;
weightEnd = 0;
}
if (index - 1 >= 0) {
weightStart = dataSource[index - 1]?.weightEnd;
weightEnd='∞'
}
if (
ref?.current?.getRowsData().length > 0 &&
ref?.current?.getRowsData().length < index
) {
setTimeout(() => {
ref.current.setRowData(index - 1, {
weightStart: ref?.current?.getRowData(index - 2)?.weightEnd,
});
}, 0);
}
return {
weightEnd,
weightStart,
id,
};
},
}:false}
As this component library, EditableProTable is the most powerful component in my opinion. Although it is very cool to use, it still has some pitfalls:
In terms of linkage effect: when I link an item that cannot be edited, the linkage effect will be invalid. What does it mean? Okay, here's the code:
{
title: '杂费',
dataIndex: 'poundage',
valueType: "select",
request: async () => {
let res = await getPoundageList({ projectId: initialState?.project?.projectId })
if (res.status == "00") {
return res?.data?.map(item => {
return {
label: item.name,
value: item.id
}
})
}
},
fieldProps: (_, { rowIndex }) => {
return {
onChange: async (value) => {
debugger
let res = await getPoundageDetail(value)
if (res.status == "00") {
let { mode, chargeDescribe } = res?.obj
// tableRef?.current?.setRowData(rowIndex,{
// modeDescribe: mode.toString(),
// chargeDescribe: chargeDescribe || ""
// })
const data = form.getFieldValue('inboundPoundageItemList')
data[rowIndex].modeDescribe = mode.toString()
data[rowIndex].chargeDescribe = chargeDescribe
form.setFieldsValue({
inboundPoundageItemList: data,
})
}
}
}
}
},
At first, I tried to use setRowData to modify the linked data of other items in the current row, but it didn't work.
In layman's terms here: his editing and non-editing are two corresponding forms. When I edit the editable form, I cannot set the data to the non-editable form.
Yes, what about that?
Start from the data source: form.getFieldValue gets the data after modification
form.setFieldsValue is reassigned to the corresponding form item of this EditableProTable
ModalForm
The advantage of using trigger in this way is that the visible state of the component does not need to be passed in from the outside,
As long as this component is introduced, the component maintains this state internally.
submitter: You can customize the text of the confirmation button and cancel button of this form
<ModalForm
width="400px"
title="导入"
modalProps={{
maskClosable: false,
}}
trigger={<Button key="add">导入</Button>}
submitter={{
searchConfig: {
submitText: '导入',
resetText: '取消',
},
}}
onFinish={async (values) => onSubmit()}
>
ProList ProFormTreeSelect ProFormRadio.Group
ProFormTreeSelect, ProFormRadio.Group selection cannot be modified when it is worthwhile, and the value corresponding to the modification of the value attribute appears here.
const [libraryValue, setLibraryValue] = useState([]);
const [areaValue, setAreaValue] = useState({});
/* 头部元素 */
const ProListHeader = () => {
return (
<Form
form={form}
>
<ProFormTreeSelect
label="选择区域"
name="name"
placeholder="请选择区域"
value={areaValue}
allowClear
width={330}
secondary
request={async () => {
let res = await areaSituation({})
if (res.status == "00") {
return res?.data
}
}}
fieldProps={{
showArrow: false,
filterTreeNode: true,
showSearch: true,
dropdownMatchSelectWidth: false,
labelInValue: true,
autoClearSearchValue: true,
// multiple: true,
treeNodeFilterProp: 'title',
fieldNames: {
label: 'title',
lvl: 'lvl',
value: 'nodeId',
children: 'childrenList',
},
onChange: (item) => {
setAreaValue(item)
initDataList({
limitIdList: [item?.value]
})
initTotalData({
limitIdList: [item?.value]
})
}
}}
/>
<ProFormRadio.Group
width="md"
name="libraryValue"
value={libraryValue}
label=""
options={typeData}
onChange={(e) => {
setLibraryValue(e?.target?.value)
initDataList({ resourceType: e?.target?.value })
}}
/>
</Form>
);
};
const ItemColorMap = {
0: {
bgColor: 'rgba(0, 153, 255, 1)',
},
1: {
bgColor: 'gray',
},
2: {
bgColor: 'rgba(0, 153, 255, 1)',
},
3: {
bgColor: 'red',
},
};
<ProList
className="operateContent"
pagination={{
defaultPageSize: 8,
showSizeChanger: false,
}}
grid={{ gutter: 16, column: 1 }}
metas={{
title: {},
subTitle: {},
type: {},
avatar: {},
content: {},
actions: {},
}}
rowKey="id"
headerTitle={<ProListHeader />}
dataSource={
listData.map((unit, unitIndex) => (
{
title: ``,
subTitle: false,
actions: false,
avatar: false,
content: (
<div key={unit?.id} >
<div className='identification'><span className='lineStyle' />{unit?.floorNumber}楼</div>
<div className="floorBox">
{unit?.list?.map((item, index) => (
<div key={item?.locationId} className="floor" style={{ backgroundColor: ItemColorMap[item?.type]?.bgColor
}} onClick={()=>{setItemObj(item),setVisible(true)}}>
<div className='actualAreaBox'>
<span>{item?.locationName}</span>
<span className='actualArea'>{item?.actualArea}m²</span>
<span>{item?.inventory}T</span>
</div>
<div className='actualAreaBox'>
<span>{item?.type == 1 ? "闲置" : item?.usage}</span>
{
item?.type == 2 && <span className='overDay'>即将过期</span>
}
{
item?.type == 3 && <span className='overDay'>已过期</span>
}
</div>
</div>
))
}
</div>
</div>
)
}
))
}
/>
StatisticCard indicator card
<StatisticCard.Group direction={responsive ? 'column' : undefined}>
<StatisticCard
statistic={{
title: '仓库容量',
value: `${statistic?.totalCapacity}T`,
icon: <IconFont type="icon-shujuku" style={{ fontSize: 42 }} />,
}}
/>
</StatisticCard.Group>
The last article, if it helps you, or makes you understand something, I hope you can give it a thumbs up and follow~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。