import { DateSelector } from '@/components/DateSelector'; import { FloatingSelectionModal, SelectionItem } from '@/components/ui/FloatingSelectionModal'; import { HeaderBar } from '@/components/ui/HeaderBar'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useAuthGuard } from '@/hooks/useAuthGuard'; import { fetchCircumferenceAnalysis, selectCircumferenceData, selectCircumferenceError, selectCircumferenceLoading } from '@/store/circumferenceSlice'; import { selectUserProfile, updateUserBodyMeasurements, UserProfile } from '@/store/userSlice'; import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date'; import dayjs from 'dayjs'; import weekOfYear from 'dayjs/plugin/weekOfYear'; import { LinearGradient } from 'expo-linear-gradient'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, Dimensions, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { LineChart } from 'react-native-chart-kit'; dayjs.extend(weekOfYear); // 围度类型数据 const CIRCUMFERENCE_TYPES = [ { key: 'chestCircumference', label: '胸围', color: '#FF6B6B' }, { key: 'waistCircumference', label: '腰围', color: '#4ECDC4' }, { key: 'upperHipCircumference', label: '上臀围', color: '#45B7D1' }, { key: 'armCircumference', label: '臂围', color: '#96CEB4' }, { key: 'thighCircumference', label: '大腿围', color: '#FFEAA7' }, { key: 'calfCircumference', label: '小腿围', color: '#DDA0DD' }, ]; import { CircumferencePeriod } from '@/services/circumferenceAnalysis'; type TabType = CircumferencePeriod; export default function CircumferenceDetailScreen() { const dispatch = useAppDispatch(); const userProfile = useAppSelector(selectUserProfile); const { ensureLoggedIn } = useAuthGuard(); // 日期相关状态 const [selectedIndex, setSelectedIndex] = useState(getTodayIndexInMonth()); const [activeTab, setActiveTab] = useState('week'); // 弹窗状态 const [modalVisible, setModalVisible] = useState(false); const [selectedMeasurement, setSelectedMeasurement] = useState<{ key: string; label: string; currentValue?: number; } | null>(null); // Redux状态 const chartData = useAppSelector(state => selectCircumferenceData(state, activeTab)); const isLoading = useAppSelector(state => selectCircumferenceLoading(state, activeTab)); const error = useAppSelector(selectCircumferenceError); console.log('chartData', chartData); // 图例显示状态 - 控制哪些维度显示在图表中 const [visibleTypes, setVisibleTypes] = useState>( new Set(CIRCUMFERENCE_TYPES.map(type => type.key)) ); // 获取当前选中日期 const currentSelectedDate = useMemo(() => { const days = getMonthDaysZh(); return days[selectedIndex]?.date?.toDate() ?? new Date(); }, [selectedIndex]); // 判断选中日期是否是今天 const isSelectedDateToday = useMemo(() => { const today = new Date(); const selectedDate = currentSelectedDate; return dayjs(selectedDate).isSame(today, 'day'); }, [currentSelectedDate]); // 当前围度数据 const measurements = [ { key: 'chestCircumference', label: '胸围', value: userProfile?.chestCircumference, color: '#FF6B6B', }, { key: 'waistCircumference', label: '腰围', value: userProfile?.waistCircumference, color: '#4ECDC4', }, { key: 'upperHipCircumference', label: '上臀围', value: userProfile?.upperHipCircumference, color: '#45B7D1', }, { key: 'armCircumference', label: '臂围', value: userProfile?.armCircumference, color: '#96CEB4', }, { key: 'thighCircumference', label: '大腿围', value: userProfile?.thighCircumference, color: '#FFEAA7', }, { key: 'calfCircumference', label: '小腿围', value: userProfile?.calfCircumference, color: '#DDA0DD', }, ]; // 日期选择回调 const onSelectDate = (index: number) => { setSelectedIndex(index); }; // Tab切换 const handleTabPress = useCallback((tab: TabType) => { setActiveTab(tab); // 切换tab时重新获取数据 dispatch(fetchCircumferenceAnalysis(tab)); }, [dispatch]); // 初始化加载数据 useEffect(() => { dispatch(fetchCircumferenceAnalysis(activeTab)); }, [dispatch, activeTab]); // 处理图例点击,切换显示/隐藏 const handleLegendPress = (typeKey: string) => { const newVisibleTypes = new Set(visibleTypes); if (newVisibleTypes.has(typeKey)) { // 至少保留一个维度显示 if (newVisibleTypes.size > 1) { newVisibleTypes.delete(typeKey); } } else { newVisibleTypes.add(typeKey); } setVisibleTypes(newVisibleTypes); }; // 根据不同围度类型获取合理的默认值 const getDefaultCircumferenceValue = (measurementKey: string, userProfile?: UserProfile): number => { // 如果用户已有该围度数据,直接使用 const existingValue = userProfile?.[measurementKey as keyof UserProfile] as number; if (existingValue) { return existingValue; } // 根据性别设置合理的默认值 const isMale = userProfile?.gender === 'male'; switch (measurementKey) { case 'chestCircumference': // 胸围:男性 85-110cm,女性 75-95cm return isMale ? 95 : 80; case 'waistCircumference': // 腰围:男性 70-90cm,女性 60-80cm return isMale ? 80 : 70; case 'upperHipCircumference': // 上臀围: return 30; case 'armCircumference': // 臂围:男性 25-35cm,女性 20-30cm return isMale ? 30 : 25; case 'thighCircumference': // 大腿围:男性 45-60cm,女性 40-55cm return isMale ? 50 : 45; case 'calfCircumference': // 小腿围:男性 30-40cm,女性 25-35cm return isMale ? 35 : 30; default: return 70; // 默认70cm } }; // Generate circumference options (30-150 cm) const circumferenceOptions: SelectionItem[] = Array.from({ length: 121 }, (_, i) => { const value = i + 30; return { label: `${value} cm`, value: value, }; }); // 处理围度数据点击 const handleMeasurementPress = async (measurement: typeof measurements[0]) => { // 只有选中今天日期才能编辑 if (!isSelectedDateToday) { return; } const isLoggedIn = await ensureLoggedIn(); if (!isLoggedIn) { // 如果未登录,用户会被重定向到登录页面 return; } // 使用智能默认值,如果用户已有数据则使用现有数据,否则使用基于性别的合理默认值 const defaultValue = getDefaultCircumferenceValue(measurement.key, userProfile); setSelectedMeasurement({ key: measurement.key, label: measurement.label, currentValue: measurement.value || defaultValue, }); setModalVisible(true); }; // 处理围度数据更新 const handleUpdateMeasurement = (value: string | number) => { if (!selectedMeasurement) return; const updateData = { [selectedMeasurement.key]: Number(value), }; dispatch(updateUserBodyMeasurements(updateData)); setModalVisible(false); setSelectedMeasurement(null); }; // 处理图表数据 const processedChartData = useMemo(() => { if (!chartData || chartData.length === 0) { return { labels: [], datasets: [] }; } // 根据activeTab生成标签 const labels = chartData.map(item => { switch (activeTab) { case 'week': // 将YYYY-MM-DD格式转换为星期几 const weekDay = dayjs(item.label).format('dd'); return weekDay; case 'month': // 将YYYY-MM-DD格式转换为第几周 const weekOfYear = dayjs(item.label).week(); const firstWeekOfMonth = dayjs(item.label).startOf('month').week(); return `第${weekOfYear - firstWeekOfMonth + 1}周`; case 'year': // 将YYYY-MM格式转换为月份 return dayjs(item.label).format('M月'); default: return item.label; } }); // 为每个可见的围度类型生成数据集 const datasets: any[] = []; CIRCUMFERENCE_TYPES.forEach((type) => { if (visibleTypes.has(type.key)) { const data = chartData.map(item => { const value = item[type.key as keyof typeof item] as number | null; return value || 0; // null值转换为0,图表会自动处理 }); // 只有数据中至少有一个非零值才添加到数据集 if (data.some(value => value > 0)) { datasets.push({ data, color: () => type.color, strokeWidth: 2, }); } } }); return { labels, datasets }; }, [chartData, activeTab, visibleTypes]); return ( {/* 背景渐变 */} {/* 头部导航 */} {/* 日期选择器 */} {/* 当前日期围度数据 */} {measurements.map((measurement, index) => ( handleMeasurementPress(measurement)} activeOpacity={isSelectedDateToday ? 0.7 : 1} disabled={!isSelectedDateToday} > {measurement.label} {measurement.value ? measurement.value.toString() : '--'} ))} {/* 围度统计 */} 围度统计 {/* Tab 切换 */} handleTabPress('week')} activeOpacity={0.7} > 按周 handleTabPress('month')} activeOpacity={0.7} > 按月 handleTabPress('year')} activeOpacity={0.7} > 按年 {/* 图例 - 支持点击切换显示/隐藏 */} {CIRCUMFERENCE_TYPES.map((type, index) => { const isVisible = visibleTypes.has(type.key); return ( handleLegendPress(type.key)} activeOpacity={0.7} > {type.label} ); })} {/* 折线图 */} {isLoading ? ( 加载中... ) : error ? ( 加载失败: {error} dispatch(fetchCircumferenceAnalysis(activeTab))} activeOpacity={0.7} > 重试 ) : processedChartData.datasets.length > 0 ? ( `rgba(100, 100, 100, ${opacity * 0.8})`, labelColor: (opacity = 1) => `rgba(60, 60, 60, ${opacity})`, style: { borderRadius: 16, }, propsForDots: { r: "3", strokeWidth: "2", stroke: "#ffffff" }, propsForBackgroundLines: { strokeDasharray: "2,2", stroke: "#E0E0E0", strokeWidth: 1 }, }} bezier style={styles.chart} /> ) : ( {processedChartData.datasets.length === 0 && !isLoading && !error ? '暂无数据' : '请选择要显示的围度数据' } )} {/* 围度编辑弹窗 */} { setModalVisible(false); setSelectedMeasurement(null); }} title={selectedMeasurement ? `设置${selectedMeasurement.label}` : '设置围度'} items={circumferenceOptions} selectedValue={selectedMeasurement?.currentValue} onValueChange={() => { }} // Real-time update not needed onConfirm={handleUpdateMeasurement} confirmButtonText="确认" pickerHeight={180} /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#FFFFFF', }, gradientBackground: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }, scrollView: { flex: 1, }, dateContainer: { marginTop: 16, marginBottom: 20, }, currentDataCard: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, marginBottom: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 4, }, shadowOpacity: 0.12, shadowRadius: 12, elevation: 6, }, currentDataTitle: { fontSize: 16, fontWeight: '700', color: '#192126', marginBottom: 16, textAlign: 'center', }, measurementsContainer: { flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap', }, measurementItem: { alignItems: 'center', width: '16%', marginBottom: 12, }, measurementItemDisabled: { opacity: 0.6, }, colorIndicator: { width: 12, height: 12, borderRadius: 6, marginBottom: 4, }, label: { fontSize: 12, color: '#888', marginBottom: 8, textAlign: 'center', }, valueContainer: { backgroundColor: '#F5F5F7', borderRadius: 10, paddingHorizontal: 8, paddingVertical: 6, minWidth: 32, alignItems: 'center', justifyContent: 'center', }, value: { fontSize: 14, fontWeight: '600', color: '#192126', textAlign: 'center', }, statsCard: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 4, }, shadowOpacity: 0.12, shadowRadius: 12, elevation: 6, }, statsTitle: { fontSize: 16, fontWeight: '700', color: '#192126', marginBottom: 16, }, tabContainer: { flexDirection: 'row', backgroundColor: '#F5F5F7', borderRadius: 12, padding: 4, marginBottom: 20, }, tab: { flex: 1, paddingVertical: 8, paddingHorizontal: 16, borderRadius: 8, alignItems: 'center', }, activeTab: { backgroundColor: '#FFFFFF', shadowColor: '#000', shadowOffset: { width: 0, height: 1, }, shadowOpacity: 0.1, shadowRadius: 2, elevation: 2, }, tabText: { fontSize: 14, fontWeight: '600', color: '#888', }, activeTabText: { color: '#192126', }, legendContainer: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 16, gap: 6, }, legendItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: 4, paddingHorizontal: 8, borderRadius: 8, }, legendItemHidden: { opacity: 0.5, }, legendColor: { width: 4, height: 4, borderRadius: 4, marginRight: 4, }, legendText: { fontSize: 10, color: '#666', fontWeight: '500', }, legendTextHidden: { color: '#999', }, chart: { marginVertical: 8, borderRadius: 16, }, emptyChart: { height: 220, alignItems: 'center', justifyContent: 'center', backgroundColor: '#F8F9FA', borderRadius: 16, marginVertical: 8, }, emptyChartText: { fontSize: 14, color: '#999', fontWeight: '500', }, loadingChart: { height: 220, alignItems: 'center', justifyContent: 'center', backgroundColor: '#F8F9FA', borderRadius: 16, marginVertical: 8, }, loadingText: { fontSize: 14, color: '#666', marginTop: 8, fontWeight: '500', }, errorChart: { height: 220, alignItems: 'center', justifyContent: 'center', backgroundColor: '#FFF5F5', borderRadius: 16, marginVertical: 8, padding: 20, }, errorText: { fontSize: 14, color: '#E53E3E', textAlign: 'center', marginBottom: 12, }, retryButton: { backgroundColor: '#4ECDC4', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 8, }, retryText: { color: '#FFFFFF', fontSize: 14, fontWeight: '600', }, });