import { useMemo, useState, useCallback } from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { Button } from "~/components/ui/button"; import { cn } from "~/lib/utils"; interface RecordingCalendarProps { /** 有录像的日期列表,格式为 "YYYY-MM-DD" */ recordingDates: string[]; /** 当前选中的日期 */ selectedDate: Date; /** 日期选择回调 */ onDateSelect: (date: Date) => void; /** 月份变化回调(用于加载该月的录像统计) */ onMonthChange?: (year: number, month: number) => void; /** 是否加载中 */ isLoading?: boolean; } const WEEKDAYS = ["日", "一", "二", "三", "四", "五", "六"]; /** * 录像日历组件 * 显示月历视图,标记有录像的日期 */ export function RecordingCalendar({ recordingDates, selectedDate, onDateSelect, onMonthChange, isLoading = false, }: RecordingCalendarProps) { const [viewDate, setViewDate] = useState(() => { return new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1); }); // 切换到上个月 const goToPrevMonth = useCallback(() => { setViewDate((prev) => { const newDate = new Date(prev.getFullYear(), prev.getMonth() - 1, 1); onMonthChange?.(newDate.getFullYear(), newDate.getMonth() + 1); return newDate; }); }, [onMonthChange]); // 切换到下个月 const goToNextMonth = useCallback(() => { setViewDate((prev) => { const newDate = new Date(prev.getFullYear(), prev.getMonth() + 1, 1); onMonthChange?.(newDate.getFullYear(), newDate.getMonth() + 1); return newDate; }); }, [onMonthChange]); // 回到今天 const goToToday = useCallback(() => { const today = new Date(); setViewDate(new Date(today.getFullYear(), today.getMonth(), 1)); onDateSelect(today); onMonthChange?.(today.getFullYear(), today.getMonth() + 1); }, [onDateSelect, onMonthChange]); // 生成日历网格 const calendarDays = useMemo(() => { const year = viewDate.getFullYear(); const month = viewDate.getMonth(); // 当月第一天是星期几 const firstDayOfMonth = new Date(year, month, 1).getDay(); // 当月有多少天 const daysInMonth = new Date(year, month + 1, 0).getDate(); // 上个月有多少天 const daysInPrevMonth = new Date(year, month, 0).getDate(); const days: { date: Date; day: number; isCurrentMonth: boolean; isToday: boolean; isSelected: boolean; hasRecording: boolean; }[] = []; // 填充上个月的日期 for (let i = firstDayOfMonth - 1; i >= 0; i--) { const day = daysInPrevMonth - i; const date = new Date(year, month - 1, day); days.push({ date, day, isCurrentMonth: false, isToday: false, isSelected: false, hasRecording: false, }); } // 填充当月日期 const today = new Date(); const todayStr = formatDate(today); const selectedStr = formatDate(selectedDate); for (let day = 1; day <= daysInMonth; day++) { const date = new Date(year, month, day); const dateStr = formatDate(date); days.push({ date, day, isCurrentMonth: true, isToday: dateStr === todayStr, isSelected: dateStr === selectedStr, hasRecording: recordingDates.includes(dateStr), }); } // 填充下个月的日期(补满 6 行) const remainingDays = 42 - days.length; for (let day = 1; day <= remainingDays; day++) { const date = new Date(year, month + 1, day); days.push({ date, day, isCurrentMonth: false, isToday: false, isSelected: false, hasRecording: false, }); } return days; }, [viewDate, selectedDate, recordingDates]); // 格式化日期为 YYYY-MM-DD function formatDate(date: Date): string { const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, "0"); const day = date.getDate().toString().padStart(2, "0"); return `${year}-${month}-${day}`; } return (
{/* 头部:月份导航 */}
{viewDate.getFullYear()}年{viewDate.getMonth() + 1}月
{/* 星期标题 */}
{WEEKDAYS.map((day) => (
{day}
))}
{/* 日期网格 */}
{calendarDays.map((dayInfo, index) => ( ))}
{/* 加载状态 */} {isLoading && (
)}
); } export default RecordingCalendar;