React + Ts 自定义 日历插件

React + Ts 自定义 日历插件

ant-design 内置了日历组件,但是功能单一并不能够满足项目需求,因此自定义日历组件。

日历算法

一个月最多跨6周,即6*7格式
在这里插入图片描述

本月第一天 : fistDay : new Date(year, month–1 , 1)

本月最后一天: lastDay: new Date(year, month, 0) 下月的第0天即本月最后一天

上月最后一天: lastDayofLastMonth : new Date(year, month–1 ,0) 本月第0天即上月最后一天

明白这些就可以写一个日历算法了。

 //定义result变量用来保存最后的结果集
  let result: any[] = [];
  let date = new Date();
  year = date.getFullYear();
  month = date.getMonth() + 1; //月份修正
  //获取当前月的第一天,用于计算上月预留天数
  let firstDayOfMonth = new Date(year, month - 1, 1);
  let preMonthDay = firstDayOfMonth.getDay();
  //获取上月的最后一天,本月第0天即为上月最后一天
  let lastDayOfLastMonth = new Date(year, month - 1, 0);
  let lastDateOfLastMonth = lastDayOfLastMonth.getDate();
  //获取本月最后一天,下月第0天即为本月最后一天
  let lastDayOfMonth = new Date(year, month, 0);
  let lastDateOfMonth = lastDayOfMonth.getDate();

  for (let i = 0; i < 6 * 7; i++) {
    // 获取当前排序的日期数,+1是为了修正
    let thisYear: number = year,
      thisMonth: number = month,
      date: number = i + 1 - preMonthDay,
      showDate: number = date;
    if (date <= 0) {
      //如果date小于等于0,则说明是上月预留天数,月和日都要加以修正
      thisMonth -= 1;
      showDate = date + lastDateOfLastMonth;
    } else if (date > lastDateOfMonth) {
      //如果date大于本月最后一天说明为下月预留天数,月和日加以修正
      thisMonth += 1;
      showDate = date - lastDateOfMonth;
    }
    // 修正年月,因为当月份为1时,上月减1为0,需要同时修正月和年
    // 当月份为12时,下月加1为13,需要同时修正年和月
    if (thisMonth === 13) {
      (thisMonth = 1), (thisYear += 1);
    }
    if (thisMonth === 0) {
      (thisMonth = 12), (thisYear -= 1);
    }
    result.push({
      year: thisYear,
      month: thisMonth,
      date,
      showDate,
    });
  }
  return result;

以上便是实现日历算法的核心

完整的日历组件(仅供参考)

// index.tsx
import React, { useState, useEffect } from 'react';
import {
  week,
  getMonthData,
  btnStyle,
  nowYear,
  nowMonth,
  Iprops,
  DateRet,
} from './util';
import styles from './index.less';
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import { Button } from 'antd';
export default function Calender({
  handleClick,
  handleMouseEnter,
  onPanelChange,
  dateCellRender,
}: Iprops) {
  // 记录上一个月
  const [lastMonth, setLastMonth] = useState<number>(0),
    // 当前年份
    [currentYear, setCurrentYear] = useState<number>(nowYear),
    // 当前月份
    [currentMonth, setCurrentMonth] = useState<number>(nowMonth),
    // 日历展示数据
    [monthList, setMonthList] = useState<any[]>([]),
    // 鼠标移过日期
    [currentMouseHoverDate, setCurrentMouseHoverDate] = useState<any>(null);

  useEffect(() => {
    setMonthList(getMonthData(currentYear, currentMonth));
  }, [currentYear, currentMonth]);

  /**
   *  触发事件 点击事件|鼠标移入事件|面板改变事件
   * @param param 日期
   * @param type 类型 1、点击 2、鼠标移入
   */
  function onClick({ year, month, showDate: date }: any, type: number) {
    const data = { year, month, date };
    if (type === 1) {
      if (lastMonth !== 0 && lastMonth !== month) {
        // 面板改变时触发
        handlePanelChange(data)
      }
      setLastMonth(month);
      handleClick && handleClick(data);
    } else {
      handleMouseEnter && handleMouseEnter(data);
    }
  }

  // 下一月
  function handleNextMonth() {
    const month = currentMonth + 1;
    if (month >= 13) {
      const year = currentYear + 1;
      setCurrentYear(year);
      setCurrentMonth(1);
      // 面板改变时触发
      const data = {
        year,
        month: 1,
        date: 1,
      };
      handlePanelChange(data);
    } else {
      setCurrentMonth(month);
      // 面板改变时触发
      const data = {
        year: currentYear,
        month,
        date: 1,
      };
      handlePanelChange(data);
    }
  }

  /**
   * 查看上一个月
   */
  function handlePrevMonth() {
    const month = currentMonth - 1;
    if (month <= 0) {
      const year = currentYear - 1;
      setCurrentYear(year);
      setCurrentMonth(12);
      // 面板改变时触发
      const data = {
        year,
        month: 12,
        date: 1,
      };
      handlePanelChange(data);
    } else {
      setCurrentMonth(month);
      // 面板改变时触发
      const data = {
        year: currentYear,
        month,
        date: 1,
      };
      handlePanelChange(data);
    }
  }

  /**
   * 面板修改
   */
  function handlePanelChange(data: DateRet) {
    onPanelChange && onPanelChange(data);
  }

  // 当前时间
  function handleSetTimeNow() {
    setCurrentYear(currentYear);
    setCurrentMonth(currentMonth);
  }

  return (
    <div className={styles.calenderContainer}>
      <div className={styles.header}>
        <div className={styles.headerLeft}>
          <Button
            className={styles.headerLeftBtn}
            size="small"
            onClick={handleSetTimeNow}
          >
            今天
          </Button>
        </div>
        <div className={styles.headerMain}>
          <LeftOutlined style={btnStyle} onClick={handlePrevMonth} />
          <div>
            {currentYear}{currentMonth}</div>
          <RightOutlined style={btnStyle} onClick={handleNextMonth} />
        </div>
        <div className={styles.headerRight}></div>
      </div>
      <div className={styles.main}>
        <div className={styles.week}>
          {week.map((it) => {
            return (
              <div key={it} className={styles.weekCell}>
                {it}
              </div>
            );
          })}
        </div>
        <div className={styles.date}>
          {monthList.map((date, i) => {
            return (
              <div
                className={styles.dateCell}
                key={i}
                onClick={() => onClick(date, 1)}
                onMouseEnter={() => onClick(date, 0)}
                style={{
                  background: currentMonth === date.month ? '#fff' : '#fafafa',
                }}
              >
                <div
                  className={styles.dateCellHeader}
                  style={{
                    color:
                      currentMonth === date.month
                        ? 'rgba(0,0,0,.85)'
                        : 'rgba(0,0,0,.3)',
                  }}
                >
                  {date.showDate}
                </div>
                <div className={styles.dateCellMain}>
                  {dateCellRender && dateCellRender(date)}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// util.ts
export interface DateRet {
  year: number;
  month: number;
  date: number;
  showDate?: number;
}

export interface Iprops {
  handleMouseEnter?(date: DateRet): void;
  handleClick?(date: DateRet): void;
  // 渲染每格
  dateCellRender?(date: DateRet): React.ReactElement;
  // 面板变化
  onPanelChange?(date: DateRet): void;
}

// 星期
export const week: string[] = ['日', '一', '二', '三', '四', '五', '六'];

const nowTime = new Date();

export const nowYear = nowTime.getFullYear();
export const nowMonth = nowTime.getMonth() + 1;

/**
 * 日历日期算法
 * @param year
 * @param month
 */
export function getMonthData(year?: number, month?: number): DateRet[] {
  //定义result变量用来保存最后的结果集
  let result: any[] = [];
  if (!year || !month) {
    let date = new Date();
    year = date.getFullYear();
    month = date.getMonth() + 1; //月份修正
  }
  //获取当前月的第一天,用于计算上月预留天数
  let firstDayOfMonth = new Date(year, month - 1, 1);
  let preMonthDay = firstDayOfMonth.getDay();
  //获取上月的最后一天,本月第0天即为上月最后一天
  let lastDayOfLastMonth = new Date(year, month - 1, 0);
  let lastDateOfLastMonth = lastDayOfLastMonth.getDate();
  //获取本月最后一天,下月第0天即为本月最后一天
  let lastDayOfMonth = new Date(year, month, 0);
  let lastDateOfMonth = lastDayOfMonth.getDate();

  for (let i = 0; i < 6 * 7; i++) {
    // 获取当前排序的日期数,+1是为了修正
    let thisYear: number = year,
      thisMonth: number = month,
      date: number = i + 1 - preMonthDay,
      showDate: number = date;
    if (date <= 0) {
      //如果date小于等于0,则说明是上月预留天数,月和日都要加以修正
      thisMonth -= 1;
      showDate = date + lastDateOfLastMonth;
    } else if (date > lastDateOfMonth) {
      //如果date大于本月最后一天说明为下月预留天数,月和日加以修正
      thisMonth += 1;
      showDate = date - lastDateOfMonth;
    }
    // 修正年月,因为当月份为1时,上月减1为0,需要同时修正月和年
    // 当月份为12时,下月加1为13,需要同时修正年和月
    if (thisMonth === 13) {
      (thisMonth = 1), (thisYear += 1);
    }
    if (thisMonth === 0) {
      (thisMonth = 12), (thisYear -= 1);
    }
    result.push({
      year: thisYear,
      month: thisMonth,
      date,
      showDate,
    });
  }
  return result;
}

/**
 * 切换按钮样式
 */
export const btnStyle = {
  fontSize: 20,
  cursor: 'pointer',
};
// index.less
.calenderContainer {
  width: 100%;
  background: #fff;
}

.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 60px;
  margin-bottom: 18px;

  .headerLeft {
    flex: 0 1 auto;
    .headerLeftBtn {
      margin-left: 16px;
    }
  }

  .headerMain {
    width: 226px;
    flex: 0 0 auto;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 19px;
    font-weight: 600;
    color: #333333;
  }

  .headerRight {
    flex: 0 1 auto;
  }
}

.main {
  display: flex;
  flex-wrap: nowrap;
  flex-direction: column;
}

.week {
  height: 44px;
  background: #FAFAFA;
  display: flex;
  justify-content: space-between;
  line-height: 44px;

  .weekCell {
    font-size: 16px;
    font-weight: 400;
    color: #333333;
    text-align: center;
    width: calc(100% / 7);
  }
}

.date {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;

  .dateCell {
    width: calc(100% / 7);
    height: 142px;
    border: 1px solid rgba(217, 217, 217, .35);
    border-right: 0px;
    border-bottom: 0px;
    box-sizing: border-box;
    transition: all .2s;

    &:hover {
      background: #f5f5f5;
      cursor: pointer;
    }

    .dateCellHeader {
      text-align: right;
      font-size: 16px;
      font-weight: 400;
      color: #999999;
      line-height: 22px;
      padding: 10px;
      box-sizing: border-box;
    }

    .dateCellMain {
      height: 68%;
    }
  }
}

代码仅供参考

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值