相信很多同学都用过 Ant Design 这一 react 著名组件库,而 ProComponents 则是在 antd 之上进行封装的页面级组件库(指一个组件就可以搞定一个页面)。它同时也是 Ant Design Pro 中后台框架所用的主要组件库。如果你手上有要用 react 开发的中后台新项目又人手不够的话,我强烈推荐你体验一下这个库。可以极大的减少日常 CRUD 的代码量并统一风格。
在 ProComponent 中,最为核心的就是 ProForm(表单)和 ProTable(表格)两个组件,面对数量繁多的配置项,很多人都会在文档中迷失自我,本文就来介绍一些常见的配置,希望可以对你有所帮助。
本文的所有内容均来自于 ProComponent 官方文档,如果你遇到了实在解决不了的问题时请熟读文档或者去 issue 区 里查一查。我也很推荐你在使用一段时间后重读一下 官方文档 - 通用配置总览。相信可以让你对 pro 组件有更全面的了解
ProTable
官方文档,ProTable 最大的好处就是它集成了表格的查询条件和分页配置。只需要配置表格列就可以自动生成对应的查询 form。除此之外它提供的插槽和细节小功能用起来也十分舒服。
1、基础流程
先来简单介绍一下基本的使用,主要就是两部分:表格列配置、查询请求,最后把他俩塞给 ProTable 就完事了:
import { fetchCompanyList } from '@/service';
import ProTable from '@ant-design/pro-table';
const CompanyList = () => {
// 表格列配置项
const columns = [
{
title: '企业名称',
dataIndex: 'corName',
},
{
title: '申请日期',
dataIndex: 'applyDate',
valueType: 'date',
align: 'center'
},
];
/** 获取数据 */
const getData = async (params) => {
// 组装查询参数,比如这里用 pageIndex 代替了 current
const query = {
...params,
pageIndex: params.current
};
delete query.current;
// 发起请求
const { data, success } = await fetchCompanyList(query);
// 格式化返回数据
return {
data: data.records,
success,
total: data.total,
};
};
return (
<ProTable
columns={columns}
request={getData}
rowKey="id"
/>
);
};
然后你就得到了这个简单但五脏俱全的表格:
在 ProTable 中查询条件和表格列是一一对应的,也就是说每个列都会生成一个对应的查询条件,注意列配置中的 valueType
字段,它标注了这个字段是一个日期,然后 ProTable 就会按照对应的格式渲染表格字段和查询表单项。
注意这个 valueType 是 ProComponent 的灵魂,你可以在 这里 找到其支持的所有 valueType。
在发起查询时,你所有的查询条件和分页参数都会被传递给 request 参数对应的函数上,最后把查询到的结果按照给定的格式返回出去即可,注意这个函数可以是异步函数,ProTable 会根据这个函数的状态自动渲染相关的加载动画,非常舒服。
2、隐藏查询条件
ProTable 默认会显示所有列的查询条件,如果你想隐藏指定列的查询条件,可以在列配置项里添加 hideInSearch
:
const columns = [
{
title: '企业名称',
dataIndex: 'corName',
// 添加这个属性
hideInSearch: true
},
// ...
];
或者如果想直接隐藏整个查询表单的话,可以在 ProTable 上关闭 search
项:
<ProTable
// 不显示搜索表单
search={false}
columns={columns}
request={getData}
rowKey="id"
/>
3、tag 列及查询条件
下图这种标签列在表格里是非常常见的,下面就来介绍下怎么实现:
import { keyBy } from 'lodash';
import { Tag } from 'antd';
/** 企业申请状态配置项 */
const REQUEST_STATUS_OPTION = [
{ text: '待审核', value: 0, color: '' },
{ text: '审核通过', value: 1, color: 'green' },
{ text: '审核不通过', value: 2, color: 'red' }
]
// {
// '0': { text: '待审核', /** ... */ },
// '1': { text: '审核通过', /** ... */ },
// '2': { text: '审核不通过', /** ... */ },
// }
const requestStatusEnum = keyBy(REQUEST_STATUS_OPTION, 'value')
// 表格行配置项
const columns = [
{
title: '审批状态',
dataIndex: 'status',
valueType: 'select',
valueEnum: requestStatusEnum,
// 设置为多选
// fieldProps: { mode: 'multiple' },
render: (text, { status }) => {
const tagStatus = requestStatusEnum[status] || { color: '', text: '未知' };
return <Tag color={tagStatus.color}>{tagStatus.text}</Tag>;
},
},
// ...
];
首先,在列配置项里使用 valueType: 'select'
将查询项渲染成下拉框,然后使用 valueEnum
属性配置下拉框的内容,注意它接受的是一个 kv 对象,键为选项 value,值中的 text 属性作为选项的 label。这里直接使用 lodash 的 keyBy 来生成。最后通过列配置的 render
属性渲染标签,这里的用法和 antd 里是一样的。
之所以把选项搞成数组形式然后再转换成 kv 对象,是因为 ProForm 那边也同样会用到这个配置,而那里的下拉框是需要数组形式的。
这里你也可以通过设置列配置项的 fieldProps: { mode: 'multiple' }
来将下拉框设置成多选。:
注意,列配置项 fieldProps
里的配置会被透传到 valueType 对应的 antd 组件上,也就是说这里的多选配置实际上是 antd Select 组件的配置。
4、查询默认值
你可以通过列表项的 initialValue
来给查询表单项设置默认值:
const columns = [
{
title: '申请日期',
dataIndex: 'applyDate',
valueType: 'date',
align: 'center',
// 设置默认值
initialValue: '2021-7-1'
},
// ...
];
5、列宽调整
ProTable 默认会等分所有列,想调整指定列宽的话可以修改列配置的 width
属性,支持百分比和数字。
// 表格行配置项
const columns = [
{
title: '企业名称',
dataIndex: 'corName',
// 设置为百分比
width: '70%'
},
{
title: '申请日期',
dataIndex: 'applyDate',
valueType: 'date',
// 设置为固定值
width: 240
}
];
这个配置在 ProTable 的文档里并没有标注(起码我没找到),但是实际上,所有 antd Table Column 支持的配置项都可以用在这里。
而 ProTable 组件则会把自己 form
属性上的对象传递给自己内部封装的 antd Form 组件,你可以用这个属性来自定义表单 form:
6、查询条件顺序调整
ProTable 默认按照列配置的索引排列查询条件,你可以通过指定列配置项中的 order
来调整查询条件的顺序,其值越大就越靠前:
const columns = [
{
title: '企业名称',
dataIndex: 'corName',
order: 9
},
{
title: '申请日期',
dataIndex: 'applyDate',
valueType: 'date',
align: 'center',
order: 10
},
];
7、查询条件长度调整
你可以通过指定列配置的 colSize
项来调整具体查询条件的长度,官方介绍如下:
默认情况下一个 colSize 的长度就是 8 span,那么也就是说 colSize: 4 时就可以占满一行:
const columns = [
{
title: '企业名称',
dataIndex: 'corName',
colSize: 4
},
// ...
];
注意这个值没必要是整数,你可以设置为 0.5 来将其缩短,但是要注意最后相乘得到的 span 值最好是个整数。
8、时间查询条件改为范围
列表中时间列的查询条件一般都是个范围,如下:
但是默认的 valueType: 'date'
只会显示单个日期选择器,所以我们需要使用其他方法来实现这个需求,具体做法也很简单,把表格列的查询项关掉,然后放置一个时间范围的查询条件,注意时间范围使用 hideInTable
参数让它不会显示在表格里:
// 表格行配置项
const columns = [
{
title: '申请日期',
dataIndex: 'applyDate',
valueType: 'date',
hideInSearch: true
},
{
title: '申请日期',
dataIndex: 'applyDateRange',
valueType: 'dateRange',
fieldProps: { placeholder: ['开始时间', '结束时间'] },
hideInTable: true
},
];
然后你就可以在 request 里处理他们:
const getData = async (params) => {
const query = {
...params,
applyDateStart: (params.applyDateRange || [])[0],
applyDateEnd: (params.applyDateRange || [])[1]
};
delete query.applyDateRange;
// 发起请求 ...
};
你可能想说直接把日期列的 valueType 设置为
dateRange
可以么?实际上不行的,因为这会让它在表格里也显示成时间范围的形式 :xxx - xxx
。
ProForm
ProForm 文档,ProFormFields 表单项文档。ProForm 的好处是封装了不同的表单外观,你只需要切换一个字段就可以把页面表单切换成弹出表单或者抽屉表单。除此之外还封装了提交和重置行为、以及对常用的表单项都进行了封装。
1、基础流程
ProTable 的用法更加简单,在 ProForm 标签里插入封装好的 表单项。然后给 onFinish
设置一个函数即可,这个函数会接受到 通过校验 的表单项,然后你就可以在这里访问后端进行提交了。
import ProForm, {
ProFormRadio,
ProFormText,
ProFormDatePicker,
} from '@ant-design/pro-form';
const PolicyDetail = (props) => {
// 只有通过校验之后才会触发这个方法
const onSubmit = async (values) => {
console.log(values);
};
return (
<ProForm onFinish={onSubmit}>
<ProFormText
name="name"
label="名称"
placeholder="请输入名称"
/>
<ProFormRadio.Group
name="status"
label="状态"
options={[
{ label: '已生效', value: '已生效' },
{ label: '已作废', value: '已作废' }
]}
rules={[{ required: true, message: '状态不能为空' }]}
/>
<ProFormDatePicker
name="publishTime"
label="时间"
placeholder="请选择时间"
rules={[{ required: true, message: '时间不能为空' }]}
/>
</ProForm>
);
};
注意,在 ProForm 中,select, checkbox, radio, radioButton 这些表单项都支持通过 options 参数配置选项内容,其值为 { value: '', label: ''}
格式的数组。具体介绍可以看 这里。
注意,所有的 ProForm 表单组件都是用 Form.Item + 对应的组件封装的来的,都支持使用 fieldProps
属性来支持设置输入组件的 props。
也就是说,fieldProps
参数里的属性都会被透传给内部的输入组件,而直接设置给 ProForm 表单组件的字段会被透传给 Form.Item。切记切记,不要一看到表单项列表里一点属性都没贴就觉得这玩意没法用了。
这一点在 ProFormFields - 表单项 (ant.design) 最开头就说了,但是有很多人都是直接点下面的具体组件看文档,结果根本不知道这一点从而又浪费了很多时间,比如我。
2、对齐提交按钮
大多数表单开发的习惯都是底部的操作按钮组和字段输入框对齐,但如果你给 form 配置了 layout="horizontal"
和 labelCol={{ span: 4 }}
后就会发现,底部的按钮组还是左侧开始的。
解决这个对齐问题需要用到 ProForm 的 submitter
属性,而其中的 render 函数则可以控制底部按钮组的渲染,这个函数的第二个入参是一个数组,其元素分别是已经渲染好的重置和提交按钮,如果你有需要的话也可以在这里进行更复杂的自定义行为:
<ProForm
submitter={{
render: (_, dom) => (
<Form.Item wrapperCol={{ offset: 4 }}>
<Space>{dom}</Space>
</Form.Item>
),
}}
// ...
>
{/* ... */}
</ProForm>
注意我上面 wrapperCol 的 offset 是 4,你需要根据你的 ProForm 配置自行调整。
3、数据回填
想回填数据的话需要用到 ProForm 的 initialValues
,整个表单的数据都应该在这里设置。注意在数据获取到之前不能渲染表单,不然初始值就无效了。
import React, { useState, useEffect } from 'react';
import ProForm from '@ant-design/pro-form';
import { Skeleton } from 'antd';
import { isEmpty } from 'lodash';
import { getDetail } from '@/service';
const Detail = () => {
// 详情数据
const [detail, setDetail] = useState({});
// 获取详情
useEffect(async () => {
const { data } = await getDetail();
setDetail(data);
}, []);
const loadingPage = isEmpty(detail);
// 数据获取到之前展示加载动画,让 form 渲染时肯定可以得到初始值
return loadingPage ? (
<Skeleton active />
) : (
<ProForm initialValues={detail}>
{/* ... */}
</ProForm>
)
};
我注意到有很多人在回填表单数据的时候喜欢用
form.setFieldsValue
设置,但是这么做是不对的,一方面在数据抵达的时候 form 表单项可能还没加载出来,另一方面使用initialValues
可以让重置按钮可以恢复到初始值,而不是重置成全空表单。
4、表单项联动
表单里经常会出现如下这种表单项联动的需求:
ProComponent 中提供了 ProFormDependency
组件可以响应式的处理这种需求:
import ProForm, { ProFormRadio, ProFormDependency } from '@ant-design/pro-form';
<ProFormRadio.Group
name="status"
label="结论"
options={[
{ label: '审核通过', value: 1 },
{ label: '审核不通过', value: 2 }
]}
/>
<ProFormDependency name={['status']}>
{({ status }) => {
if (!status || status === 1) return null;
return (<ProFormTextArea
name="refuseReason"
label="驳回原因"
placeholder="请填写驳回原因"
/>);
}}
</ProFormDependency>
ProFormDependency
在 name 字段里绑定的值变更后会自动触发 children 里的函数进行渲染。
5、表单项联动 - 异步
上面的例子里提到了如何进行同步的联动,但是这可是在 render 里,我们不能直接设置一个 async 函数来阻塞渲染。想要进行异步联动我们需要使用 onValuesChange
监听值变更并使用 formRef
修改表单值:
代码如下:
import React, { useRef } from 'react';
import ProForm, { ProFormSelect, ProFormUploadButton } from '@ant-design/pro-form';
/** 附件获取接口 mock */
const getFileById = async (id) => {
return new Promise(resolve => setTimeout(() => {
if (id === 1) resolve([]);
else resolve([
{ name: '测试文件1.png', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' }
])
}, 500))
}
const MyForm = (props) => {
const formRef = useRef(undefined);
/** 变化时监听,并将返回值重设回 form */
const onChange = async ({ selectVal }) => {
const file = await getFileById(selectVal);
formRef.current.setFieldsValue({ file });
}
return (
<ProForm
layout="horizontal"
labelCol={{ span: 4 }}
onValuesChange={onChange}
formRef={formRef}
>
<ProFormSelect
options={[
{ label: '没有附件', value: 1 },
{ label: '有附件', value: 2 },
]}
name="selectVal"
label="选项"
/>
<ProFormUploadButton
label="选项附件"
name="file"
// 更好的显示附件
readonly
fieldProps={{
showUploadList: { showRemoveIcon: false }
}}
/>
</ProForm>
);
};
在使用 ProForm 时尽量不要使用单独表单项的 onChange 事件,因为 ProForm 会自动注册这个回调来接管数据量,手动调用会导致覆盖其注册,从而出现某些表单数据失去响应式的问题。
并且注意其中的 ProFormUploadButton 组件,这里使用了它的 readonly 属性来隐藏了上传按钮,ProForm 所有的表单项都支持这个字段并提供了特别的显示效果,所以比单纯的 disabled 更好(除此之外还使用了 fieldProps 自定义内部的 Upload 组件的属性)。
写在最后
可以看到本文里的配置基本都是非常简单的,但是鉴于其数量众多的配置项,一开始使用的时候经常会看文档好久才能找到期望的配置写法。我在一开始使用的时候也因此浪费了不少时间,再加上网上关于 ProComponent 的文章几乎等于没有,所以萌生了整理这个文档的想法。
如果你也遇到了类似的困扰了你半天但最终配置很简单的情况,欢迎评论交流。