基于antd组件封装自定义表单

文章介绍了如何使用React和antd库中的TableForm组件来构建一个可复用的表单,展示了如何集成Select、DatePicker、SearchSelect和NumberRange等组件,以及如何通过ForwardRef和useImperativeHandle进行实例管理和ref暴露。
摘要由CSDN通过智能技术生成

TableForm组件`

import React, { useRef, useImperativeHandle, } from 'react'
// import zhCN from '@saas/saas-ui-pc-locale/es/zh_CN'
import {Row, Col, Form, Select, Button, DatePicker,} from 'antd'
import moment from 'moment'
import SearchSelect from './SearchSelect'
import NumberRange from './NumberRange'
// import Screen from './Screen'
import './index.less'
const { Option } = Select
const { RangePicker } = DatePicker


const defaultLayout = {
  xxl: 6,
  lg: 8,
  md: 12,
}

export default React.forwardRef(function TableForm(props,ref,) {
  const formRef = useRef()

  const { fields = [], initialValues, submit } = props
  /**
   * 函数组件没有实例 通过forwardRef与useImperativeHandle钩子函数暴露出ref实例
   */
  useImperativeHandle(ref, () => formRef.current)
  const renderFormElement = (ele)=> {
    // const Element = window.steamer.services[ele.type]
    const { extraFields } = props || {}
    switch (ele.type) {
      case 'select':
        return (
          <Select
            getPopupContainer={(triggerNode) => triggerNode.parentNode}
            {...ele?.selectProps?.elementProps}
          >
            {ele?.selectProps?.list?.map((option, index) => {
              return (
                <Option
                  key={index}
                  value={option[ele?.selectProps?.optionValueField]}
                >
                  {option[ele?.selectProps?.optionLabelField]}
                </Option>
              )
            })}
          </Select>
        )
      case 'searchSelect':
        return <SearchSelect {...ele.selectProps}></SearchSelect>
      case 'timerange':
        return (
          <RangePicker
            style={{ width: '100%' }}
            getPopupContainer={(triggerNode) => triggerNode.parentNode}
            // locale={zhCN.DatePicker}
            showTime={{
              defaultValue: [
                moment('00:00:00', 'HH:mm:ss'),
                moment('23:59:59', 'HH:mm:ss'),
              ],
            }}
            {...ele?.props}
          />
        )
      // case 'ChoiceBox':
        
      //   return <ChoiceBox {...ele?.props} ></ChoiceBox>
      case 'NumberRange':
        return <NumberRange {...ele?.props}></NumberRange>
     
      case 'extra':
        return extraFields?.[ele.field] || <></>
      default:
        return <></>
        // return Element ? <Element {...ele.props}></Element> : <></>
    }
  }
  const handleSubmit = () => {
    formRef.current
      .check()
      .then((values) => {
        submit?.(values)
      })
      .catch((e) => {
        console.log(e)
      })
  }
  return (
    <div className="service-table-form">
      <Form ref={formRef} initialValues={initialValues}>
        {fields?.map((item, index) => {
          return (
            <div className="contioner" key={item.groupTitle}>
              {item.groupTitle ? (
                <div className="groupTitle">{item.groupTitle}</div>
              ) : null}
              <div style={{ flex: 1 }}>
                <Row gutter={24}>
                  {item.component.length &&
                    item.component.map((ele) => {
                      return (
                        <Col key={ele.field} {...(ele.layout || defaultLayout)}>
                          <Form.Item
                            {...ele?.formItemProps}
                            label={ele.label}
                            name={ele.field}
                          >
                            {renderFormElement(ele)}
                          </Form.Item>
                        </Col>
                      )
                    })}
                  {index === fields.length - 1 ? (
                    <Col xxl={6} lg={8} md={12}>
                      <Form.Item>
                        {props?.preBtns}
                        <Button type="primary" onClick={handleSubmit}>
                          {props?.searchText || '查询'}
                        </Button>
                        {props?.extraBtns}
                      </Form.Item>
                    </Col>
                  ) : null}
                </Row>
              </div>
            </div>
          )
        })}
      </Form>
    </div>
  )
})

SearchSelect组件
import React, { useEffect, useState } from 'react'
import { get, debounce } from 'lodash'
import { Select,} from 'antd'
import axios from 'axios'


const { Option } = Select
export default function SearchSelect(props){
  const {
    elementProps,
    optionLabelField,
    optionValueField,
    onChange,
    needItem,
  } = props
  const [optionList, setOptionList] = useState([])
  const [selectedValue, setSelectedValue] = useState(undefined)


  /**
   * 回显
   */
  useEffect(()=>{
    onSearch(props.value, true)
  }, [])

  /**
   * 查询
   * keyword 输入框的关键字
   * isInit 是否初始化查询
   */
  const onSearch = debounce(async (keyword,isInit)=> {
    if (!keyword) {
      return
    }
    const { req } = props
    let reqParmas = {}
    // 当配置项req.params是函数时,默认使用该函数组装请求参数(req.field字段自动失效)
    if (typeof req.params === 'function') {
      reqParmas = req.params(keyword)
    } else {
      reqParmas = Object.assign(
        {
          [req.field || 'keyword']: keyword,
        },
        req?.params,
      )
    }
    const reqConfig = {
      url: req?.url,
      method: req?.method || 'GET',
      params: reqParmas,
    }
    // 根据项目的请
    const res = await axios(reqConfig)
    if (typeof req.getter === 'function') {
      setOptionList(req.getter(res) || [])
    } else {
      const list = get(res, req.getter) || []
      if(isInit){
       const curItem= list.find(i=>String(i[optionValueField]) === String(keyword))
       setSelectedValue(JSON.stringify(curItem))
      }
      setOptionList(list)
    }
  }, 500)

  const onSelected = (itemValue) => {
    setSelectedValue(itemValue)
    const { mode } = elementProps || {}
    if (mode === 'multiple') {
      const _itemValue = itemValue.map((item) => JSON.parse(item))
      needItem
        ? onChange?.(_itemValue) // 多选且需要完整对象
        : onChange?.(_itemValue.map((i) => i[optionValueField])) // 多选且不需要完整对象,只传递id
    } else {
      needItem
        ? onChange?.(itemValue ? JSON.parse(itemValue) : null) // 单选且需要完整对象
        : onChange?.(
            itemValue ? JSON.parse(itemValue)?.[optionValueField] : null,
          ) // 单选不需要完整对象,只传递id
    }
  }

  /**
   *
   * @param option 渲染的每一项
   * @returns 自定义渲染的返回格式
   */
  const getSearchOptionLabel = (option) => {
    // 自定义函数渲染
    if (typeof optionLabelField === 'function') {
      return optionLabelField?.(option)
    } else if (
      // 自定义模版渲染
      optionLabelField?.includes('{{') &&
      optionLabelField?.includes('}}')
    ) {
      return handleTemplate(optionLabelField, option)
    } else {
      return option[optionLabelField]
    }
  }

  /**
   * 模板处理
   * 例如template为 {{name}}-{{id}}
   * 那name和id字段请传入params中
   */
  const handleTemplate = (template, params) =>
    template.replace(/\{\{(\w+)\}\}/g, ($1, $2) =>
      $2 in params ? params[$2] : $1,
    )

  return (
    <Select
      {...elementProps}
      showSearch
      filterOption={false}
      onSearch={onSearch}
      value={selectedValue}
      onChange={onSelected}
      getPopupContainer={(triggerNode) => triggerNode.parentNode}
    >
      {optionList.map((option, index) => {
        return (
          <Option key={`${index}`} value={JSON.stringify(option)}>
            {getSearchOptionLabel(option)}
          </Option>
        )
      })}
    </Select>
  )
}

NumberRange 组件
import React, { Component } from 'react'
import {InputNumber} from 'antd'

const DEFAULT_FROM_FIELD = 'from'
const DEFAULT_TO_FIELD = 'to'
export default class NumberRange extends Component {
  constructor(props) {
    super(props)
    const { fromField, toField, value, isCanEqual = true } = props
    this.state = {
      fromValue: value?.[fromField],
      toValue: value?.[toField],
    }
  }

  numChanged = (type, num) => {
    const { fromValue, toValue } = this.state || {}
    const { onChange, fromField, toField,isCanEqual } = this.props || {}
    // 最小值改变
    if (type === DEFAULT_FROM_FIELD) {
      let fromValue
      if(toValue){
        isCanEqual?(fromValue = num > toValue ? '' : num):(fromValue = num >= toValue ? '' : num)
      } else {
        fromValue = num
      }
      this.setState({
        fromValue,
      })
      onChange?.({ [fromField]: fromValue, [toField]: toValue })
    }
    // 最大值改变
    if (type === DEFAULT_TO_FIELD) {
      let toValue
      if(fromValue){
        isCanEqual?(toValue = num < fromValue ? '' : num):(toValue = num <= fromValue ? '' : num)
      } else {
        toValue = num
      }
      
      this.setState({
        toValue,
      })
      onChange?.({ [fromField]: fromValue, [toField]: toValue })
    }
  }

  render() {
    const { fromProps, toProps, middleStr = '-' } = this.props

    const { fromValue, toValue } = this.state
    return (
      <div className='input-number-continer'>
        <InputNumber
          {...fromProps}
          value={fromValue}
          onChange={(value) => this.numChanged(DEFAULT_FROM_FIELD, value)}
          className='input-number'
        />
        <span>&nbsp;{middleStr}&nbsp;</span>
        <InputNumber

          className='input-number'
          {...toProps}
          value={toValue}
          onChange={(value) => this.numChanged(DEFAULT_TO_FIELD, value)}
        />
      </div>
    )
  }
}

样式
.service-table-form{
    .contioner{
        display: flex;
        .groupTitle{
            width: 60px;
            margin: 6px 20px 0 0;
        }
        .groupTitle::before{
            content: '';
            border-left: 4px solid #2e7bfd;
            padding: 3px 5px;
        }
    }
}

.input-number-continer{
    display: flex;
    .input-number{
        flex: 1;
    }

}


使用示例


import React from 'react'
import {Button} from 'antd'
import TableForm from './App'

const bb=[{
  id:'a',
  name:'hah'
},
{
  id:'aa',
  name:'haha'
}]
const aa =  [
  {
    groupTitle: '门店',
    component: [
      {
        label: '门店名称/ID',
        field: 'keyword',
        type: 'searchSelect',
        selectProps: {
          elementProps: {
            allowClear: true,
            placeholder: '请输入门店名称/ID',
          },
          req: {
            url: '/apiwg/bdop/sjst.m.bdop/BdopThriftService/queryPois',
            method: 'GET',
            field: 'keyword',
            getter: 'poiItemTOs',
            params: (keyword) => {
              return {
                queryPoiReq: {
                  queryPoiConditionTO: {
                    keyword,
                    sort: 'created_time',
                    scope: 'all',
                  },
                  pageTO: {
                    currentPage: 1,
                    pageSize: 10,
                  },
                },
              }
            },
          },
          optionLabelField: '{{poiName}}({{poiId}})',
          optionValueField: 'poiId',
        },
      },
      {
        type: 'extra',
        field: 'org',
        label: '组织',
      },
      
    ],
  },
  {
    groupTitle: '意向',
    component: [
      {
        type: 'select',
        label: '创建来源',
        field: 'createType',
        selectProps: {
          elementProps: {
            allowClear: true,
            placeholder: '请选择',
          },
          list: bb,
          optionValueField: 'id',
          optionLabelField: 'name',
        },
      },
      {
        type: 'select',
        label: '意向来源',
        field: 'intentionSource',
        selectProps: {
          elementProps: {
            allowClear: true,
            placeholder: '请选择',
          },
          list: bb,
          optionValueField: 'id',
          optionLabelField: 'name',
        },
      },
      {
        type: 'timerange',
        label: '创建时间',
        field: 'createdTimeRange',
      },
      {
        type: 'timerange',
        label: '更新时间',
        field: 'modifiedTimeRange',
      },
      {
        type: 'timerange',
        label: '进阶时间 (C→AB)',
        field: 'saleStageAdvancedABTimeRange',
      },
      {
        type: 'NumberRange',
        label: '当前阶段停留天数',
        props: {
          fromProps: {
            placeholder: '请输入整数',
            min: 0,
            precision: 0,
          },
          toProps: {
            placeholder: '空代表无上限',
            min: 0,
            precision: 0,
          },
          fromField: 'low',
          toField: 'high',
          isCanEqual: true,
        },
        field: 'saleStageRetentionDayCntRange',
      },
      {
        type: 'NumberRange',
        label: '当前阶段拜访次数',
        props: {
          fromProps: {
            placeholder: '请输入整数',
            min: 0,
            precision: 0,
          },
          toProps: {
            placeholder: '空代表无上限',
            min: 0,
            precision: 0,
          },
          fromField: 'low',
          toField: 'high',
          isCanEqual: true,
        },
        field: 'saleStageActivityCntRange',
      },

     
     
      {
        type: 'screenPlus',
        field: 'saleChanceInfo',
        label: '意向信息',
        props: {
          dataArr: [
            {
              childrenArr: [
                {
                  renderType: 'RangePicker',
                  fieldProps: {
                    name: 'expectedDealTimeRange',
                    label: '预计成交日期',
                  },
                  componentProps: {
                    style: {
                      width: 520,
                    },
                  },
                },
              ],
            },
          ],
        },
      },
    ],
  },
  {
    groupTitle: '点评',
    component: [
      {
        type: 'searchSelect',
        label: '点评人',
        field: 'commentator',
        selectProps: {
          elementProps: {
            allowClear: true,
            placeholder: '请输入姓名/MIS',
          },
          req: {
            url: '/apiwg/bdopthriftservice/queryEmpsByUsername',
            method: 'GET',
            field: 'username',
            getter: 'employees',
          },
          optionLabelField: '{{userName}}({{login}})',
          optionValueField: 'employeeId',
        },
      },
      {
        type: 'select',
        label: '意向点评状态',
        field: 'saleChanceCommentStatus',
        selectProps: {
          elementProps: {
            allowClear: true,
            placeholder: '请选择',
          },
          list:bb,
          optionValueField: 'id',
          optionLabelField: 'name',
        },
      },
      {
        type: 'select',
        label: '当前阶段点评状态',
        field: 'saleStageCommentStatus',
        selectProps: {
          elementProps: {
            allowClear: true,
            placeholder: '请选择',
          },
          list: bb,
          optionValueField: 'id',
          optionLabelField: 'name',
        },
      },
      {
        type: 'timerange',
        label: '最近点评时间',
        field: 'lastCommentTimeRange',
      },
      
    ],
  },
  {
    groupTitle: '待办',
    component: [
      {
        type: 'select',
        label: '待办状态',
        field: 'taskStatus',
        selectProps: {
          elementProps: {
            allowClear: true,
            placeholder: '请选择',
          },
          list: bb,
          optionValueField: 'id',
          optionLabelField: 'name',
        },
      },
      // {
      //   type: 'select',
      //   label: '待办类型',
      //   field: 'taskType',
      //   selectProps: {
      //     elementProps: {
      //       allowClear: true,
      //       placeholder: '请选择',
      //     },
      //     list: data.enumMap?.taskType,
      //     optionValueField: 'id',
      //     optionLabelField: 'name',
      //   },
      // },
      // {
      //   type: 'select',
      //   label: '检核目标',
      //   field: 'taskTargetType',
      //   selectProps: {
      //     elementProps: {
      //       allowClear: true,
      //       placeholder: '请选择',
      //     },
      //     list: data.enumMap?.taskTargetType,
      //     optionValueField: 'id',
      //     optionLabelField: 'name',
      //   },
      // },
      // {
      //   type: 'select',
      //   label: '检核目标结果',
      //   field: 'taskTargetResult',
      //   selectProps: {
      //     elementProps: {
      //       allowClear: true,
      //       placeholder: '请选择',
      //     },
      //     list: data.enumMap?.taskTargetStatus,
      //     optionValueField: 'id',
      //     optionLabelField: 'name',
      //   },
      // },
      {
        type: 'timerange',
        label: '有效期',
        field: 'taskValidTimeRange',
      },
      {
        type: 'timerange',
        label: '创建时间',
        field: 'taskCreateTimeRange',
      },
      {
        type: 'timerange',
        label: '处理时间',
        field: 'taskHandleTimeRange',
      },
    ],
  },
]
export default function Test() {
  return (
        <TableForm
              // ref={this.formRef}
              fields={aa}
              // submit={this.submit}
              // initialValues={initialValues}
              extraFields={{
                // org: (
                //   <OrgTreeSelect
                //     mode={ORGTREESELECT_TYPE.ORG_EMP}
                //     inputProps={{
                //       onChangeCum: () => {},
                //     }}
                //     ORGType={17}
                //     multiple={false}
                //     isShowSearch={true}
                //     searchMode={3}
                //     onlySubordinate={true}
                //   />
                // ),
              }}
              extraBtns={[
                <Button
                  key="rest"
                  type="primary"
                  style={{
                    margin: '0 20px',
                  }}
                  // onClick={this.reset}
                >
                  重置
                </Button>,
              ]}
            ></TableForm>
  )
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
antd 是一个非常优秀的 React UI 组件库,其提供了丰富、易用的组件,可以帮助我们快速构建美观、高效的 Web 应用程序。如果你想封装自己的组件库并基于 antd 进行扩展,可以按照以下步骤进行: 1. 创建一个基础组件 首先,你需要创建一个基础组件,它包含了你要封装组件的基本结构和样式,可以使用 antd 提供的组件进行组合。例如,如果你要封装一个按钮组件,可以创建一个 Button 组件,代码如下: ```jsx import React from 'react'; import { Button } from 'antd'; const MyButton = ({ children, ...restProps }) => { return ( <Button {...restProps}> {children} </Button> ); }; export default MyButton; ``` 2. 根据需求进行扩展 基础组件创建完成后,你可以根据自己的需求进行扩展,添加一些额外的功能或者修改样式等。例如,如果你想为按钮组件添加一个 loading 状态,可以在 MyButton 组件中添加一个 loading 属性,代码如下: ```jsx import React from 'react'; import { Button } from 'antd'; const MyButton = ({ children, loading, ...restProps }) => { return ( <Button {...restProps} loading={loading}> {children} </Button> ); }; export default MyButton; ``` 3. 导出组件并使用 最后,你需要将封装好的组件导出,然后在你的应用程序中使用它。例如,如果你想在应用程序中使用 MyButton 组件,可以按照以下方式进行: ```jsx import React from 'react'; import MyButton from './path/to/MyButton'; const App = () => { return ( <div> <MyButton type="primary" loading={true}>Click Me</MyButton> </div> ); }; export default App; ``` 这样,你就可以基于 antd 进行简单的组件封装了。当然,如果你需要封装更复杂的组件,可能需要更多的步骤和技巧,但这是一个很好的起点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值