记录 @ant-design/pro-form 表单身份证号联动

记录 @ant-design/pro-form 表单身份证号联动

这只是我在学习使用 umi 过程中的一次记录,以下代码并没有跑在生产环境,仅供参考。当然,并不是完全没有试验,我们线上项目是使用 vue2 写的,部分逻辑判断还是跑在了生产环境。

1. 需求

在用户输入正确的身份证号码后,自动生成年龄,性别和出生日期。


2. 需求分析

我们知道,身份证号码包含有用户的部分信息,我们可以将这部分信息提取出来,让用户少填写一些字段,简化用户的输入。

何为正确的身份证号码?按照常规来分析(只分析二代身份证,一代身份证不探究)。可能考虑不是很全,如果还有其他的,可以自己尝试去添加。(如果不太好添加,可以留言一起讨论)

  • 长度为 18
  • 由 18 位数字或 17 位数字 + X 组成
  • 关联身份证生日部分,月份不能大于 12
  • 关联身份证生日部分,当前月的号数不能大于当前月的天数

当然,你还可以加判断,例如年龄计算出来有几百岁了,也可以提示一下用户身份证号码是不是写错了。


3. 一些零零碎碎的提示

因为是第一次使用 umi 直接创建的项目,里面封装好的 pro-form 可能使用起来并不是很熟练,毕竟我在上一次使用 react 写项目的时候,那时候的 react 最新版本还不是 16.8。

如果有不好的地方请指正,感谢。


4. 开始编码

@ant-design/pro-form 官方文档

4.1 表单部分

src/pages/testForm/index.tsx

import { Card, Space, Popconfirm } from 'antd';
import { PageContainer } from '@ant-design/pro-layout';
import type {
  ProFormInstance
} from '@ant-design/pro-form';
import ProForm, {
  ProFormText,
  ProFormSelect,
  ProFormDatePicker,
  ProFormUploadButton,
  ProFormCascader,
  ProFormDigit
} from '@ant-design/pro-form';
import { Row, Col, Button } from 'antd';
import { useRef } from 'react';

import useSiteList from '@/hooks/useSiteList';
import styles from './index.less';
import useDictData from '@/hooks/useDictData';
import { validateIdCard } from '@/utils/rules';

const fieldLabels = {
  // 其他字段省略...
  cardId: '身份证号',
  sex: '患者性别',
  birthday: '出生日期',
  age: '患者年龄'
};

const TestForm = () => {
  const formRef = useRef<
    ProFormInstance<{
      // 其他字段省略...
      cardId: string;
      sex: string;
      birthday: string;
      age: number;
    }>
  >();
  const { siteList } = useSiteList(false, 'workstation,site,center');

  const { dictArr: insuranceArr } = useDictData('insured_situation');
  const { dictArr: employmentStatusArr } = useDictData('employment_status');
  const { dictArr: educationalLevelArr } = useDictData('culture_degree');

  // 提交表单
  const onFinish = async (values: Record<string, any>) => {
    console.log(values);
    // 代码省略...
  }

  // 重置表单
  const onReset = () => {
    formRef.current?.resetFields();
  }

  // 生成生日
  const genBirthday = (birthday: string) => {
    formRef.current?.setFieldsValue({ birthday });
  }

  // 生成年龄
  const genAge = (birthday: string) => {
    const birth = Date.parse(birthday.replace('/-/g', '/'));
    const year = 1000 * 60 * 60 * 24 * 365;
    const now = new Date().valueOf();
    const _birthday = new Date(birth).valueOf();
    const age = parseInt(((now - _birthday) / year) + '');
    formRef.current?.setFieldsValue({ age });
  }

  // 生成性别
  const genSex = (num: number) => {
    const sex = num % 2 === 0 ? '2' : '1';
    formRef.current?.setFieldsValue({ sex });
  }

  // 联动生成函数
  const linkage = () => {
    return {
      // 身份证号输入自动生成生日、年龄和性别的函数
      cardId: (val: string) => {
        const birthday = val.substring(6, 10) + '-' + val.substring(10, 12) + '-' + val.substring(12, 14);
        genBirthday(birthday);
        genAge(birthday);
        genSex(parseInt(val[16]));
      },
      // 用户选择生日自动生成年龄函数
      birthday: (val: string) => {
        genAge(val);
      }
    }
  }

  // 基础信息表单输入变化时,部分字段要联动,
  // 该函数是在用户输入的时候就调用,所以可以使用防抖
  const onValuesChange = async (changeValues: Record<string, any>) => {
    // 需要联动的字段数组,并不是每个字段都要联动,
    // 所以使用一个数组来存储要联动的字段,
    // 当然,如果你表单很大,联动字段很多,可以提出去单独维护
    const linkageFields = ['cardId', 'birthday'];
    const key = Object.keys(changeValues)[0];
    // 如果字段不是需要联动的字段,则直接返回
    if (linkageFields.indexOf(key) === -1) return;
    const val = changeValues[key];
    // 将获取表单错误信息的逻辑加入到异步宏任务中
    // 因为底层封装表单的错误信息是通过异步更改的
    // 当然你可以使用其他方式,或者说 pro-form 封装了更好的方法,只是我没有找到
    setTimeout(() => {
      // 获取表单提示的错误信息数组的长度
      // 如果长度为 0,则表示基础验证通过,可以提交表单
      const errLen = formRef.current?.getFieldError(key).length;
      if (errLen === 0) {
        // 调用联动生成函数,当然,你也可以使用对象的方式
        // 使用函数的方式方便以后万一要对数据再做一些处理,预留起,方便以后罢了
        linkage()[key](val);
      }
    })
  }

  return (
    <PageContainer>
      <ProForm
        onFinish={onFinish}
        submitter={false}
        formRef={formRef}
        onValuesChange={onValuesChange}
      >
        {/* 如果字段很多,可以参考官方文档中的 Schema Form,很简单的,这里就不作探讨 */}
        <Card title="基本信息" className={styles.card} bordered={false}>
          <Row gutter={24}>
            <Col lg={16} sm={24}>
              <Row gutter={24}>
                {/* 其他字段代码省略... */}
                <Col xl={12} md={12} sm={24}>
                  <ProFormText
                    label={fieldLabels.cardId}
                    rules={[{ required: true, validator: validateIdCardNum }]}
                    name="cardId"
                    placeholder="请输入身份证号"
                  />
                </Col>
              </Row>
            </Col>
          </Row>
          <Row gutter={24}>
            <Col xl={8} md={12} sm={24}>
              <ProFormSelect
                label={fieldLabels.sex}
                name="sex"
                rules={[{ required: true, message: '请选择患者性别' }]}
                options={[
                  { value: '1', label: '男' },
                  { value: '2', label: '女' },
                ]}
                placeholder="请选择患者性别"
              />
            </Col>
            <Col xl={8} md={12} sm={24}>
              <ProFormDatePicker
                label={fieldLabels.birthday}
                name="birthday"
                fieldProps={{
                  style: {
                    width: '100%',
                  },
                }}
                rules={[{ required: true, message: '请选择出生日期' }]}
              />
            </Col>
            <Col xl={8} md={12} sm={24}>
              <ProFormText
                label={fieldLabels.age}
                name="age"
                placeholder="请输入患者年龄"
              />
            </Col>
            {/* 其他字段省略... */}
            <Col xl={8} md={12} sm={24}>
              <ProFormText label=" ">
                <Space>
                  <Popconfirm title="是否重置表单?重置后表单所填写内容将完全清空!" onConfirm={onReset}>
                    <Button htmlType="button" key="reset">重置</Button>
                  </Popconfirm>
                  <Button type="primary" htmlType="submit">提交</Button>
                </Space>
              </ProFormText>
            </Col>
          </Row>
        </Card>
      </ProForm>
    </PageContainer>
  )
}

export default TestForm;

4.2 验证身份证号部分

src/utils/rules.ts

import { getLastDayOfMonth } from '@/utils/utils';
import type { RuleObject } from 'rc-field-form/lib/interface';

// 主要是为了防止编码过程中 TS 报错,又不想写 any,
// 所以就去看了 pro-form 底层的一些代码写出来的,
// 不懂也没关系,反正也不影响是不
type Validator = (rule: RuleObject, value: any) => Promise<void | any> | void;

/**
 * 判断身份证号码
 * 写得很乱,你可以继续优化,比如抽离优化代码结构,封装什么的
 * @param {*} rule
 * @param {*} value
 */
export const validateIdCard: Validator = (rule, value: string) => {
  if (value.length !== 18) {
    return Promise.reject(new Error('身份证号码长度为18位【二代身份证】'))
  }
  if (value.length === 18) {
    if (!/[0-9]{17}[0-9xX]$/.test(value)) {
      return Promise.reject(new Error('请输入正确格式的身份证号'))
    }
    const _yearStr = value.substring(6, 10)
    const _year = _yearStr && parseInt(_yearStr) || 0
    const _monthStr = value.substring(10, 12)
    const _month = _monthStr && parseInt(_monthStr) || 0
    if (_month > 12) {
      return Promise.reject(new Error('请输入正确格式的身份证号【生日部分月份不能大于12】'))
    }
    const _lastDay = getLastDayOfMonth(_year, _month)
    const _maxDay = _lastDay.substring(_lastDay.length - 2, _lastDay.length)
    if (value.substring(12, 14) > _maxDay) {
      return Promise.reject(new Error(`请输入正确格式的身份证号【生日部分当月天数不能大于${_maxDay}`))
    }
  }
  return Promise.resolve()
}

4.3 一个封装的工具函数,获取某年某月的最后一天

src/utils/utils.ts

/**
 * 获取某年某月的最后一天
 * 当然是随便写的,没有经过大量的测试
 * @param {Number} year  年份
 * @param {Number} month 月份
 * @returns {String}     返回当前传入年传入月的最后一天
 */
 export function getLastDayOfMonth (year: number, month: number) {
  const date = new Date(year, month - 1, 1)
  // 设置日期
  date.setDate(1)
  // 设置月份
  date.setMonth(date.getMonth() + 1)
  // 获取本月的最后一天
  const lastDay = new Date(date.getTime() - 1000 * 60 * 60 * 24)
  // 返回结果
  return year + '-' + month + '-' + lastDay.getDate()
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值