自己写一个关于范围选择日历,本篇重点介绍范围选择跟时间(时分)的交互方式。带样式的哦~
如果看不清楚的,更多详细内容可看我历史写的关于日历的文章
上一片文章 React - 自己写一个日历📅 这个是说如何写一个选择日期的组件,并且可以切换时间
上上篇 两年前的 《小程序-日历签到》这个说的是单纯的一个列表然后点击选择
按照惯例,我们先看效果图
Prview
功能介绍
范围选择的时候,出现两个相邻的月份,如果跨月,那么可以通过点击切换月份实现。
比如,我现在选择的是2022年8月,到2023年1月,这时候先选择一个时间,然后切换到2023年1月,这是再点击一个时间,就完成选择。
也就是界面渲染两个相邻月的页面,然后两个月份列表共用一个事件。
具体选择代码如下,特别简单
const [mutiSlectState, __mutiSlectState] = useState(MUTI_SELECT.NOSELECT)
const dateCallback = (d: number) => {
if (mutiSlectState === MUTI_SELECT.NOSELECT) {
__startDate(d)
__mutiSlectState(MUTI_SELECT.START)
}
if (mutiSlectState === MUTI_SELECT.START) {
if (startDate && d > startDate) {
__endDate(d)
__mutiSlectState(MUTI_SELECT.END)
} else {
__startDate(d)
}
}
if (mutiSlectState === MUTI_SELECT.END) {
__startDate(d)
__endDate(void 0)
__mutiSlectState(MUTI_SELECT.START)
}
}
不管再复杂,就三个状态
- 未选择
- 选择一个
- 选择两个
选择一个,就是开始,选择第二个,如果这个时间是合理的,就是选择结束。
选择完成结束,继续选择,那么就是重新选择。
选择,搞定了,那么我们再实现页面的渲染即可。
渲染也好办,先把选中的两个值传进去,选中的高亮。
然后判断区间,选择区间的是什么样式就可以。
小时、分钟
我们想要精确到分钟的话(如果需要)
获取输入的小时和分钟,合并到选择的天那边即可。
如下
const { value: statrTimeHour, View: statrTimeHourInput } = useTimeInput({ max: 24 })
const { value: statrTimeMin, View: statrTimeMinInput } = useTimeInput({ max: 60 })
const { value: endTimeHour, View: endTimeHourInput } = useTimeInput({ max: 24 })
const { value: endTimeMin, View: endTimeMinInput } = useTimeInput({ max: 60 })
useEffect(() => {
const getTimeNum = (h: string, m: string) => {
const SH = (h ? parseInt(h) : 0)
const SM = (m ? parseInt(m) : 0)
return SH * 60 * 60 * 1000 + SM * 60 * 1000
}
const startTime = getTimeNum(statrTimeHour, statrTimeMin)
const endTime = getTimeNum(endTimeHour, endTimeMin)
if (startDate && endDate) {
onChange([new Date(startDate + startTime), new Date(endDate + endTime)])
}
if (startDate && !endDate) {
onChange([new Date(startDate + endTime)])
}
if (!startDate && !endDate) {
onChange([])
}
}, [startDate, endDate, statrTimeHour, statrTimeMin, endTimeHour, endTimeMin])
这里是触发回调的,没次变化都把小时分钟算进去即可。
具体实现
时间列表的获取
使用useDayList就可以获取当月列表
下一个月+1就可以
const { list, year, month, mothsText, onUpMoth, onDownMoth, date, setDate } =
useDayList();
const nextMonthList = {
month: month === 12 ? 1 : month + 1,
year: month === 12 ? year + 1 : year
}
const list2 = useMemo(() => getMothList(nextMonthList.year, nextMonthList.month), [nextMonthList.month, nextMonthList.year])
为什么要多判断12,因为要跨年...
这样 两个列表就好了,渲染就是了。
列表数据怎么来的,有兴趣看历史文章,没兴趣就复制粘贴。
input怎么控制输入合理的值
先判断是不是数字,再判断是否是大于指定最大的数24、60
import { useState } from "react"
export const useTimeInput = ({
max,
}: {
max: number;
}) => {
const [inputValue, __inputValue] = useState('')
return {
value: inputValue,
View: <>
<input placeholder="00" value={inputValue} style={{
width: '27px', background: 'transparent', outline: 'none', borderRadius: '3px', color: '#9BA3AF', borderBottom: '0px solid #464646', padding: '5px 6px',
}}
onInput={function (e) {
const value = (e.target as any)?.value
if (value === '') {
return __inputValue("")
}
const res = verifyNum(value, max)
if (res > 0) {
__inputValue(`${res}`)
}
}}
/>
</>
}
}
function verifyNum(v: string, n: number) {
const ns = '0123456789'.split('')
for (const token of v.split('')) {
if (!ns.includes(token)) {
return -1
}
}
if (parseInt(v) <= n && parseInt(v) > 0) {
return +v
}
return -2
}
上面代码发现是一个hooks而不是组件,这样有一个好处是用的时候不需要去写onchange...
值本身动态改动
选中两个时间中间样式怎么写
const isDateStart = startDate ? formatDate(new Date(item.date)) === formatDate(new Date(startDate)) : false
const isDateEnd = endDate ? formatDate(new Date(item.date)) === formatDate(new Date(endDate)) : false
const isCheckedDay = isDateStart || isDateEnd
const isPeriod = startDate && endDate && startDate <= item.date && endDate >= item.date
<Box sx={{
...classes.dayItem,
...isToday ? classes.currentDay : {},
...isCheckedDay ? classes.checkedDay : {},
...isNotSelect ? classes.disableDay : {},
}}>{item?.number}</Box>
再加样式即可
源码分享
先写一个入口文件
import { Box, ClickAwayListener, IconButton, Typography } from '@mui/material';
import { SVGProps, useEffect, useMemo, useState } from 'react';
import Button from '../Button/button';
import { getMothList } from './dayListTool';
import { useDayList } from './hooks/useDayList';
import { DateMonthList, formatDate } from './List';
import { useTimeInput } from './timeInput';
import { dayListStyles } from './useDayListStyles';
enum MUTI_SELECT {
NOSELECT,
START,
END,
}
export const DayListSelect: React.FC<{
onChange: (day: Date[]) => void;
Footer?: JSX.Element;
}> = ({ onChange, Footer }) => {
const classes = dayListStyles;
const { list, year, month, mothsText, onUpMoth, onDownMoth, date, setDate } =
useDayList();
const nextMonthList = {
month: month === 12 ? 1 : month + 1,
year: month === 12 ? year + 1 : year
}
const list2 = useMemo(() => getMothList(nextMonthList.year, nextMonthList.month), [nextMonthList.month, nextMonthList.year])
const [startDate, __startDate] = useState<number>()
const [endDate, __endDate] = useState<number>()
const [mutiSlectState, __mutiSlectState] = useState(MUTI_SELECT.NOSELECT)
const dateCallback = (d: number) => {
if (mutiSlectState === MUTI_SELECT.NOSELECT) {
__startDate(d)
__mutiSlectState(MUTI_SELECT.START)
}
if (mutiSlectState === MUTI_SELECT.START) {
if (startDate && d > startDate) {
__endDate(d)
__mutiSlectState(MUTI_SELECT.END)
} else {
__startDate(d)
}
}
if (mutiSlectState === MUTI_SELECT.END) {
__startDate(d)
__endDate(void 0)
__mutiSlectState(MUTI_SELECT.START)
}
}
const { value: statrTimeHour, View: statrTimeHourInput } = useTimeInput({ max: 24 })
const { value: statrTimeMin, View: statrTimeMinInput } = useTimeInput({ max: 60 })
const { value: endTimeHour, View: endTimeHourInput } = useTimeInput({ max: 24 })
const { value: endTimeMin, View: endTimeMinInput } = useTimeInput({ max: 60 })
useEffect(() => {
const getTimeNum = (h: string, m: string) => {
const SH = (h ? parseInt(h) : 0)
const SM = (m ? parseInt(m) : 0)
return SH * 60 * 60 * 1000 + SM * 60 * 1000
}
const startTime = getTimeNum(statrTimeHour, statrTimeMin)
const endTime = getTimeNum(endTimeHour, endTimeMin)
if (startDate && endDate) {
onChange([new Date(startDate + startTime), new Date(endDate + endTime)])
}
if (startDate && !endDate) {
onChange([new Date(startDate + endTime)])
}
if (!startDate && !endDate) {
onChange([])
}
}, [startDate, endDate, statrTimeHour, statrTimeMin, endTimeHour, endTimeMin])
return (
<Box sx={classes.root}>
<Box style={{ display: 'flex', justifyContent: 'space-between' }}>
<Box sx={classes.valueBox}>
<Box sx={classes.valueText}>{'Start date & time'}</Box>
<Box sx={classes.valueInput}>
<CalendarIcon />
<span style={classes.valueInputText}>{startDate ? formatDate(new Date(startDate)) : 'Start Date'}</span>
</Box>
</Box>
<Box sx={classes.valueBox}>
<Box sx={classes.valueText}>{'End date & time'}</Box>
<Box sx={classes.valueInput}>
<CalendarIcon />
<span style={classes.valueInputText}>{endDate ? formatDate(new Date(endDate)) : 'End Date'}</span>
</Box>
</Box>
</Box>
<Box>
<Box style={{ display: 'flex', justifyContent: 'space-between', marginTop: 16 }}>
<Box display="flex" alignItems="center" sx={{ ...classes.valueInput }}>
<CalendarTimeIcon />
<Box>
{statrTimeHourInput}
<span style={{ fontSize: 14 }}>Hour</span>
{statrTimeMinInput}
<span style={{ fontSize: 14 }}>Min</span>
</Box>
</Box>
<Box display="flex" alignItems="center" sx={{ ...classes.valueInput }}>
<CalendarTimeIcon />
<Box>
{endTimeHourInput}
<span style={{ fontSize: 14 }}>Hour</span>
{endTimeMinInput}
<span style={{ fontSize: 14 }}>Min</span>
</Box>
</Box>
</Box>
</Box>
<Box sx={classes.dateContainer}>
<Box sx={classes.boxs}>
<Box sx={classes.dateContent}>
<Box sx={classes.calendarPopup}>
<Box sx={classes.calendarHead}>
<IconButton
sx={classes.iconButton}
onClick={onDownMoth}
>
<ArrowLeftIcon />
</IconButton>
<Typography>
{mothsText[month - 1]} {year}
</Typography>
<div></div>
</Box>
<DateMonthList startDate={startDate} endDate={endDate} list={list} today={date} onChange={dateCallback} />
</Box>
</Box>
<Box sx={classes.dateContent}>
<Box sx={classes.calendarPopup}>
<Box sx={classes.calendarHead}>
<div></div>
<Typography>
{mothsText[nextMonthList.month - 1]} {nextMonthList.year}
</Typography>
<IconButton sx={classes.iconButton} onClick={onUpMoth}>
<ArrowRightIcon />
</IconButton>
</Box>
<DateMonthList startDate={startDate} endDate={endDate} list={list2} today={date} onChange={dateCallback} />
</Box>
</Box>
</Box>
</Box>
{Footer}
</Box>
);
};
const ArrowLeftIcon: React.FC<SVGProps<SVGSVGElement>> = props => {
return (
<svg
width="8"
height="14"
viewBox="0 0 8 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M6.66797 1.66797L1.33463 7.0013L6.66797 12.3346"
stroke="#3F3F3F"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
const ArrowRightIcon: React.FC<SVGProps<SVGSVGElement>> = props => {
return (
<svg
width="8"
height="14"
viewBox="0 0 8 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M1.33203 1.66797L6.66537 7.0013L1.33203 12.3346"
stroke="#3F3F3F"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
const CircleArrowLeftIcon: React.FC<SVGProps<SVGSVGElement>> = props => {
return (
<svg
width="36"
height="36"
viewBox="0 0 36 36"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<circle cx="18" cy="18" r="17.5" fill="white" stroke="#C8C8C8" />
<path
d="M16.7071 11L18.1213 12.4142L13.828 16.707L27 16.7071V18.7071L13.828 18.707L18.1213 23L16.7071 24.4142L10 17.7071L16.7071 11Z"
fill="#3F3F3F"
/>
</svg>
);
};
const CircleArrowRightIcon: React.FC<SVGProps<SVGSVGElement>> = props => {
return (
<svg
width="36"
height="36"
viewBox="0 0 36 36"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="18"
cy="18"
r="17.5"
transform="rotate(-180 18 18)"
fill="white"
stroke="#C8C8C8"
/>
<path
d="M19.2929 25L17.8787 23.5858L22.172 19.293L9 19.2929V17.2929L22.172 17.293L17.8787 13L19.2929 11.5858L26 18.2929L19.2929 25Z"
fill="#3F3F3F"
/>
</svg>
);
};
const CalendarIcon: React.FC<SVGProps<SVGSVGElement>> = props => {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.49999 3.0998H3.99999V2.5998V1.6998C3.99999 1.59372 4.04213 1.49198 4.11715 1.41696C4.19216 1.34195 4.2939 1.2998 4.39999 1.2998C4.50607 1.2998 4.60782 1.34195 4.68283 1.41696C4.75785 1.49198 4.79999 1.59372 4.79999 1.6998V2.5998V3.0998H5.29999H10.7H11.2V2.5998V1.6998C11.2 1.59372 11.2421 1.49198 11.3171 1.41696C11.3922 1.34195 11.4939 1.2998 11.6 1.2998C11.7061 1.2998 11.8078 1.34195 11.8828 1.41696C11.9578 1.49198 12 1.59372 12 1.6998V2.5998V3.0998H12.5H13.4C13.7448 3.0998 14.0754 3.23677 14.3192 3.48057C14.563 3.72436 14.7 4.05502 14.7 4.3998V13.3998C14.7 13.7446 14.563 14.0752 14.3192 14.319C14.0754 14.5628 13.7448 14.6998 13.4 14.6998H2.59999C2.25521 14.6998 1.92455 14.5628 1.68075 14.319C1.43695 14.0752 1.29999 13.7446 1.29999 13.3998V4.3998C1.29999 4.05502 1.43695 3.72436 1.68075 3.48057C1.92455 3.23677 2.25521 3.0998 2.59999 3.0998H3.49999ZM4.39999 4.7998C4.02868 4.7998 3.67259 4.9473 3.41004 5.20986C3.14749 5.47241 2.99999 5.8285 2.99999 6.1998C2.99999 6.57111 3.14749 6.9272 3.41004 7.18975C3.67259 7.4523 4.02868 7.5998 4.39999 7.5998H11.6C11.9713 7.5998 12.3274 7.4523 12.5899 7.18975C12.8525 6.9272 13 6.57111 13 6.1998C13 5.8285 12.8525 5.47241 12.5899 5.20986C12.3274 4.9473 11.9713 4.7998 11.6 4.7998H4.39999Z" fill="#9CA2B5" stroke="#9CA2B5" />
</svg>
);
};
const CalendarTimeIcon: React.FC<SVGProps<SVGSVGElement>> = props => {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.59999 7.9998V7.99991C6.60007 8.37118 6.74762 8.72722 7.01019 8.98971L9.55527 11.5357L9.55534 11.5358C9.68538 11.6658 9.83977 11.769 10.0097 11.8393C10.1796 11.9097 10.3617 11.946 10.5456 11.946C10.7296 11.946 10.9117 11.9097 11.0816 11.8393C11.2515 11.769 11.4059 11.6658 11.5359 11.5358C11.666 11.4057 11.7692 11.2513 11.8395 11.0814C11.9099 10.9115 11.9461 10.7294 11.9461 10.5455C11.9461 10.3615 11.9099 10.1794 11.8395 10.0095C11.7691 9.83959 11.666 9.6852 11.5359 9.55515L11.5359 9.55508L9.39999 7.42004V4.3998C9.39999 4.0285 9.25249 3.67241 8.98994 3.40986C8.72739 3.1473 8.37129 2.9998 7.99999 2.9998C7.62868 2.9998 7.27259 3.1473 7.01004 3.40986C6.74749 3.67241 6.59999 4.0285 6.59999 4.3998V7.9998ZM12.7376 12.7374C11.4811 13.9939 9.77694 14.6998 7.99999 14.6998C6.22304 14.6998 4.51887 13.9939 3.26237 12.7374C2.00588 11.4809 1.29999 9.77676 1.29999 7.9998C1.29999 6.22285 2.00588 4.51868 3.26237 3.26219C4.51887 2.0057 6.22304 1.2998 7.99999 1.2998C9.77694 1.2998 11.4811 2.0057 12.7376 3.26219C13.9941 4.51868 14.7 6.22285 14.7 7.9998C14.7 9.77676 13.9941 11.4809 12.7376 12.7374Z" fill="#9CA2B5" stroke="#9CA2B5" />
</svg>
);
};
然后一个样式
export const dayListStyles = {
root: {
width: '642px',
background: '#171717',
borderRadius: '12px',
// display: 'flex',
padding: '32px 38px',
boxSizing: 'border-box',
margin: '0px auto',
marginTop: '32px',
} as React.CSSProperties,
dateContainer: {
position: 'relative',
display: 'inline-block',
userSelect: 'none',
color: '#fff',
fontFamily: `'DM Sans'`,
background: '#171717',
marginTop: 32,
border: '1px solid #464646',
borderRadius: '8px',
},
boxs: {
display: 'flex',
filter: 'drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25))',
},
iconButton: {
border: 0,
padding: 0,
'&:hover': {
border: 0,
},
},
circleArrowLeft: {
position: 'absolute',
left: 30,
top: 0,
},
circleArrowRight: {
position: 'absolute',
right: 30,
top: 0,
},
disabledBtn: {
opacity: 0.3,
},
dateContent: {
},
valueBox: {
},
valueText: {
fontSize: 16,
fontweight: 500,
marginBottom: 8,
lineHeight: '24px',
},
valueInput: {
border: '1px solid #464646',
borderRadius: '8px',
background: '#242427',
height: 48,
padding: '0px 12px',
boxSizing: 'border-box',
color: '#9BA3AF',
fontSize: 14,
fontweight: 500,
display: 'flex',
alignItems: 'center',
width: 260,
},
valueInputText: {
marginLeft: 10,
},
calendarPopup: {
marginTop: 8,
width: 224,
margin: '16px 30px',
},
calendarHead: {
padding: '0 0px 12px 0px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
'& .MuiTypography-root': {
fontSize: 16,
fontWeight: 500,
},
'& button': {
width: 20,
height: 20,
},
},
weeksBox: {
display: 'flex',
justifyContent: 'space-evenly',
alignItems: 'center',
},
weekItem: {
position: 'relative',
lineHeight: '32px',
textAlign: 'center',
color: '#9BA3AF',
fontWeight: 500,
fontSize: 12,
},
rowDay: {
display: 'flex',
justifyContent: 'space-evenly',
alignItems: 'center',
},
dayItemBox: {
width: '32px',
height: '32px',
position: 'relative',
cursor: 'pointer',
fontSize: 12,
},
dayItem: {
position: 'absolute',
top: 0,
left: 0,
width: '32px',
height: '32px',
lineHeight: '32px',
textAlign: 'center',
borderRadius: '50%',
fontWeight: 500,
fontSize: 12,
boxSizing: 'border-box',
'&:hover': {
background: '#18E16A',
color: '#fff',
},
},
currentDay: {
border: '1px solid #3F3F3F',
},
checkedDay: {
background: '#18E16A!important',
},
disableDay: {
color: 'rgba(255,255,255, 0.3)',
cursor: 'not-allowed',
'&:hover': {
background: 'none',
color: 'rgba(255,255,255, 0.3)',
},
},
period: {
background: '#052E15',
},
periodStart: {
borderTopLeftRadius: '50%',
borderBottomLeftRadius: '50%',
},
periodEnd: {
borderTopRightRadius: '50%',
borderBottomRightRadius: '50%',
},
}
然后具体渲染的列表组件
List.tsx
import { dayListStyles as classes } from './useDayListStyles';
import { Box, ClickAwayListener, IconButton, Typography } from '@mui/material';
export const DateMonthList = ({
onChange,
list,
today,
startDate,
endDate,
}: {
onChange: (d: number) => void;
list: {
date: number;
number: number;
}[][],
today: Date;
startDate?: number;
endDate?: number;
}) => {
return <>
<Box sx={classes.weeksBox}>
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((e, key) => {
return (
<Box
sx={classes.weekItem}
key={key}
style={{
display: 'inline-block',
width: 32,
height: 32,
}}
>
{e}
</Box>
);
})}
</Box>
<Box style={{ display: 'grid', gridRowGap: '2px' }}>
{list.map((row, index) => {
return (
<Box sx={classes.rowDay} key={index}>
{row.map((item, key) => {
const style = {
display: 'inline-block',
width: 32,
height: 32,
};
if (!item) {
return (
<Box
key={key}
style={{ ...style, opacity: 0 }}
sx={classes.dayItemBox}
>
0
</Box>
);
}
const isToday =
formatDate(new Date(item.date)) ===
// formatDate(new Date())
formatDate(today)
const isDateStart = startDate ? formatDate(new Date(item.date)) === formatDate(new Date(startDate)) : false
const isDateEnd = endDate ? formatDate(new Date(item.date)) === formatDate(new Date(endDate)) : false
const isCheckedDay = isDateStart || isDateEnd
const isPeriod = startDate && endDate && startDate <= item.date && endDate >= item.date
// const isNotSelect = item.date > new Date().getTime();
const isNotSelect = item.date < (new Date().getTime() - (86400 * 1000))
return (
<Box
key={key}
onClick={() => {
if (isNotSelect) return;
onChange(item.date);
}}
sx={{
...classes.dayItemBox,
...isPeriod ? classes.period : {},
...isDateStart ? classes.periodStart : {},
...isDateEnd ? classes.periodEnd : {},
}}
>
<Box sx={{
...classes.dayItem,
...isToday ? classes.currentDay : {},
...isCheckedDay ? classes.checkedDay : {},
...isNotSelect ? classes.disableDay : {},
}}>{item?.number}</Box>
</Box>
);
})}
</Box>
);
})}
</Box>
</>
}
export const formatDate = (v: Date) => {
return `${v.getFullYear()}-${v.getMonth() + 1}-${v.getDate()}`;
};
然后输入组件我们说过了,在上面也就是timeInput.tsx 然后就是两个辅助js
dayListTool.ts
const _mothZh = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'].map(e => e + '月')
const _mothEn = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
export const mothsText = true ? _mothEn : _mothZh
// eslint-disable-next-line
const getMaxY = (y: number) => Boolean(y % 4 === 0 && y % 100 !== 0 || y % 400 === 0)
const getAugNum = (n: number) => getMaxY(n) ? 29 : 28
const getMothNum = (y: number) => ([31, getAugNum(y), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31])
export const getMothList = (year: number, month: number) => {
var star = new Date(Date.UTC(year, month - 1, 1)).getDay()
let mn = getMothNum(year)[month - 1]
var row: { date: number; number: number }[] = []
var res: (typeof row)[] = []
new Array(6 * 7)
.fill('')
.map((_, i) => i - star + 1)
.map(e =>
(e > 0 && e <= mn)
? ({
date: new Date(year, month - 1, e).getTime(),
number: e
})
: (null)
)
.forEach((item, i) => {
row.push(JSON.parse(JSON.stringify(item)))
if ((i + 1) % 7 === 0) {
if (row.map(e => e || '').join('') !== '') {
res.push(row)
}
row = []
}
})
return res
}
day.ts
这个代码也来源于我之前的文章,更多见JS -- 获取倒计时相差值的几种方式
export const diffTime = (time: number | Date, now?: number | Date) => {
try {
const diffM = (new Date(time).getTime() - new Date(now || new Date()).getTime()) / 1000 / 60
const D = diffM / 60 / 24
const H = (D - parseInt(D.toString())) * 24
const m = (H - parseInt(H.toString())) * 60
return { D: parseInt(D.toString()), H: parseInt(H.toString()), m: parseInt(m.toString()) }
} catch (error) {
return {D: 0, H: 0, m: 0}
}
}
然后一个hooks
import { useMemo, useState } from "react"
import { getMothList, mothsText } from "../dayListTool"
const today = new Date()
export const useDayList = () => {
const [date, setDate] = useState(today)
const [month, setMonth] = useState(date.getMonth() + 1)
const [year, setYear] = useState(date.getFullYear())
const list = useMemo(() => getMothList(year, month), [month, year])
const onUpdateMoth = (_month: number, _year: number) => {
if(_month > 0 && _month <= 12){
setMonth(_month)
setYear(_year)
return
}
if(_month < 1){
setMonth(12)
setYear(_year - 1)
return
}
setMonth(1)
setYear(_year + 1)
}
const onUpMoth = () => onUpdateMoth(month + 1, year)
const onDownMoth = () => onUpdateMoth(month - 1, year)
return {
list,
year,
month,
onUpMoth,
onDownMoth,
mothsText,
date,
setDate,
// other
onUpdateMoth,
}
}
至此,都完成啦~~
完
纯原创,如有帮助请点赞 hhhh
后面会考虑出组件库,和市场上的不一样的是
分两种使用方式,一种是只提供 js的处理,页面自己渲染,但是js会处理很多便捷排版等工具,也可以不使用。 一种是带UI,然后部分地方自定义渲染。
夜已深
晚安😴