Remember once the mobile terminal encapsulates the custom time selection control

Foreword:

Recently, I was working on a mobile terminal project and encountered a need for a custom selection time control; the specific interaction logic to be implemented is as follows:

1. When placing an appointment, it must be 30 minutes longer than the current system time. The selected time is in 24-hour format, and the minutes are displayed every 15 minutes (for example: 15, 30, 45, 00); 2. If the current time is
14 At 30:00, the appointment time at this time can only be used after 15:00 (including 15:00);

3. If the current system time is 14:31, the reservation time can only be used after 15:00 (excluding 15:00);

4. If you select an appointment time but do not submit it, the appointment time does not need to be refreshed at this time. If you choose an appointment time again, you need to filter the available time according to the rules, and do not choose to adjust the appointment time;

5. The reservation days can only be reserved for the next 6 days;

The effect diagram is shown as follows:

 

The specific implementation code is as follows:

/*
 * @Description: 预约时间控件
 * @Author: quan.wang
 * @Date: 2022-11-25 10:53:03
 * @LastEditors: quan.wang
 * @LastEditTime: 2022-12-02 19:17:22
 */
/* eslint-disable no-param-reassign */
import React, { useEffect, useState } from 'react'
import { Picker } from 'antd-mobile'
import _ from 'lodash'

import './index.less'

export const everyMonthForDaysMap = {
  1: 31,
  2: 28,
  3: 31,
  4: 30,
  5: 31,
  6: 30,
  7: 31,
  8: 31,
  9: 30,
  10: 31,
  11: 30,
  12: 31,
}

const hoursEnum = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]

const minutesEnum = [
  { label: '00分', value: '00' },
  { label: '15分', value: '15' },
  { label: '30分', value: '30' },
  { label: '45分', value: '45' },
]

// 计算小时
const calcHours = currentHour => {
  return hoursEnum
    .map(hour => {
      if (hour >= currentHour) {
        return {
          label: `${hour}点`,
          value: hour,
        }
      }
      return ''
    })
    .filter(Boolean)
}

// 计算月、日
const calcMonthDay = (month, day) => {
  const maxDay = everyMonthForDaysMap[month]
  if (day > maxDay) {
    return `${month + 1}月${day - maxDay}日`
  }
  return `${month}月${day}日`
}

// 计算日期(年、月、日)
const calcDate = (year, month, day) => {
  if (year % 4 === 0) {
    // 闰年,2月29天
    everyMonthForDaysMap[2] = 29
  }
  const maxDay = everyMonthForDaysMap[month]
  if (day > maxDay) {
    return `${year}-${month + 1}-${day - maxDay}`
  }
  return `${year}-${month}-${day}`
}

const AppointTime = () => {
  const newDate = new Date()
  const currentDay = newDate.getDate()
  const currentHour = newDate.getHours()
  const currentMinute = newDate.getMinutes()
  const currentYear = newDate.getFullYear()
  const currentMonth = newDate.getMonth() + 1

  const [visible, setVisible] = useState(false)
  const [selectInfo, setSelectInfo] = useState('') // 由于Picker组件onSelect API钩子函数只能返回value, 所以需要将显示label和传给后端的时间格式拼接成一个字符串(为什么不写成对象格式,是因为对象格式的话,react无法作唯一性判断)
  const [initialStartHour, setInitialStartHour] = useState(0) // 初始化小时数

  const [data, setData] = useState([])

  // 计算初始化开始小时
  const calcInitialStartHour = dates => {
    // 当minute超过15分钟时,16 + 30 ~ 60,所以初始化小时需要加1,并且还要考虑是否是23小时,如果是:当日不能预约,只能预约明天的
    if (currentHour < 23) {
      setInitialStartHour(currentHour + 1)
    } else {
      dates.shift()
      setInitialStartHour(currentHour)
    }
    return dates
  }

  // 初始化获取范围日期
  const getScopeDate = () => {
    let dates = []

    // 只能预约选择一个星期范围内时间
    for (let i = 0; i < 7; i += 1) {
      switch (i) {
        case 0:
          dates.push({
            label: '今天',
            value: `${currentYear}-${currentMonth}-${currentDay},今天`,
          })

          break
        case 1:
          dates.push({
            label: '明天',
            value: `${calcDate(currentYear, currentMonth, currentDay + i)},明天`,
          })
          break
        case 2:
          dates.push({
            label: '后天',
            value: `${calcDate(currentYear, currentMonth, currentDay + i)},后天`,
          })
          break
        default:
          dates.push({
            label: calcMonthDay(currentMonth, currentDay + i),
            value: `${calcDate(currentYear, currentMonth, currentDay + i)},${calcMonthDay(
              currentMonth,
              currentDay + i
            )}`,
          })
          break
      }
    }

    // 根据当前分钟数,按照30分钟一个间隔,来推算初始化可以选择的分钟集合
    let minutes = []
    if (currentMinute === 0) {
      minutes = [
        { label: '30分', value: '30' },
        { label: '45分', value: '45' },
      ]
      setInitialStartHour(currentHour)
    } else if (currentMinute > 0 && currentMinute <= 15) {
      minutes = [{ label: '45分', value: '45' }]
      setInitialStartHour(currentHour)
    } else if (currentMinute > 15 && currentMinute <= 30) {
      minutes = minutesEnum
      dates = calcInitialStartHour(dates)
    } else if (currentMinute > 30 && currentMinute <= 45) {
      minutes = [
        { label: '15分', value: '15' },
        { label: '30分', value: '30' },
        { label: '45分', value: '45' },
      ]
      dates = calcInitialStartHour(dates)
    } else if (currentMinute > 45) {
      minutes = [
        { label: '30分', value: '30' },
        { label: '45分', value: '45' },
      ]
      dates = calcInitialStartHour(dates)
    }
    setData([dates, calcHours(currentMinute <= 15 ? currentHour : currentHour + 1), minutes])
  }

  useEffect(() => {
    getScopeDate()
  }, [])

  const isToday = selectDate => {
    const [selectYear, selectMonth, selectDay] = selectDate.split('-')

    return (
      parseInt(selectYear, 10) === currentYear &&
      parseInt(selectMonth, 10) === currentMonth &&
      parseInt(selectDay, 10) === currentDay
    )
  }

  const isinitialStartHour = selectHour => {
    return initialStartHour === parseInt(selectHour, 10)
  }

  // 选择的日期是:当天并且是初始化小时的一些日期格式判断
  const renderFinalDateByCurrentMinuteInInitialHour = () => {
    let minutes = []
    let hour = currentHour
    if (currentMinute === 0) {
      minutes = [
        { label: '30分', value: '30' },
        { label: '45分', value: '45' },
      ]
      setData(prev => {
        prev[1] = calcHours(currentHour)
        prev[2] = minutes
        return _.cloneDeep(prev)
      })
    } else if (currentMinute > 0 && currentMinute <= 15) {
      minutes = [{ label: '45分', value: '45' }]
      setData(prev => {
        prev[1] = calcHours(currentHour)
        prev[2] = minutes
        return _.cloneDeep(prev)
      })
    } else if (currentMinute > 15 && currentMinute <= 30) {
      hour = currentHour + 1
      minutes = minutesEnum
      setData(prev => {
        prev[1] = calcHours(hour)
        prev[2] = minutes
        return _.cloneDeep(prev)
      })
    } else if (currentMinute > 30 && currentMinute <= 45) {
      hour = currentHour + 1
      minutes = [
        { label: '15分', value: '15' },
        { label: '30分', value: '30' },
        { label: '45分', value: '45' },
      ]
      setData(prev => {
        prev[1] = calcHours(initialStartHour)
        prev[2] = minutes
        return _.cloneDeep(prev)
      })
    } else if (currentMinute > 45) {
      hour = currentHour + 1
      minutes = [
        { label: '30分', value: '30' },
        { label: '45分', value: '45' },
      ]
      setData(prev => {
        prev[1] = calcHours(initialStartHour)
        prev[2] = minutes
        return _.cloneDeep(prev)
      })
    }
  }

  // 选择的日期是:当天并且是后面小时的日期格式化判断
  const renderFinalDateByCurrentMinuteInNextHour = () => {
    setData(prev => {
      prev[1] = calcHours(initialStartHour)
      // 分钟集合可以取全量的数据
      prev[2] = minutesEnum
      return _.cloneDeep(prev)
    })
  }

  // 选择的日期是:未来天的日期格式化判断
  const renderFinalDateInNextDay = () => {
    setData(prev => {
      prev[1] = calcHours(0)
      prev[2] = minutesEnum
      return _.cloneDeep(prev)
    })
  }

  return (
    <>
      <p onClick={() => setVisible(true)}>{selectInfo ? selectInfo.label : '请选择用车时间'}</p>
      <Picker
        title="请选择时间"
        onSelect={val => {
          const [selectDate, selectHour] = val

          // 转化成时间格式判断
          const newSelectDate = selectDate.split(',')[0]

          if (isToday(newSelectDate)) {
            // 选择的小时是否是初始化设置的小时
            if (isinitialStartHour(selectHour)) {
              renderFinalDateByCurrentMinuteInInitialHour()
            } else {
              renderFinalDateByCurrentMinuteInNextHour()
            }
          } else {
            renderFinalDateInNextDay()
          }
        }}
        columns={data}
        visible={visible}
        onClose={() => {
          setVisible(false)
        }}
        onConfirm={val => {
          const [date, hour, minute] = val
          const [formatDate, label] = date.split(',')
          setSelectInfo({
            label: `${label}${hour}点${minute}分`,
            value: `${formatDate} ${hour}:${minute}`,
          })
        }}
      />
    </>
  )
}

export default AppointTime

Guess you like

Origin blog.csdn.net/u014165391/article/details/128153554