2.基于antd_mobile,实现智能会议室选择预约时间段

283 阅读6分钟

一、需求:在预约列表中实现点击可选择预约的时间段,类似钉钉的交互。

二、功能介绍:以半小时为预约的时间段,交互上存在两种区别,1.当只考虑选中的情况,当选择两个时间中间还有空白时间,也应该被选中(比如第一个选择11:00~11:30,第二个选择为15:00-15:30,那么就需要把11:00-15:30所包含的全部选中)2.只考虑取消时,取消交互分为两种情况,前提是需要将目前选中的时间段一分为二,当取消的在上半部分时,需要将选择取消的时间之前全部取消(比如目前选择的是11:00-15:30,取消选择的是12:00-12:30,那么12:30之前选中的都应该取消),下半部分同理。

三、分析:需要初始化48个div,label对应时间段,需要将列表的48个div合并,每个时间段此时会有预约状态,当已预约时,选中状态,禁止修改并展示已预约。当已过期时,为未选中状态,禁止修改并展示已过期,未预约可以进行操作。已预约优先级最该展示。交互上方式通过ref绑定,根据index区分,操作dom实现选中状态

四、实际实现:对数据进行梳理,通过人为交互,考虑多种情况。此时不仅仅作为开发的角度,应作为使用着的角度,因为此时不关系后端,就纯前端处理数据和交互。

五、步骤分析:

4.1初始化48个ref绑定,初始化label并合并列表状态

4.2当点击时保存当前点击的index,需要根据比较点击的index,让区间的index选中或取消

4.3当为选中时,需要对保存的index数组排序,第一个就为开始时间的index,最后一个就为结束时间的index,遍历48个ref ,根据比较index让区域内的都选中

4.4当为取消选中时,需要判断当取消的是最后一个时,需要将保存index重置为空数组,当不为最后一个时,再根据保存的index数组长度,判断是奇数还是偶数,判断当前取消的在数组的上半部分还是下半部分,根据所点击的index,过滤掉取消的index,再通过遍历ref,让区域内的都取消选中

六、总结:对于这种没有数据支撑,纯前端来实现,局限性比较大,每次交互都需要前端来保存状态,实际的难度还是跟交互关系很大,虽然这个交互看起来很号实现,但实际操作起来还是很麻烦,遇到这种提供出思维导图比较方便,根据大模块依次向下分析

七:代码实现

    import { useEffect, useState, useRef } from 'react';
    import styles from './index.less';
    import { history, useDispatch, useSelector } from 'umi';
    import { List, ActionSheet, Checkbox } from 'antd-mobile';

    const TimeSheet = (props) => {
      const dispatch = useDispatch();
      var checkRef = [];
      [...Array(47)].forEach((m, n) => {
        checkRef[n] = useRef();
      });
      const [saveIndex, setSaveIndex] = useState([]); // 保存所点击的index
      const { visibleSheet, eventChange, type } = props;
      const sheetData = useSelector((state) => state.joinApprove.detailData); // 点击会议预约列表数据
      const templateTime = useSelector((state) => state.meetingTemplate.templateTime); // 会议模板会议室预约时间
      const [checkTime, setCheckTime] = useState([]);
      const initData = [
        { label: '00:00 ~ 00:30' },
        { label: '00:30 ~ 01:00' },
        { label: '01:00 ~ 01:30' },
        { label: '01:30 ~ 02:00' },
        { label: '02:00 ~ 02:30' },
        { label: '02:30 ~ 03:00' },
        { label: '03:00 ~ 03:30' },
        { label: '03:30 ~ 04:00' },
        { label: '04:00 ~ 04:30' },
        { label: '04:30 ~ 05:00' },
        { label: '05:00 ~ 05:30' },
        { label: '05:30 ~ 06:00' },
        { label: '06:00 ~ 06:30' },
        { label: '06:30 ~ 07:00' },
        { label: '07:00 ~ 07:30' },
        { label: '07:30 ~ 08:00' },
        { label: '08:00 ~ 08:30' },
        { label: '08:30 ~ 09:00' },
        { label: '09:00 ~ 09:30' },
        { label: '09:30 ~ 10:00' },
        { label: '10:00 ~ 10:30' },
        { label: '10:30 ~ 11:00' },
        { label: '11:00 ~ 11:30' },
        { label: '11:30 ~ 12:00' },
        { label: '12:00 ~ 12:30' },
        { label: '12:30 ~ 13:00' },
        { label: '13:00 ~ 13:30' },
        { label: '13:30 ~ 14:00' },
        { label: '14:00 ~ 14:30' },
        { label: '14:30 ~ 15:00' },
        { label: '15:00 ~ 15:30' },
        { label: '15:30 ~ 16:00' },
        { label: '16:00 ~ 16:30' },
        { label: '16:30 ~ 17:00' },
        { label: '17:00 ~ 17:30' },
        { label: '17:30 ~ 18:00' },
        { label: '18:00 ~ 18:30' },
        { label: '18:30 ~ 19:00' },
        { label: '19:00 ~ 19:30' },
        { label: '19:30 ~ 20:00' },
        { label: '20:00 ~ 20:30' },
        { label: '20:30 ~ 21:00' },
        { label: '21:00 ~ 21:30' },
        { label: '21:30 ~ 22:00' },
        { label: '22:00 ~ 22:30' },
        { label: '22:30 ~ 23:00' },
        { label: '23:00 ~ 23:30' },
        { label: '23:30 ~ 00:00' },
      ];
      if (type === 'appoint') {
        if (sheetData && JSON.stringify(sheetData) != '{}') {
          initData.forEach((item, index) => {
            item['type'] = sheetData.appointmentList[index].type;
          });
        }
      } else {
        if (templateTime.length != 0) {
          initData.forEach((item, index) => {
            item['type'] = templateTime[index].type;
          });
        }
      }
      useEffect(() => {
        checkRef.forEach((m, n) => {
          checkRef[n].current?.uncheck();
        });
      }, [props]);

      const listHeader = () => {
        if (sheetData && JSON.stringify(sheetData) != '{}') {
          return (
            <div className={styles.topContent}>
              <div className={styles.topAddress}>
                <span>
                  {sheetData.officeLocation}
                  {sheetData.name}
                </span>
              </div>
              <div className={styles.topMedia}>
                {sheetData.equipment.split(',').map((item, index) => {
                  return (
                    <span key={index} style={{ marginRight: '6px' }}>
                      {item}
                    </span>
                  );
                })}
              </div>
              <div className={styles.topRage}>
                <div>
                  <img
                    style={{ width: '16px', height: '16px', marginBottom: '4px' }}
                    src={require('../../assets/appoint/person.png')}
                  ></img>
                  <span style={{ marginLeft: '3px', marginRight: '8px' }}>{sheetData.capacity}</span>
                </div>
                <div>
                  <img
                    style={{ width: '16px', height: '16px', marginBottom: '4px' }}
                    src={require('../../assets/appoint/place.png')}
                  ></img>
                  {sheetData.officeLocation}
                </div>
              </div>
            </div>
          );
        }
      };


      // 遍历生成数组
      const rangeOfNumbers = (startNum, endNum) => {
        return startNum <= endNum ? [startNum].concat(rangeOfNumbers(startNum + 1, endNum)) : [];
      };

      const checkChange = (val, item, index) => {
        // console.log(val, 'sss', item, index);
        // console.log(checkRef[index].current.check(),'checkRef')
        let transData = JSON.parse(JSON.stringify(checkTime)); // 选择的时间数组
        let transIndex = JSON.parse(JSON.stringify(saveIndex)); // 选择的index数组

        // 判断是选中还是取消选中
        if (val) {
          transIndex.push(index);
          // console.log(transIndex, '打印打印');
          // 当保存的数组有两项或者以上,是区间模式
          if (transIndex.length > 0) {
            transIndex.sort(); // 对数组元素排序
            let minIndex = transIndex[0]; // index最小值
            let maxIndex = transIndex[transIndex.length - 1]; // index最大值
            // console.log(transIndex, 'transIndextransIndex');
            // console.log(transIndex[0], 'kkk', transIndex[transIndex.length - 1]);

            // 通过遍历对 对应的ref选择
            checkRef.forEach((m, n) => {
              if (minIndex <= n && n <= maxIndex) {
                checkRef[n].current.check();
              }
            });
          }
          // 保存当前的index区间
          setSaveIndex(transIndex);
          // 保存当前的label
          transData.push(item.label);
          setCheckTime(transData);
        } else {
          // console.log(transIndex, 'transIndex');
          if (transIndex.length > 0) {
            // 判断当元素为一个,取消时则为空数组
            if (transIndex.length === 1 && checkTime.length === 1) {
              setSaveIndex([]);
            } else {
              // 通过遍历保存的index区域生成迭代数组
              let middleIndex = rangeOfNumbers(transIndex[0], transIndex[transIndex.length - 1]);
              // console.log(middleIndex, 'middleIndex');
              // 判断是奇数还是偶数
              let evenNumber = middleIndex.length % 2;
              let minIndex; // index最小值
              let maxIndex; // index最大值
              let lastIndex; // 取消后的index数组
              // 当为偶数时
              if (evenNumber === 0) {
                let middleLeft = middleIndex[middleIndex.length / 2 - 1]; // 偶数数组左边中间值
                let middleRight = middleIndex[middleIndex.length / 2]; // 偶数数组右边中间值
                if (index <= middleLeft) {
                  // 更新当前的index数组
                  setSaveIndex(middleIndex.filter((v) => v > index));
                  // 过滤掉的数组,进去下一次循环
                  lastIndex = middleIndex.filter((v) => v < index);
                  minIndex = lastIndex[0];
                  maxIndex = lastIndex[lastIndex.length - 1];
                  // console.log(lastIndex, 'lastIndex1');
                }
                if (index >= middleRight) {
                  setSaveIndex(middleIndex.filter((v) => v < index));
                  lastIndex = middleIndex.filter((v) => v > index);
                  minIndex = lastIndex[0];
                  maxIndex = lastIndex[lastIndex.length - 1];
                  // console.log(lastIndex, 'lastIndex2');
                }
              } else {
                let middle = Math.trunc(middleIndex.length / 2);
                if (index <= middleIndex[middle]) {
                  setSaveIndex(middleIndex.filter((v) => v > index));
                  lastIndex = middleIndex.filter((v) => v < index);
                  minIndex = lastIndex[0];
                  maxIndex = lastIndex[lastIndex.length - 1];
                  // console.log(lastIndex, 'lastIndex3');
                }
                if (index >= middleIndex[middle]) {
                  setSaveIndex(middleIndex.filter((v) => v < index));
                  lastIndex = middleIndex.filter((v) => v > index);
                  minIndex = lastIndex[0];
                  maxIndex = lastIndex[lastIndex.length - 1];
                  // console.log(lastIndex, 'lastIndex4');
                }
              }
              // 通过遍历ref,将对应的ref设置为未选择
              checkRef.forEach((m, n) => {
                if (minIndex <= n && n <= maxIndex) {
                  checkRef[n].current.uncheck();
                }
              });
            }
          }
          // 更新当前选择的时间
          setCheckTime(transData.filter((v) => v != item.label));
        }
      };
      const listData = () => {
        return (
          <div>
            <List
              header={listHeader()}
              style={{ '--align-items': 'start', '--active-background-color': '#fff' }}
            >
              {
                initData.map((item, index) => {
                  return (
                    <List.Item key={index}>
                      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                        <Checkbox
                          ref={checkRef[index]}
                          disabled={item.type == 0 ? false : true}
                          defaultChecked={item.type == 1 ? true : false}
                          onChange={(val) => checkChange(val, item, index)}
                        >
                          {item.label}
                        </Checkbox>
                        <div>{item.type === 2 ? '已过期' : item.type === 1 ? '已预约' : ''}</div>
                      </div>
                    </List.Item>
                  );
                })}
            </List>
          </div>
        );
      };
      const actions = [
        {
          text: listData(),
          key: 'copy',
        },
        {
          text: (type === 'meetingChange' || type === 'againApprove') ? '完成' : '立即预定' ,
          key: 'save',
          disabled: saveIndex.length == 0 ? true : false,
          onClick: () => {
            eventChange(false);

            // 将选择的时间存到全局状态
            if (saveIndex.length > 0) {
              let selectTime;
              if (saveIndex.length === 1) {
                selectTime = [initData[saveIndex[0]]];
              } else {
                selectTime = [initData[saveIndex[0]], initData[saveIndex[saveIndex.length - 1]]];
              }
              dispatch({
                type: 'joinApprove/saveSelectTime',
                payload: selectTime,
              });
            }
          },
        },
      ];

      return (
        <div className={styles.timeSheet} id="timeSelect">
          <ActionSheet
            visible={visibleSheet}
            actions={actions}
            getContainer={() => document.getElementById('timeSelect')}
            onClose={() => eventChange(false)}
          />
        </div>
      );
    };

    export default TimeSheet;