title: 关于FL组件库日期组件制作思路
tags:
- 组件库
- 组件开发
- React
categories: - 组件开发
- React
组件库日期组件制作思路
关于日期组件开发思路,首天的计算极为重要,要想知道一个月中第一天也就是1号在日历的第几个位置,就要计算当月1号为周几。得到之后就方便很多了😋
单页日历的数据
获取当前月第一天
getDate获取日期
假设我们要获取2022-07月的日期数据
- 先获取当月的1日是周几—date.getDay()得到周5
- 所以开始遍历的起始为第5个,依次往后遍历累加直到当月最后一天
- 填充42个格子
- 长度为42的数组,如果当月1日不为周一则前半部分为当月总日从后往前依次递减排列
- 一个月的日历涵盖了三部分,上月剩余日期,当月全部日期,下个月初始日期
- 上月剩余日期可以通过当月计算上月有几天,从开始的星期开始依次递减往前填充
- 当月日期只需要直到当月有几天从1到28/29/30/31填充
- 因为一个月的展示固定为42个格子,前两部分算出来了剩下的只需要减一下从1开始累加到结束就好了
- 其实最重要的就只需要知道年份,月份,就可以获取到该日历页面的数据
- 剩下的就是其他因素了,比如:如果当月为1号上个月的日期数据该怎么办,如果是12号呢?还有就是点击日期的切换等等…
useEffect(()=>{
// 监测月份和日期的变化
// 1.前本部分为上月最后几天
let frontArr = [];
// 2.中间为当月日期
let centerArr = [];
// 3.若长度不大于42,后半部分1-开始累加
let afterArr = [];
let day = new Date(`${year}-${math}`);
// console.log(day);
// console.log('当月第一天为周',day.getDay());//获取当月第一天周几
// console.log(day.getMonth());//获取上个月的月份(不需要加一)
// 上个月有dayMath[day.getMonth()-1]
let dayMath = [31,28,31,30,31,30,31,31,30,31,30,31];//一年每月的天数
// console.log(day.getFullYear());//年份
if (day.getFullYear()%4 === 0 && day.getFullYear()%100 !== 0) {
dayMath[1] = 29
}
// console.log('上个月有',dayMath[day.getMonth()-1]);
let forOne = dayMath[day.getMonth()-1]
if (day.getMonth()+1===1) {
forOne = 31
}
// console.log(forOne);//上月的天数
let shangMatch = day.getDay() - 1
if (day.getDay()===0) {
shangMatch = 6
}
// 如果这个月为1月,则上个月按照12月的31计算
while (frontArr.length < shangMatch) {
frontArr.unshift(forOne--)
}
// console.log('上个月的数组',frontArr);//上月的数组
// console.log('这个月',day.getMonth()+1);
let index = 1;
// while(centerArr.length < dayMath[day.getMonth()-1]){
// centerArr.push(index++)
// }
// console.log(dayMath[day.getMonth()]);
for (let i = 0; i < dayMath[day.getMonth()]; i++) {
// console.log(28);
if (index>31) {
break
}
centerArr.push(index++)
}
// console.log('这个月的数组',centerArr);//这个月的数组
let endFor = 42 - frontArr.length - centerArr.length;
// console.log(endFor);
for (let i = 1; i < endFor+1; i++) {
afterArr.push(i)
}
// console.log('最后数组',afterArr);
// 汇总数组 ---将一维数组转换为二维数组方便遍历数据渲染---有待改进算法
let countArr = [...frontArr,...centerArr,...afterArr];
// console.log('数组汇总',countArr);
let newArr = [];
newArr.push(countArr.slice(0,7))
newArr.push(countArr.slice(7,14))
newArr.push(countArr.slice(14,21))
newArr.push(countArr.slice(21,28))
newArr.push(countArr.slice(28,35))
newArr.push(countArr.slice(35,42))
// console.log(newArr);
setArr(newArr)
},[year,math])
页面渲染
这里我是用的是<table>
标签,所以我才需要将得到的数据重构成二维数组,方便渲染
日期的切换
点击下个月和上个月的交互其实很简单,因为上文我们页面的渲染有useEffect
这个钩子监测。
所以当我们改变传入的初始日期就可以自动渲染页面
// 事件
function nextMath() {
// 点击下一月份
setMath(title=>{
return title+1
})
if (math >= 12) {
setyear(title=>{
return title+1
})
setMath(title=>{
return 1
})
}
// console.log(myRefipt);
// myRefipt.current.focus();
}
function upMath() {
// 点击上一月份
setMath(title=>{
return title-1
})
if (math <= 1) {
setyear(title=>{
return title-1
})
setMath(title=>{
return 12
})
}
}
// 点击下一年
function nextYear() {
setyear(title=>{
return title+1
})
}
// 点击上一年
function upYear() {
setyear(title=>{
return title - 1
})
}
// 点击今天跳转
function teday(){
let data = new Date();
setyear(data.getFullYear());
setMath(data.getMonth()+1)
setDatas(data.getDate())
}
源码
// index.jsx
import React,{useEffect, useRef, useState} from 'react'
import style from './index.module.scss'
function DatePicker() {
// 获取焦点后的样式
let [focus,setFocus] = useState(false);
// main主体数据
let [show,setShow] = useState(false)
// 1. 初始日期
// let [day,setday] = useState(new Date());
// 2. 42格子遍历数据
let [Arr,setArr] = useState([]);
// 3. 年份 // 控制这俩就可以控制日历
let [year,setyear] = useState(2022);
// 4. 月份
let [math,setMath] = useState(1);
// 5. 日期
let [datas,setDatas] = useState(0);
// 6. 输入框
let myRefipt = useRef();
let mySection = useRef();
// 7. 输入框内容
let [iptValue,setiptValue] = useState("");
// 获取当前月第一天
// getDate获取日期
// 假设我们要获取2022-07月的日期数据
// 1. 先获取当月的1日是周几---date.getDay()得到周5
// - 所以开始遍历的起始为第5个,依次往后遍历累加直到当月最后一天
// 2. 填充42个格子
// - 长度为42的数组,如果当月1日不为周一则前半部分为当月总日从后往前依次递减排列
useEffect(()=>{
// 监测月份和日期的变化
// 1.前本部分为上月最后几天
let frontArr = [];
// 2.中间为当月日期
let centerArr = [];
// 3.若长度不大于42,后半部分1-开始累加
let afterArr = [];
let day = new Date(`${year}-${math}`);
// console.log(day);
// console.log('当月第一天为周',day.getDay());//获取当月第一天周几
// console.log(day.getMonth());//获取上个月的月份(不需要加一)
// 上个月有dayMath[day.getMonth()-1]
let dayMath = [31,28,31,30,31,30,31,31,30,31,30,31];//一年每月的天数
// console.log(day.getFullYear());//年份
if (day.getFullYear()%4 === 0 && day.getFullYear()%100 !== 0) {
dayMath[1] = 29
}
// console.log('上个月有',dayMath[day.getMonth()-1]);
let forOne = dayMath[day.getMonth()-1]
if (day.getMonth()+1===1) {
forOne = 31
}
// console.log(forOne);//上月的天数
let shangMatch = day.getDay() - 1
if (day.getDay()===0) {
shangMatch = 6
}
// 如果这个月为1月,则上个月按照12月的31计算
while (frontArr.length < shangMatch) {
frontArr.unshift(forOne--)
}
// console.log('上个月的数组',frontArr);//上月的数组
// console.log('这个月',day.getMonth()+1);
let index = 1;
// while(centerArr.length < dayMath[day.getMonth()-1]){
// centerArr.push(index++)
// }
// console.log(dayMath[day.getMonth()]);
for (let i = 0; i < dayMath[day.getMonth()]; i++) {
// console.log(28);
if (index>31) {
break
}
centerArr.push(index++)
}
// console.log('这个月的数组',centerArr);//这个月的数组
let endFor = 42 - frontArr.length - centerArr.length;
// console.log(endFor);
for (let i = 1; i < endFor+1; i++) {
afterArr.push(i)
}
// console.log('最后数组',afterArr);
// 汇总数组
let countArr = [...frontArr,...centerArr,...afterArr];
// console.log('数组汇总',countArr);
let newArr = [];
newArr.push(countArr.slice(0,7))
newArr.push(countArr.slice(7,14))
newArr.push(countArr.slice(14,21))
newArr.push(countArr.slice(21,28))
newArr.push(countArr.slice(28,35))
newArr.push(countArr.slice(35,42))
// console.log(newArr);
setArr(newArr)
},[year,math])
// 第一次进入页面当前日期
useEffect(()=>{
let data = new Date();
// console.log(data.getFullYear());
// console.log(data.getMonth());
setyear(data.getFullYear());
setMath(data.getMonth()+1)
// console.log(data.getDate());
setDatas(data.getDate())
},[])
useEffect(()=>{
// 点击除去日历以外的部分隐藏日历
document.onclick = ()=>{
setShow(false)
setFocus(false)
}
},[])
// div获取焦点
function divFocus(e) {
e.nativeEvent.stopImmediatePropagation();
// console.log('div获取了焦点');
setFocus(true)
setShow(true)
}
// 事件
function nextMath() {
// 点击下一月份
setMath(title=>{
return title+1
})
if (math >= 12) {
setyear(title=>{
return title+1
})
setMath(title=>{
return 1
})
}
// console.log(myRefipt);
// myRefipt.current.focus();
}
function upMath() {
// 点击上一月份
setMath(title=>{
return title-1
})
if (math <= 1) {
setyear(title=>{
return title-1
})
setMath(title=>{
return 12
})
}
}
// 点击下一年
function nextYear() {
setyear(title=>{
return title+1
})
}
// 点击上一年
function upYear() {
setyear(title=>{
return title - 1
})
}
// 点击今天跳转
function teday(){
let data = new Date();
setyear(data.getFullYear());
setMath(data.getMonth()+1)
setDatas(data.getDate())
}
// 鼠标移入改变输入框内容
function mouseOver(tit,index) {
// 获取到内容
myRefipt.current.value = year+'-'+math+'-'+tit;
if (index === 0) {
if (tit > 7) {
if (math > 1) {
myRefipt.current.value = year+'-'+(math-1)+'-'+tit;
}else{
myRefipt.current.value = (year-1)+'-'+ 12 +'-'+tit;
}
}
}else if(index === 4){
if (tit < 7) {
if (math < 12) {
myRefipt.current.value = year+'-'+(math+1)+'-'+tit;
}else{
myRefipt.current.value = (year+1)+'-'+1+'-'+tit;
}
}
}else if(index === 5){
if (tit <= 14) {
if (math < 12) {
myRefipt.current.value = year+'-'+(math+1)+'-'+tit;
}else{
myRefipt.current.value = (year+1)+'-'+1+'-'+tit;
}
}
}
}
// 鼠标点击改变ipt值,并且隐藏日历
function clickIpt(tit,index) {
if (index === 0) {
if (tit > 7) {
if (math > 1) {
iptValue = year+'-'+(math-1)+'-'+tit;
setiptValue(year+'-'+(math-1)+'-'+tit)
}else{
iptValue = (year-1)+'-'+12+'-'+tit;
setiptValue((year-1)+'-'+12+'-'+tit)
}
}else{
iptValue = year+'-'+math+'-'+tit;
setiptValue(year+'-'+math+'-'+tit)
}
}else if(index === 4){
if (tit < 7) {
if (math<12) {
iptValue = year+'-'+(math+1)+'-'+tit;
setiptValue(year+'-'+(math+1)+'-'+tit)
}else{
iptValue = (year+1)+'-'+1+'-'+tit;
setiptValue((year+1)+'-'+1+'-'+tit)
}
}else{
iptValue = year+'-'+math+'-'+tit;
setiptValue(year+'-'+math+'-'+tit)
}
}else if(index === 5){
if (tit <= 14) {
if (math<12) {
iptValue = year+'-'+(math+1)+'-'+tit;
setiptValue(year+'-'+(math+1)+'-'+tit)
}else{
iptValue = (year+1)+'-'+1+'-'+tit;
setiptValue((year+1)+'-'+1+'-'+tit)
}
}else{
iptValue = year+'-'+math+'-'+tit;
setiptValue(year+'-'+math+'-'+tit)
}
}else{
iptValue = year+'-'+math+'-'+tit;
setiptValue(year+'-'+math+'-'+tit)
}
// console.log(iptValue);
// console.log(iptValue);
myRefipt.current.value = iptValue
// console.log(myRefipt.current.value);
setTimeout(() => {
setShow(false)
}, 0);
}
// 没有点击鼠标移出清除input
function mouseOut() {
myRefipt.current.value = iptValue;
}
return (
<div className={style.Box}
onClick={(ev)=>divFocus(ev)}
// onBlur={divBlur}
>
<div
className={focus?[style.iptBox,style.iptBoxFocu].join(' '):style.iptBox}>
<input type="text"
placeholder="请选择日期"
ref={myRefipt}
/>
<span>icon</span>
</div>
<section
ref={mySection}
tabIndex="-1"
className={show?`${style.dropChangeUp}`:`${style.dropChangeDown}`}
>
<div className={style.sectionTop}>
<header>
<button
className={style.leftBtnOne}
onClick={upYear}
>
<span></span>
</button>
<button
className={style.leftBtnTwo}
onClick={upMath}
>
<span></span>
</button>
<div>
<button
className={style.yearBtn}
style={{marginRight:5}}
>
{year}年
</button>
<button className={style.monthBth}>
{math}月
</button>
</div>
<button
className={style.rightBtnTwo}
onClick={nextMath}
>
<span></span>
</button>
<button
className={style.rightBtnOne}
onClick={nextYear}
>
<span></span>
</button>
</header>
<main>
<table>
<thead>
<tr>
<th>一</th>
<th>二</th>
<th>三</th>
<th>四</th>
<th>五</th>
<th>六</th>
<th>七</th>
</tr>
</thead>
<tbody
onMouseOut={mouseOut}
>
{
Arr.map((title,index)=>{
return (
<tr key={index}>
{
title.map((tit,ind)=>{
return (
<td
key={ind}
className={
[index === 0 ? (tit > 7 ? `${style.lightColour}` : ""):
index === 5 ? (tit<=20 ? `${style.lightColour}` : ""):
index === 4 ? (tit<=7 ? `${style.lightColour}` : ""):
"",
tit === datas&&year===new Date().getFullYear()&&math===new Date().getMonth()+1 ? `${style.choice}`:""
].join('')
}
onMouseOver={()=>mouseOver(tit,index)}
onClick={()=>clickIpt(tit,index)}
>{tit}</td>
)
})
}
</tr>
)
})
}
</tbody>
</table>
</main>
</div>
<footer className={style.footer}>
<a href="javascript:void(0)" onClick={teday}>今天</a>
</footer>
</section>
</div>
)
}
export default DatePicker
scss
$allClor:#64abfb;
a{
text-decoration: none;
}
.iptBox{
width: 170px;
height: 30px;
border: 1px solid #afafaf;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.3s ease;
border-radius: 2px;
&:hover{
border: 1px solid $allClor;
}
input{
width: 80%;
border: none;
box-sizing: border-box;
outline: none;
height: 100%;
padding-left: 8px;
font-size: 16px;
}
span{
display: inline-block;
flex: 1;
height: 100%;
line-height: 30px;
user-select: none;
}
}
.Box{
position: relative;
}
section{
display: flex;
flex-direction: column;
width: 280px;
height: 308px;
// border: 1px solid #afafaf;
box-shadow: 0px 0px 20px -5px #afafaf;
overflow: hidden;
position: absolute;
background-color: #fff;
z-index: 100;
.sectionTop{
display:flex;
flex: 1;
flex-direction: column;
height: 32px;
header{
display: flex;
justify-content: center;
padding: 0px 20px;
align-items: center;
height: 40px;
border-bottom: 1px solid #f0f0f0;
div{
height: 100%;
flex: 1;
display: flex;
justify-content: center;
button{
height: 100%;
text-align: center;
color: #808080;
font-weight: 600;
transition: all 0.3s ease;
cursor: pointer;
&:hover{
color: $allClor;
}
}
}
button{
height: 100%;
border: none;
background-color: #fff;
}
.rightBtnOne span,
.rightBtnTwo span,
.leftBtnTwo span,
.leftBtnOne span{
width: 7px;
height: 7px;
cursor: pointer;
position: relative;
}
.leftBtnOne span::after{
top: 3px;
left: -4px;
position: absolute;
content: "";
display: inline-block;
width: 7px;
height: 7px;
border-top: 2px solid #afafaf;
border-left: 2px solid #afafaf;
transform: rotate(-45deg);
margin-left: -2px;
transition: all 0.3s ease;
}
.rightBtnOne span::after{
left: -4px;
top: 3px;
position: absolute;
content: "";
display: inline-block;
width: 7px;
height: 7px;
border-top: 2px solid #afafaf;
border-left: 2px solid #afafaf;
transform: rotate(135deg);
margin-left: -2px;
transition: all 0.3s ease;
}
.leftBtnOne span::before{
content: "";
display: inline-block;
width: 7px;
height: 7px;
border-top: 2px solid #afafaf;
border-left: 2px solid #afafaf;
transform: rotate(-45deg);
transition: all 0.3s ease;
}
.rightBtnOne span::before{
content: "";
display: inline-block;
width: 7px;
height: 7px;
border-top: 2px solid #afafaf;
border-left: 2px solid #afafaf;
transform: rotate(135deg);
transition: all 0.3s ease;
}
// 鼠标移入
.leftBtnTwo{
width: 30px;
// 鼠标移入
&:hover span::after{
border-top: 2px solid #797979;
border-left: 2px solid #797979;
}
}
.rightBtnTwo{
width: 30px;
// 鼠标移入
&:hover span::after{
border-top: 2px solid #797979;
border-left: 2px solid #797979;
}
}
.rightBtnOne{
width: 20px;
// 鼠标移入
&:hover span::after,
&:hover span::before{
border-top: 2px solid #797979;
border-left: 2px solid #797979;
}
}
.leftBtnOne{
width: 20px;
// 鼠标移入
&:hover span::after,
&:hover span::before{
border-top: 2px solid #797979;
border-left: 2px solid #797979;
}
}
.leftBtnTwo span::after{
content: "";
display: inline-block;
width: 7px;
height: 7px;
border-top: 2px solid #afafaf;
border-left: 2px solid #afafaf;
transform: rotate(-45deg);
margin-left: -2px;
transition: all 0.3s ease;
}
.rightBtnTwo span::after{
content: "";
display: inline-block;
width: 7px;
height: 7px;
border-top: 2px solid #afafaf;
border-left: 2px solid #afafaf;
transform: rotate(135deg);
margin-left: -2px;
transition: all 0.3s ease;
}
}
main{
flex: 1;
}
}
.footer{
display: flex;
height: 35px;
border-top: 1px solid #f0f0f0;
a{
margin: auto;
color: $allClor;
}
}
main{
padding: 10px;
table{
height: 100%;
display: flex;
flex-direction: column;
width: 100%;
color: #363636;
thead{
height: 30px;
tr{
display: flex;
th{
flex: 1;
}
}
}
tbody{
display: flex;
flex-direction: column;
flex: 1;
tr{
display: flex;
flex: 1;
td{
flex: 1;
text-align: center;
line-height: 29px;
cursor: pointer;
font-weight: 400;
transition: all 0.3s ease;
user-select: none;
&:hover{
background-color: #f0f0f0;
}
}
}
.lightColour{
color: #afafaf;
}
.choice{
background-color: #64abfb;
color: #fff;
}
}
}
}
}
.iptBoxFocu{
border: 1px solid $allClor;
box-shadow: 0px 0px 3px 0.5px $allClor;
}
.dropChangeUp{
animation: changeUp 0.2s ease 1 normal forwards
}
.dropChangeDown{
animation: changeDown 0.2s ease 1 normal forwards
}
/* 动画函数 */
@keyframes changeUp {
/*以一种写法*/
0% {
top: 0px;
opacity: 0;
height: 0px;
}
100%{
top: 40px;
opacity: 1;
height: 308px;
}
}
@keyframes changeDown {
/*以一种写法*/
from {
top: 40px;
opacity: 1;
height: 308px;
}
to {
top: 0px;
opacity: 0;
height: 0px;
}
}