antd RangePicker 限制选择范围(精确到秒)

703 阅读4分钟

项目中使用了蚂蚁的ProComponents组件库,其中依赖antd5.x

之前有个产品需求是选择日期时间,但是有一个月的范围限制。

我的实现方式如下:

const LIMIT_DAYS = 30

const getYearMonth = (date: Dayjs) =>
    date.year() * 12 + date.month()
const disabledDate: DatePickerProps['disabledDate'] = (
  current,
  { from, type }
) => {
  if (from) {
    const minDate = from.add(-(LIMIT_DAYS - 1), 'day')
    const maxDate = from.add(LIMIT_DAYS - 1, 'day')

    switch (type) {
      case 'year':
        return (
          current.year() < minDate.year() ||
          current.year() > maxDate.year()
        )

      case 'month':
        return (
          getYearMonth(current) < getYearMonth(minDate) ||
          getYearMonth(current) > getYearMonth(maxDate)
        )

      // 时间的卡控

      default:
        return Math.abs(current.diff(from, 'day')) >=
              LIMIT_DAYS
    }
  }

  return false
}


  /**
   * 设置disableTime
   * @param from: 传入的边界时间
   * @param isStartTime: 第一个参数 from 是否是起始时间
   * */
  const getDisableTime = (
    from: Dayjs,
    isStartTime = true
  ) => {
    // 找到临界值
    const limitTime = isStartTime
      ? from.add(1, 'second')
      : from.subtract(1, 'second')
    return {
      disabledHours: () => {
        const limitHour = limitTime.hour()

        return Array.from(
          { length: 24 },
          (_, i) => i
        ).filter((hour) => {
          return isStartTime
            ? hour < limitHour
            : hour > limitHour
        })
      },

      disabledMinutes: (selectedHour: number) => {
        const limitHour = limitTime.hour()
        const limitMinute = limitTime.minute()
        if (selectedHour === limitHour) {
          return Array.from(
            { length: 60 },
            (_, i) => i
          ).filter((minute) =>
            isStartTime
              ? minute < limitMinute
              : minute > limitMinute
          )
        }

        return []
      },

      disabledSeconds: (
        selectedHour: number,
        selectedMinute: number
      ) => {
        const limitHour = limitTime.hour()
        const limitMinute = limitTime.minute()
        const limitSecond = limitTime.second()

        if (
          selectedHour === limitHour &&
          selectedMinute === limitMinute
        ) {
          return Array.from(
            { length: 60 },
            (_, i) => i
          ).filter((second) =>
            isStartTime
              ? second < limitSecond
              : second > limitSecond
          )
        }

        return []
      },
    }
  }

  // 设置disabledTime 控制选择的时间
  const disabledTime: RangePickerProps['disabledTime'] = (
    current,
    range,
    info
  ) => {
    if (info.from) {
      const minDate = info.from.add(-LIMIT_DAYS, 'day')
      const maxDate = info.from.add(LIMIT_DAYS, 'day')

      if (current.isSame(maxDate, 'day')) {
        return getDisableTime(info.from, false)
      }

      if (current.isSame(minDate, 'day')) {
        return getDisableTime(info.from, true)
      }
    }

    return {}
  }



export default Page() {
  return <ProFormDateTimeRangePicker
           // xxxx 省略部分熟悉
           fieldProps={{
             disabledDate,
             disabledTime,
             showTime: {
               hideDisabledOptions: true,
             },
           }}
           />
}

设置了两个属性disabledDatedisabledTime,其中disableDate限制具体日期的选择,disabledTime限制时间。

disabledDate中内容和官方Demo差不多,只不过我们选的日期(info.from)这里会选择时分秒,

例如:起始时间选择了2024-10-01 02:00:00,时间限制为7天,那结束时间就该是:2024-10-08 01:59:59

起始时间选择了2024-10-01 00:00:00,时间限制为7天,那结束时间就该是:2024-10-07 23:59:59

又上看出,如果选择了时分秒后,日期控制的时间可以多选一天。

const LIMIT_DAYS = 30

const disabledDate: DatePickerProps['disabledDate'] = (
  current,
  { from, type }
) => {
  if (from) {
    // ... 省略部分代码

    // 除了日期外,是否还选择了时分秒
    const hasSelectHourMinuteSecond =
      from.hour() || from.minute() || from.second()

    switch (type) {
    
      // ... 省略部分代码

      // 时间的卡控
      /**
       * 起始时间没有选择时分秒
       * 2024-10-01 00:00:00 -> 2024-10-30 23:59:59
       *
       * 起始时间选择了时分秒
       * 2024-10-01 01:00:00 -> 2024-10-31 00:59:59
       *
       * 选择了时分秒后 时间期限默认要多一天
       *
       */
      default:
        return hasSelectHourMinuteSecond
          ? Math.abs(current.diff(from, 'day')) >
              LIMIT_DAYS
          : Math.abs(current.diff(from, 'day')) >=
              LIMIT_DAYS
    }
  }

  return false
}

disabledTime中我们只要控制住边界中最大的天数和最小的天数对应的时分秒。

例如:

如果我选择了2024-10-08 02:00:00,那么我能选择的最大时间是2024-10-15 01:59:59

我能选择的最小时间是2024-10-01 02:00:01

我们能够选择的最大日期在disabledDate中已经被限制了,这边我们只要判断对应的时分秒就行。

const getDisableTime = (
  from: Dayjs,
  isStartTime = true
) => {
  // 找到临界值
  const limitTime = isStartTime
    ? from.add(1, 'second')
    : from.subtract(1, 'second')

  // ... 省略
}

isStartTime判断当前选择的时间是否是区间的起始时间,算出limitTime

小时判断很简单,根据判断出来的limitTime, isStartTime来判断即可。

const getDisableTime = (
  from: Dayjs,
  isStartTime = true
) => {
  // 找到临界值
  // ...

  return {
     disabledHours: () => {
        const limitHour = limitTime.hour()

        return Array.from(
          { length: 24 },
          (_, i) => i
        ).filter((hour) => {
          return isStartTime
            ? hour < limitHour
            : hour > limitHour
        })
      },
  }
}

分,秒的计算不能直接判断,我们要判断出当前选择的小时(小时,分钟)

举个🌰:

2024-10-01 02:02:02 - 2024-10-08 02:02:01

日期选择了 2024-10-08 ,此时能选择的小时只有 0,1,2

当小时选择2时,分钟选只有0,1,2

当分钟选择了2时,秒的只能选1。

const getDisableTime = (
  from: Dayjs,
  isStartTime = true
) => {
  // 找到临界值
  // ...

  return {
    // 控制小时
    // ...

   disabledMinutes: (selectedHour: number) => {
      const limitHour = limitTime.hour()
      const limitMinute = limitTime.minute()
      if (selectedHour === limitHour) {
        return Array.from(
          { length: 60 },
          (_, i) => i
        ).filter((minute) =>
          isStartTime
            ? minute < limitMinute
            : minute > limitMinute
        )
      }

      return []
    },

    disabledSeconds: (
      selectedHour: number,
      selectedMinute: number
    ) => {
      const limitHour = limitTime.hour()
      const limitMinute = limitTime.minute()
      const limitSecond = limitTime.second()

      if (
        selectedHour === limitHour &&
        selectedMinute === limitMinute
      ) {
        return Array.from(
          { length: 60 },
          (_, i) => i
        ).filter((second) =>
          isStartTime
            ? second < limitSecond
            : second > limitSecond
        )
      }

      return []
    },
  }
}

如果有更好的实现方案,欢迎提出👏👏👏