闲着没事,写着玩
需求分析:
-
头部
用于切换月份使用(下面的没有实现自定义查找年月)
-
星期Title
展示周一,二,三.....
-
实现日历table
我自己是这么想的,借鉴网上的日历来说,大家都是使用的 x * y 的二维布局,那么我自己就以 6 * 7的布局来设置
(1)日历第一行一般会有上个月和这个月的日期,因此我需要根据当前月的1号是星期几,确定上个月在第一行展示的日期 - 例如这个月的1号是星期天,那么日历的第一行就是 上个月的末尾6天 + 这个月的第一天
(2)这个月肯定是28 29 30 或者31个展示日期
(3)下个月就是 6 * 7 - 前两个月展示的天数,就是下个月可以展示的天数
(4)然后我们将前面三部分组合成一个 队列(queue) ,这个就是日历上当前页所有展示内容
代码分解
类型声明
tsx
// 用于切换上下月使用的枚举类型
enum ToggleMonth {
Last = -1,
Next = 1,
}
// 用于week-title的枚举类型(周一,二,。。。)
enum ZhCnNumberCharactor {
一,
二,
三,
四,
五,
六,
日,
}
// 声明当前的年月日数据类型
type FilterMonthDate = {
year: number;
month: number;
day?: number;
};
// 声明队列成员类型
type DateItem = {
day: number;
isLastMonth?: ToggleMonth;
isNextMonth?: ToggleMonth;
[keys: string]: any;
};
// 声明队列类型
type TableData = Array<DateItem>;
初始化状态
tsx
// header
// 当前:年-月-日
const [filterMonthDate, setFilterMonthDate] = useState<FilterMonthDate>({
year: new Date().getFullYear(),
month: new Date().getMonth() + 1,
day: new Date().getDay(),
});
// table
// 日历当前页数据
const [tableData, setTableData] = useState<TableData>([]);
// 选中日期
const [selectedDate, setSelectedDate] = useState(
`${filterMonthDate.year}/${filterMonthDate.month}/${filterMonthDate.day}`
);
三个渲染函数(切换月份,title,日历table)
tsx
// render-header-filter 切换月份
const renderHeaderFilter = () => {
return (
<div className={styles.headerFilter}>
<div onClick={() => toggleMonth(ToggleMonth.Last)}><</div>
<div>
<span>{`${filterMonthDate.year}-${
filterMonthDate.month < 10
? `0${filterMonthDate.month}`
: filterMonthDate.month
}`}</span>
</div>
<div onClick={() => toggleMonth(ToggleMonth.Next)}>></div>
</div>
);
};
// render-week-title title
const renderWeekTitle = () => {
return (
<div className={styles.weekTitle}>
{new Array(7).fill(1).map((_item: unknown, index: number) => (
<span
key={ZhCnNumberCharactor[index]}
>{`周${ZhCnNumberCharactor[index]}`}</span>
))}
</div>
);
};
// render-calender-table 渲染日历表格,我自己通过flex-wrap的换行实现的,没有使用二维数组去渲染
const renderCalenderTable = () => {
return (
<div className={styles.tableContent}>
{tableData.map((item: DateItem, index: number) => (
<div
key={index}
className={classNames([
styles.tableItem,
{
[styles.isOpacity]: item.isLastMonth || item.isNextMonth,
},
])}
>
<span
onClick={() => {
// 这里选中的日期,可能是上个月,这个月,下个月的日期
toggleMonth(item.isLastMonth || item.isNextMonth, item.day);
}}
className={classNames([
styles.tableItemContent,
{
// 判断是否为周末
[styles.isWeekend]: index % 7 === 5 || index % 7 === 6,
// 如果选中的日期是当前年,月,日那么就展示选中
[styles.selected]:
selectedDate ===
`${filterMonthDate.year}/${filterMonthDate.month}/${item.day}` &&
!item.isLastMonth &&
!item.isNextMonth,
},
])}
>
{item.day}
</span>
</div>
))}
</div>
);
};
切换月份 或者 在当前页选中上一个或下一个月的日期
tsx
// 点击前一个月或者后一个月,或者是点击了日期
const toggleMonth = (type?: ToggleMonth, day?: number) => {
// type可选是因为可能点的是这个月的日期,在数据队列中没有给成员定义isNextMonth or isLastMonth
// 下面newYear和newMonth在判断是否是从1月切换到上一年,或者是从12月切换到下一年,type! 表示不为空的type
let newYear = type
? filterMonthDate.month === (type === ToggleMonth.Last ? 1 : 12)
? filterMonthDate.year + type!
: filterMonthDate.year
: filterMonthDate.year;
let newMonth = type
? filterMonthDate.month === (type === ToggleMonth.Last ? 1 : 12)
? type === ToggleMonth.Last
? 12
: 1
: filterMonthDate.month + type!
: filterMonthDate.month;
setFilterMonthDate((prev) =>
Object.assign({}, prev, {
year: newYear,
month: newMonth,
})
);
if (day) {
setSelectedDate(`${newYear}/${newMonth}/${day}`);
console.log(`${newYear}/${newMonth}/${day}`);
}
};
更新日历
和需求分析的第三步一样
tsx
// 当年或月发生改变的时候
useEffect(() => {
let allDaysOnThisMonth = new Date(
filterMonthDate.year,
filterMonthDate.month,
0
).getDate(); // 获取当前年月中的天数
// 获取当月的1号是星期几
const currentDayOfFirstDate = new Date(
`${filterMonthDate.year}/${filterMonthDate.month}/01`
).getDay();
// 计算第一行需要补几天(补上一个月末尾的几天),currentDayOfFirstDate只会取到0-6
let supplyDaysOfLastMonth = 0;
if (currentDayOfFirstDate > 1 && currentDayOfFirstDate <= 6) {
supplyDaysOfLastMonth = currentDayOfFirstDate - 1;
} else {
supplyDaysOfLastMonth = 6;
默认补6天;
}
// 获取上个月的最后一天是几号(获取上个月一共多少天)
let lastDayOflastMonth = new Date(
filterMonthDate.year === 12
? filterMonthDate.year - 1
: filterMonthDate.year,
filterMonthDate.month - 1,
0
).getDate();
// 计算下一个月需要补充多少天(42 - (单月天数+上个月补充的天数))
let supplyDaysOfNextMonth = 42 - (allDaysOnThisMonth + supplyDaysOfLastMonth);
// 组装队列
let queue = [];
// 上一个月
let lastMonthQueue = new Array(supplyDaysOfLastMonth).fill(undefined);
while (supplyDaysOfLastMonth > 0) {
--supplyDaysOfLastMonth;
lastMonthQueue[supplyDaysOfLastMonth] = {
day: lastDayOflastMonth,
isLastMonth: ToggleMonth.Last,
};
lastDayOflastMonth--;
}
// 当前月
let currentMonthQueue = new Array(allDaysOnThisMonth).fill(undefined);
while (allDaysOnThisMonth > 0) {
currentMonthQueue[allDaysOnThisMonth - 1] = {
day: allDaysOnThisMonth,
};
allDaysOnThisMonth--;
}
// 下一个月
let nextMonthQueue = new Array(supplyDaysOfNextMonth).fill(undefined);
while (supplyDaysOfNextMonth > 0) {
nextMonthQueue[supplyDaysOfNextMonth - 1] = {
day: supplyDaysOfNextMonth,
isNextMonth: ToggleMonth.Next,
};
supplyDaysOfNextMonth--;
}
queue = lastMonthQueue.concat(currentMonthQueue, nextMonthQueue);
setTableData(queue);
}, [filterMonthDate.year, filterMonthDate.month]);
渲染组件
tsx
<div className={styles.calender}>
{/* render-header-filter */}
{renderHeaderFilter()}
{/* render-week-title */}
{renderWeekTitle()}
{/* render-calender-table */}
{renderCalenderTable()}
</div>;
样式文件
scss
.calender {
width: 560px;
height: auto
}
.headerFilter{
display: flex;
justify-content: space-between;
padding: 16px 24px;
padding-bottom: 0;
> * {
cursor: pointer;
}
}
.weekTitle{
display: flex;
width: 100%;
> span{
display: inline-block;
width: calc(100% / 7);
text-align: center;
padding: 12px 0;
}
}
.tableContent{
width: 100%;
display: flex;
flex-wrap: wrap;
.tableItem{
width: calc(100% / 7);
text-align: center;
margin-top: 10px;
box-sizing: border-box;
padding: 0 10px 0px 10px;
cursor: pointer;
&.isOpacity{
opacity: 0.3;
}
}
.tableItemContent{
display: flex;
justify-content: center;
align-items: center;
width: 60px;
height: 54px;
padding: 10px 12px;
color: #333;
font-size: 24px;
border-radius: 10px;
font-weight: bold;
box-sizing: border-box;
&:hover{
background-color: rgba($color: #4e6ef2, $alpha: 0.2);
}
&.isWeekend{
color: #eb3333;
}
&.selected {
border: 2px solid #4e6ef2;
background-color: rgba($color: #4e6ef2, $alpha: 0.2);
}
}
}
呈现效果
CodeSandBox
当然这也仅仅是一个基础版的日历,如果需要实现其他效果,还可以进一步封装