4
头图

ProComponent就是算是对antd的又一次集成和封装,减少了前端对于细节和联动的处理,总之就是用起来特别爽。
那这里就不对ProComponent做过多介绍了,我们直奔主题,如何使用,或者说如何更优雅、更方便的使用组件和编写代码一直是任何一位程序员的核心追求,我也是PiuPiuPiu~!

columns的配置

treeSelect

request可以用来调接口获取值
labelInValue:是设置的取到的是这个表单项值的对象,不仅仅包括value,下面的例子取到的就是fieldNames对象

{
            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这个是可取值到年月日时分秒
ProFormDatePicker 是支持到年月日
可用moment设置初始值,
这个默认取得值是一个数组 如果搜索后端需要的这个时间字段不是数组而是其他的两个字段;可用下面的写法

{
            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可设置渲染到table的状态
sorter表格的某一项排序

{
                  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

可用SelectAccount直接渲染组件在单列表框里面 比如在EditableProTable里面,
当然这里是用在搜索框的(还是提倡用valueType的方式,但是如果在多个地方使用,封装成组件这样使用也是上选)

 {
              title: '冷藏费记账账户',
              dataIndex: 'coldStorageFeeAccountId',
              formItemProps: { rules: [{ required: true }] },
              renderFormItem: (_, { type, ...rest }) => (
                <SelectAccount labelInValue params={{ projectId: rest.recordKey }} />
              ),
            },
digit

渲染成只能输入数字的搜索框

{
              title: '件数',
              dataIndex: 'quantity',
              valueType: 'digit',
              fieldProps: { precision: 0, min: 1 },
              formItemProps: { rules: [{ required: true }] },
            },

或者可自定义在search和table的title文字不一样

fieldProps:可设置搜素框的状态

{
      title: (_, type) => (type === 'table' ? '操作账号' : ''),
      dataIndex: 'operatorMobile',
      key: 'operatorMobile',
      //fieldProps: { readOnly: true, placeholder: '请先选择杂费' },
      fieldProps: {
        placeholder: '操作人账号',
      },
    },

ProForm表单项

ProFormDependency

可用来监听表单项的某一项改变之后所做的操作,
name为什么是数组呢?

  • 是因为在表单提交的时候呢contractType是作为inboundAppointment对象的一个属性值进行提交的,
  • 这样写的好处就是前端在提交的函数中不用再重新组装一下这个提交参数了
<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

上面我们说到name设置成数组的形式,可以帮我们省略组装参数的操作。
那这个更厉害了,可以帮我们提前组装或者拆分提交表单的参数。
那我们看一下官方这里是怎么说的:“ProFormFieldSet 可以将内部的多个 children 的值组合并且存储在 ProForm 中,并且可以通过 transform 在提交时转化”,
言而简之,就是我刚说的意思,对吧~
下面的例子也就是将取到的SelectSubject 对象值进行拆分,保存在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一个简单的表单项
ProFormDateTimePicker:时间选择器年月日可精确到时分秒
ProForm.Group: 可将表单项在空间范围内允许的情况下,做一行展示

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

一个只能输入数字的表单项

<ProFormDigit
                                label="预收天数"
                                name="unitTotal"
                                min={1}
                                max={10}
                                placeholder="请输入预收天数"
                                fieldProps={{ precision: 0 }}
                            />
ProFormGroup

列表项归类

 <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

官方言:高级描述列表组件,提供一个更加方便快速的方案来构建描述列表。
我是用在列表项的详情里面的,通table的用法类似。

<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

两种表格, 一种用的最多的普通表格,一种是可编辑的表格
使用相似度很高。

ProTable

这个ProTable的配置有意思的地方有三:

  1. params自带pageSize,pageNumber
  2. rowSelection可做批量配置操作
  3. 只有单个条件搜索的时候可search设置为false,options的search单独设置,那如果你需要多个条件搜索的时候,可单独对搜索框做一些定制化配置
    当然作为一个高级定制化组件,只有这么些集成肯定是不够的,

高级配置
tableExtraRender
这是配置table列表上部,搜索区域的下部的中间区域,当然如果你有把这个移动到最顶部的需求,可利用css样式去覆盖,倾囊写上

<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

这个组件的使用场景一般是在修改或添加的弹框中使用居多,不要问我怎么知道,因为我常用~
1.trigger="onValuesChange":保证了我当前的诸如DrawerForm类似的form组件可实时拿到这个EditableProTable的值,比如下面在onfinsh中拿到的就是inboundGoodsList的值
2.recordCreatorProps:是手动填加列表的一些配置
3.editable:是编辑的一些配置

   <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}

EditableProTable作为这个组件库我觉得最牛逼的组件,虽然使用起来很爽,但是还是有点点坑的存在的:
联动效果来说:当我联动的是一个设置了不能编辑的一项,那这个联动效果会失效,什么意思呢,好的,那上代码:

{
                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,
                        })
                      }
                    }
                  }
                }
              },

一开始我尝试使用setRowData对当前这一行的其他项做联动数据修改,但是失效的,
这里通俗讲就是:他的编辑和不能编辑是对应的两个表单,当我编辑可编辑的这个表单,是设置不了数据到不可编辑的那个表单的,
对,那怎么办?
从数据源处下手呗:form.getFieldValue得到数据修改完之后
form.setFieldsValue重新赋值给这个EditableProTable对应的表单项

ModalForm

这样使用trigger的好处:是组件的状态visible不需要从外部传入了,
只要引入这个组件,这个组件内部维护这个状态。
submitter:可以自定义这个表单的确认按钮和取消按钮的文字

 <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选取值得时候修改不了的情况在这里出现了
加上value属性对应修改的值

 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 指标卡

 <StatisticCard.Group direction={responsive ? 'column' : undefined}>
          <StatisticCard
            statistic={{
              title: '仓库容量',
              value: `${statistic?.totalCapacity}T`,
              icon: <IconFont type="icon-shujuku" style={{ fontSize: 42 }} />,
            }}
          />
      

        </StatisticCard.Group>

image.png

最后这篇文章,如果帮助到你,或者让你有所领悟,还希望可以不吝 点赞关注~


HappyCodingTop
526 声望847 粉丝

Talk is cheap, show the code!!


« 上一篇
前端导出