React - 实现一个基于 Antd 的数值范围组件

最近公司的产品需求要做一个数据采集流程,这个流程里面有比较多的表单数据,其中有一个输入数值范围的控件,这个功能还是很常见的,但是之前的开发没有封装成一个公共组件,特此自己造轮子。

需求原型

使用场景

当需求中需要录入数值范围的表单数据

实现思路

考虑到组件的共用性和拓展性,它应具备以下功能:

代码结构

由于这个功能实现还是比较简单的,组件的细节便不多描述,直接上代码:

typescript 复制代码
/*
 * @Author: 白雾茫茫丶<baiwumm.com>
 * @Date: 2023-12-13 14:34:55
 * @LastEditors: 白雾茫茫丶<baiwumm.com>
 * @LastEditTime: 2023-12-13 18:08:35
 * @Description: 数字范围输入组件
 */
import { Col, InputNumber, Row } from 'antd'
import type { InputNumberProps } from 'antd/es/input-number'
import { gt, toNumber } from 'lodash'
import React, { FC, FocusEventHandler } from 'react'

import type { EnumValues } from '@/utils/types'

enum INPUT_TYPE {
  MIN, // 最小值
  MAX, // 最大值
}

type InputType = EnumValues<typeof INPUT_TYPE>

type ValuePair = (string | number | undefined)[];

type FormDigitRangeProps = {
  value?: ValuePair; // 表单控件的值
  onChange?: (value: ValuePair) => void; // 表单控件改变值的回调
  separator: string; // 分割线
  separatorGap: number; // 分割线和数据框的 gap
  placeholder: [string, string]; // 占位符
  suffix: string; // 后缀,不传则不显示
} & InputNumberProps

const FormDigitRange: FC<FormDigitRangeProps> = ({
  value = [],
  onChange,
  separator = '~',
  separatorGap = 15,
  placeholder = ['最小值', '最大值'],
  precision = 2,
  min = 0,
  max = 99999999.99,
  suffix,
  ...inputNumberProps
}) => {
  // 输入值失去焦点回调
  const handleChangeValue = (e: FocusEventHandler<HTMLInputElement>, type: InputType) => {
    // 获取输入框的值,这里转成 number 类型
    const result = e.target.value !== '' ? toNumber(e.target.value) : undefined;
    // 解构获取最值
    const [min, max] = value;
    switch (type) {
      case INPUT_TYPE.MIN:
        // 判断最小值是否大于最大值,为真就调换位置
        onChange?.(gt(result, max) ? [max, result] : [result, max])
        break;
      case INPUT_TYPE.MAX:
        // 判断最大值是否小于最小值,为真就调换位置
        onChange?.(gt(min, result) ? [result, min] : [min, result])
        break;
    }
  }
  // 渲染输入框
  const renderInputNumber = (type: InputType) => (
    <InputNumber
      {...inputNumberProps}
      min={min}
      max={max}
      value={toNumber(value[type])}
      precision={precision}
      placeholder={placeholder[type]}
      onBlur={(e) => handleChangeValue(e, type)}
      style={{ width: '100%' }}
    />
  )

  return (
    <Row gutter={separatorGap} align='middle' wrap={false}>
      <Col flex={1}>
        {renderInputNumber(INPUT_TYPE.MIN)}
      </Col>
      <Col flex="none">
        <div>{separator}</div>
      </Col>
      <Col flex={1}>
        {renderInputNumber(INPUT_TYPE.MAX)}
      </Col>
      {
        suffix && (
          <Col flex="none">{suffix}</Col>
        )
      }
    </Row>
  )
}
export default FormDigitRange

代码不到100行,怎么样,是不是很容易?

使用方式

typescript 复制代码
import { Button, Col, Form, Row, Space } from 'antd'
import { compact, isNumber } from 'lodash'
import React, { FC, useEffect, useState } from 'react'

import PageContainer from '@/components/PageContainer'

import FormDigitRange from './components/FormDigitRange'


const DataAcquisition: FC = () => {
  const [form] = Form.useForm();
  const [fields, setFields] = useState({});
  const onFinish = (values: any) => {
    setFields(values)
  };

  useEffect(() => {
    form.setFieldValue('money', undefined)
  }, [])
  return (
    <PageContainer title="数字范围输入组件">
      <Form form={form} onFinish={onFinish}>
        <Row>
          <Col span={12}>
            <Form.Item
              name="money"
              label="租金涨跌金额"
              rules={[
                { type: 'array', required: true, message: '' },
                () => ({
                  validator(_, value) {
                    if (!value || !compact(value).length) {
                      return Promise.reject(new Error('请输入租金涨跌金额'));
                    } else if (!isNumber(value[0])) {
                      return Promise.reject(new Error('请输入最小值'));
                    } else if (!isNumber(value[1])) {
                      return Promise.reject(new Error('请输入最大值'));
                    }
                    return Promise.resolve();
                  },
                }),
              ]}
            >
              <FormDigitRange suffix="元/㎡/月" />
            </Form.Item>
          </Col>
        </Row>
        <Row>
          <Col span={12}>
            <Space direction="vertical" size="middle" style={{ display: 'flex' }} align='center'>
              <pre style={{ background: '#f5f5f5', padding: '12px 20px', width: 400 }}>
                {JSON.stringify(fields, null, 2)}
              </pre>
              <Button htmlType="submit" type="primary">提交</Button>
            </Space>
          </Col>
        </Row>
      </Form>
    </PageContainer>
  )
}
export default DataAcquisition

参数说明

参数 说明 类型 默认值 是否必传
separator 分隔符 string ~ -
separatorGap 分隔符间距 number 15 -
placeholder 占位符 [string,string] ['最小值', '最大值'] -
precision 数值精度 number 2 -
min 最小值 number 0 -
max 最大值 number 99999999.99 -
suffix 后缀 string - -

除此之外支持所有 InputNumber属性

效果预览

注意事项

  1. 组件是根据公司具体业务需求开发的,不一定符合每个人的要求
  2. 该组件只是提供一个思路,可在此拓展更复杂的业务场景
相关推荐
FinGet9 小时前
那总结下来,react就是落后了
前端·react.js
王解12 小时前
Jest项目实战(2): 项目开发与测试
前端·javascript·react.js·arcgis·typescript·单元测试
AIoT科技物语1 天前
免费,基于React + ECharts 国产开源 IoT 物联网 Web 可视化数据大屏
前端·物联网·react.js·开源·echarts
初遇你时动了情1 天前
react 18 react-router-dom V6 路由传参的几种方式
react.js·typescript·react-router
番茄小酱0011 天前
ReactNative中实现图片保存到手机相册
react native·react.js·智能手机
王解1 天前
Jest进阶知识:深入测试 React Hooks-确保自定义逻辑的可靠性
前端·javascript·react.js·typescript·单元测试·前端框架
小牛itbull1 天前
ReactPress—基于React的免费开源博客&CMS内容管理系统
前端·react.js·开源·reactpress
~甲壳虫1 天前
react中得类组件和函数组件有啥区别,怎么理解这两个函数
前端·react.js·前端框架
用户8185216881172 天前
react项目搭建create-router-dom,redux详细解说
react.js
new Vue()2 天前
Vue vs React:两大前端框架的区别解析
vue.js·react.js·前端框架