feat(auth): 预加载用户数据并优化登录状态同步
- 在启动屏预加载用户 token 与资料,避免首页白屏 - 新增 rehydrateUserSync 同步注入 Redux,减少异步等待 - 登录页兼容 ERR_REQUEST_CANCELED 取消场景 - 各页面统一依赖 isLoggedIn 判断,移除冗余控制台日志 - 步数卡片与详情页改为实时拉取健康数据,不再缓存至 Redux - 后台任务注册移至顶层,防止重复定义 - 体重记录、HeaderBar 等 UI 细节样式微调
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Animated,
|
||||
StyleSheet,
|
||||
@@ -8,26 +8,52 @@ import {
|
||||
ViewStyle
|
||||
} from 'react-native';
|
||||
|
||||
import { HourlyStepData } from '@/utils/health';
|
||||
import { fetchHourlyStepSamples, fetchStepCount, HourlyStepData } from '@/utils/health';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { AnimatedNumber } from './AnimatedNumber';
|
||||
import dayjs from 'dayjs';
|
||||
// 使用原生View来替代SVG,避免导入问题
|
||||
// import Svg, { Rect } from 'react-native-svg';
|
||||
|
||||
interface StepsCardProps {
|
||||
stepCount: number | null;
|
||||
curDate: Date
|
||||
stepGoal: number;
|
||||
hourlySteps: HourlyStepData[];
|
||||
style?: ViewStyle;
|
||||
onPress?: () => void; // 新增点击事件回调
|
||||
}
|
||||
|
||||
const StepsCard: React.FC<StepsCardProps> = ({
|
||||
stepCount,
|
||||
stepGoal,
|
||||
hourlySteps,
|
||||
curDate,
|
||||
style,
|
||||
onPress
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
||||
const [stepCount, setStepCount] = useState(0)
|
||||
const [hourlySteps, setHourSteps] = useState<HourlyStepData[]>([])
|
||||
|
||||
|
||||
const getStepData = async (date: Date) => {
|
||||
try {
|
||||
logger.info('获取步数数据...');
|
||||
const [steps, hourly] = await Promise.all([
|
||||
fetchStepCount(date),
|
||||
fetchHourlyStepSamples(date)
|
||||
])
|
||||
|
||||
setStepCount(steps)
|
||||
setHourSteps(hourly)
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取步数数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (curDate) {
|
||||
getStepData(curDate);
|
||||
}
|
||||
}, [curDate]);
|
||||
|
||||
// 为每个柱体创建独立的动画值
|
||||
const animatedValues = useRef(
|
||||
Array.from({ length: 24 }, () => new Animated.Value(0))
|
||||
@@ -56,7 +82,7 @@ const StepsCard: React.FC<StepsCardProps> = ({
|
||||
useEffect(() => {
|
||||
// 检查是否有实际数据(不只是空数组)
|
||||
const hasData = chartData && chartData.length > 0 && chartData.some(data => data.steps > 0);
|
||||
|
||||
|
||||
if (hasData) {
|
||||
// 重置所有动画值
|
||||
animatedValues.forEach(animValue => animValue.setValue(0));
|
||||
@@ -154,24 +180,19 @@ const StepsCard: React.FC<StepsCardProps> = ({
|
||||
</>
|
||||
);
|
||||
|
||||
// 如果有点击事件,包装在TouchableOpacity中
|
||||
if (onPress) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.container, style]}
|
||||
onPress={onPress}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<CardContent />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
// 否则使用普通View
|
||||
return (
|
||||
<View style={[styles.container, style]}>
|
||||
<TouchableOpacity
|
||||
style={[styles.container, style]}
|
||||
onPress={() => {
|
||||
// 传递当前日期参数到详情页
|
||||
const dateParam = dayjs(curDate).format('YYYY-MM-DD');
|
||||
router.push(`/steps/detail?date=${dateParam}`);
|
||||
}}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<CardContent />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { router } from 'expo-router';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
|
||||
export type HeaderBarProps = {
|
||||
title: string | React.ReactNode;
|
||||
onBack?: () => void;
|
||||
@@ -76,23 +76,24 @@ export function HeaderBar({
|
||||
},
|
||||
]}
|
||||
>
|
||||
{onBack ? (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={onBack}
|
||||
style={styles.backButton}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons
|
||||
name="chevron-back"
|
||||
size={24}
|
||||
color={backColor || theme.text}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={{ width: 32 }} />
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={() => {
|
||||
if (onBack) {
|
||||
onBack();
|
||||
return
|
||||
}
|
||||
router.back()
|
||||
}}
|
||||
style={styles.backButton}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons
|
||||
name="chevron-back"
|
||||
size={24}
|
||||
color={backColor || theme.text}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.titleContainer}>
|
||||
{typeof title === 'string' ? (
|
||||
<Text style={[
|
||||
|
||||
@@ -69,7 +69,7 @@ export const WeightRecordCard: React.FC<WeightRecordCardProps> = ({
|
||||
overshootRight={false}
|
||||
>
|
||||
<View
|
||||
style={[styles.recordCard, colorScheme === 'dark' && styles.recordCardDark]}
|
||||
style={[styles.recordCard]}
|
||||
>
|
||||
<View style={styles.recordHeader}>
|
||||
<Text style={[styles.recordDateTime, { color: themeColors.textSecondary }]}>
|
||||
@@ -111,14 +111,11 @@ export const WeightRecordCard: React.FC<WeightRecordCardProps> = ({
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
recordCard: {
|
||||
backgroundColor: '#F0F0F0',
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
marginBottom: 12,
|
||||
},
|
||||
recordCardDark: {
|
||||
backgroundColor: '#1F2937',
|
||||
},
|
||||
recordHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
@@ -145,8 +142,8 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '500',
|
||||
},
|
||||
recordWeightValue: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#192126',
|
||||
marginLeft: 4,
|
||||
flex: 1,
|
||||
|
||||
Reference in New Issue
Block a user