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%;
}
}
}
代码仅供参考