feat: 更新心情记录功能和界面

- 调整启动画面中的图片宽度,提升视觉效果
- 移除引导页面相关组件,简化应用结构
- 新增心情统计页面,支持用户查看和分析心情数据
- 优化心情卡片组件,增强用户交互体验
- 更新登录页面标题,提升品牌一致性
- 新增心情日历和编辑功能,支持用户记录和管理心情
This commit is contained in:
richarjiang
2025-08-21 17:59:22 +08:00
parent a7607e0f74
commit 72e75b602e
24 changed files with 2964 additions and 1238 deletions

View File

@@ -1,23 +1,25 @@
import { AnimatedNumber } from '@/components/AnimatedNumber';
import { BMICard } from '@/components/BMICard';
import { FitnessRingsCard } from '@/components/FitnessRingsCard';
import { MoodModal } from '@/components/MoodModal';
import { MoodCard } from '@/components/MoodCard';
import { NutritionRadarCard } from '@/components/NutritionRadarCard';
import { ProgressBar } from '@/components/ProgressBar';
import { StressMeter } from '@/components/StressMeter';
import { WeightHistoryCard } from '@/components/WeightHistoryCard';
import { Colors } from '@/constants/Colors';
import { getTabBarBottomPadding } from '@/constants/TabBar';
import { useAppSelector } from '@/hooks/redux';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useColorScheme } from '@/hooks/useColorScheme';
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date';
import { ensureHealthPermissions, fetchHealthDataForDate } from '@/utils/health';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useFocusEffect } from '@react-navigation/native';
import dayjs from 'dayjs';
import { LinearGradient } from 'expo-linear-gradient';
import { router } from 'expo-router';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Animated,
@@ -163,29 +165,45 @@ export default function ExploreScreen() {
const [isNutritionLoading, setIsNutritionLoading] = useState(false);
// 心情相关状态
const [moodModalVisible, setMoodModalVisible] = useState(false);
const [moodRecords, setMoodRecords] = useState<Array<{ mood: string, date: string, time: string }>>([]);
const dispatch = useAppDispatch();
const [isMoodLoading, setIsMoodLoading] = useState(false);
// 从 Redux 获取当前日期的心情记录
const currentMoodCheckin = useAppSelector(selectLatestMoodRecordByDate(
days[selectedIndex]?.date?.format('YYYY-MM-DD') || dayjs().format('YYYY-MM-DD')
));
// 记录最近一次请求的"日期键",避免旧请求覆盖新结果
const latestRequestKeyRef = useRef<string | null>(null);
const getDateKey = (d: Date) => `${dayjs(d).year()}-${dayjs(d).month() + 1}-${dayjs(d).date()}`;
// 心情保存处理函数
const handleMoodSave = (mood: string, time: string) => {
const today = new Date();
const dateString = `${today.getFullYear()}${today.getMonth() + 1}${today.getDate()}`;
// 加载心情数据
const loadMoodData = async (targetDate?: Date) => {
if (!isLoggedIn) return;
const newRecord = {
mood,
date: dateString,
time
};
try {
setIsMoodLoading(true);
setMoodRecords(prev => [newRecord, ...prev]);
setMoodModalVisible(false);
// 确定要查询的日期:优先使用传入的日期,否则使用当前选中索引对应的日期
let derivedDate: Date;
if (targetDate) {
derivedDate = targetDate;
} else {
derivedDate = days[selectedIndex]?.date?.toDate() ?? new Date();
}
const dateString = dayjs(derivedDate).format('YYYY-MM-DD');
await dispatch(fetchDailyMoodCheckins(dateString));
} catch (error) {
console.error('加载心情数据失败:', error);
} finally {
setIsMoodLoading(false);
}
};
const loadHealthData = async (targetDate?: Date) => {
try {
console.log('=== 开始HealthKit初始化流程 ===');
@@ -290,6 +308,7 @@ export default function ExploreScreen() {
loadHealthData(currentDate);
if (isLoggedIn) {
loadNutritionData(currentDate);
loadMoodData(currentDate);
}
}
}, [selectedIndex])
@@ -303,6 +322,7 @@ export default function ExploreScreen() {
loadHealthData(target);
if (isLoggedIn) {
loadNutritionData(target);
loadMoodData(target);
}
}
};
@@ -424,23 +444,11 @@ export default function ExploreScreen() {
</FloatingCard>
{/* 心情卡片 */}
<FloatingCard style={[styles.masonryCard, styles.moodCard]} delay={1500}>
<TouchableOpacity onPress={() => setMoodModalVisible(true)} style={styles.moodCardContent}>
<View style={styles.cardHeaderRow}>
<View style={styles.moodIconContainer}>
<Text style={styles.moodIcon}>😊</Text>
</View>
<Text style={styles.cardTitle}></Text>
</View>
<Text style={styles.moodSubtitle}></Text>
{moodRecords.length > 0 ? (
<View style={styles.moodPreview}>
<Text style={styles.moodPreviewText}>{moodRecords[0].mood}</Text>
<Text style={styles.moodPreviewTime}>{moodRecords[0].time}</Text>
</View>
) : (
<Text style={styles.moodEmptyText}></Text>
)}
</TouchableOpacity>
<MoodCard
moodCheckin={currentMoodCheckin}
onPress={() => router.push('/mood/calendar')}
isLoading={isMoodLoading}
/>
</FloatingCard>
</View>
@@ -484,13 +492,6 @@ export default function ExploreScreen() {
</View>
</ScrollView>
</SafeAreaView>
{/* 心情弹窗 */}
<MoodModal
visible={moodModalVisible}
onClose={() => setMoodModalVisible(false)}
onSave={handleMoodSave}
/>
</View>
);
}
@@ -866,46 +867,4 @@ const styles = StyleSheet.create({
moodCard: {
backgroundColor: '#F0FDF4',
},
moodCardContent: {
width: '100%',
},
moodIconContainer: {
width: 24,
height: 24,
borderRadius: 8,
backgroundColor: '#DCFCE7',
alignItems: 'center',
justifyContent: 'center',
marginRight: 10,
},
moodIcon: {
fontSize: 14,
},
moodSubtitle: {
fontSize: 12,
color: '#6B7280',
marginTop: 4,
marginBottom: 8,
},
moodPreview: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 4,
},
moodPreviewText: {
fontSize: 14,
color: '#059669',
fontWeight: '600',
},
moodPreviewTime: {
fontSize: 12,
color: '#6B7280',
},
moodEmptyText: {
fontSize: 12,
color: '#9CA3AF',
fontStyle: 'italic',
marginTop: 4,
},
});