4.基于Umi4使用ProForm、ProTable、组件封装

452 阅读4分钟

1.ProForm表单使用

1.1父组件 index.js

    import React, { useRef } from 'react';
    import type { ProFormInstance } from '@ant-design/pro-components';
    
    const baseFormRef = useRef<ProFormInstance>();
    
    // 函数
      const handleFormValuesChange = useCallback((changedValues: any, formValues: Record<string, any>) => {
    const changedKey = Object.keys(changedValues || {})[0];

    if (changedKey === 'createDeptName') {
        // 做页面联动操作
    }

    const values = cloneDeep(formValues);
    setBaseFormData(values);
  }, [baseFormRef])
    // 组件
    <BaseForm
        formRef={baseFormRef}
        changecreateDeptName={(name) => {
          setgecreateDeptName(name)
        }}
        onFormValuesChange={handleFormValuesChange}
    />

formRef 是为了控制整个表单,在父组件能拿到子组件的表单项,ProForm负责接收

onFormValuesChange 是当表单任意一项值发生变化时都能监听到,达到组件间的联动效果,ProForm负责接收

changecreateDeptName 传递给子组件的自定义事件

1.2子组件 baseForm.js

    import { Col, Row } from 'antd';
    import {
      ProForm,
      ProFormDatePicker,
      ProFormSelect,
      ProFormText,
      ProFormDigit,
      ProFormTextArea
    } from '@ant-design/pro-components';
    const formItemLayout = {
        labelCol: { span: 8 },
        wrapperCol: { span: 16 },
    };
    <ProForm
        layout="horizontal"
        {...formItemLayout}
        formRef={formRef}
        submitter={false}
        onValuesChange={(changedValues, formValues) => {
          onFormValuesChange(changedValues, formValues);
        }}
        omitNil={false}
      >
         <Row gutter={[24, 0]} justify="start">

          <Col span={8}>
            <CProInputToSearch
              label="哈哈哈"
              name="managerName"
              width="xl"
              rules={[
                {
                  required: true,
                },
              ]}
              fieldProps={{
                onClick: (e) => {
                  toSearch('managerCode');
                },
              }}
            />
          </Col>


          <Col span={8}>
             <CProInputToSearch
              name="createDeptName"
              label="啦啦啦"
              width="xl"
              disabled={!isSpecialRole}
              width="xl"
              // disabled={true}
              rules={[
                {
                  required: true,
                  message: '填报部门不能为空!',
                },
              ]}
              fieldProps={{
                onClick: (e) => {
                 changecreateDeptName(formRef?.current?.getFieldValue('createDeptCode'))
                 toSearchDept('createDeptCode');
                },
              }}
             />
          </Col>

        </Row>
    </ProForm>

1.3封装的CProInputToSearch组件

index.js

    import { Component, PureComponent } from "react";
    import {
      ProFormText,
    } from '@ant-design/pro-components';
    import { SearchOutlined } from '@ant-design/icons';
    import "./index.less";

    interface CInputToSearchInterface {
      [x: string]: any; 
    }

    export default class CProInputToSearch extends Component<CInputToSearchInterface> {

      render() {
        const {
          fieldProps = {},
          disabled,
          ...other
        } = this.props;

        const { onClick, hasSearchIcon = true,readOnly=true, ...otherFieldProps } = fieldProps || {};

        return (
          <ProFormText
            { ...other }
            disabled={disabled}
            fieldProps={{
              ...otherFieldProps,
              className: "c-pro-input-search",
              onClick: onClick,
              readOnly,
              // maxLength: 0,
              addonAfter: (
                !disabled && hasSearchIcon ? <SearchOutlined
                  onClick={onClick}
                /> : null
              ),
            }}
          />
        )
      }
    }

index.less

    .c-pro-input-search {

      .ant-input-affix-wrapper-readonly > .ant-input-suffix > .ant-input-clear-icon-hidden {
        visibility: visible;
      }
      .ant-input-affix-wrapper-disabled > .ant-input-suffix > .ant-input-clear-icon-hidden {
        visibility: hidden;
      }
    }

2.ProTable使用

需求: 点击表单项,弹窗,可选择列表,选择完成后,更新表单

      /**
       * 点击查询图标
       * @param type
       */
      const toSearch = (type: string) => {
        setVisible(true);
        setQueryType(type);
      };

2.1封装单选列表

2.1.1配置文件
    export const queryModalMap = {
      createDeptCode: {
        title: "封装的标题",
        queryApi: "",
        isPagination: true,
        rowKey: "organizationCode",
        columns: [
          {
            title: '',
            dataIndex: '',
            width: '50%',
            align: 'center',
          },
          {
            title: '',
            dataIndex: '', 
            width: '50%',
            align: 'center',
          },
        ]
      },
}

title定义的标题 queryApi请求的接口会返回数据源 isPagination是否分页 rowKey列表的唯一值 columns列表的columns

2.1.2父组件
  const closeModal = () => {
    setVisible(false);
    setQueryType('');
  };
  
  const getQueryModalParams = (queryType: string) => {
    let params = {};
    switch (queryType) {
      case 'createDeptCode':
        const companyCode = formRef.current.getFieldValue('companyCode');
        params = {
          parentOrgCode: companyCode,
        };
        break;
    }
    return params;
  };
 
 
  /**
   * 查询结果
   * @param queryData
   */
  const queryRet = (queryData: { queryType: string; selectedRow: Record<string, string> }) => {
    const { queryType, selectedRow } = queryData;
    let nowFormData = formRef?.current?.getFieldsValue();
    nowFormData = cloneDeep(nowFormData);

    switch (queryType) {
      case 'createDeptCode':
        const changedValues = {
          createDeptName: selectedRow.orgNameKk,
          createDeptCode: selectedRow.organizationCode,
          managerCode: '',
          givenName: '',
        };
        Object.assign(nowFormData, changedValues);
        formRef?.current?.setFieldsValue({
          ...changedValues,
        })
        formRef?.current?.setFieldsValue({
          managerList: [],
          managerName: '',
          managerCode: []
        })
        onFormValuesChange(changedValues, nowFormData);
        break;
    }
  };
 <QueryModal
  queryType={queryType}
  config={queryModalMap[queryType]}
  visible={deptVisible}
  onCancel={closeModal}
  onSubmit={queryRet}
  baseParams={getQueryModalParams(queryType)}
/>
)}

queryType当前弹窗类型标识 config弹窗的配置文件 visible是否显示 onCancel取消 onSubmit确定 baseParams接口的额外参数

2.1.3子组件
import React, { useRef, useState } from 'react';
import { request } from '@umijs/max'

import { Button, Form, Modal } from 'antd';
import { ProTable } from '@ant-design/pro-components';
import type { ActionType, ProColumns } from '@ant-design/pro-components';

export type IAny = {
  [property:string]: any;
}


export type UpdateFormProps = {
  onCancel: (flag?: boolean, formVals?: IAny) => void;
  onSubmit: (val: {queryType: string; selectedRow: IAny; selectionType: 'radio'|'checkbox'}, helpData?: Record<string, any>) => void;
  visible: boolean;
  config: {
    title: string;
    queryApi: string;
    columns: ProColumns<IAny>;
    isPagination?: boolean;
    rowKey?: string;
    selectionType?: 'checkbox' | 'radio';
  },
  queryType?: string;
  baseParams?: Record<string, any>;
};

const QueryModal: React.FC<UpdateFormProps> =  (props) => {
  const {
    queryType,
    visible,
    config,
    onCancel,
    onSubmit,
    baseParams = {},
  } = props;

  const { title, queryApi, columns, isPagination, rowKey, selectionType } = config || {};

  const [form] = Form.useForm();
  const actionRef = useRef<ActionType>();
  const [selectedRows, setSelectedRows] = useState<API.StandardItem[]>([]);

  const handleOk = async () => {
    onCancel();
    onSubmit({
      queryType: queryType || '',
      selectedRow: selectionType === 'checkbox' ? selectedRows: selectedRows[0],
      selectionType: selectionType || 'radio',
    }, {
      _baseParams: baseParams,
    });
  }

  return (
    <Modal
      title={title || ''}
      visible={visible}
      onOk={handleOk}
      onCancel={() => {
        onCancel()
      }}
      width={800}
      footer={[
        <Button onClick={onCancel} key="close">取消</Button>,
        <Button type="primary" key="confirm" onClick={handleOk} disabled={!selectedRows.length}>确定</Button>,
      ]}
    >
      <ProTable<API.StrategyItem, API.PageParams>
        actionRef={actionRef}
        rowKey={rowKey || "id"}
        // 如果值不设置为0, 样式会有问题
        search={{
          labelWidth: 0,
          // collapsed: false,
        }}
        // params={baseParams}
        request={async (params = {}, sort, filter) => {
          console.log('params====', params)
          const res = await request<API.RuleList>(queryApi, {
            method: 'POST',
            data: {
              ...(baseParams || {}),
              ...params,

            },
          });

          if (isPagination) {
            return Promise.resolve({
              data: res.data?.list ?? [],
              total: res.data?.total ?? 0,
              success: true,
            });
          }

          return Promise.resolve({
            data: res.data ?? [],
            success: true,
          });
          
        }}
        columns={columns}
        toolBarRender={false}
        rowSelection={{
          onChange: (_, selectedRows) => {
            setSelectedRows(selectedRows);
          },
          type: selectionType || 'radio'
        }}
        scroll={{ y: 200 }}
        tableAlertRender={false}
      />
    </Modal>
  );
};

export default QueryModal

2.2多选列表,穿梭框

2.2.1 配置文件
    export const queryModalMap = {
        managerCode: {
        title: "项目",
        queryApi: "/cmcc-budget/user/selectUserByDepartment",
        isPagination: true,
        selectionType: 'checkbox',
        rowKey: "userAccount",
        columns: [
          {
            title: '姓名',
            dataIndex: 'userName',,
            width: '50%',
            align: 'center',
          },
          {
            title: '账号',
            dataIndex: 'userAccount',
            width: '50%',
            // hideInSearch: true,
            align: 'center',
          },
        ],
        LFTableFieldTies: {
          userAccount: "managerCode",
          userName: "managerName",
        },
      },
    }
2.2.2 父组件
    const getQuerySelectedContent = (queryType: string) => {
        if (queryType === 'managerCode') {
          const xx = formRef.current.getFieldValue('managerList');
          return xx;
        }

        return [];
    };
    <PartCheckModal
      queryType={queryType}
      config={queryModalMap[queryType]}
      visible={visible}
      onCancel={closeModal}
      onSubmit={queryRet}
      baseParams={getQueryModalParams(queryType)}
      selectedDataSource={getQuerySelectedContent(queryType)}
    />
2.2.3 子组件

index.tsx

    import React, { useRef, useState } from 'react';
    import { request } from '@umijs/max'
    import { Button, Form, Modal, Table, message } from 'antd';
    import { ProTable } from '@ant-design/pro-components';
    import type { ActionType, ProColumns } from '@ant-design/pro-components';
    import { SwapRightOutlined, SwapLeftOutlined } from '@ant-design/icons';
    import styles from "./index.less"

    export type IAny = {
      [property:string]: any;
    }

    export type QueryModalProps = {
      onCancel: (flag?: boolean, formVals?: IAny) => void;
      onSubmit: (val: {queryType: string; selectedRow: IAny; selectionType: 'radio'|'checkbox'}) => void;
      visible: boolean;
      config: {
        title: string;
        queryApi: string;
        columns: ProColumns<IAny>;
        isPagination?: boolean;
        rowKey?: string;
        selectionType?: 'checkbox' | 'radio';
        LFTableFieldTies?: Record<string, string>;
      },
      queryType?: string;
      baseParams?: Record<string, any>;
      selectedDataSource?: Record<string, any>[];
    };

    const QueryModalToCheckBox: React.FC<QueryModalProps> =  (props) => {
      const {
        queryType,
        visible,
        config,
        onCancel,
        onSubmit,
        baseParams = {},
        selectedDataSource,
      } = props;
      const {
        title,
        queryApi,
        columns,
        isPagination,
        rowKey = "id",
        selectionType,
        LFTableFieldTies,
      } = config || {};


      const formatSelectedDataSource = () => {
        let nextData: Record<string, string>[] = [];
        let nextRowKey: React.Key[] = [];

        if(!LFTableFieldTies) {
          return selectedDataSource || [];
        }

        (selectedDataSource || []).forEach(item => {
          const modifidFileds = {};
          Object.keys(LFTableFieldTies).forEach(key => {
            modifidFileds[key] = item[LFTableFieldTies[key]]
          })
          const dataItem = {
            ...item,
            ...modifidFileds,
          }
          nextData.push(dataItem)
          nextRowKey.push(dataItem[rowKey])
        })

        return [nextData, nextRowKey];
      }

      const [_SDataSource = [], _SKeys = []] = formatSelectedDataSource();

      const actionRef = useRef<ActionType>();
      const [leftSelectedRows, setLeftSelectedRows] = useState<API.StandardItem[]>([]);
      const [leftSelectedRowsKeys, setLeftSelectedRowsKeys] = useState<React.Key[]>([])
      const [rightDataSource, setRightDataSource] = useState(_SDataSource);
      const [rightRowKeys, setRightRowKeys] = useState<React.Key[]>(_SKeys);
      const [rightSelectedRowsKeys, setRightSelectedRowsKeys] = useState<React.Key[]>([])

      const handleOk = async () => {

        onSubmit({
          queryType: queryType || '',
          selectedRow: rightDataSource,
          selectionType: selectionType || 'radio',
        });
        onCancel();
      }

      const handleAdd = () => {
        if (!leftSelectedRowsKeys.length) {
          return;
        }

        setRightDataSource([
          ...rightDataSource,
          ...leftSelectedRows,
        ])
        setRightRowKeys([
          ...rightRowKeys,
          ...leftSelectedRowsKeys,
        ])
        setLeftSelectedRows([]);
        setLeftSelectedRowsKeys([]);
      }

      const handleDel = () => {
        const nextRightDataSource = rightDataSource.filter(row => (!rightSelectedRowsKeys.includes(row[rowKey])));

        const nextRightRowKeys = rightRowKeys.filter(rowkey => (!rightSelectedRowsKeys.includes(rowkey)));

        setRightDataSource(nextRightDataSource);
        setRightRowKeys(nextRightRowKeys)
        setRightSelectedRowsKeys([]);
      }

      return (
        <Modal
          title={title || ''}
          visible={visible}
          onOk={handleOk}
          onCancel={() => {
            onCancel()
          }}
          width={800}
          footer={[
            <Button
              onClick={onCancel}
              key="close"
            >取消</Button>,
            <Button
              type="primary"
              key="confirm"
              onClick={handleOk}
              disabled={!rightDataSource.length}
            >确定</Button>,
          ]}
        >
          <ProTable<API.StrategyItem, API.PageParams>
            className={styles.QueryModalToCheckBox}
            actionRef={actionRef}
            rowKey={rowKey}
            search={true}
            request={async (params = {}, sort, filter) => {
              const res = await request<API.RuleList>(queryApi, {
                method: 'POST',
                data: {
                  ...(baseParams || {}),
                  ...params,
                },
              });

              if (isPagination) {
                return Promise.resolve({
                  data: res.data.list,
                  total: res.data.total,
                  success: true,
                });
              }

              return Promise.resolve({
                data: res.data,
                success: true,
              });
            }}
            columns={columns}
            toolBarRender={false}
            rowSelection={{
              selectedRowKeys: leftSelectedRowsKeys,
              onChange: (_, selectedRows) => {
                if ((selectedRows?.length + rightRowKeys?.length) > 5) {
                  message.warning('最多选择5位项目责任人!')
                } else {
                  setLeftSelectedRowsKeys(_)
                  setLeftSelectedRows(selectedRows);
                }
              },
              getCheckboxProps: (record: IAny) => {
                return {
                  disabled: rightRowKeys.includes(record[rowKey]),
                }
              },
              type: selectionType || 'radio'
            }}
            options={false}
            scroll={{ y: 200, x: 200 }}
            tableAlertRender={false}
            tableRender={(_, dom) => {
              return (
                <div className="table-render-box">
                  <div className="table-container-left">
                    {dom}
                  </div>

                  <div className="table-container-action">
                    <div className='icon-box'>
                      <SwapRightOutlined
                        style={{
                          fontSize: '32px',
                          color: leftSelectedRowsKeys.length ? "#4a90e5" : "gray",
                        }} 
                        onClick={handleAdd}
                      />

                      <SwapLeftOutlined 
                        style={{
                          fontSize: '32px',
                          transform: "rotateX(180deg)",
                          color: rightSelectedRowsKeys.length ? "#4a90e5" : "gray",
                        }}
                        onClick={handleDel}
                      />
                    </div>
                  </div>

                  <div className="table-container-right">
                    <Table 
                      rowKey={rowKey}
                      columns={columns}
                      dataSource={rightDataSource || []}
                      rowSelection={{
                        selectedRowKeys: rightSelectedRowsKeys,
                        onChange: (_, selectedRows) => {
                          setRightSelectedRowsKeys(_)
                        },
                        type: selectionType || 'radio'
                      }}
                      scroll={{ y: 200, x: 200 }}
                    />
                  </div>
                </div>
              )
            }}
          />
        </Modal>
      );
    };

    export default QueryModalToCheckBox;

index.less

    :local(.QueryModalToCheckBox) {
      :global {
        overflow-x: hidden;
        .table-render-box {
          display: flex;
          width: 100%;
        }
        .table-container-left, .table-container-right {
          flex: 0 0 45%;
          overflow-x: scroll;
        }
        .table-container-action {
          flex: 0 0 10%;
          position: relative;
          .icon-box {
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            display: flex;
            flex-direction: column;
          }
        }
        .table-container-right {
          .ant-table-thead .ant-table-cell {
            padding-top: 12px;
            padding-bottom: 12px;
          }
        }

      }
    }

总结:antdPro中表单和表格的简单使用,更多的是封装组件的思想,达到可配置化,可拓展化,由数据驱动页面拆分为配置化工程驱动页面。