feat: 添加心情记录功能

在统计页面新增心情卡片和弹窗组件,支持用户记录和查看每日心情状态。
This commit is contained in:
richarjiang
2025-08-21 15:34:47 +08:00
parent b93a863e25
commit a7607e0f74
4 changed files with 656 additions and 10 deletions

View File

@@ -1,6 +1,7 @@
import { AnimatedNumber } from '@/components/AnimatedNumber';
import { BMICard } from '@/components/BMICard';
import { FitnessRingsCard } from '@/components/FitnessRingsCard';
import { MoodModal } from '@/components/MoodModal';
import { NutritionRadarCard } from '@/components/NutritionRadarCard';
import { ProgressBar } from '@/components/ProgressBar';
import { StressMeter } from '@/components/StressMeter';
@@ -88,7 +89,7 @@ export default function ExploreScreen() {
const { pushIfAuthedElseLogin, isLoggedIn } = useAuthGuard();
// 使用 dayjs当月日期与默认选中今天
// 使用 dayjs当月日期与默认选中"今天"
const days = getMonthDaysZh();
const [selectedIndex, setSelectedIndex] = useState(getTodayIndexInMonth());
const tabBarHeight = useBottomTabBarHeight();
@@ -161,11 +162,29 @@ export default function ExploreScreen() {
const [nutritionSummary, setNutritionSummary] = useState<NutritionSummary | null>(null);
const [isNutritionLoading, setIsNutritionLoading] = useState(false);
// 记录最近一次请求的“日期键”,避免旧请求覆盖新结果
// 心情相关状态
const [moodModalVisible, setMoodModalVisible] = useState(false);
const [moodRecords, setMoodRecords] = useState<Array<{ mood: string, date: string, time: string }>>([]);
// 记录最近一次请求的"日期键",避免旧请求覆盖新结果
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 newRecord = {
mood,
date: dateString,
time
};
setMoodRecords(prev => [newRecord, ...prev]);
setMoodModalVisible(false);
};
const loadHealthData = async (targetDate?: Date) => {
try {
@@ -288,7 +307,6 @@ export default function ExploreScreen() {
}
};
// 使用统一的渐变背景色
const backgroundGradientColors = [colorTokens.backgroundGradientStart, colorTokens.backgroundGradientEnd] as const;
@@ -404,6 +422,26 @@ export default function ExploreScreen() {
showLabel={false}
/>
</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>
</FloatingCard>
</View>
{/* 右列 */}
@@ -413,7 +451,6 @@ export default function ExploreScreen() {
weight={userProfile?.weight ? parseFloat(userProfile.weight) : undefined}
height={userProfile?.height ? parseFloat(userProfile.height) : undefined}
style={styles.bmiCardOverride}
// compact={true}
/>
</FloatingCard>
@@ -441,15 +478,19 @@ export default function ExploreScreen() {
<Text style={styles.sleepValue}></Text>
)}
</FloatingCard>
</View>
</View>
</ScrollView>
</SafeAreaView>
{/* 心情弹窗 */}
<MoodModal
visible={moodModalVisible}
onClose={() => setMoodModalVisible(false)}
onSave={handleMoodSave}
/>
</View>
);
}
@@ -580,7 +621,6 @@ const styles = StyleSheet.create({
trainingCard: {
backgroundColor: '#EEE9FF',
},
cardTitleSecondary: {
color: '#9AA3AE',
fontSize: 10,
@@ -823,4 +863,49 @@ const styles = StyleSheet.create({
top: 0,
padding: 4,
},
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,
},
});