业务需求
- 多选近三个月的日期。
- 不能选择当日之前的日期。
因为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)
}
}
}
}