From eb7aeb586b85e99d06ca8e7a4fcfb1c3cbc62451 Mon Sep 17 00:00:00 2001 From: ddshi <8811906+ddshi@user.noreply.gitee.com> Date: Sat, 28 Feb 2026 10:37:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E7=BA=AA=E5=BF=B5?= =?UTF-8?q?=E6=97=A5=E5=86=9C=E5=8E=86=E6=97=A5=E6=9C=9F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复农历年度重复的计算bug:正确使用农历月/日查找对应公历日期 - 增强 FixedCalendar:显示农历日期(初一、十五等特殊日期) - 增强 AnniversaryCard:显示详细农历信息(如"正月十五") Co-Authored-By: Claude Opus 4.6 --- .../anniversary/AnniversaryCard.tsx | 15 +++++- src/components/common/FixedCalendar.tsx | 49 ++++++++++++++++++- src/utils/countdown.ts | 32 ++++++++++-- 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/src/components/anniversary/AnniversaryCard.tsx b/src/components/anniversary/AnniversaryCard.tsx index 0a76f3c..a6047e0 100644 --- a/src/components/anniversary/AnniversaryCard.tsx +++ b/src/components/anniversary/AnniversaryCard.tsx @@ -1,8 +1,9 @@ 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 { calculateCountdown } from '../../utils/countdown'; import { getHolidayById } from '../../constants/holidays'; +import { getLunarInfo } from '../../utils/lunar'; interface AnniversaryCardProps { event: Event; @@ -19,7 +20,17 @@ export function AnniversaryCard({ event, onClick }: AnniversaryCardProps) { const getNextDateText = () => { if (countdown.isPast) 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颜色 diff --git a/src/components/common/FixedCalendar.tsx b/src/components/common/FixedCalendar.tsx index 77fc10f..dbbf184 100644 --- a/src/components/common/FixedCalendar.tsx +++ b/src/components/common/FixedCalendar.tsx @@ -9,6 +9,7 @@ import { IconChevronLeft, IconChevronRight, } from '@tabler/icons-react'; +import { Lunar } from 'lunar-javascript'; interface FixedCalendarProps { value: Date | null; @@ -45,6 +46,32 @@ function isDateInRange(date: Date, min?: Date, max?: Date): boolean { const WEEKDAYS = ['日', '一', '二', '三', '四', '五', '六']; 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 = { + 1: '初一', + 15: '十五', + 20: '廿', + 25: '廿五', + 30: '卅', + }; + if (specialDays[day]) { + return specialDays[day]; + } + return lunar.getDayInChinese(); + } catch { + return ''; + } +} + export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalendarProps) { const [currentMonth, setCurrentMonth] = useState(value || new Date()); @@ -62,30 +89,35 @@ export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalend isToday: boolean; isSelected: boolean; isDisabled: boolean; + lunarText?: string; // 农历日期简写 }> = []; // 上月剩余的天数 const prevMonthDays = getDaysInMonth(new Date(year, month - 1, 1)); for (let i = firstDay - 1; i >= 0; i--) { const date = new Date(year, month - 1, prevMonthDays - i); + const lunarText = getLunarDayText(date); days.push({ date, isCurrentMonth: false, isToday: isSameDay(date, new Date()), isSelected: isSameDay(date, value), isDisabled: !isDateInRange(date, minDate, maxDate), + lunarText, }); } // 当月的天数 for (let i = 1; i <= daysInMonth; i++) { const date = new Date(year, month, i); + const lunarText = getLunarDayText(date); days.push({ date, isCurrentMonth: true, isToday: isSameDay(date, new Date()), isSelected: isSameDay(date, value), isDisabled: !isDateInRange(date, minDate, maxDate), + lunarText, }); } @@ -93,12 +125,14 @@ export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalend const remainingDays = 42 - days.length; for (let i = 1; i <= remainingDays; i++) { const date = new Date(year, month + 1, i); + const lunarText = getLunarDayText(date); days.push({ date, isCurrentMonth: false, isToday: isSameDay(date, new Date()), isSelected: isSameDay(date, value), isDisabled: !isDateInRange(date, minDate, maxDate), + lunarText, }); } @@ -200,6 +234,7 @@ export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalend flex: 1, aspectRatio: '1', display: 'flex', + flexDirection: 'column', alignItems: 'center', justifyContent: 'center', fontSize: 13, @@ -233,7 +268,19 @@ export function FixedCalendar({ value, onChange, minDate, maxDate }: FixedCalend } }} > - {day.date.getDate()} + {day.date.getDate()} + {day.lunarText && day.isCurrentMonth && !day.isDisabled && ( + + {day.lunarText} + + )} ); })} diff --git a/src/utils/countdown.ts b/src/utils/countdown.ts index 46bf6c9..f7c6ae6 100644 --- a/src/utils/countdown.ts +++ b/src/utils/countdown.ts @@ -75,10 +75,17 @@ export function calculateCountdown( const originalHours = parseInt(timeParts[0]) || 0; const originalMinutes = parseInt(timeParts[1]) || 0; + // 保存农历月份和日期(用于年度重复计算) + let lunarMonth: number | null = null; + let lunarDay: number | null = null; + if (isLunar) { // 农历日期:使用安全方法创建,处理月末边界 const result = safeCreateLunarDate(originalYear, originalMonth + 1, originalDay); 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); } else { // 无法解析农历日期,使用原始公历日期作为后备 @@ -94,12 +101,27 @@ export function calculateCountdown( // 计算下一个 occurrence if (repeatType === 'yearly') { - // 年度重复:找到今年或明年的对应日期 - targetDate = safeCreateDate(today.getFullYear(), targetDate.getMonth(), targetDate.getDate()); - targetDate.setHours(originalHours, originalMinutes, 0, 0); - if (targetDate < today) { - targetDate = safeCreateDate(today.getFullYear() + 1, targetDate.getMonth(), targetDate.getDate()); + 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.setHours(originalHours, originalMinutes, 0, 0); + if (targetDate < today) { + targetDate = safeCreateDate(today.getFullYear() + 1, targetDate.getMonth(), targetDate.getDate()); + targetDate.setHours(originalHours, originalMinutes, 0, 0); + } } } else if (repeatType === 'monthly') { // 月度重复:找到本月或之后月份的对应日期