Antd时间段选择器TimeRangePicker和TimeRangePickerList

基于Antd3.x封装,复制粘贴便可直接使用。

TimeRangePicker

在原组件的基础上新增API:

  • timeRange:时间段--json,id/startTime/endTime都必传
  • showTip:开始时间大于结束时间的时候是否给提示,可以为Boolean或者string或者{text,duration,end()}三种格式
  • disabledNow:是否禁用小于当前时间的其他时间--控件在选择小时之后仍然会默认选中分钟00
  • disabledHours:禁用的小时数,格式为0-23的number数组,优先级比disabledNow高
  • disabledMinutes:禁用的分钟数,格式为0-59的number数组,优先级比disabledNow高
  • disabledSeconds:禁用的秒数,格式为0-59的number数组,优先级比disabledNow高
  • permission:返回当前组件的区间值是否合法--false是开始时间大于结束时间,可用于禁止页面其他关联的操作
  • getRangeTime: 获取当前时间段,以及开始/结束时间差的permission
/**
 * 引入該有一定的順序規則
 * 库--組件/本地組件--本地utils--前置变量let/const--当前组件/页面
 * 如果有别名,别名的引入要在相对路径之前,如:'@/utils'要在'./utils'之前
 */

/** 库 */
import React, { memo, Fragment, useState } from "react";
import moment from "moment";

/** 组件 */
import { message, TimePicker } from "antd";

/** 前置变量--let/const */
// const nowTime()[0] = moment().hours();
// const nowTime()[1] = moment().minutes();
// const nowTime()[2] = moment().seconds();
const nowTime = () => {
  // 必须实时调用,否则时间不准确
  return [
    moment().hours(), // 当前小时数
    moment().minutes() + 1, // 当前分钟数(如果不+1,当前分钟也可以选,但+1后面的秒数禁用也就没意义了,按需取用)
    moment().seconds() // 当前秒数
  ];
};
const today = moment().format("YYYY-MM-DD");
const initTimeRange = {
  /** 时间段列表--默认值,有id是为了增删时所用 */
  id: 0,
  startTime: "00:00:00",
  endTime: "00:00:00"
};

export function Picker({
  showTip /** 时间不符合的时候是否给提示,可以为Boolean或者string或者{text,duration,end()}三种格式 */,
  format = "HH:mm" /** formart格式,默认HH:mm,可以设为HH:mm:ss等时分秒格式 */,
  disabledNow /** 是否禁用小于当前时间的其他时间--控件在选择小时之后仍然会默认选中分钟00 */,
  disabledHours /** 禁用的小时数,格式为0-23的number数组,优先级比disabledNow高 */,
  disabledMinutes /** 禁用的分钟数,格式为0-59的number数组,优先级比disabledNow高 */,
  disabledSeconds /** 禁用的秒数,格式为0-59的number数组,优先级比disabledNow高 */,
  timeRange = initTimeRange /** 时间段--json,id/startTime/endTime都必传 */,
  permission /** 返回当前组件的区间值是否合法--false是开始时间大于结束时间,可用于禁止页面其他关联的操作 */,
  getRangeTime /** 获取当前时间段,以及开始/结束时间差的permission */,
  ...props /** 继承官方组件(不止)其他属性 */
}) {
  const [thisTimeRange, setTimeRange] = useState(timeRange);

  /** 获取前后时间段对照 */
  const getTimeRangeState = (status, str) => {
    const timeObj = { ...thisTimeRange };
    if (!timeObj.startTime || !timeObj.endTime) {
      return false;
    }
    let isOk = true;

    let startHour = moment(`${today} ${timeObj.startTime}`).hours();
    let startMinute = moment(`${today} ${timeObj.startTime}`).minutes();
    let startSeconds = moment(`${today} ${timeObj.startTime}`).seconds();
    let endHour = moment(`${today} ${timeObj.endTime}`).hours();
    let endMinute = moment(`${today} ${timeObj.endTime}`).minutes();
    let endSeconds = moment(`${today} ${timeObj.endTime}`).seconds();

    if (status === "startTime") {
      /** 开始时间的 小时数 和 分钟数 */
      startHour = str ? moment(`${today} ${str}`).hours() : 0;
      startMinute = str ? moment(`${today} ${str}`).minutes() : 0;
      startSeconds = str ? moment(`${today} ${str}`).seconds() : 0;
    }

    if (status === "endTime") {
      /** 结束时间的 小时数 和 分钟数 */
      endHour = str ? moment(`${today} ${str}`).hours() : 0;
      endMinute = str ? moment(`${today} ${str}`).minutes() : 0;
      endSeconds = str ? moment(`${today} ${str}`).seconds() : 0;
    }

    const numHour = startHour - endHour;
    const numMin = startMinute - endMinute;
    const numSeconds = startSeconds - endSeconds;

    if (
      numHour > 0 ||
      (numHour === 0 && numMin > 0) ||
      (numHour === 0 && numMin === 0 && numSeconds >= 0)
    ) {
      // 开始hour·大于·结束hour
      // 开始hour·等于·结束hour 并且 开始minute·大于·结束minute
      isOk = false;
    }

    /** 避免页面上出现多个message先销毁 */
    if (!isOk) {
      permission && permission(false);
      if ((status === "startTime" && timeObj.endTime) || status === "endTime") {
        const duration = 1.5;
        let text = "结束时间必须大于开始时间";
        if (showTip && typeof showTip === "string") {
          text = showTip;
        }
        if (showTip && typeof showTip === "object") {
          showTip.end && showTip.end();
        }
        showTip &&
          message.error(
            showTip.text || text,
            (showTip && showTip.duration) || duration
          );
      }
    } else {
      message.destroy();
      permission && permission(true);
    }
    return isOk;
  };

  /** 时间改变 */
  const onTimeChange = (time, str, status) => {
    /** time值不取用,因为当前"antd": "^3.13.0"的此控件有bug */
    const timeArr = str.split(":") || [];
    const maxHour = timeArr.length > 0 ? timeArr[0] : 0;
    const maxMinute = timeArr.length > 1 ? timeArr[1] : 0;
    const obj = { ...thisTimeRange };
    obj[status] = str;

    setTimeRange(obj);
    if (str) {
      const can = getTimeRangeState(status, str);
      getRangeTime && getRangeTime(obj, can);
    } else {
      getRangeTime && getRangeTime(obj);
    }
  };

  /** 时间面板开启/关闭事件 */
  const onOpenChange = flag => {
    if (!flag /** 关闭 */) {
      getTimeRangeState();
    }
  };

  /** 禁用starthours */
  const getStartDisabledHours = () => {
    let noHours = []; // allHours
    for (let i = 0; i < +nowTime()[0]; i += 1) {
      noHours.push(i);
    }
    if (disabledNow) {
      noHours = disabledHours || noHours;
    } else {
      noHours = disabledHours || [];
    }
    return noHours;
  };

  /** 禁用startminutes  */
  const getStartDisabledMinutes = (selectedHour) /** 被选中的hour */ => {
    let minutes = [];
    if (selectedHour === +nowTime()[0]) {
      for (let i = 0; i < +nowTime()[1]; i += 1) {
        minutes.push(i);
      }
    }
    if (disabledNow) {
      minutes = disabledMinutes || minutes;
    } else {
      minutes = disabledMinutes || [];
    }

    return minutes;
  };

  const getStartDisabledSeconds = (selectedHour, selectedMinute) => {
    let seconds = [];
    if (selectedHour === +nowTime()[0] && selectedMinute === +nowTime()[1]) {
      for (let i = 0; i < +nowTime()[2]; i += 1) {
        seconds.push(i);
      }
    }
    if (disabledNow) {
      seconds = disabledSeconds || seconds;
    } else {
      seconds = disabledSeconds || [];
    }

    return seconds;
  };

  return (
    <Fragment>
      <TimePicker
        {...props}
        format={format}
        defaultValue={
          thisTimeRange.startTime
            ? moment(thisTimeRange.startTime, format)
            : null
        }
        disabledHours={() => getStartDisabledHours()}
        disabledMinutes={e => getStartDisabledMinutes(e)}
        onOpenChange={e => onOpenChange(e, "startTime")}
        onChange={(e, str) => onTimeChange(e, str, "startTime")}
      />

      <TimePicker
        {...props}
        format={format}
        defaultValue={
          thisTimeRange.endTime ? moment(thisTimeRange.endTime, format) : null
        }
        disabledHours={() => getStartDisabledHours()}
        disabledMinutes={e => getStartDisabledMinutes(e)}
        disabledSeconds={(h, m) => getStartDisabledSeconds(h, m)}
        onOpenChange={e => onOpenChange(e, "endTime")}
        onChange={(e, str) => onTimeChange(e, str, "endTime")}
        style={{ margin: "0 10px" }}
      />
    </Fragment>
  );
}

export const TimeRangePicker = memo(Picker);

TimeRangePickerList

在原组件以及TimeRangePicker的基础上新增API:

  • rangeTimeList: 时间段,id/startTime/endTime都必传
  • getRangeTime:子组件的getRangeTime
  • getTimeList:获取当前的时间列表,以及permission--false是开始时间大于结束时间
/**
 * 引入該有一定的順序規則
 * 库--組件/本地組件--本地utils--前置变量let/const--当前组件/页面
 * 如果有别名,别名的引入要在相对路径之前,如:'@/utils'要在'./utils'之前
 */

/** 库 */
import React, { memo, Fragment, useState, useEffect } from "react";
import moment from "moment";

/** 组件 */
import { Icon, Button, message, TimePicker } from "antd";
import { TimeRangePicker } from "./TimeRangePicker";

/** 时间段列表--默认值,有id是为了增删时所用 */
const initTimeRangeList = [
  {
    id: 0,
    startTime: "00:00:00",
    endTime: "00:00:00"
  }
];

export function PickerList({
  rangeTimeList = initTimeRangeList /** 时间段,id/startTime/endTime都必传 */,
  getRangeTime /** 子组件的getRangeTime */,
  getTimeList /** 获取当前的时间列表,以及permission--false是开始时间大于结束时间 */,
  ...props /** 继承官方组件(不止)其他属性 */
}) {
  const [permission, setPermission] = useState(false);
  const [thisTimeRangeList, setTimeRange] = useState(rangeTimeList);

  useEffect(() => {
    getTimeList && getTimeList(thisTimeRangeList, permission);
  }, [thisTimeRangeList]);

  /** 时间改变 */
  const getItemTime = (time, index, flag) => {
    const arr = [...thisTimeRangeList];
    arr[index].startTime = time.startTime;
    arr[index].endTime = time.endTime;
    setPermission(flag);
    setTimeRange(arr);
  };

  /** 删除 */
  const onMinus = time => {
    let arr = [...thisTimeRangeList];
    arr = arr.filter(item => item.id !== time.id);
    setTimeRange(arr);
  };

  /** 增加 */
  const onPlus = time => {
    /** 因为react是用key值去做diff的,所以如果用index作为key,会出现数据正确视图不正确等问题 */
    let arr = [...thisTimeRangeList];
    const lastId = Number(time.id) + 1;
    arr = arr.concat({
      id: lastId,
      startTime: "00:00:00",
      endTime: "00:00:00"
    });
    setTimeRange(arr);
  };

  const len = thisTimeRangeList.length;
  return thisTimeRangeList.map((time, index) => {
    return (
      <Fragment>
        {/**
         *  注意,
         *  这里如果仅仅用index作为key值会导致增删出现问题,
         *  这就为什么官方不建议使用index作为key值的原因,
         *  所以id必须有且呈递增趋势
         *
         *  细节:{...props} 放在最后可覆盖当前的属性
         */}
        <TimeRangePicker
          key={time.id}
          timeRange={time}
          getRangeTime={(range, per) => {
            getRangeTime && getRangeTime(range, per);
            getItemTime(range, index, per);
          }}
          {...props}
        />
        {len > 1 && index < len - 1 && (
          <Button
            ghost
            size="small"
            shape="circle"
            type="primary"
            icon="minus"
            {...props}
            onClick={() => onMinus(time)}
          />
        )}
        {index === len - 1 && (
          <Button
            ghost
            size="small"
            shape="circle"
            type="primary"
            icon="plus"
            {...props}
            onClick={() => onPlus(time)}
          />
        )}
        <br />
      </Fragment>
    );
  });
}

export const TimeRangePickerList = memo(PickerList);

使用Demo

/* eslint-disable no-irregular-whitespace */
import React, { Fragment } from "react";
import { TimeRangePicker, TimeRangePickerList } from "./index";

export function TimeRangeDemo() {
  return (
    <Fragment>
      <TimeRangePicker
        // ...props
        getRangeTime={(tRange, per) => console.log(tRange, per)}
      />

      <TimeRangePickerList
        // ...props
        size="large"
        format="HH:mm"
        disabledNow
        rangeTimeList={[
          {
            id: 0,
            startTime: "00:00:00",
            endTime: "00:00:00"
          },
          {
            id: 1,
            startTime: "00:10:00",
            endTime: "00:10:00"
          }
        ]}
        disabledMinutes={[1, 2, 3]}
        disabledSeconds={[11, 12, 13]}
        getTimeList={(tList, permission) => console.log(tList, permission)}
      />
    </Fragment>
  );
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354