记一次移动端封装自定义时间选择控件

前言:

近期在做一个移动端项目,遇到一个自定义选择时间控件的需求;具体要实现的交互逻辑如下:

1. 预约下单的时候必须大于当前系统时间30分后,选择时间按照24小时制,分钟按照每隔15分钟进行显示(例如:15、30、45、00);
2. 如当前时间为14点30分,此时预约时间只能大于15点之后的时间用车(包含15点整);

3. 如当前系统时间为14点31分,此时预约时间只能大于15点整之后的时候用车(不包含15点整);

4.如选择了预约时间,但未提交,此时预约时间无需刷新,如再去选择预约时间则需要按照规则进行过滤可选择的时间,未去选择调整预约时间;

5.预约天数只能预约未来6天的时间;

效果图展示如下:

具体的实现代码如下:

/*
 * @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

猜你喜欢

转载自blog.csdn.net/u014165391/article/details/128153554
今日推荐