feat: 更新应用版本和主题设置
- 将应用版本更新至 1.0.3,修改相关配置文件 - 强制全局使用浅色主题,确保一致的用户体验 - 在训练计划功能中新增激活计划的 API 接口,支持用户激活训练计划 - 优化打卡功能,支持自动同步打卡记录至服务器 - 更新样式以适应新功能的展示和交互
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { CreateTrainingPlanDto, trainingPlanApi } from '@/services/trainingPlanApi';
|
||||
import { CreateTrainingPlanDto, trainingPlanApi, TrainingPlanSummary } from '@/services/trainingPlanApi';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
@@ -29,6 +29,7 @@ export type TrainingPlan = {
|
||||
export type TrainingPlanState = {
|
||||
plans: TrainingPlan[];
|
||||
currentId?: string | null;
|
||||
editingId?: string | null;
|
||||
draft: Omit<TrainingPlan, 'id' | 'createdAt'>;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
@@ -50,6 +51,7 @@ function nextMondayISO(): string {
|
||||
const initialState: TrainingPlanState = {
|
||||
plans: [],
|
||||
currentId: null,
|
||||
editingId: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
draft: {
|
||||
@@ -72,17 +74,7 @@ export const loadPlans = createAsyncThunk('trainingPlan/loadPlans', async (_, {
|
||||
// 尝试从服务器获取数据
|
||||
const response = await trainingPlanApi.list(1, 100); // 获取所有计划
|
||||
console.log('response', response);
|
||||
const plans: TrainingPlan[] = response.list.map(summary => ({
|
||||
id: summary.id,
|
||||
createdAt: summary.createdAt,
|
||||
startDate: summary.startDate,
|
||||
goal: summary.goal as PlanGoal,
|
||||
mode: 'daysOfWeek', // 默认值,需要从详情获取
|
||||
daysOfWeek: [],
|
||||
sessionsPerWeek: 3,
|
||||
preferredTimeOfDay: '',
|
||||
name: '',
|
||||
}));
|
||||
const plans: TrainingPlanSummary[] = response.list;
|
||||
|
||||
// 读取最后一次使用的 currentId(从本地存储)
|
||||
const currentId = (await AsyncStorage.getItem(`${STORAGE_KEY_LIST}__currentId`)) || null;
|
||||
@@ -170,6 +162,89 @@ export const saveDraftAsPlan = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 加载某个计划用于编辑:会写入 draft 与 editingId
|
||||
*/
|
||||
export const loadPlanForEdit = createAsyncThunk(
|
||||
'trainingPlan/loadPlanForEdit',
|
||||
async (id: string, { getState, rejectWithValue }) => {
|
||||
try {
|
||||
const detail = await trainingPlanApi.detail(id);
|
||||
const draft = {
|
||||
startDate: detail.startDate,
|
||||
name: detail.name,
|
||||
mode: detail.mode,
|
||||
daysOfWeek: detail.daysOfWeek,
|
||||
sessionsPerWeek: detail.sessionsPerWeek,
|
||||
goal: detail.goal as PlanGoal,
|
||||
startWeightKg: detail.startWeightKg ?? undefined,
|
||||
preferredTimeOfDay: detail.preferredTimeOfDay,
|
||||
} as TrainingPlanState['draft'];
|
||||
return { id, draft } as { id: string; draft: TrainingPlanState['draft'] };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '加载计划详情失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 使用当前 draft 更新 editingId 对应的计划
|
||||
*/
|
||||
export const updatePlanFromDraft = createAsyncThunk(
|
||||
'trainingPlan/updatePlanFromDraft',
|
||||
async (_: void, { getState, rejectWithValue }) => {
|
||||
try {
|
||||
const s = (getState() as any).trainingPlan as TrainingPlanState;
|
||||
if (!s.editingId) throw new Error('无有效编辑对象');
|
||||
const draft = s.draft;
|
||||
const dto: CreateTrainingPlanDto = {
|
||||
startDate: draft.startDate,
|
||||
name: draft.name,
|
||||
mode: draft.mode,
|
||||
daysOfWeek: draft.daysOfWeek,
|
||||
sessionsPerWeek: draft.sessionsPerWeek,
|
||||
goal: draft.goal,
|
||||
startWeightKg: draft.startWeightKg,
|
||||
preferredTimeOfDay: draft.preferredTimeOfDay,
|
||||
};
|
||||
const resp = await trainingPlanApi.update(s.editingId, dto);
|
||||
const updated: TrainingPlan = {
|
||||
id: resp.id,
|
||||
createdAt: resp.createdAt,
|
||||
startDate: resp.startDate,
|
||||
mode: resp.mode,
|
||||
daysOfWeek: resp.daysOfWeek,
|
||||
sessionsPerWeek: resp.sessionsPerWeek,
|
||||
goal: resp.goal as PlanGoal,
|
||||
startWeightKg: resp.startWeightKg ?? undefined,
|
||||
preferredTimeOfDay: resp.preferredTimeOfDay,
|
||||
name: resp.name,
|
||||
};
|
||||
// 更新本地 plans
|
||||
const idx = (s.plans || []).findIndex(p => p.id === updated.id);
|
||||
const nextPlans = [...(s.plans || [])];
|
||||
if (idx >= 0) nextPlans[idx] = updated; else nextPlans.push(updated);
|
||||
await AsyncStorage.setItem(STORAGE_KEY_LIST, JSON.stringify(nextPlans));
|
||||
return { plans: nextPlans } as { plans: TrainingPlan[] };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '更新训练计划失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 激活计划
|
||||
export const activatePlan = createAsyncThunk(
|
||||
'trainingPlan/activatePlan',
|
||||
async (planId: string, { rejectWithValue }) => {
|
||||
try {
|
||||
await trainingPlanApi.activate(planId);
|
||||
return { id: planId } as { id: string };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '激活训练计划失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/** 删除计划 */
|
||||
export const deletePlan = createAsyncThunk(
|
||||
'trainingPlan/deletePlan',
|
||||
@@ -244,6 +319,9 @@ const trainingPlanSlice = createSlice({
|
||||
clearError(state) {
|
||||
state.error = null;
|
||||
},
|
||||
setEditingId(state, action: PayloadAction<string | null>) {
|
||||
state.editingId = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
@@ -281,6 +359,48 @@ const trainingPlanSlice = createSlice({
|
||||
state.loading = false;
|
||||
state.error = action.payload as string || '创建训练计划失败';
|
||||
})
|
||||
// loadPlanForEdit
|
||||
.addCase(loadPlanForEdit.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(loadPlanForEdit.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.editingId = action.payload.id;
|
||||
state.draft = action.payload.draft;
|
||||
})
|
||||
.addCase(loadPlanForEdit.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload as string || '加载计划详情失败';
|
||||
})
|
||||
// updatePlanFromDraft
|
||||
.addCase(updatePlanFromDraft.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(updatePlanFromDraft.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.plans = action.payload.plans;
|
||||
})
|
||||
.addCase(updatePlanFromDraft.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload as string || '更新训练计划失败';
|
||||
})
|
||||
// activatePlan
|
||||
.addCase(activatePlan.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.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;
|
||||
state.error = action.payload as string || '激活训练计划失败';
|
||||
})
|
||||
// deletePlan
|
||||
.addCase(deletePlan.pending, (state) => {
|
||||
state.loading = true;
|
||||
@@ -311,6 +431,7 @@ export const {
|
||||
setStartDateNextMonday,
|
||||
resetDraft,
|
||||
clearError,
|
||||
setEditingId,
|
||||
} = trainingPlanSlice.actions;
|
||||
|
||||
export default trainingPlanSlice.reducer;
|
||||
|
||||
Reference in New Issue
Block a user