import React, { useReducer, useCallback, useMemo } from 'react'; import classnames from 'classnames'; import dayjs from 'dayjs'; import { formatNumber } from '@jy-fe/utils'; import CalendarHeader from './xui-calendar-head'; import CalendarBody from './xui-calendar-body'; import CalendarFooter from './xui-calendar-footer'; import { CalendarProps, InitialStateType } from './xui-calendar.d'; const displayDaysPerMonth = (year: number) => { // 定义每个月的天数,如果是闰年第二月改为29天 const daysInMonth: number[] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) { daysInMonth[1] = 29; } // 以下为了获取一年中每一个月在日历选择器上显示的数据, // 从上个月开始,接着是当月,最后是下个月开头的几天 // 定义一个数组,保存上一个月的天数 const daysInPreviousMonth: number[] = ([] as number[]).concat(daysInMonth); daysInPreviousMonth.unshift(daysInPreviousMonth.pop() || 31); // 获取每一个月显示数据中需要补足上个月的天数 const addDaysFromPreMonth = new Array(12).fill(null).map((item, index) => { const day = new Date(year, index, 1).getDay(); if (day === 0) { return 6; } return day - 1; }); // 已数组形式返回一年中每个月的显示数据,每个数据为6行*7天 return new Array(12).fill([]).map((month, monthIndex) => { let addDays = addDaysFromPreMonth[monthIndex]; const daysCount = daysInMonth[monthIndex]; let daysCountPrevious = daysInPreviousMonth[monthIndex]; const monthData: number[] = []; // 补足上一个月 for (; addDays > 0; addDays--) { monthData.unshift(daysCountPrevious--); } // 添入当前月 for (let i = 0; i < daysCount; i++) { monthData.push(i + 1); } // 补足下一个月 for (let i = 42 - monthData.length, j = 0; j < i; j++) { monthData.push(j + 1); } return monthData; }); }; const monthArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; const monthMap = { 1: '一月', 2: '二月', 3: '三月', 4: '四月', 5: '五月', 6: '六月', 7: '七月', 8: '八月', 9: '九月', 10: '十月', 11: '十一月', 12: '十二月', }; const now = new Date(); const initialState: InitialStateType = { yearRangeIndex: 0, year: now.getFullYear(), month: now.getMonth(), day: now.getDate(), dates: [], selectYear: false, selectMonth: false, showYearQuickSelect: false, showMonthQuickSelect: false, }; const reducer = ( state: InitialStateType, action: { type: string; payload?: { [key: string]: any }; }, ) => { switch (action.type) { case 'update': return { ...state, ...action.payload }; default: throw new Error(); } }; const Index: React.FC = ({ className: wrapperClassName = '', style = {}, defaultDates = [], selectYear: selectYearProp = true, selectMonth: selectMonthProp = true, multiple = false, minDate = '', maxDate = '', onDayClick, }) => { /** 格式化dates时间格式 */ const datesFormat = useCallback((dates: (string | number)[] | undefined) => { const formatDates: string[] = []; if (dates && dates.length > 0) { dates.forEach((date: string | number) => { if (typeof date === 'string') { formatDates.push(dayjs(date).format('YYYY-MM-DD')); } if (typeof date === 'number') { formatDates.push(dayjs(date * 1000).format('YYYY-MM-DD')); } }); } return formatDates; }, []); const [state, hookDispatch] = useReducer(reducer, { ...initialState, dates: datesFormat(defaultDates), selectYear: selectYearProp, selectMonth: selectMonthProp, } as InitialStateType); const { yearRangeIndex, year, month, day, dates, selectYear, selectMonth, showYearQuickSelect, showMonthQuickSelect, } = state; const triggerDispatch = useCallback( (payload: Partial) => { hookDispatch({ type: 'update', payload, }); }, [state], ); /** 处理返回的字符串dateString */ const dateToString = useCallback((currentDates: string[]) => { const newDates = currentDates.sort(); const diffArr: string[][] = []; let diffIndex = 0; let newIndex = 1; newDates.forEach((item, i) => { if (Number(i) === 0) { diffArr[diffIndex] = [item, item]; return; } const d = new Date(item); const newDate = d.setDate(d.getDate() - newIndex); if ( dayjs(dayjs(newDate).format('YYYY-MM-DD')).unix() === dayjs(diffArr[diffIndex][0]).unix() ) { diffArr[diffIndex][1] = item; newIndex += 1; return; } diffIndex += 1; diffArr[diffIndex] = [item, item]; newIndex = 1; }); const formatMD = (d: string) => dayjs(d).format('MM-DD'); const dateString = diffArr .map((item: string[]) => { if (item[0] === item[1]) { return formatMD(item[0]); } return `${formatMD(item[0])}至${formatMD(item[1])}`; }) .join(','); return dateString; }, []); /** 触发onDayClick */ const recordDatesCallback = useCallback((currentDates: string[]) => { const dateString = dateToString(currentDates); if (onDayClick) { onDayClick({ dates: currentDates, dateString }); } }, []); /** 记录所有选中的日期 */ const recordDates = useCallback( (dateString: string) => { let currentDates = dates.slice(); if (multiple) { if (currentDates.indexOf(dateString) > -1) { currentDates.splice(currentDates.indexOf(dateString), 1); } else { currentDates.push(dateString); } } else { currentDates = [dateString]; } triggerDispatch({ dates: currentDates, }); recordDatesCallback(currentDates); }, [multiple, dates], ); /** 点击年份快速选择 */ const handleSelectYear = useCallback(() => { triggerDispatch({ showYearQuickSelect: !showYearQuickSelect, yearRangeIndex: 0, }); }, [showYearQuickSelect]); /** 点击月份快速选择 */ const handleSelectMonth = useCallback(() => { triggerDispatch({ showMonthQuickSelect: !showMonthQuickSelect, yearRangeIndex: 0, }); }, [showMonthQuickSelect]); /** 选择年份 */ const changeYear = useCallback((currentYear: number) => { triggerDispatch({ year: currentYear, showYearQuickSelect: false, yearRangeIndex: 0, }); }, []); /** 选择月份 */ const changeMonth = useCallback((currentMonth: number) => { triggerDispatch({ month: currentMonth, showMonthQuickSelect: false, }); }, []); /** 选择日期 */ const datePick = useCallback( (currentDay: number, currentYear?: number, currentMonth?: number) => { triggerDispatch({ day: currentDay, }); const dateString = `${currentYear || year}-${formatNumber( (currentMonth || currentMonth === 0 ? currentMonth : month) + 1, )}-${formatNumber(currentDay)}`; recordDates(dateString); }, [year, month, dates], ); /** 切换上一年范围 */ const prevYearQuick = useCallback(() => { triggerDispatch({ yearRangeIndex: yearRangeIndex - 1, }); }, [yearRangeIndex]); /** 切换下一年范围 */ const nextYearQuick = useCallback(() => { triggerDispatch({ yearRangeIndex: yearRangeIndex + 1, }); }, [yearRangeIndex]); /** 切换到上一年 */ const prevYear = useCallback(() => { triggerDispatch({ year: year - 1, }); }, [year]); /** 切换到下一年 */ const nextYear = useCallback(() => { triggerDispatch({ year: year + 1, }); }, [year]); /** 切换上一个月 */ const prevMonth = useCallback( (currentDay?: number) => { let currentYear = year; let currentMonth = month; if (month === 0) { currentYear = year - 1; currentMonth = 11; triggerDispatch({ year: currentYear, month: currentMonth, }); } else { currentMonth = month - 1; triggerDispatch({ month: currentMonth, }); } if (currentDay || currentDay === 0) { datePick(currentDay, currentYear, currentMonth); } }, [year, month], ); /** 切换下一个月 */ const nextMonth = useCallback( (currentDay?: number) => { let currentYear = year; let currentMonth = month; if (month === 11) { currentYear = year + 1; currentMonth = 0; triggerDispatch({ year: currentYear, month: currentMonth, }); } else { currentMonth = month + 1; triggerDispatch({ month: currentMonth, }); } if (currentDay || currentDay === 0) { datePick(currentDay, currentYear, currentMonth); } }, [year, month], ); /** 获取年份选择区间 */ const getYearSelectRange = useMemo(() => { const rangeStartYear = Math.floor(year / 12) * 12 + yearRangeIndex * 12; const yearArray: { index: number; year: number; isSelect: boolean }[] = []; for (let i = 0; i < 12; i++) { yearArray.push({ index: i, year: rangeStartYear + i, isSelect: rangeStartYear + i === year, }); } return yearArray; }, [year, yearRangeIndex]); const props = { minDate, maxDate, viewData: displayDaysPerMonth(year), datePicked: `${year} 年 ${month + 1} 月 ${day} 日`, }; const className = 'xui-ant-calendar'; const yearArray = getYearSelectRange; return (
{showYearQuickSelect && (
{yearArray.map((item: { index: number; year: number; isSelect: boolean }) => ( changeYear(item.year)} > {item.year} ))}
)} {showMonthQuickSelect && (
{monthArray.map((item: number) => ( changeMonth(item - 1)} > {monthMap[item]} ))}
)}
); }; export default Index;