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