计算任意时间内课时出现次数以及冲突情况判断

背景

整体由四部分组成,报名时间、报名周期、上课时间、上课周期 

通过选择报名时间、报名周期、以及上课时间,去计算在培训周期内总的培训课时,并当上课时间冲突时,给出提示。

需求:

  • 报名时间(日期+时分),不可选今日之前的日期
  • 培训周期(日期),不可选报名结束时间之前的日期,如果未选择报名时间,则不可选今日之前的日期
  • 上课时间:每周一到每周日,时间:24小时可选,可添加多条上课时间,一条上课时间为一次课
  • 报名开始时间必须大于当前时间
  • 培训周期开始时间必须大于报名时间
  • 上课时间不能冲突,任意两节课的开始时间或者结束时间相等也视为上课冲突。
  • 计算出培训周期范围内的总课时。

设计思想

当选择完培训周期后,计算出培训周期范围内,各个星期出现的次数,再遍历上课时间列表,通过列表的每周几作为key去进行匹配,当匹配成功时,将星期出现的次数加入总课时中。

时间冲突判断,通过将时间转换为数字,通过数字大小进行比较,通过双重循环判断是否存在数字相等或者数字重叠的情况。

界面搭建

自定义组件封装

上课时间是由一个选择框 和 一个 时间选择框共同组成,因此我们只需要对这两个组件进行封装形成一个新的selectTime组件即可。

SelectTime 

扫描二维码关注公众号,回复: 14981017 查看本文章
import { Button, Form, Input, Select, TimePicker } from 'antd';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
const { Option } = Select;

const SelectTime = ({ value = {}, onChange, }) => {
  const options = [{ value: 1, label: "每周一" }, { value: 2, label: "每周二" }, { value: 3, label: "每周三" }, { value: 4, label: "每周四" }, { value: 5, label: "每周五" }, { value: 6, label: "每周六" }, { value: 7, label: "每周日" }]
  const [week, setWeek] = useState();
  const [timeRange, setTimeRange] = useState([]);

  const triggerChange = (changedValue) => {
    onChange?.({
      week,
      timeRange,
      ...value,
      ...changedValue,
    });
  };
  const onNumberChange = (value) => {
    setWeek(value);
    triggerChange({
      week: value,
    });
  };
  const onCurrencyChange = (newTimeRange) => {
    setTimeRange(newTimeRange);
    triggerChange({
      timeRange: newTimeRange,
    });
  };
  return (
    <>
      <Select
        style={
   
   {
          width: 100,
          margin: '0 8px',
        }}
        options={options}
        allowClear={true}
        placeholder={"每周几"}
        onChange={onNumberChange}
        value={value.week || week}
      >

      </Select>
      <TimePicker.RangePicker format={"HH:mm"} onChange={onCurrencyChange} value={value.timeRange || timeRange} />
    </>
  )
}
export default SelectTime;

通过将SelectTime 组件放在Form.List内部即可实现组件的多个添加。当添加到第二条数据的时候,由于没有label,所以需要对样式进行调整,所以引入formItemLayoutWithOutLabel。

formItemLayoutWithOutLabel 

  const formItemLayoutWithOutLabel = {
    wrapperCol: {
      xs: {
        span: 24,
        offset: 0,
      },
      sm: {
        span: 20,
        offset: 6,
      },
    },
  };

 formItemLayout 

  const formItemLayout = {
    labelCol: {
      xs: {
        span: 24,
      },
      sm: {
        span: 6,
      },
    },
    wrapperCol: {
      xs: {
        span: 24,
      },
      sm: {
        span: 20,
      },
    },
  };

报名时间 

 <FormItem name={"signRange"} label={"报名时间"} rules={[{ required: true }]}>
              <RangePicker
                disabledDate={disabledDate}
                // disabledTime={disabledRangeTime}
                showTime={
   
   {
                  format: 'HH:mm',
                }}
                format="YYYY-MM-DD HH:mm"
                onChange={getEndDate}
              />
            </FormItem>

培训周期 

<FormItem name={"trainRange"} label={"培训周期"} rules={[{ required: true }]}>
              <RangePicker disabledDate={disabledDate2} onChange={(value, dateString) => { isIncludeWeeks(dateString[0], dateString[1]); }} />
            </FormItem>

 上课时间 

 <Form.List
              name="trainingTime"
              initialValue={['1']}
              rules={[
                {
                  validator: async (_, trainingTime) => {
                    if (!trainingTime || trainingTime.length < 1) {
                      return Promise.reject(new Error('至少一条数据'));
                    }
                  },
                },
              ]}
            >
              {(fields, { add, remove }, { errors }) => (
                <>
                  {fields.map((field, index) => (
                    <Form.Item
                      label={index === 0 ? '上课时间' : ''}
                      {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
                      rules={[{ required: true, message: "请输入上课时间" }]}
                      key={field.key}
                      name={[field.name, 'timeRange']}
                      validateTrigger={['onChange', 'onBlur']}

                    >
                      <Form.Item
                        {...field}
                        validateTrigger={['onChange', 'onBlur']}
                        rules={[
                          {
                            required: true,
                            message: "请输入上课时间",
                          },
                        ]}
                        noStyle
                      >
                        <SelectTime onChange={() => { onChangetime(); }}></SelectTime>
                      </Form.Item>
                      <PlusCircleOutlined style={
   
   { margin: "0 10px" }} onClick={() => add()} />
                      {fields.length > 1 ? (
                        <MinusCircleOutlined
                          className="dynamic-delete-button"
                          onClick={() => {
                            remove(field.name);
                            if (form.getFieldValue('trainRange') !== undefined) {
                              isIncludeWeeks(form.getFieldValue('trainRange')[0], form.getFieldValue('trainRange')[1]);
                            }
                          }}
                        />
                      ) : null}
                    </Form.Item>
                  ))}
                </>
              )}
            </Form.List>

培训总课时 

 <FormItem name={"totalTrainingHours"} label={"培训总课时"} rules={[{ required: true }]}>
              <Input readOnly style={
   
   { border: 'none', boxShadow: "none" }}></Input>
            </FormItem>

功能实现

报名时间与培训时间的限制

添加报名时间限制——disabledDate

  const disabledDate = (current) => {
    var time = current.clone();
    return time.startOf('day') < moment().startOf('day');
  };

添加培训周期时间限制——disabledDate2

注意特殊情况:当报名时间未选择时,限制范围为前一日

  const disabledDate2 = (current) => {
    if (form.getFieldValue('signRange') !== undefined && endDate !== null) {
      return current.format('YYYY-MM-DD') < endDate.format('YYYY-MM-DD');
    } else {
      return current < moment().subtract(1, "days");
    }
  };

当报名时间选择后,培训周期的可选时间范围受限,因为我们需要对报名时间添加onchange事件——getEndDate

  const getEndDate = () => {
    if (form.getFieldValue('signRange') !== null) {
      endDate = form.getFieldValue('signRange')[1].clone();
      endDate = endDate.add(1, "days")
    }
  }

培训总课时与上课时间以及培训时间有关,所以当我们选择对应的培训周期后,就应该计算出这段时间内周一到周日出现的次数。添加onchange事件——isIncludeWeeks

将培训周期内的星期放入weeksArr中(1代表星期一,....,7代表星期日)

  const isIncludeWeeks = (startDate, endDate) => {
    let beginDateObj = new Date(startDate);
    let endDateObj = new Date(endDate);
    let weeksArr = [];
    while (beginDateObj <= endDateObj) {
      //直到结束日期,跳出循环
      let todayWeekNum = beginDateObj.getDay();
      if (todayWeekNum == 0) {
        //如果为0,则对应“周日”
        todayWeekNum = 7;
      }
      weeksArr.push(todayWeekNum);
      beginDateObj.setTime(beginDateObj.getTime() + 24 * 60 * 60 * 1000); //每次递增1天
    }
    console.log(getEleNums(weeksArr));
    weekNum = getEleNums(weeksArr);
    // setWeekNum(getEleNums(weeksArr));
    canClassTotalTime();
  }

计算周一到周日出现的次数

  const getEleNums = (data) => {
    var map = {}
    for (var i = 0; i < data.length; i++) {
      var key = data[i]
      if (map[key]) {
        map[key] += 1
      } else {
        map[key] = 1
      }
    }
    return map;
  }

当选择上课时间后,计算该课时在周期内出现次数,即星期出现次数。因此,对于课时的计算只需考虑对应的每周几即可,时间用于判断上课时间是否冲突。对于上课时间添加onchange事件——onChangetime

对于几种特殊情况通过if判断排除出去,避免报错。

  • 未选每周几,只选了上课时间
  • 未选上课时间,只选了每周几
  • 未选培训周期
  // 当选择上课时间时 计算总课时以及判断冲突
  const onChangetime = () => {
    // canClassTotalTime();
    if (form.getFieldValue('trainRange') !== undefined) {
      isIncludeWeeks(form.getFieldValue('trainRange')[0], form.getFieldValue('trainRange')[1]);
    }
    let rangetime = [];
    console.log(form.getFieldValue('trainingTime'));
    if (form.getFieldValue('trainingTime') !== undefined) {
      rangetime = form.getFieldValue('trainingTime').map((item) => {
        if (item.week !== undefined && item.timeRange[0] !== undefined) {
          return {
            week: item.week,
            timeRange: [item.timeRange[0].format('HH:mm'), item.timeRange[1].format('HH:mm')]
          }
        }
      }).filter(item => item !== undefined)
      judgmentTimeConflict(rangetime);
    }
  }

canClassTotalTime 

  const canClassTotalTime = () => {
    let total = 0;
    form.getFieldValue('trainingTime')
    console.log(form.getFieldValue('trainingTime'), weekNum);
    if (form.getFieldValue('trainRange') !== undefined) {
      form.getFieldValue('trainingTime').map((item) => {
        if (item.week !== undefined && weekNum[item.week] !== undefined)
          total += weekNum[item.week];
      })
    }
    console.log('total', total);
    form.setFieldsValue({ totalTrainingHours: total })
  }

报名时间冲突判断——judgmentTimeConflict

通过将时间转成字符串再转成数字类型,通过数字类型进行大小比较完成范围冲突的判断。

几种冲突情况分类讨论 (范围A[0]-A[1],范围B[0]-B[1])

  • A[0]<A[1]=B[0]<B[1]
  • A[0]<B[0]<A[1]<B[1]
  • A[0]<B[0]<A[1]=B[1]
  • A[0]<B[0]<B[1]<A[1]
  • A[0]=B[0]<B[1]<A[1]
  • B[0]<A[0]<B[1]<A[1]
  • A[0]<B[0]<A[1]=B[0]
  • B[0]<A[0]<A[1]<B[1]

judgmentTimeConflict 

  // 判断时间是否冲突
  const judgmentTimeConflict = (timeRangeList) => {
    let newrange = timeRangeList.map((item) => {
      return {
        week: item.week,
        timeRange: [parseInt(item.timeRange[0].replace(':', '')), parseInt(item.timeRange[1].replace(':', ''))]
      }
    })
    for (let i = 0; i < newrange.length; i++) {
      for (let j = i; j < newrange.length; j++) {
        if (i !== j && newrange[i].week === newrange[j].week) {
          if ((newrange[i].timeRange[1] === newrange[j].timeRange[0]) || (newrange[j].timeRange[1] === newrange[i].timeRange[0])) {
            message.error("上课时间冲突,请重新选择!")
            return false;
          } else if ((newrange[i].timeRange[0] < newrange[j].timeRange[0]) && (newrange[j].timeRange[0] < newrange[i].timeRange[1])) {
            message.error("上课时间冲突,请重新选择!")
            return false;
          } else if ((newrange[i].timeRange[0] < newrange[j].timeRange[1]) && (newrange[j].timeRange[1] < newrange[i].timeRange[1])) {
            message.error("上课时间冲突,请重新选择!")
            return false;
          } else if ((newrange[i].timeRange[0] === newrange[j].timeRange[0]) || (newrange[i].timeRange[1] === newrange[j].timeRange[1])) {
            message.error("上课时间冲突,请重新选择!")
            return false;
          }else if((newrange[i].timeRange[0] > newrange[j].timeRange[0]) && (newrange[i].timeRange[1] < newrange[j].timeRange[1])){
            message.error("上课时间冲突,请重新选择!")
            return false;
          }
        }
      }
    }
  }

效果展示

猜你喜欢

转载自blog.csdn.net/weixin_45369856/article/details/130401415
今日推荐