feat(challenges): 添加自定义挑战功能和多语言支持

- 新增自定义挑战创建页面,支持设置挑战类型、时间范围、目标值等
- 实现挑战邀请码系统,支持通过邀请码加入自定义挑战
- 完善挑战详情页面的多语言翻译支持
- 优化用户认证状态检查逻辑,使用token作为主要判断依据
- 添加阿里字体文件支持,提升UI显示效果
- 改进确认弹窗组件,支持Liquid Glass效果和自定义内容
- 优化应用启动流程,直接读取onboarding状态而非预加载用户数据
This commit is contained in:
richarjiang
2025-11-26 16:39:01 +08:00
parent 3ad0e08d58
commit 39671ed70f
24 changed files with 3124 additions and 727 deletions

View File

@@ -5,65 +5,51 @@ import AsyncStorage from '@/utils/kvStore';
import { createAsyncThunk, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import dayjs from 'dayjs';
// 预加载的用户数据存储
let preloadedUserData: {
token: string | null;
profile: UserProfile;
privacyAgreed: boolean;
onboardingCompleted: boolean;
} | null = null;
// 预加载用户数据的函数
export async function preloadUserData() {
/**
* 同步加载用户数据(在 Redux store 初始化时立即执行)
* 使用 getItemSync 确保数据在 store 创建前就已加载
*/
function loadUserDataSync() {
try {
const [profileStr, privacyAgreedStr, token, onboardingCompletedStr] = await Promise.all([
AsyncStorage.getItem(STORAGE_KEYS.userProfile),
AsyncStorage.getItem(STORAGE_KEYS.privacyAgreed),
AsyncStorage.getItem(STORAGE_KEYS.authToken),
AsyncStorage.getItem(STORAGE_KEYS.onboardingCompleted),
]);
const profileStr = AsyncStorage.getItemSync(STORAGE_KEYS.userProfile);
const token = AsyncStorage.getItemSync(STORAGE_KEYS.authToken);
const onboardingCompletedStr = AsyncStorage.getItemSync(STORAGE_KEYS.onboardingCompleted);
let profile: UserProfile = {
memberNumber: 0
};
if (profileStr) {
try {
profile = JSON.parse(profileStr) as UserProfile;
} catch {
profile = {
memberNumber: 0
};
profile = { memberNumber: 0 };
}
}
const privacyAgreed = privacyAgreedStr === 'true';
const onboardingCompleted = onboardingCompletedStr === 'true';
// 如果有 token需要设置到 API 客户端
// 如果有 token需要异步设置到 API 客户端(但不阻塞初始化)
if (token) {
await setAuthToken(token);
setAuthToken(token).catch(err => {
console.error('设置 auth token 失败:', err);
});
}
preloadedUserData = { token, profile, privacyAgreed, onboardingCompleted };
return preloadedUserData;
return { token, profile, onboardingCompleted };
} catch (error) {
console.error('加载用户数据失败:', error);
preloadedUserData = {
console.error('同步加载用户数据失败:', error);
return {
token: null,
profile: {
memberNumber: 0
},
privacyAgreed: false,
profile: { memberNumber: 0 },
onboardingCompleted: false
};
return preloadedUserData;
}
}
// 获取预加载的用户数据
function getPreloadedUserData() {
return preloadedUserData || { token: null, profile: {}, privacyAgreed: false, onboardingCompleted: false };
}
// 在模块加载时立即同步加载用户数据
const preloadedUserData = loadUserDataSync();
export type Gender = 'male' | 'female' | '';
@@ -120,22 +106,23 @@ export type UserState = {
export const DEFAULT_MEMBER_NAME = '朋友';
const getInitialState = (): UserState => {
const preloaded = getPreloadedUserData();
// 使用模块加载时同步加载的数据
console.log('初始化 Redux state使用预加载数据:', preloadedUserData);
return {
token: preloaded.token,
token: preloadedUserData.token,
profile: {
name: DEFAULT_MEMBER_NAME,
isVip: false,
freeUsageCount: 3,
memberNumber: 0,
maxUsageCount: 5,
...preloaded.profile, // 合并预加载的用户资料
...preloadedUserData.profile, // 合并预加载的用户资料(包含 memberNumber
},
loading: false,
error: null,
weightHistory: [],
activityHistory: [],
onboardingCompleted: preloaded.onboardingCompleted, // 引导完成状态
onboardingCompleted: preloadedUserData.onboardingCompleted, // 引导完成状态
};
};
@@ -198,8 +185,11 @@ export const login = createAsyncThunk(
if (!token) throw new Error('登录响应缺少 token');
// 先持久化到本地存储
await AsyncStorage.setItem(STORAGE_KEYS.authToken, token);
await AsyncStorage.setItem(STORAGE_KEYS.userProfile, JSON.stringify(profile ?? {}));
// 再设置到 API 客户端(内部会同步更新 AsyncStorage
await setAuthToken(token);
return { token, profile } as { token: string; profile: UserProfile };
@@ -222,12 +212,15 @@ export const setOnboardingCompleted = createAsyncThunk('user/setOnboardingComple
});
export const logout = createAsyncThunk('user/logout', async () => {
// 先清除 API 客户端的 token内部会清除 AsyncStorage
await setAuthToken(null);
// 再清除其他本地存储数据
await Promise.all([
AsyncStorage.removeItem(STORAGE_KEYS.authToken),
AsyncStorage.removeItem(STORAGE_KEYS.userProfile),
AsyncStorage.removeItem(STORAGE_KEYS.privacyAgreed),
]);
await setAuthToken(null);
return true;
});