feat: 更新应用配置和引入新依赖

- 修改 app.json,禁用平板支持以优化用户体验
- 在 package.json 和 package-lock.json 中新增 react-native-toast-message 依赖,支持消息提示功能
- 在多个组件中集成 Toast 组件,提升用户交互反馈
- 更新训练计划相关逻辑,优化状态管理和数据处理
- 调整样式以适应新功能的展示和交互
This commit is contained in:
2025-08-16 09:42:33 +08:00
parent 3312250f2d
commit 5a4d86ff7d
16 changed files with 192 additions and 255 deletions

View File

@@ -1,41 +1,17 @@
import { CreateTrainingPlanDto, trainingPlanApi, TrainingPlanSummary } from '@/services/trainingPlanApi';
import { CreateTrainingPlanDto, PlanGoal, PlanMode, TrainingPlan, trainingPlanApi } from '@/services/trainingPlanApi';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
export type PlanMode = 'daysOfWeek' | 'sessionsPerWeek';
export type PlanGoal =
| 'postpartum_recovery' // 产后恢复
| 'fat_loss' // 减脂塑形
| 'posture_correction' // 体态矫正
| 'core_strength' // 核心力量
| 'flexibility' // 柔韧灵活
| 'rehab' // 康复保健
| 'stress_relief'; // 释压放松
export type TrainingPlan = {
id: string;
createdAt: string; // ISO
startDate: string; // ISO (当天或下周一)
mode: PlanMode;
daysOfWeek: number[]; // 0(日) - 6(六)
sessionsPerWeek: number; // 1..7
goal: PlanGoal | '';
startWeightKg?: number;
preferredTimeOfDay?: 'morning' | 'noon' | 'evening' | '';
name?: string;
};
export type TrainingPlanState = {
plans: TrainingPlan[];
currentId?: string | null;
editingId?: string | null;
draft: Omit<TrainingPlan, 'id' | 'createdAt'>;
loading: boolean;
error: string | null;
};
const STORAGE_KEY_LEGACY_SINGLE = '@training_plan';
const STORAGE_KEY_LIST = '@training_plans';
function nextMondayISO(): string {
@@ -50,7 +26,6 @@ function nextMondayISO(): string {
const initialState: TrainingPlanState = {
plans: [],
currentId: null,
editingId: null,
loading: false,
error: null,
@@ -73,13 +48,9 @@ export const loadPlans = createAsyncThunk('trainingPlan/loadPlans', async (_, {
try {
// 尝试从服务器获取数据
const response = await trainingPlanApi.list(1, 100); // 获取所有计划
console.log('response', response);
const plans: TrainingPlanSummary[] = response.list;
const plans = response.list;
// 读取最后一次使用的 currentId从本地存储
const currentId = (await AsyncStorage.getItem(`${STORAGE_KEY_LIST}__currentId`)) || null;
return { plans, currentId } as { plans: TrainingPlan[]; currentId: string | null };
return { plans };
} catch (error: any) {
// 如果API调用失败回退到本地存储
console.warn('API调用失败使用本地存储:', error.message);
@@ -89,27 +60,13 @@ export const loadPlans = createAsyncThunk('trainingPlan/loadPlans', async (_, {
if (listStr) {
try {
const plans = JSON.parse(listStr) as TrainingPlan[];
const currentId = (await AsyncStorage.getItem(`${STORAGE_KEY_LIST}__currentId`)) || null;
return { plans, currentId } as { plans: TrainingPlan[]; currentId: string | null };
return { plans } as { plans: TrainingPlan[] };
} catch {
// 解析失败则视为无数据
}
}
// 旧版:单计划
const legacyStr = await AsyncStorage.getItem(STORAGE_KEY_LEGACY_SINGLE);
if (legacyStr) {
try {
const legacy = JSON.parse(legacyStr) as TrainingPlan;
const plans = [legacy];
const currentId = legacy.id;
return { plans, currentId } as { plans: TrainingPlan[]; currentId: string | null };
} catch {
// ignore
}
}
return { plans: [], currentId: null } as { plans: TrainingPlan[]; currentId: string | null };
return { plans: [] } as { plans: TrainingPlan[] };
}
});
@@ -134,28 +91,9 @@ export const saveDraftAsPlan = createAsyncThunk(
preferredTimeOfDay: draft.preferredTimeOfDay,
};
const response = await trainingPlanApi.create(createDto);
const newPlan = await trainingPlanApi.create(createDto);
const plan: TrainingPlan = {
id: response.id,
createdAt: response.createdAt,
startDate: response.startDate,
mode: response.mode,
daysOfWeek: response.daysOfWeek,
sessionsPerWeek: response.sessionsPerWeek,
goal: response.goal as PlanGoal,
startWeightKg: response.startWeightKg || undefined,
preferredTimeOfDay: response.preferredTimeOfDay,
name: response.name,
};
const nextPlans = [...(s.plans || []), plan];
// 同时保存到本地存储作为缓存
await AsyncStorage.setItem(STORAGE_KEY_LIST, JSON.stringify(nextPlans));
await AsyncStorage.setItem(`${STORAGE_KEY_LIST}__currentId`, plan.id);
return { plans: nextPlans, currentId: plan.id } as { plans: TrainingPlan[]; currentId: string };
return newPlan;
} catch (error: any) {
return rejectWithValue(error.message || '创建训练计划失败');
}
@@ -257,16 +195,11 @@ export const deletePlan = createAsyncThunk(
// 更新本地状态
const nextPlans = (s.plans || []).filter((p) => p.id !== planId);
let nextCurrentId = s.currentId || null;
if (nextCurrentId === planId) {
nextCurrentId = nextPlans.length > 0 ? nextPlans[nextPlans.length - 1].id : null;
}
// 同时更新本地存储
await AsyncStorage.setItem(STORAGE_KEY_LIST, JSON.stringify(nextPlans));
await AsyncStorage.setItem(`${STORAGE_KEY_LIST}__currentId`, nextCurrentId ?? '');
return { plans: nextPlans, currentId: nextCurrentId } as { plans: TrainingPlan[]; currentId: string | null };
return { plans: nextPlans } as { plans: TrainingPlan[] };
} catch (error: any) {
return rejectWithValue(error.message || '删除训练计划失败');
}
@@ -277,11 +210,6 @@ const trainingPlanSlice = createSlice({
name: 'trainingPlan',
initialState,
reducers: {
setCurrentPlan(state, action: PayloadAction<string | null | undefined>) {
state.currentId = action.payload ?? null;
// 保存到本地存储
AsyncStorage.setItem(`${STORAGE_KEY_LIST}__currentId`, action.payload ?? '');
},
setMode(state, action: PayloadAction<PlanMode>) {
state.draft.mode = action.payload;
},
@@ -333,13 +261,6 @@ const trainingPlanSlice = createSlice({
.addCase(loadPlans.fulfilled, (state, action) => {
state.loading = false;
state.plans = action.payload.plans;
state.currentId = action.payload.currentId;
// 若存在历史计划,初始化 draft 基于该计划(便于编辑)
const current = state.plans.find((p) => p.id === state.currentId) || state.plans[state.plans.length - 1];
if (current) {
const { id, createdAt, ...rest } = current;
state.draft = { ...rest };
}
})
.addCase(loadPlans.rejected, (state, action) => {
state.loading = false;
@@ -352,8 +273,6 @@ const trainingPlanSlice = createSlice({
})
.addCase(saveDraftAsPlan.fulfilled, (state, action) => {
state.loading = false;
state.plans = action.payload.plans;
state.currentId = action.payload.currentId;
})
.addCase(saveDraftAsPlan.rejected, (state, action) => {
state.loading = false;
@@ -393,9 +312,6 @@ const trainingPlanSlice = createSlice({
})
.addCase(activatePlan.fulfilled, (state, action) => {
state.loading = false;
state.currentId = action.payload.id;
// 保存到本地存储
AsyncStorage.setItem(`${STORAGE_KEY_LIST}__currentId`, action.payload.id);
})
.addCase(activatePlan.rejected, (state, action) => {
state.loading = false;
@@ -409,7 +325,6 @@ const trainingPlanSlice = createSlice({
.addCase(deletePlan.fulfilled, (state, action) => {
state.loading = false;
state.plans = action.payload.plans;
state.currentId = action.payload.currentId;
})
.addCase(deletePlan.rejected, (state, action) => {
state.loading = false;
@@ -419,7 +334,6 @@ const trainingPlanSlice = createSlice({
});
export const {
setCurrentPlan,
setMode,
toggleDayOfWeek,
setSessionsPerWeek,