antd ProComponent 常用操作总结

相信很多同学都用过 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 组件的配置。

ProTable 官网介绍

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:

ProTable 入参文档

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 的文章几乎等于没有,所以萌生了整理这个文档的想法。

如果你也遇到了类似的困扰了你半天但最终配置很简单的情况,欢迎评论交流。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容