feat(auth): 预加载用户数据并优化登录状态同步

- 在启动屏预加载用户 token 与资料,避免首页白屏
- 新增 rehydrateUserSync 同步注入 Redux,减少异步等待
- 登录页兼容 ERR_REQUEST_CANCELED 取消场景
- 各页面统一依赖 isLoggedIn 判断,移除冗余控制台日志
- 步数卡片与详情页改为实时拉取健康数据,不再缓存至 Redux
- 后台任务注册移至顶层,防止重复定义
- 体重记录、HeaderBar 等 UI 细节样式微调
This commit is contained in:
richarjiang
2025-09-15 09:56:42 +08:00
parent 55d133c470
commit 91df01bd79
18 changed files with 967 additions and 1018 deletions

View File

@@ -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>
);
};

View File

@@ -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={[

View File

@@ -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,