feat(auth): 预加载用户数据并优化登录状态同步
- 在启动屏预加载用户 token 与资料,避免首页白屏 - 新增 rehydrateUserSync 同步注入 Redux,减少异步等待 - 登录页兼容 ERR_REQUEST_CANCELED 取消场景 - 各页面统一依赖 isLoggedIn 判断,移除冗余控制台日志 - 步数卡片与详情页改为实时拉取健康数据,不再缓存至 Redux - 后台任务注册移至顶层,防止重复定义 - 体重记录、HeaderBar 等 UI 细节样式微调
This commit is contained in:
@@ -4,6 +4,52 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { createAsyncThunk, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 预加载的用户数据存储
|
||||
let preloadedUserData: {
|
||||
token: string | null;
|
||||
profile: UserProfile;
|
||||
privacyAgreed: boolean;
|
||||
} | null = null;
|
||||
|
||||
// 预加载用户数据的函数
|
||||
export async function preloadUserData() {
|
||||
try {
|
||||
const [profileStr, privacyAgreedStr, token] = await Promise.all([
|
||||
AsyncStorage.getItem(STORAGE_KEYS.userProfile),
|
||||
AsyncStorage.getItem(STORAGE_KEYS.privacyAgreed),
|
||||
AsyncStorage.getItem(STORAGE_KEYS.authToken),
|
||||
]);
|
||||
|
||||
let profile: UserProfile = {};
|
||||
if (profileStr) {
|
||||
try {
|
||||
profile = JSON.parse(profileStr) as UserProfile;
|
||||
} catch {
|
||||
profile = {};
|
||||
}
|
||||
}
|
||||
|
||||
const privacyAgreed = privacyAgreedStr === 'true';
|
||||
|
||||
// 如果有 token,需要设置到 API 客户端
|
||||
if (token) {
|
||||
await setAuthToken(token);
|
||||
}
|
||||
|
||||
preloadedUserData = { token, profile, privacyAgreed };
|
||||
return preloadedUserData;
|
||||
} catch (error) {
|
||||
console.error('预加载用户数据失败:', error);
|
||||
preloadedUserData = { token: null, profile: {}, privacyAgreed: false };
|
||||
return preloadedUserData;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取预加载的用户数据
|
||||
function getPreloadedUserData() {
|
||||
return preloadedUserData || { token: null, profile: {}, privacyAgreed: false };
|
||||
}
|
||||
|
||||
export type Gender = 'male' | 'female' | '';
|
||||
|
||||
export type UserProfile = {
|
||||
@@ -48,21 +94,27 @@ export type UserState = {
|
||||
|
||||
export const DEFAULT_MEMBER_NAME = '小海豹';
|
||||
|
||||
const initialState: UserState = {
|
||||
token: null,
|
||||
profile: {
|
||||
name: DEFAULT_MEMBER_NAME,
|
||||
isVip: false,
|
||||
freeUsageCount: 3,
|
||||
maxUsageCount: 5,
|
||||
},
|
||||
loading: false,
|
||||
error: null,
|
||||
privacyAgreed: false,
|
||||
weightHistory: [],
|
||||
activityHistory: [],
|
||||
const getInitialState = (): UserState => {
|
||||
const preloaded = getPreloadedUserData();
|
||||
return {
|
||||
token: preloaded.token,
|
||||
profile: {
|
||||
name: DEFAULT_MEMBER_NAME,
|
||||
isVip: false,
|
||||
freeUsageCount: 3,
|
||||
maxUsageCount: 5,
|
||||
...preloaded.profile, // 合并预加载的用户资料
|
||||
},
|
||||
loading: false,
|
||||
error: null,
|
||||
privacyAgreed: preloaded.privacyAgreed,
|
||||
weightHistory: [],
|
||||
activityHistory: [],
|
||||
};
|
||||
};
|
||||
|
||||
const initialState: UserState = getInitialState();
|
||||
|
||||
export type LoginPayload = Record<string, any> & {
|
||||
// 可扩展:用户名密码、Apple 身份、短信验证码等
|
||||
username?: string;
|
||||
@@ -132,6 +184,17 @@ export const login = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
// 同步重新hydrate用户数据,避免异步状态更新
|
||||
export const rehydrateUserSync = createAsyncThunk('user/rehydrateSync', async () => {
|
||||
// 立即从预加载的数据获取,如果没有则异步获取
|
||||
if (preloadedUserData) {
|
||||
return preloadedUserData;
|
||||
}
|
||||
|
||||
// 如果预加载的数据不存在,则执行正常的异步加载
|
||||
return await preloadUserData();
|
||||
});
|
||||
|
||||
export const rehydrateUser = createAsyncThunk('user/rehydrate', async () => {
|
||||
const [profileStr, privacyAgreedStr, token] = await Promise.all([
|
||||
AsyncStorage.getItem(STORAGE_KEYS.userProfile),
|
||||
@@ -144,12 +207,12 @@ export const rehydrateUser = createAsyncThunk('user/rehydrate', async () => {
|
||||
try { profile = JSON.parse(profileStr) as UserProfile; } catch { profile = {}; }
|
||||
}
|
||||
const privacyAgreed = privacyAgreedStr === 'true';
|
||||
|
||||
|
||||
// 如果有 token,需要设置到 API 客户端
|
||||
if (token) {
|
||||
await setAuthToken(token);
|
||||
}
|
||||
|
||||
|
||||
return { profile, privacyAgreed, token } as { profile: UserProfile; privacyAgreed: boolean; token: string | null };
|
||||
});
|
||||
|
||||
@@ -197,7 +260,6 @@ export const fetchWeightHistory = createAsyncThunk('user/fetchWeightHistory', as
|
||||
export const fetchActivityHistory = createAsyncThunk('user/fetchActivityHistory', async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const data: ActivityHistoryItem[] = await api.get('/api/users/activity-history');
|
||||
console.log('fetchActivityHistory', data);
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(err?.message ?? '获取用户活动历史记录失败');
|
||||
@@ -284,6 +346,14 @@ const userSlice = createSlice({
|
||||
state.profile.name = DEFAULT_MEMBER_NAME;
|
||||
}
|
||||
})
|
||||
.addCase(rehydrateUserSync.fulfilled, (state, action) => {
|
||||
state.profile = action.payload.profile;
|
||||
state.privacyAgreed = action.payload.privacyAgreed;
|
||||
state.token = action.payload.token;
|
||||
if (!state.profile?.name || !state.profile.name.trim()) {
|
||||
state.profile.name = DEFAULT_MEMBER_NAME;
|
||||
}
|
||||
})
|
||||
.addCase(fetchMyProfile.fulfilled, (state, action) => {
|
||||
state.profile = action.payload || {};
|
||||
if (!state.profile?.name || !state.profile.name.trim()) {
|
||||
|
||||
Reference in New Issue
Block a user