feat: 完善纪念日农历日期功能
- 修复农历年度重复的计算bug:正确使用农历月/日查找对应公历日期 - 增强 FixedCalendar:显示农历日期(初一、十五等特殊日期) - 增强 AnniversaryCard:显示详细农历信息(如"正月十五") Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e7b6864b42
commit
eb7aeb586b
@ -1,8 +1,9 @@
|
|||||||
import { Paper, Text, Group, Stack, Badge } from '@mantine/core';
|
import { Paper, Text, Group, Stack, Badge } from '@mantine/core';
|
||||||
import { IconRepeat, IconCalendar, IconFlag } from '@tabler/icons-react';
|
import { IconRepeat } from '@tabler/icons-react';
|
||||||
import type { Event } from '../../types';
|
import type { Event } from '../../types';
|
||||||
import { calculateCountdown } from '../../utils/countdown';
|
import { calculateCountdown } from '../../utils/countdown';
|
||||||
import { getHolidayById } from '../../constants/holidays';
|
import { getHolidayById } from '../../constants/holidays';
|
||||||
|
import { getLunarInfo } from '../../utils/lunar';
|
||||||
|
|
||||||
interface AnniversaryCardProps {
|
interface AnniversaryCardProps {
|
||||||
event: Event;
|
event: Event;
|
||||||
@ -19,7 +20,17 @@ export function AnniversaryCard({ event, onClick }: AnniversaryCardProps) {
|
|||||||
const getNextDateText = () => {
|
const getNextDateText = () => {
|
||||||
if (countdown.isPast) return '已过';
|
if (countdown.isPast) return '已过';
|
||||||
if (countdown.isToday) return '今天';
|
if (countdown.isToday) return '今天';
|
||||||
return `${countdown.nextDate.getMonth() + 1}月${countdown.nextDate.getDate()}日${isLunar ? ' (农历)' : ''}`;
|
|
||||||
|
const month = countdown.nextDate.getMonth() + 1;
|
||||||
|
const day = countdown.nextDate.getDate();
|
||||||
|
|
||||||
|
if (isLunar) {
|
||||||
|
// 显示农历信息
|
||||||
|
const lunarInfo = getLunarInfo(countdown.nextDate);
|
||||||
|
return `${lunarInfo.monthInChinese}${lunarInfo.dayInChinese}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${month}月${day}日`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取循环icon颜色
|
// 获取循环icon颜色
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
IconChevronLeft,
|
IconChevronLeft,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
|
import { Lunar } from 'lunar-javascript';
|
||||||
|
|
||||||
interface FixedCalendarProps {
|
interface FixedCalendarProps {
|
||||||
value: Date | null;
|
value: Date | null;
|
||||||
@ -45,6 +46,32 @@ function isDateInRange(date: Date, min?: Date, max?: Date): boolean {
|
|||||||
const WEEKDAYS = ['日', '一', '二', '三', '四', '五', '六'];
|
const WEEKDAYS = ['日', '一', '二', '三', '四', '五', '六'];
|
||||||
const MONTH_NAMES = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
|
const MONTH_NAMES = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
|
||||||
|
|
||||||
|
// 获取农历日期简写(用于日历显示)
|
||||||
|
function getLunarDayText(date: Date): string {
|
||||||
|
try {
|
||||||
|
const lunar = Lunar.fromYmd(date.getFullYear(), date.getMonth() + 1, date.getDate());
|
||||||
|
const day = lunar.getDay();
|
||||||
|
// 初一显示为"初一",其他日期只显示日期数字
|
||||||
|
if (day === 1) {
|
||||||
|
return lunar.getMonthInChinese();
|
||||||
|
}
|
||||||
|
// 初一、十五、廿五等特殊日子
|
||||||
|
const specialDays: Record<number, string> = {
|
||||||
|
1: '初一',
|
||||||
|
15: '十五',
|
||||||
|
20: '廿',
|
||||||
|
25: '廿五',
|
||||||
|
30: '卅',
|
||||||
|
};
|
||||||
|
if (specialDays[day]) {
|
||||||
|
return specialDays[day];
|
||||||
|
}
|
||||||
|
return lunar.getDayInChinese();
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalendarProps) {
|
export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalendarProps) {
|
||||||
const [currentMonth, setCurrentMonth] = useState(value || new Date());
|
const [currentMonth, setCurrentMonth] = useState(value || new Date());
|
||||||
|
|
||||||
@ -62,30 +89,35 @@ export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalend
|
|||||||
isToday: boolean;
|
isToday: boolean;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
|
lunarText?: string; // 农历日期简写
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
// 上月剩余的天数
|
// 上月剩余的天数
|
||||||
const prevMonthDays = getDaysInMonth(new Date(year, month - 1, 1));
|
const prevMonthDays = getDaysInMonth(new Date(year, month - 1, 1));
|
||||||
for (let i = firstDay - 1; i >= 0; i--) {
|
for (let i = firstDay - 1; i >= 0; i--) {
|
||||||
const date = new Date(year, month - 1, prevMonthDays - i);
|
const date = new Date(year, month - 1, prevMonthDays - i);
|
||||||
|
const lunarText = getLunarDayText(date);
|
||||||
days.push({
|
days.push({
|
||||||
date,
|
date,
|
||||||
isCurrentMonth: false,
|
isCurrentMonth: false,
|
||||||
isToday: isSameDay(date, new Date()),
|
isToday: isSameDay(date, new Date()),
|
||||||
isSelected: isSameDay(date, value),
|
isSelected: isSameDay(date, value),
|
||||||
isDisabled: !isDateInRange(date, minDate, maxDate),
|
isDisabled: !isDateInRange(date, minDate, maxDate),
|
||||||
|
lunarText,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当月的天数
|
// 当月的天数
|
||||||
for (let i = 1; i <= daysInMonth; i++) {
|
for (let i = 1; i <= daysInMonth; i++) {
|
||||||
const date = new Date(year, month, i);
|
const date = new Date(year, month, i);
|
||||||
|
const lunarText = getLunarDayText(date);
|
||||||
days.push({
|
days.push({
|
||||||
date,
|
date,
|
||||||
isCurrentMonth: true,
|
isCurrentMonth: true,
|
||||||
isToday: isSameDay(date, new Date()),
|
isToday: isSameDay(date, new Date()),
|
||||||
isSelected: isSameDay(date, value),
|
isSelected: isSameDay(date, value),
|
||||||
isDisabled: !isDateInRange(date, minDate, maxDate),
|
isDisabled: !isDateInRange(date, minDate, maxDate),
|
||||||
|
lunarText,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,12 +125,14 @@ export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalend
|
|||||||
const remainingDays = 42 - days.length;
|
const remainingDays = 42 - days.length;
|
||||||
for (let i = 1; i <= remainingDays; i++) {
|
for (let i = 1; i <= remainingDays; i++) {
|
||||||
const date = new Date(year, month + 1, i);
|
const date = new Date(year, month + 1, i);
|
||||||
|
const lunarText = getLunarDayText(date);
|
||||||
days.push({
|
days.push({
|
||||||
date,
|
date,
|
||||||
isCurrentMonth: false,
|
isCurrentMonth: false,
|
||||||
isToday: isSameDay(date, new Date()),
|
isToday: isSameDay(date, new Date()),
|
||||||
isSelected: isSameDay(date, value),
|
isSelected: isSameDay(date, value),
|
||||||
isDisabled: !isDateInRange(date, minDate, maxDate),
|
isDisabled: !isDateInRange(date, minDate, maxDate),
|
||||||
|
lunarText,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,6 +234,7 @@ export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalend
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
aspectRatio: '1',
|
aspectRatio: '1',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@ -233,7 +268,19 @@ export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalend
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{day.date.getDate()}
|
<span>{day.date.getDate()}</span>
|
||||||
|
{day.lunarText && day.isCurrentMonth && !day.isDisabled && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 9,
|
||||||
|
color: day.isSelected ? 'rgba(255,255,255,0.8)' : '#FF9500',
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: 1.2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{day.lunarText}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -75,10 +75,17 @@ export function calculateCountdown(
|
|||||||
const originalHours = parseInt(timeParts[0]) || 0;
|
const originalHours = parseInt(timeParts[0]) || 0;
|
||||||
const originalMinutes = parseInt(timeParts[1]) || 0;
|
const originalMinutes = parseInt(timeParts[1]) || 0;
|
||||||
|
|
||||||
|
// 保存农历月份和日期(用于年度重复计算)
|
||||||
|
let lunarMonth: number | null = null;
|
||||||
|
let lunarDay: number | null = null;
|
||||||
|
|
||||||
if (isLunar) {
|
if (isLunar) {
|
||||||
// 农历日期:使用安全方法创建,处理月末边界
|
// 农历日期:使用安全方法创建,处理月末边界
|
||||||
const result = safeCreateLunarDate(originalYear, originalMonth + 1, originalDay);
|
const result = safeCreateLunarDate(originalYear, originalMonth + 1, originalDay);
|
||||||
if (result) {
|
if (result) {
|
||||||
|
// 保存农历日期
|
||||||
|
lunarMonth = result.lunar.getMonth();
|
||||||
|
lunarDay = result.lunar.getDay();
|
||||||
targetDate = new Date(result.solar.getYear(), result.solar.getMonth() - 1, result.solar.getDay(), originalHours, originalMinutes);
|
targetDate = new Date(result.solar.getYear(), result.solar.getMonth() - 1, result.solar.getDay(), originalHours, originalMinutes);
|
||||||
} else {
|
} else {
|
||||||
// 无法解析农历日期,使用原始公历日期作为后备
|
// 无法解析农历日期,使用原始公历日期作为后备
|
||||||
@ -94,13 +101,28 @@ export function calculateCountdown(
|
|||||||
|
|
||||||
// 计算下一个 occurrence
|
// 计算下一个 occurrence
|
||||||
if (repeatType === 'yearly') {
|
if (repeatType === 'yearly') {
|
||||||
// 年度重复:找到今年或明年的对应日期
|
if (isLunar && lunarMonth !== null && lunarDay !== null) {
|
||||||
|
// 农历年度重复:根据农历日期查找今年或明年的对应公历日期
|
||||||
|
const thisYearResult = safeCreateLunarDate(today.getFullYear(), lunarMonth, lunarDay);
|
||||||
|
if (thisYearResult) {
|
||||||
|
targetDate = new Date(thisYearResult.solar.getYear(), thisYearResult.solar.getMonth() - 1, thisYearResult.solar.getDay(), originalHours, originalMinutes);
|
||||||
|
}
|
||||||
|
// 如果今年的农历日期已过,查找明年
|
||||||
|
if (!targetDate || targetDate < today) {
|
||||||
|
const nextYearResult = safeCreateLunarDate(today.getFullYear() + 1, lunarMonth, lunarDay);
|
||||||
|
if (nextYearResult) {
|
||||||
|
targetDate = new Date(nextYearResult.solar.getYear(), nextYearResult.solar.getMonth() - 1, nextYearResult.solar.getDay(), originalHours, originalMinutes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 公历年度重复:找到今年或明年的对应日期
|
||||||
targetDate = safeCreateDate(today.getFullYear(), targetDate.getMonth(), targetDate.getDate());
|
targetDate = safeCreateDate(today.getFullYear(), targetDate.getMonth(), targetDate.getDate());
|
||||||
targetDate.setHours(originalHours, originalMinutes, 0, 0);
|
targetDate.setHours(originalHours, originalMinutes, 0, 0);
|
||||||
if (targetDate < today) {
|
if (targetDate < today) {
|
||||||
targetDate = safeCreateDate(today.getFullYear() + 1, targetDate.getMonth(), targetDate.getDate());
|
targetDate = safeCreateDate(today.getFullYear() + 1, targetDate.getMonth(), targetDate.getDate());
|
||||||
targetDate.setHours(originalHours, originalMinutes, 0, 0);
|
targetDate.setHours(originalHours, originalMinutes, 0, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (repeatType === 'monthly') {
|
} else if (repeatType === 'monthly') {
|
||||||
// 月度重复:找到本月或之后月份的对应日期
|
// 月度重复:找到本月或之后月份的对应日期
|
||||||
const originalDay = targetDate.getDate();
|
const originalDay = targetDate.getDate();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user