利用antd组件库的Modal组件进行的封装,通过点击事件控制弹出或隐藏
功能上如果有需要可以自行增加业务逻辑,这只是一个基础版日历组件,根据自己业务需求可增加逻辑代码,代码注释基本上写的很详细了,欢迎大佬指正!
效果图
// Calendar 组件
import React, { useState, useEffect, useRef, memo } from 'react';
import { Modal, Icon } from 'antd-mobile';
import './style.less';
const weekDay = [
{
label: '日',
value: 0,
},
{
label: '一',
value: 1,
},
{
label: '二',
value: 2,
},
{
label: '三',
value: 3,
},
{
label: '四',
value: 4,
},
{
label: '五',
value: 5,
},
{
label: '六',
value: 6,
},
];
const basic = {
year: new Date().getFullYear(),
month: new Date().getMonth() + 1,
day: new Date().getDate(),
};
const Calendar = memo((props) => {
let { show, onClose, defaultDate } = props;
// 日期展示
const [showDate, setShowDate] = useState([]);
// 选择的日期
const [select, setSelect] = useState({});
const date = useRef({ year: basic.year, month: basic.month, day: basic.day });
useEffect(() => {
setSelect({ ...basic });
Day(basic.year, basic.month, basic.day);
}, []);
useEffect(() => {
// 保存确定后的日期,取消则显示上次日期
date.current = { ...defaultDate };
Day(date.current.year, date.current.month, date.current.day);
}, [show]);
// 上月
const handlePrev = () => {
let month;
let year;
let day = select.day;
if (Number(select.month) > 1) {
month = select.month - 1;
year = select.year;
}
if (Number(select.month) === 1) {
month = 12;
year = select.year - 1;
}
setSelect({ year: year, month: month, day: day });
Day(year, month, day);
};
// 下月
const handleNext = () => {
let month;
let year;
let day = select.day;
if (select.month < 12) {
month = Number(select.month) + 1;
year = select.year;
}
if (select.month == 12) {
month = 1;
year = Number(select.year) + 1;
}
setSelect({ year: year, month: month, day: day });
Day(year, month, day);
};
// 日期选择
const handleChangeDate = (val) => {
// 使用 useRef 存贮点击的日期,可以同步更新
date.current = { year: select.year, month: select.month, day: val };
setSelect({ year: select.year, month: select.month, day: val });
Day(select.year, select.month, val);
};
// 用来计算生成日历面板
const Day = (year, month, day) => {
let total;
//判断月分是大月还是小月
//就可以得出这个月除了2月外是30天还是31天
if (month !== 2) {
if (month === 4 || month === 6 || month === 9 || month === 11) {
total = 30;
} else {
total = 31;
}
} else {
//判断是否是闰年
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
total = 29;
} else {
total = 28;
}
}
let firstday = new Date(year, month - 1, 1);
let week = firstday.getDay();
let dom = [];
//显示星期
if (week !== 0) {
for (let i = 0; i < week; i++) {
dom.push('');
}
}
//显示每一天的号数
for (let j = 1; j <= total; j++) {
//选中的日期使用 check
let check = false;
// 这里没有使用 select 的日期做比较,是因为 useState 更新是异步的,切换上月或者下月的时候,月份就会匹配不上,就会出现选中一个日期,切换一下月份再回来选择的日期就会丢失,所以使用 useRef 存贮(同步更新)
if (year == date.current.year && month == date.current.month && j == date.current.day) {
check = true;
}
dom.push({ label: j, check: check });
}
setShowDate(dom);
};
return (
// 这里使用的Modal是从下往上弹出,可参照 antd-mobile 文档API自行修改
<Modal popup visible={show} className="calendar-modal" animationType="slide-up">
<div className="calendar-content">
<div className="calendar-bar">
<div
className="close"
onClick={() => {
onClose(false, date.current);
}}
>
取消
</div>
<div className="bar-left"> {select.year}年</div>
<div className="bar-right">
<Icon type="left" onClick={handlePrev} />
<span className="showtext">{select.month}月</span>
<Icon type="right" onClick={handleNext} />
</div>
<div
className="close"
onClick={() => {
onClose(true, date.current);
}}
>
确认
</div>
</div>
<div className="calendar-data-content">
{weekDay.map((item) => {
return (
<li className="everyday" key={item.value}>
{item.label}
</li>
);
})}
{showDate &&
showDate.map((item, index) => {
return (
<div key={index} className="everyday" onClick={() => handleChangeDate(item.label)}>
<span className={`everyday-text ${item.check ? 'check' : ''}`}>{item.label}</span>
</div>
);
})}
</div>
</div>
</Modal>
);
});
调用方式
const Index = () => {
const [show, setShow] = useState(false);
const [defaultDate, setDefaultDate] = useState({ year: basic.year, month: basic.month, day: basic.day });
const onClose = (type, date) => {
if (type) {
// 确定
setDefaultDate(date);
console.log('%c 🍏嘿嘿5🍏:', 'color: CornflowerBlue; background: DeepPink; font-size: 20px;', 'todo');
// 这是彩色的console.log打印,喜欢的可以去vscode自行下载插件(插件名:张大伟--同事自己开发的(大佬))
} else {
// 取消
console.log('%c 🐭嘿嘿77🐭:', 'color: LightPink; background: Yellow; font-size: 20px;', 'todo');
}
setShow(false);
};
const onOk = () => {
setShow(true);
};
return (
<div>
<div onClick={onOk}>呵呵</div>
<Calendar show={show} onClose={onClose} defaultDate={defaultDate} />
</div>
);
};
export default Index;
样式
.calendar-content {
margin: 0 20px;
height: 350px;
.calendar-bar {
display : flex;
align-items : center;
justify-content: space-between;
margin-bottom : 30px;
padding : 10px 15px;
.bar-left {
font-weight: bold;
color : #999999;
font-size : 18px;
}
.bar-right {
display : flex;
align-items: center;
.showtext {
color : #999999;
font-weight: bold;
font-size : 18px;
display : inline-block;
width : 60px;
}
}
.close {
font-weight: bold;
color : #333333;
font-size : 18px;
}
}
.calendar-data-content {
overflow: hidden;
.everyday {
font-size : 16px;
list-style : none;
width : 14%;
height : 38px;
float : left;
text-align : center;
position : relative;
font-weight: 700;
.everyday-text {
display : inline-block;
height : 34px;
width : 34px;
text-align : center;
line-height: 34px;
}
}
.active {
color: red;
}
.check {
background-color: skyblue;
color : #ffffff;
border-radius : 4px;
}
}
}
.calendar-modal {
.am-modal-content {
border-top-left-radius : 24px;
border-top-right-radius: 24px;
}
}