前端生成日历的算法(抽象版)

前端生成日历的算法(抽象版)

假设我们要实现一个类似于下面的这样一个日历(上面这个是windows自带的,下面这个是我们完成的基础版)

image-20241011195733756

image-20241011200722345

1.日历算法

每个日历是一个6*7的表格,我们定义一个 calendar 二维数组来存储,首先我们需要计算这个月的第一天是周几,用startDay标识,

比如 24年 9 月 startDay为6则表示为周日,表格第一行 第七列的位置。

24年9月的表格中包含上个月的末尾,那么calendar[0]这个数组可能是这样的[26,27,28,29,30,31,1]。其中1是本月的数据,26-31都是8月的数据。也就是说需要填充startDay-1 个上个月的数据,但是startDay可能会为0和1,所以这里加一个周期,需要填充 (startDay-1+7)%7个上个月的数据。并且如果是一月份的话调用的还是去年12月份的数据。

  // 获取上个月的天数
  const prevMonthDays = month === 0 ? new Date(year - 1, 12, 0).getDate() : new Date(year, month, 0).getDate();
  let prevMonthDay = prevMonthDays - (startDay + 6) % 7 + 1;

使用 (startDay + 6) % 7防止因为startDay为0的时候带来的报错。

然后顺序往下写这个月的日期,最后如果6*7表格没有填写完,那么就从1开始接着填知道填充完成为止。

1.1 高效的算法

简洁易懂 简直就是艺术

./createCalendar

export function createCalendar(year, month) {
    let calendar = new Array(6).fill(null).map(() => new Array(7).fill(null));
    let day = 1;
    const startDay = new Date(year, month, 1).getDay();
    console.log('月份开始第一天', startDay);
    const daysInMonth = new Date(year, month + 1, 0).getDate();
    console.log('daysInMonth', daysInMonth);
  
    // 获取上个月的天数
    const prevMonthDays = month === 0 ? new Date(year - 1, 12, 0).getDate() : new Date(year, month, 0).getDate();
    let prevMonthDay = prevMonthDays - (startDay + 6) % 7 + 1;
  
    for (let i = 0; i < 6; i++) {
      for (let j = 0; j < 7; j++) {
        // 填充上一个月的数据
        if (i === 0 && j < (startDay + 6) % 7) {
          calendar[i][j] = {
            originDate: month === 0 ? `${year - 1}-12-${prevMonthDay}` : `${year}-${month}-${prevMonthDay}`,
            isMonth: false,
            value: prevMonthDay,
            selected: false
          };
          prevMonthDay++;
          continue;
        }
        // 填充月份数据
        if (day <= daysInMonth) {
          calendar[i][j] = {
            originDate: `${year}-${month + 1}-${day}`,
            value: day,
            isMonth: true,
            selected: false
          };
          day++;
        } else {
          // 填充下一个月的数据
          calendar[i][j] = {
            originDate: '',
            isMonth: false,
            value: day - daysInMonth,
            selected: false
          };
          day++;
        }
      }
    }
  
    return calendar;
  }
  

2.月份组件设计

公司真实的代码需要保密,我这里只抽象出来一个最简单的设计 具体功能需要具体实现,

接收月份,年份 和width三个参数,如若后续扩展会对该组件设计更多的api。写了一套这个组件的less

MonthCalendar.jsx

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { createCalendar } from './createCalendar';
import './index.less';

const MonthCalendar = ({ year, month, width = 100 }) => {
  const [calendar, setCalendar] = useState(createCalendar(year, month));
  const daysOfWeek = ['一', '二', '三', '四', '五', '六', '七'];

  const monthToChinese = (month) => {
    const months = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];
    return months[month];
  };

  const handleClick = (day) => {
    alert(day.originDate);
  };

  useEffect(() => {
    setCalendar(createCalendar(year, month));
  }, [year, month]);

return (
    <>
      <div className='month-container' style={{ width: `${width}%` }}>
        <h3 className='calendar-month-title'>
          {" "}
          {monthToChinese(month)} {intl("226870", "月")}
        </h3>
        <div className='calendar-table-container'>
          <table className='calendar-month-table'>
            <thead className='calendar-month-header'>
              <tr>
                {daysOfWeek.map((day, dayIndex) => (
                  <th key={dayIndex}>{day}</th>
                ))}
              </tr>
            </thead>
            <tbody className='calendar-month-body'>
              {calendar.map((week, weekIndex) => (
                <tr key={weekIndex}>
                  {week.map((day, dayIndex) => (
                    <td key={dayIndex} className={getClassName(day)} onClick={() => handleClick(day)}>
                      {day.value}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
};

MonthCalendar.propTypes = {
  year: PropTypes.number.isRequired,
  month: PropTypes.number.isRequired,
};

export default MonthCalendar;

3.样式

./index.less

// 月份表格容器设置
.month-container{
	height: 24.1vh;
	display: flex;
	background-color: #212328;
	justify-content: center;
	align-items: center;
	flex-direction: column;
	// border-bottom: 1px solid black; 
	// border-right: 1px solid black;

}
// 月份标题
.calendar-month-title{
	width: 100%;
	height: 2.82vh;
	text-align: center;
	color: #E8E8E8;
	border-bottom: 1px solid black;
	margin-bottom: 1px;
	font-weight: 400;
	font-size: 14px;
	font-family:  PingFangSC-Regular, Arial;
}

// 关于表格
.calendar-month-table {
	width: 100%;
	border-collapse: separate;
	// border:1px solid pink;
	td, th {
		border-right: 0.26vw solid transparent;
		border-left: 0.26vw solid transparent; 
		// border-bottom: 1px solid transparent;
		// border-top: 1px solid transparent;

	  }
	//   td, th {
	// 	border-right: 0.26vw solid pink;
	// 	border-left: 0.26vw solid pink; 

	//   }
}

.calendar-month-table>thead>tr>th,
.calendar-month-table>tbody>tr>.selectable {
	text-align: center;
	width: 2.1vw;
	height: 2.73vh;
	color: #E8E8E8;
	font-weight: 400;
	border-radius: 2px;
}
.calendar-month-table>tbody>tr>.selectable:hover{
	border-radius: 2px;
	background-color:  #0596B3;
	cursor:pointer
}
.calendar-month-table>tbody>tr>.selected{
	border-radius: 2px;

	text-align: center;
	background-color:  #0596B3;
	cursor: pointer;
}
.calendar-month-table>tbody>tr>.nodata{
	color:#5E5E5E;
	border-radius: 2px;
	text-align: center;
	background-color:  #0596B3;
	cursor: pointer;
	// box-shadow: 0 0 0 3px #0596B3; // 添加外框
}
.calendar-month-table>tbody>tr>.disselectable{
	border-radius: 2px;

	text-align: center;
	width: 2.1vw;
	height: 2.73vh;
	font-weight: 400;
	border-radius: 2px;
 	color:#5E5E5E;
	cursor: default;
}

.calendar-month-body {
	font-family:  PingFangSC-Regular, Arial;
	font-size: 0.80vw;
	color: #DDDDDD;
	font-weight: 400;
	background-color: #212328;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜业

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值