模仿antd手写一个多选日期日历组件

业务需求

  1. 多选近三个月的日期。
  2. 不能选择当日之前的日期。

因为antd的日期组件都是选择单个日期或者日期范围。不符合需求,所以自己就实现了一个。写的不好的地方大家请指教

效果展示

在这里插入图片描述

测试组件

<CheckCalendar
      visible={this.state.showCalendar}
       onClose={()=>{
            this.setState({
                 showCalendar:false
            })
       }}
       onConfirm={(isCheck)=>{
            console.log(isCheck)
            this.setState({
                 showCalendar:false
            })
       }}
       />

CheckCalendar.jsx

import React, { Component } from "react";
import { cloneDeep, chunk } from "lodash";
import "animate.css";
import "./checkCalendar.scss"
const weeks = ["日", "一", "二", "三", "四", "五", "六"];
class CheckCalendar extends Component {
  constructor(props) {
    super(props)
    this.state = {
      dateTable: [],
      isCheck: [],
    }
    this.calendar = React.createRef();
    this.mask = React.createRef();
  }
  componentWillMount() {
    this.initDateTable()
  }
  initDateTable() {
    let temp = []
    for (let i = 0; i < 3; i++) {  // 取近三个月内的日期
      let obj = this.getDateTable(i);
      temp.push(obj);
    }
    console.log(temp);
    this.setState({
      dateTable: temp
    })
  }
  getDateTable(plus) {
    let curDate = new Date()  //现在时间
    let curYear = curDate.getFullYear();
    let curMonth = curDate.getMonth() + 1;
    let curDay = curDate.getDate();
    if (curMonth + plus > 12) {
      curYear++
      curMonth = curMonth + plus - 12
    } else {
      curMonth = curMonth + plus
    }
    let date = new Date(curYear, curMonth, 0);
    let year = date.getFullYear(); // 当前年
    let month = date.getMonth() + 1; // 当前月
    // console.log(`${year}年${month}月.`);

    let date2 = new Date(year, month, 0);
    let days = date2.getDate(); // 当月有多少天
    // console.log(`当月有${days}天.`);

    date2.setDate(1);
    let day = date2.getDay(); // 当月第一天是星期几
    // console.log(`当月第一天是星期${day}.`);

    let list = [];

    for (let i = 0; i < days + day; i++) {
      if (i < day) {  // 头部补零
        list.push({
          isActive: false,
          number: 0
        });
      } else {
        if (plus === 0) {
          if ((i - day + 1) < curDay) {
            list.push({
              disable: true,
              isActive: false,
              number: i - day + 1
            });
          } else {
            list.push({
              isActive: false,
              number: i - day + 1
            });
          }
        } else {
          list.push({
            isActive: false,
            number: i - day + 1
          });
        }
      }
    }
    let hlist = chunk(list, 7); // 转换为二维数组
    let len = hlist.length;
    let to = 7 - hlist[len - 1].length;

    // 循环尾部补0
    for (let i = 0; i < to; i++) {
      hlist[len - 1].push({
        isActive: false,
        number: 0
      });
    }
    if (month < 10) {
      month = "0" + month
    }
    const str = `${year}-${month}`
    return {
      "list": hlist,
      "desc": str
    }
  }
  handleItemClick(desc, number, index, index1, index2) {
    let temp = cloneDeep(this.state.dateTable)
    const flag = !temp[index].list[index1][index2].isActive
    temp[index].list[index1][index2].isActive = flag
    this.setState({
      dateTable: temp,
    })
    const arr = desc.split("-");
    if (number < 10) {
      number = "0" + number
    }
    if (flag) {
      let temp = cloneDeep(this.state.isCheck);
      temp.push(arr[0] + "-" + arr[1] + "-" + number)
      this.setState({
        isCheck: temp
      })
    } else {
      let temp = cloneDeep(this.state.isCheck);
      let filted = temp.filter((item) => {
        return item !== arr[0] + "-" + arr[1] + "-" + number
      })
      this.setState({
        isCheck: filted
      })
    }
  }
  render() {
    return this.props.visible ? (
      <div ref={this.mask} className="calendar-mask">
        <div ref={this.calendar} className="calendar-wrap animated fadeInUp">
          <div className="header">日期多选<div className="exit" onClick={() => {
            this.calendar.current.classList.remove("fadeInUp")
            this.calendar.current.classList.add("fadeOutDown")
            this.mask.current.classList.add("animated", "fadeOut")
            setTimeout(() => {
              this.props.onCancel()
            }, 150)
          }}></div></div>
          <div className="week-wrap">
            {
              weeks.map((item, index) => (
                <div className="week-item" key={index}>{item}</div>
              ))
            }
          </div>
          {this.state.dateTable.map((item, index) => {
            const arr = item.desc.split("-");
            return (
              <div className="date-table" key={index}>
                <div className="desc">{arr[0] + "年" + arr[1] + "月"}</div>
                {item.list.map((item2, index2) => {
                  return (<div className="row" key={index2}>
                    {item2.map((item3, index3) => {
                      return (<DateItem itemClick={this.handleItemClick.bind(this, item.desc, item3.number, index, index2, index3)} active={item3.isActive} disable={item3.disable ? item3.disable : false} number={item3.number} key={index3}></DateItem>)
                    })}
                  </div>)
                })}
              </div>
            );
          })}
          <div className="fake-area"></div>
        </div>
        <div className="confirm-wrap">
          <div className="confirm" onClick={() => {
            this.calendar.current.classList.remove("fadeInUp")
            this.calendar.current.classList.add("fadeOutDown")
            this.mask.current.classList.add("animated", "fadeOut")
            setTimeout(() => {
              this.props.onConfirm(this.state.isCheck)
            }, 150)
          }}>
            确定
          </div>
        </div>
      </div>
    ) : (<span></span>)
  }
}
function DateItem(props) {
  return props.number === 0 ? (<div className="date-wrap">
    <span className="left"></span><div className="item"></div><span className="right"></span>
  </div>) : props.disable ? (<div className="date-wrap">
    <span className="left"></span><div className="item disable">{props.number}</div><span className="right"></span>
  </div>) : (<div className="date-wrap">
    <span className="left"></span><div className={`item ${props.active ? 'active' : ''}`} onClick={props.itemClick} >{props.number}</div><span className="right"></span>
  </div>)
}
export default CheckCalendar;

checkCalendar.scss

.calendar-mask {
    position: fixed;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background-color: rgba(0, 0, 0, 0.5);

    .calendar-wrap {
        width: 100%;
        height: 100%;
        background-color: #ffffff;
        overflow: auto;
        animation-duration: .3s;

        .header {
            color: black;
            font-size: 17px;
            font-weight: bold;
            height: 30px;
            line-height: 30px;

            .exit {
                width: 20px;
                height: 20px;
                position: relative;
                float: left;
                left: 20px;
                top: 5px;
            }

            .exit::before,
            .exit::after {
                content: "";
                position: absolute;
                height: 20px;
                width: 1.5px;
                left: 8.5px;
                background: #098fef;
            }

            .exit::before {
                transform: rotate(45deg);
            }

            .exit::after {
                transform: rotate(-45deg);
            }

        }

        .week-wrap {
            display: flex;
            font-size: 16px;
            border-bottom: 1px solid rgb(221, 221, 221);

            .week-item {
                height: 30px;
                line-height: 30px;
                width: 14.28571429%;
            }
        }

        .date-table {
            margin-top: 20px;

            .desc {
                text-align: left;
                text-indent: 12px;
                font-size: 18px;
            }

            .row {
                display: flex;
                margin: 8px 0px;

                .date-wrap {
                    height: 35px;
                    width: 14.28571429%;
                    line-height: 30px;

                    .left {
                        width: 100%;
                    }

                    .item {
                        display: inline-block;
                        width: 35px;
                        height: 35px;
                        font-size: 15px;
                        font-weight: bold;
                        line-height: 35px;
                        border-radius: 50%;
                    }

                    .disable {
                        background-color: rgb(238, 238, 238);
                        color: rgb(187, 187, 187);
                    }

                    .active {
                        background-color: #108ee9;
                        color: #ffffff;
                    }

                    .right {
                        width: 100%;
                    }
                }
            }
        }

        .fake-area {
            height: 53px;
            width: 100%;
        }
    }

    .confirm-wrap {
        position: fixed;
        bottom: 0;
        height: 54px;
        width: 100%;
        box-sizing: border-box;
        border-top: 1px solid rgb(221, 221, 221);
        background-color: rgb(247, 247, 247);
        display: flex;
        align-items: center;
        justify-content: center;

        .confirm {
            border-radius: 5px;
            width: 90%;
            background-color: #108ee9;
            font-size: 18px;
            color: #ffffff;
            padding: 8px 0;

            &:active {
                background-color: rgb(14, 128, 210);
                color: rgb(85, 166, 223)
            }
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,428评论 1 33
  • 公司最近要做react的项目,所以,小白继续学习中……整理笔记如下…… React 和 Vue的对比 模块化:从代...
    MickeyMcneil阅读 394评论 0 0
  • 背景 当web应用存在着预览场景时,在进阶体验中势必存在主题配色这样的需求。 切换主题即整体修改网页中各元素的样式...
    nekron阅读 2,153评论 0 0
  • 窝棚内,一只背部通黑、腹部及四只爪爪雪白,眉心正八字开的漂亮小母猫正卧在碎布片做的小窝里,小窝被搁置在一只...
    木沁馨阅读 1,074评论 0 2
  • 姓名:王晓妹 烟台倍生商贸有限公司 日精进打卡第17天 【打卡始于2017.10.12,持续于2017.11.1】...
    王晓妹123阅读 148评论 0 0