import { HeaderBar } from '@/components/ui/HeaderBar'; import { useAppSelector } from '@/hooks/redux'; import { useMoodData } from '@/hooks/useMoodData'; import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding'; import { getMoodOptions } from '@/services/moodCheckins'; import { selectLatestMoodRecordByDate } from '@/store/moodSlice'; import dayjs from 'dayjs'; import { LinearGradient } from 'expo-linear-gradient'; import { router, useFocusEffect, useLocalSearchParams } from 'expo-router'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Dimensions, Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; const { width } = Dimensions.get('window'); // 心情日历数据生成函数 const generateCalendarData = (targetDate: Date) => { // 使用 dayjs 确保时区一致性 const targetDayjs = dayjs(targetDate); const year = targetDayjs.year(); const month = targetDayjs.month(); // dayjs month is 0-based const daysInMonth = targetDayjs.daysInMonth(); // 使用 dayjs 获取月初第一天是周几(0=周日,1=周一...6=周六) const firstDayOfWeek = targetDayjs.startOf('month').day(); // 转换为中国习惯(周一为一周开始):周日(0)转为6,其他减1 const firstDayAdjusted = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1; const calendar = []; const weeks = []; // 添加空白日期(基于周一开始) for (let i = 0; i < firstDayAdjusted; i++) { weeks.push(null); } // 添加实际日期 for (let day = 1; day <= daysInMonth; day++) { weeks.push(day); } // 按周分组 for (let i = 0; i < weeks.length; i += 7) { calendar.push(weeks.slice(i, i + 7)); } // 使用 dayjs 获取今天的日期,确保时区一致 const today = dayjs(); return { calendar, today: today.date(), month: month + 1, // 转回1-based用于显示 year }; }; export default function MoodCalendarScreen() { const safeAreaTop = useSafeAreaTop() const params = useLocalSearchParams(); const { fetchMoodRecords, fetchMoodHistoryRecords } = useMoodData(); // 使用 useRef 来存储函数引用,避免依赖循环 const fetchMoodRecordsRef = useRef(fetchMoodRecords); const fetchMoodHistoryRecordsRef = useRef(fetchMoodHistoryRecords); // 更新 ref 值 fetchMoodRecordsRef.current = fetchMoodRecords; fetchMoodHistoryRecordsRef.current = fetchMoodHistoryRecords; const { selectedDate } = params; const initialDate = selectedDate ? dayjs(selectedDate as string).toDate() : new Date(); const [currentMonth, setCurrentMonth] = useState(initialDate); const [selectedDay, setSelectedDay] = useState(null); // 使用 Redux store 中的数据 const moodRecords = useAppSelector(state => state.mood.moodRecords); // 获取选中日期的数据 const selectedDateMood = useAppSelector(state => { if (!selectedDay) return null; const selectedDateString = dayjs(currentMonth).date(selectedDay).format('YYYY-MM-DD'); return selectLatestMoodRecordByDate(selectedDateString)(state); }); const moodOptions = getMoodOptions(); const weekDays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']; const monthNames = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']; // 生成当前月份的日历数据 const { calendar, today, month, year } = generateCalendarData(currentMonth); // 加载整个月份的心情数据 const loadMonthMoodData = useCallback(async (targetMonth: Date) => { try { const startDate = dayjs(targetMonth).startOf('month').format('YYYY-MM-DD'); const endDate = dayjs(targetMonth).endOf('month').format('YYYY-MM-DD'); await fetchMoodHistoryRecordsRef.current({ startDate, endDate }); } catch (error) { console.error('加载月份心情数据失败:', error); } }, []); // 加载选中日期的心情记录 const loadDailyMoodCheckins = useCallback(async (dateString: string) => { try { await fetchMoodRecordsRef.current(dateString); } catch (error) { console.error('加载心情记录失败:', error); } }, []); // 初始化选中日期 useEffect(() => { if (selectedDate) { const date = dayjs(selectedDate as string); setCurrentMonth(date.toDate()); setSelectedDay(date.date()); const dateString = date.format('YYYY-MM-DD'); loadDailyMoodCheckins(dateString); loadMonthMoodData(date.toDate()); } else { const today = dayjs().toDate(); setCurrentMonth(today); setSelectedDay(dayjs().date()); const dateString = dayjs().format('YYYY-MM-DD'); loadDailyMoodCheckins(dateString); loadMonthMoodData(today); } }, [selectedDate, loadDailyMoodCheckins, loadMonthMoodData]); // 监听页面焦点变化,当从编辑页面返回时刷新数据 useFocusEffect( useCallback(() => { // 当页面获得焦点时,刷新当前月份的数据和选中日期的数据 const refreshData = async () => { if (selectedDay) { const selectedDateString = dayjs(currentMonth).date(selectedDay).format('YYYY-MM-DD'); await fetchMoodRecordsRef.current(selectedDateString); } const startDate = dayjs(currentMonth).startOf('month').format('YYYY-MM-DD'); const endDate = dayjs(currentMonth).endOf('month').format('YYYY-MM-DD'); await fetchMoodHistoryRecordsRef.current({ startDate, endDate }); }; refreshData(); }, [currentMonth, selectedDay]) ); // 月份切换函数 const goToPreviousMonth = () => { const newMonth = dayjs(currentMonth).subtract(1, 'month').toDate(); setCurrentMonth(newMonth); setSelectedDay(null); loadMonthMoodData(newMonth); }; const goToNextMonth = () => { const newMonth = dayjs(currentMonth).add(1, 'month').toDate(); setCurrentMonth(newMonth); setSelectedDay(null); loadMonthMoodData(newMonth); }; // 日期选择函数 const onSelectDate = (day: number) => { setSelectedDay(day); const selectedDateString = dayjs(currentMonth).date(day).format('YYYY-MM-DD'); loadDailyMoodCheckins(selectedDateString); }; // 跳转到心情编辑页面 const openMoodEdit = () => { const selectedDateString = selectedDay ? dayjs(currentMonth).date(selectedDay).format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD'); const moodId = selectedDateMood?.id; router.push({ pathname: '/mood/edit', params: { date: selectedDateString, ...(moodId && { moodId }) } }); }; const renderMoodRing = (day: number | null, isSelected: boolean) => { if (!day) return null; // 检查该日期是否有心情记录 - 现在从 Redux store 中获取 const dayDateString = dayjs(currentMonth).date(day).format('YYYY-MM-DD'); const dayRecords = moodRecords[dayDateString] || []; const moodRecord = dayRecords.length > 0 ? dayRecords[0] : null; const isToday = day === dayjs().date() && month === dayjs().month() + 1 && year === dayjs().year(); if (moodRecord) { const mood = moodOptions.find(m => m.type === moodRecord.moodType); return ( ); } return ( ); }; return ( {/* 装饰性圆圈 */} router.back()} withSafeTop={false} transparent={true} tone="light" /> {/* 日历视图 */} {/* 月份导航 */} {year}年{monthNames[month - 1]} {weekDays.map((day, index) => ( {day} ))} {calendar.map((week, weekIndex) => ( {week.map((day, dayIndex) => { const isSelected = day === selectedDay; const isToday = day === today && month === dayjs().month() + 1 && year === dayjs().year(); const isFutureDate = Boolean(day && dayjs(currentMonth).date(day).isAfter(dayjs(), 'day')); return ( {day && ( !isFutureDate && day && onSelectDate(day)} disabled={isFutureDate} > {day.toString().padStart(2, '0')} {renderMoodRing(day, isSelected)} )} ); })} ))} {/* 选中日期的记录 */} {selectedDay ? dayjs(currentMonth).date(selectedDay).format('YYYY年M月D日') : '请选择日期'} 记录 {selectedDay ? ( selectedDateMood ? ( m.type === selectedDateMood.moodType)?.image} style={styles.moodIconImage} /> {moodOptions.find(m => m.type === selectedDateMood.moodType)?.label} 强度: {selectedDateMood.intensity} {selectedDateMood.description && ( {selectedDateMood.description} )} {dayjs(selectedDateMood.createdAt).format('HH:mm')} ) : ( 暂无心情记录 点击右上角"记录"按钮添加心情 ) ) : ( 请先选择一个日期 点击日历中的日期,然后点击"记录"按钮添加心情 )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, gradientBackground: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }, decorativeCircle1: { position: 'absolute', top: 40, right: 20, width: 60, height: 60, borderRadius: 30, backgroundColor: '#7a5af8', opacity: 0.08, }, decorativeCircle2: { position: 'absolute', bottom: -15, left: -15, width: 40, height: 40, borderRadius: 20, backgroundColor: '#7a5af8', opacity: 0.04, }, safeArea: { flex: 1, }, content: { flex: 1, }, calendar: { backgroundColor: 'rgba(255,255,255,0.95)', margin: 16, borderRadius: 20, justifyContent: 'center', alignItems: 'center', padding: 20, shadowColor: '#7a5af8', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 12, elevation: 6, }, monthNavigation: { flexDirection: 'row', width: '100%', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24, }, navButton: { width: 44, height: 44, borderRadius: 22, backgroundColor: 'rgba(122,90,248,0.1)', justifyContent: 'center', alignItems: 'center', shadowColor: '#7a5af8', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 2, }, navButtonText: { fontSize: 24, color: '#7a5af8', fontWeight: '700', }, monthTitle: { fontSize: 20, fontWeight: '800', color: '#192126', }, weekHeader: { flexDirection: 'row', justifyContent: 'flex-start', marginBottom: 20, }, weekDay: { fontSize: 13, color: '#5d6676', textAlign: 'center', width: (width - 96) / 7, fontWeight: '600', }, weekRow: { flexDirection: 'row', justifyContent: 'flex-start', marginBottom: 16, }, dayContainer: { width: (width - 96) / 7, alignItems: 'center', }, dayButton: { width: 44, height: 44, borderRadius: 22, justifyContent: 'center', alignItems: 'center', marginBottom: 8, backgroundColor: 'transparent', }, dayButtonSelected: { backgroundColor: '#FFFFFF', shadowColor: '#7a5af8', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.15, shadowRadius: 6, elevation: 4, }, dayButtonToday: { borderWidth: 2, borderColor: '#7a5af8', }, dayContent: { position: 'relative', width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center', }, dayNumber: { fontSize: 14, color: '#777f8c', fontWeight: '600', position: 'absolute', top: 2, zIndex: 1, }, dayNumberSelected: { color: '#192126', fontWeight: '700', }, dayNumberToday: { color: '#7a5af8', fontWeight: '700', }, dayNumberDisabled: { color: '#c0c4ca', }, moodIconContainer: { position: 'absolute', bottom: 2, width: 22, height: 22, borderRadius: 11, justifyContent: 'center', alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 2, elevation: 1, }, todayMoodIconContainer: { position: 'absolute', bottom: 1, width: 20, height: 20, borderRadius: 10, justifyContent: 'center', alignItems: 'center', shadowColor: '#7a5af8', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.2, shadowRadius: 2, elevation: 2, }, moodIcon: { width: 18, height: 18, borderRadius: 9, backgroundColor: 'rgba(255,255,255,0.95)', justifyContent: 'center', alignItems: 'center', }, moodIconImage: { width: 28, height: 28, borderRadius: 9, }, defaultMoodIcon: { position: 'absolute', bottom: 2, width: 22, height: 22, borderRadius: 11, borderWidth: 1.5, borderColor: 'rgba(122,90,248,0.3)', borderStyle: 'dashed', justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(122,90,248,0.05)', }, todayDefaultMoodIcon: { position: 'absolute', bottom: 1, width: 20, height: 20, borderRadius: 10, borderWidth: 1.5, borderColor: 'rgba(122,90,248,0.4)', borderStyle: 'dashed', justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(122,90,248,0.08)', }, moodRingContainer: { position: 'absolute', bottom: 2, width: 22, height: 22, justifyContent: 'center', alignItems: 'center', }, moodIntensityText: { fontSize: 8, fontWeight: '800', textAlign: 'center', position: 'absolute', zIndex: 1, textShadowColor: 'rgba(0,0,0,0.3)', textShadowOffset: { width: 0, height: 0.5 }, textShadowRadius: 1, }, selectedDateSection: { backgroundColor: 'rgba(255,255,255,0.95)', margin: 16, marginTop: 0, borderRadius: 20, padding: 20, shadowColor: '#7a5af8', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 12, elevation: 6, }, selectedDateHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, }, selectedDateTitle: { fontSize: 22, fontWeight: '800', color: '#192126', }, addMoodButton: { paddingHorizontal: 20, height: 36, borderRadius: 18, backgroundColor: '#7a5af8', justifyContent: 'center', alignItems: 'center', shadowColor: '#7a5af8', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, elevation: 3, }, addMoodButtonText: { color: '#fff', fontSize: 14, fontWeight: '700', }, moodRecord: { flexDirection: 'row', alignItems: 'flex-start', paddingVertical: 16, backgroundColor: 'rgba(122,90,248,0.05)', borderRadius: 16, paddingHorizontal: 16, }, recordIcon: { width: 52, height: 52, borderRadius: 26, backgroundColor: '#e9e7f1ff', justifyContent: 'center', alignItems: 'center', marginRight: 16, shadowColor: '#7a5af8', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.15, shadowRadius: 4, elevation: 2, }, recordContent: { flex: 1, }, recordMood: { fontSize: 18, color: '#192126', fontWeight: '700', marginBottom: 4, }, recordIntensity: { fontSize: 14, color: '#5d6676', marginTop: 2, fontWeight: '500', }, recordDescription: { fontSize: 14, color: '#5d6676', marginTop: 6, fontStyle: 'italic', lineHeight: 20, }, spacer: { flex: 1, }, recordTime: { fontSize: 14, color: '#777f8c', fontWeight: '500', }, emptyRecord: { alignItems: 'center', paddingVertical: 32, }, emptyRecordText: { fontSize: 16, color: '#5d6676', marginBottom: 8, fontWeight: '600', }, emptyRecordSubtext: { fontSize: 13, color: '#777f8c', textAlign: 'center', lineHeight: 18, }, });