feat: 移除目标管理功能模块
删除了完整的目标管理功能,包括目标创建、编辑、任务管理等相关页面和组件。同时移除了相关的API服务、Redux状态管理、类型定义和通知功能。应用版本从1.0.20升级到1.0.21。
This commit is contained in:
@@ -1,603 +0,0 @@
|
||||
import { goalsApi } from '@/services/goalsApi';
|
||||
import {
|
||||
BatchGoalOperationRequest,
|
||||
CompleteGoalRequest,
|
||||
CreateGoalRequest,
|
||||
GetGoalCompletionsQuery,
|
||||
GetGoalsQuery,
|
||||
GoalCompletion,
|
||||
GoalDetailResponse,
|
||||
GoalListItem,
|
||||
GoalStats,
|
||||
GoalStatus,
|
||||
UpdateGoalRequest
|
||||
} from '@/types/goals';
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
// 目标管理状态类型
|
||||
export interface GoalsState {
|
||||
// 目标列表
|
||||
goals: GoalListItem[];
|
||||
goalsLoading: boolean;
|
||||
goalsError: string | null;
|
||||
goalsPagination: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
};
|
||||
|
||||
// 当前查看的目标详情
|
||||
currentGoal: GoalDetailResponse | null;
|
||||
currentGoalLoading: boolean;
|
||||
currentGoalError: string | null;
|
||||
|
||||
// 目标完成记录
|
||||
completions: GoalCompletion[];
|
||||
completionsLoading: boolean;
|
||||
completionsError: string | null;
|
||||
completionsPagination: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
};
|
||||
|
||||
// 目标统计
|
||||
stats: GoalStats | null;
|
||||
statsLoading: boolean;
|
||||
statsError: string | null;
|
||||
|
||||
// 创建/更新目标
|
||||
createLoading: boolean;
|
||||
createError: string | null;
|
||||
updateLoading: boolean;
|
||||
updateError: string | null;
|
||||
|
||||
// 批量操作
|
||||
batchLoading: boolean;
|
||||
batchError: string | null;
|
||||
|
||||
// 筛选和搜索
|
||||
filters: GetGoalsQuery;
|
||||
}
|
||||
|
||||
const initialState: GoalsState = {
|
||||
goals: [],
|
||||
goalsLoading: false,
|
||||
goalsError: null,
|
||||
goalsPagination: {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
hasMore: false,
|
||||
},
|
||||
|
||||
currentGoal: null,
|
||||
currentGoalLoading: false,
|
||||
currentGoalError: null,
|
||||
|
||||
completions: [],
|
||||
completionsLoading: false,
|
||||
completionsError: null,
|
||||
completionsPagination: {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
hasMore: false,
|
||||
},
|
||||
|
||||
stats: null,
|
||||
statsLoading: false,
|
||||
statsError: null,
|
||||
|
||||
createLoading: false,
|
||||
createError: null,
|
||||
updateLoading: false,
|
||||
updateError: null,
|
||||
|
||||
batchLoading: false,
|
||||
batchError: null,
|
||||
|
||||
filters: {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
sortBy: 'createdAt',
|
||||
sortOrder: 'desc',
|
||||
},
|
||||
};
|
||||
|
||||
// 异步操作
|
||||
|
||||
/**
|
||||
* 获取目标列表
|
||||
*/
|
||||
export const fetchGoals = createAsyncThunk(
|
||||
'goals/fetchGoals',
|
||||
async (query: GetGoalsQuery = {}, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await goalsApi.getGoals(query);
|
||||
|
||||
console.log('fetchGoals response', response);
|
||||
return { query, response };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '获取目标列表失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 加载更多目标
|
||||
*/
|
||||
export const loadMoreGoals = createAsyncThunk(
|
||||
'goals/loadMoreGoals',
|
||||
async (_, { getState, rejectWithValue }) => {
|
||||
try {
|
||||
const state = getState() as { goals: GoalsState };
|
||||
const { filters, goalsPagination } = state.goals;
|
||||
|
||||
if (!goalsPagination.hasMore) {
|
||||
return { goals: [], pagination: goalsPagination };
|
||||
}
|
||||
|
||||
const query = {
|
||||
...filters,
|
||||
page: goalsPagination.page + 1,
|
||||
};
|
||||
|
||||
const response = await goalsApi.getGoals(query);
|
||||
|
||||
console.log('response', response);
|
||||
|
||||
return { query, response };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '加载更多目标失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取目标详情
|
||||
*/
|
||||
export const fetchGoalDetail = createAsyncThunk(
|
||||
'goals/fetchGoalDetail',
|
||||
async (goalId: string, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await goalsApi.getGoalById(goalId);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '获取目标详情失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 创建目标
|
||||
*/
|
||||
export const createGoal = createAsyncThunk(
|
||||
'goals/createGoal',
|
||||
async (goalData: CreateGoalRequest, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await goalsApi.createGoal(goalData);
|
||||
console.log('createGoal response', response);
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '创建目标失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 更新目标
|
||||
*/
|
||||
export const updateGoal = createAsyncThunk(
|
||||
'goals/updateGoal',
|
||||
async ({ goalId, goalData }: { goalId: string; goalData: UpdateGoalRequest }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await goalsApi.updateGoal(goalId, goalData);
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '更新目标失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 删除目标
|
||||
*/
|
||||
export const deleteGoal = createAsyncThunk(
|
||||
'goals/deleteGoal',
|
||||
async (goalId: string, { rejectWithValue }) => {
|
||||
try {
|
||||
await goalsApi.deleteGoal(goalId);
|
||||
return goalId;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '删除目标失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 记录目标完成
|
||||
*/
|
||||
export const completeGoal = createAsyncThunk(
|
||||
'goals/completeGoal',
|
||||
async ({ goalId, completionData }: { goalId: string; completionData?: CompleteGoalRequest }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await goalsApi.completeGoal(goalId, completionData);
|
||||
return { goalId, completion: response.data };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '记录目标完成失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取目标完成记录
|
||||
*/
|
||||
export const fetchGoalCompletions = createAsyncThunk(
|
||||
'goals/fetchGoalCompletions',
|
||||
async ({ goalId, query }: { goalId: string; query?: GetGoalCompletionsQuery }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await goalsApi.getGoalCompletions(goalId, query);
|
||||
return { query, response: response.data };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '获取完成记录失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取目标统计
|
||||
*/
|
||||
export const fetchGoalStats = createAsyncThunk(
|
||||
'goals/fetchGoalStats',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await goalsApi.getGoalStats();
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '获取目标统计失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 批量操作目标
|
||||
*/
|
||||
export const batchOperateGoals = createAsyncThunk(
|
||||
'goals/batchOperateGoals',
|
||||
async (operationData: BatchGoalOperationRequest, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await goalsApi.batchOperateGoals(operationData);
|
||||
return { operation: operationData, results: response.data };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '批量操作失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Redux Slice
|
||||
const goalsSlice = createSlice({
|
||||
name: 'goals',
|
||||
initialState,
|
||||
reducers: {
|
||||
// 设置筛选条件
|
||||
setFilters: (state, action: PayloadAction<Partial<GetGoalsQuery>>) => {
|
||||
state.filters = { ...state.filters, ...action.payload };
|
||||
},
|
||||
|
||||
// 重置筛选条件
|
||||
resetFilters: (state) => {
|
||||
state.filters = {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
sortBy: 'createdAt',
|
||||
sortOrder: 'desc',
|
||||
};
|
||||
},
|
||||
|
||||
// 清除错误
|
||||
clearErrors: (state) => {
|
||||
state.goalsError = null;
|
||||
state.currentGoalError = null;
|
||||
state.completionsError = null;
|
||||
state.statsError = null;
|
||||
state.createError = null;
|
||||
state.updateError = null;
|
||||
state.batchError = null;
|
||||
},
|
||||
|
||||
// 清除当前目标详情
|
||||
clearCurrentGoal: (state) => {
|
||||
state.currentGoal = null;
|
||||
state.currentGoalError = null;
|
||||
},
|
||||
|
||||
// 本地更新目标状态(用于乐观更新)
|
||||
updateGoalStatus: (state, action: PayloadAction<{ goalId: string; status: GoalStatus }>) => {
|
||||
const { goalId, status } = action.payload;
|
||||
|
||||
// 更新目标列表中的状态
|
||||
const goalIndex = state.goals.findIndex(goal => goal.id === goalId);
|
||||
if (goalIndex !== -1) {
|
||||
state.goals[goalIndex].status = status;
|
||||
}
|
||||
|
||||
// 更新当前目标详情中的状态
|
||||
if (state.currentGoal && state.currentGoal.id === goalId) {
|
||||
state.currentGoal.status = status;
|
||||
}
|
||||
},
|
||||
|
||||
// 本地增加完成次数(用于乐观更新)
|
||||
incrementGoalCompletion: (state, action: PayloadAction<{ goalId: string; count?: number }>) => {
|
||||
const { goalId, count = 1 } = action.payload;
|
||||
|
||||
// 更新目标列表中的完成次数
|
||||
const goalIndex = state.goals.findIndex(goal => goal.id === goalId);
|
||||
if (goalIndex !== -1) {
|
||||
state.goals[goalIndex].completedCount += count;
|
||||
// 重新计算进度百分比
|
||||
if (state.goals[goalIndex].targetCount && state.goals[goalIndex].targetCount > 0) {
|
||||
state.goals[goalIndex].progressPercentage = Math.round(
|
||||
(state.goals[goalIndex].completedCount / state.goals[goalIndex].targetCount) * 100
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新当前目标详情中的完成次数
|
||||
if (state.currentGoal && state.currentGoal.id === goalId) {
|
||||
state.currentGoal.completedCount += count;
|
||||
if (state.currentGoal.targetCount && state.currentGoal.targetCount > 0) {
|
||||
state.currentGoal.progressPercentage = Math.round(
|
||||
(state.currentGoal.completedCount / state.currentGoal.targetCount) * 100
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
// 获取目标列表
|
||||
.addCase(fetchGoals.pending, (state) => {
|
||||
state.goalsLoading = true;
|
||||
state.goalsError = null;
|
||||
})
|
||||
.addCase(fetchGoals.fulfilled, (state, action) => {
|
||||
state.goalsLoading = false;
|
||||
const { query, response } = action.payload;
|
||||
|
||||
// 如果是第一页,替换数据;否则追加数据
|
||||
if (query.page === 1) {
|
||||
state.goals = response.list;
|
||||
} else {
|
||||
state.goals = [...state.goals, ...response.list];
|
||||
}
|
||||
|
||||
state.goalsPagination = {
|
||||
page: response.page,
|
||||
pageSize: response.pageSize,
|
||||
total: response.total,
|
||||
hasMore: response.page * response.pageSize < response.total,
|
||||
};
|
||||
})
|
||||
.addCase(fetchGoals.rejected, (state, action) => {
|
||||
state.goalsLoading = false;
|
||||
state.goalsError = action.payload as string;
|
||||
})
|
||||
|
||||
// 加载更多目标
|
||||
.addCase(loadMoreGoals.pending, (state) => {
|
||||
state.goalsLoading = true;
|
||||
})
|
||||
.addCase(loadMoreGoals.fulfilled, (state, action) => {
|
||||
state.goalsLoading = false;
|
||||
const { response } = action.payload;
|
||||
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.goals = [...state.goals, ...response.list];
|
||||
state.goalsPagination = {
|
||||
page: response.page,
|
||||
pageSize: response.pageSize,
|
||||
total: response.total,
|
||||
hasMore: response.page * response.pageSize < response.total,
|
||||
};
|
||||
})
|
||||
.addCase(loadMoreGoals.rejected, (state, action) => {
|
||||
state.goalsLoading = false;
|
||||
state.goalsError = action.payload as string;
|
||||
})
|
||||
|
||||
// 获取目标详情
|
||||
.addCase(fetchGoalDetail.pending, (state) => {
|
||||
state.currentGoalLoading = true;
|
||||
state.currentGoalError = null;
|
||||
})
|
||||
.addCase(fetchGoalDetail.fulfilled, (state, action) => {
|
||||
state.currentGoalLoading = false;
|
||||
state.currentGoal = action.payload;
|
||||
})
|
||||
.addCase(fetchGoalDetail.rejected, (state, action) => {
|
||||
state.currentGoalLoading = false;
|
||||
state.currentGoalError = action.payload as string;
|
||||
})
|
||||
|
||||
// 创建目标
|
||||
.addCase(createGoal.pending, (state) => {
|
||||
state.createLoading = true;
|
||||
state.createError = null;
|
||||
})
|
||||
.addCase(createGoal.fulfilled, (state, action) => {
|
||||
state.createLoading = false;
|
||||
// 将新目标添加到列表开头
|
||||
const newGoal: GoalListItem = {
|
||||
...action.payload,
|
||||
progressPercentage: action.payload.targetCount && action.payload.targetCount > 0
|
||||
? Math.round((action.payload.completedCount / action.payload.targetCount) * 100)
|
||||
: 0,
|
||||
};
|
||||
state.goals.unshift(newGoal);
|
||||
state.goalsPagination.total += 1;
|
||||
})
|
||||
.addCase(createGoal.rejected, (state, action) => {
|
||||
state.createLoading = false;
|
||||
state.createError = action.payload as string;
|
||||
})
|
||||
|
||||
// 更新目标
|
||||
.addCase(updateGoal.pending, (state) => {
|
||||
state.updateLoading = true;
|
||||
state.updateError = null;
|
||||
})
|
||||
.addCase(updateGoal.fulfilled, (state, action) => {
|
||||
state.updateLoading = false;
|
||||
const updatedGoal = action.payload;
|
||||
|
||||
console.log('updateGoal.fulfilled', updatedGoal);
|
||||
|
||||
// 更新目标列表中的目标
|
||||
const goalIndex = state.goals.findIndex(goal => goal.id === updatedGoal.id);
|
||||
if (goalIndex !== -1) {
|
||||
state.goals[goalIndex] = {
|
||||
...state.goals[goalIndex],
|
||||
...updatedGoal,
|
||||
};
|
||||
}
|
||||
|
||||
// 更新当前目标详情
|
||||
if (state.currentGoal && state.currentGoal.id === updatedGoal.id) {
|
||||
state.currentGoal = {
|
||||
...state.currentGoal,
|
||||
...updatedGoal,
|
||||
};
|
||||
}
|
||||
})
|
||||
.addCase(updateGoal.rejected, (state, action) => {
|
||||
state.updateLoading = false;
|
||||
state.updateError = action.payload as string;
|
||||
})
|
||||
|
||||
// 删除目标
|
||||
.addCase(deleteGoal.fulfilled, (state, action) => {
|
||||
const goalId = action.payload;
|
||||
|
||||
// 从目标列表中移除
|
||||
state.goals = state.goals.filter(goal => goal.id !== goalId);
|
||||
state.goalsPagination.total -= 1;
|
||||
|
||||
// 如果删除的是当前查看的目标,清除详情
|
||||
if (state.currentGoal && state.currentGoal.id === goalId) {
|
||||
state.currentGoal = null;
|
||||
}
|
||||
})
|
||||
|
||||
// 记录目标完成
|
||||
.addCase(completeGoal.fulfilled, (state, action) => {
|
||||
const { goalId, completion } = action.payload;
|
||||
|
||||
// 增加完成次数
|
||||
goalsSlice.caseReducers.incrementGoalCompletion(state, {
|
||||
type: 'goals/incrementGoalCompletion',
|
||||
payload: { goalId, count: completion.completionCount },
|
||||
});
|
||||
|
||||
// 将完成记录添加到列表开头
|
||||
state.completions.unshift(completion);
|
||||
})
|
||||
|
||||
// 获取完成记录
|
||||
.addCase(fetchGoalCompletions.pending, (state) => {
|
||||
state.completionsLoading = true;
|
||||
state.completionsError = null;
|
||||
})
|
||||
.addCase(fetchGoalCompletions.fulfilled, (state, action) => {
|
||||
state.completionsLoading = false;
|
||||
const { query, response } = action.payload;
|
||||
|
||||
// 如果是第一页,替换数据;否则追加数据
|
||||
if (query?.page === 1) {
|
||||
state.completions = response.list;
|
||||
} else {
|
||||
state.completions = [...state.completions, ...response.list];
|
||||
}
|
||||
|
||||
state.completionsPagination = {
|
||||
page: response.page,
|
||||
pageSize: response.pageSize,
|
||||
total: response.total,
|
||||
hasMore: response.page * response.pageSize < response.total,
|
||||
};
|
||||
})
|
||||
.addCase(fetchGoalCompletions.rejected, (state, action) => {
|
||||
state.completionsLoading = false;
|
||||
state.completionsError = action.payload as string;
|
||||
})
|
||||
|
||||
// 获取目标统计
|
||||
.addCase(fetchGoalStats.pending, (state) => {
|
||||
state.statsLoading = true;
|
||||
state.statsError = null;
|
||||
})
|
||||
.addCase(fetchGoalStats.fulfilled, (state, action) => {
|
||||
state.statsLoading = false;
|
||||
state.stats = action.payload;
|
||||
})
|
||||
.addCase(fetchGoalStats.rejected, (state, action) => {
|
||||
state.statsLoading = false;
|
||||
state.statsError = action.payload as string;
|
||||
})
|
||||
|
||||
// 批量操作
|
||||
.addCase(batchOperateGoals.pending, (state) => {
|
||||
state.batchLoading = true;
|
||||
state.batchError = null;
|
||||
})
|
||||
.addCase(batchOperateGoals.fulfilled, (state, action) => {
|
||||
state.batchLoading = false;
|
||||
const { operation, results } = action.payload;
|
||||
|
||||
// 根据操作类型更新状态
|
||||
results.forEach(result => {
|
||||
if (result.success) {
|
||||
const goalIndex = state.goals.findIndex(goal => goal.id === result.goalId);
|
||||
if (goalIndex !== -1) {
|
||||
switch (operation.action) {
|
||||
case 'pause':
|
||||
state.goals[goalIndex].status = 'paused';
|
||||
break;
|
||||
case 'resume':
|
||||
state.goals[goalIndex].status = 'active';
|
||||
break;
|
||||
case 'complete':
|
||||
state.goals[goalIndex].status = 'completed';
|
||||
break;
|
||||
case 'delete':
|
||||
state.goals = state.goals.filter(goal => goal.id !== result.goalId);
|
||||
state.goalsPagination.total -= 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.addCase(batchOperateGoals.rejected, (state, action) => {
|
||||
state.batchLoading = false;
|
||||
state.batchError = action.payload as string;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setFilters,
|
||||
resetFilters,
|
||||
clearErrors,
|
||||
clearCurrentGoal,
|
||||
updateGoalStatus,
|
||||
incrementGoalCompletion,
|
||||
} = goalsSlice.actions;
|
||||
|
||||
export default goalsSlice.reducer;
|
||||
@@ -1,13 +1,9 @@
|
||||
import { persistActiveFastingSchedule } from '@/utils/fasting';
|
||||
import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit';
|
||||
import challengesReducer from './challengesSlice';
|
||||
import checkinReducer, { addExercise, autoSyncCheckin, removeExercise, replaceExercises, setNote, toggleExerciseCompleted } from './checkinSlice';
|
||||
import circumferenceReducer from './circumferenceSlice';
|
||||
import exerciseLibraryReducer from './exerciseLibrarySlice';
|
||||
import foodLibraryReducer from './foodLibrarySlice';
|
||||
import foodRecognitionReducer from './foodRecognitionSlice';
|
||||
import membershipReducer from './membershipSlice';
|
||||
import goalsReducer from './goalsSlice';
|
||||
import healthReducer from './healthSlice';
|
||||
import fastingReducer, {
|
||||
clearActiveSchedule,
|
||||
completeActiveSchedule,
|
||||
@@ -15,15 +11,17 @@ import fastingReducer, {
|
||||
scheduleFastingPlan,
|
||||
setRecommendedSchedule,
|
||||
} from './fastingSlice';
|
||||
import foodLibraryReducer from './foodLibrarySlice';
|
||||
import foodRecognitionReducer from './foodRecognitionSlice';
|
||||
import healthReducer from './healthSlice';
|
||||
import membershipReducer from './membershipSlice';
|
||||
import moodReducer from './moodSlice';
|
||||
import nutritionReducer from './nutritionSlice';
|
||||
import scheduleExerciseReducer from './scheduleExerciseSlice';
|
||||
import tasksReducer from './tasksSlice';
|
||||
import trainingPlanReducer from './trainingPlanSlice';
|
||||
import userReducer from './userSlice';
|
||||
import waterReducer from './waterSlice';
|
||||
import workoutReducer from './workoutSlice';
|
||||
import { persistActiveFastingSchedule } from '@/utils/fasting';
|
||||
|
||||
// 创建监听器中间件来处理自动同步
|
||||
const listenerMiddleware = createListenerMiddleware();
|
||||
@@ -99,11 +97,9 @@ export const store = configureStore({
|
||||
challenges: challengesReducer,
|
||||
checkin: checkinReducer,
|
||||
circumference: circumferenceReducer,
|
||||
goals: goalsReducer,
|
||||
health: healthReducer,
|
||||
mood: moodReducer,
|
||||
nutrition: nutritionReducer,
|
||||
tasks: tasksReducer,
|
||||
trainingPlan: trainingPlanReducer,
|
||||
scheduleExercise: scheduleExerciseReducer,
|
||||
exerciseLibrary: exerciseLibraryReducer,
|
||||
|
||||
@@ -1,322 +0,0 @@
|
||||
import { tasksApi } from '@/services/tasksApi';
|
||||
import {
|
||||
CompleteTaskRequest,
|
||||
GetTasksQuery,
|
||||
SkipTaskRequest,
|
||||
TaskListItem,
|
||||
TaskStats,
|
||||
} from '@/types/goals';
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
// 任务管理状态类型
|
||||
export interface TasksState {
|
||||
// 任务列表
|
||||
tasks: TaskListItem[];
|
||||
tasksLoading: boolean;
|
||||
tasksError: string | null;
|
||||
tasksPagination: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
};
|
||||
|
||||
// 任务统计
|
||||
stats: TaskStats | null;
|
||||
statsLoading: boolean;
|
||||
statsError: string | null;
|
||||
|
||||
// 完成任务
|
||||
completeLoading: boolean;
|
||||
completeError: string | null;
|
||||
|
||||
// 跳过任务
|
||||
skipLoading: boolean;
|
||||
skipError: string | null;
|
||||
|
||||
// 筛选和搜索
|
||||
filters: GetTasksQuery;
|
||||
}
|
||||
|
||||
const initialState: TasksState = {
|
||||
tasks: [],
|
||||
tasksLoading: false,
|
||||
tasksError: null,
|
||||
tasksPagination: {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
hasMore: false,
|
||||
},
|
||||
|
||||
stats: null,
|
||||
statsLoading: false,
|
||||
statsError: null,
|
||||
|
||||
completeLoading: false,
|
||||
completeError: null,
|
||||
|
||||
skipLoading: false,
|
||||
skipError: null,
|
||||
|
||||
filters: {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
},
|
||||
};
|
||||
|
||||
// 异步操作
|
||||
|
||||
/**
|
||||
* 获取任务列表
|
||||
*/
|
||||
export const fetchTasks = createAsyncThunk(
|
||||
'tasks/fetchTasks',
|
||||
async (query: GetTasksQuery = {}, { rejectWithValue }) => {
|
||||
try {
|
||||
console.log('fetchTasks', fetchTasks);
|
||||
|
||||
const response = await tasksApi.getTasks(query);
|
||||
console.log('fetchTasks response', response);
|
||||
return { query, response };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '获取任务列表失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 加载更多任务
|
||||
*/
|
||||
export const loadMoreTasks = createAsyncThunk(
|
||||
'tasks/loadMoreTasks',
|
||||
async (_, { getState, rejectWithValue }) => {
|
||||
try {
|
||||
const state = getState() as { tasks: TasksState };
|
||||
const { filters, tasksPagination } = state.tasks;
|
||||
|
||||
if (!tasksPagination.hasMore) {
|
||||
return { tasks: [], pagination: tasksPagination };
|
||||
}
|
||||
|
||||
const query = {
|
||||
...filters,
|
||||
page: tasksPagination.page + 1,
|
||||
};
|
||||
|
||||
const response = await tasksApi.getTasks(query);
|
||||
console.log('loadMoreTasks response', response);
|
||||
|
||||
return { query, response };
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '加载更多任务失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 完成任务
|
||||
*/
|
||||
export const completeTask = createAsyncThunk(
|
||||
'tasks/completeTask',
|
||||
async ({ taskId, completionData }: { taskId: string; completionData?: CompleteTaskRequest }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await tasksApi.completeTask(taskId, completionData);
|
||||
console.log('completeTask response', response);
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '完成任务失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 跳过任务
|
||||
*/
|
||||
export const skipTask = createAsyncThunk(
|
||||
'tasks/skipTask',
|
||||
async ({ taskId, skipData }: { taskId: string; skipData?: SkipTaskRequest }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await tasksApi.skipTask(taskId, skipData);
|
||||
console.log('skipTask response', response);
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '跳过任务失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取任务统计
|
||||
*/
|
||||
export const fetchTaskStats = createAsyncThunk(
|
||||
'tasks/fetchTaskStats',
|
||||
async (goalId: string, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await tasksApi.getTaskStats(goalId);
|
||||
console.log('fetchTaskStats response', response);
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
return rejectWithValue(error.message || '获取任务统计失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Redux Slice
|
||||
const tasksSlice = createSlice({
|
||||
name: 'tasks',
|
||||
initialState,
|
||||
reducers: {
|
||||
// 清除错误
|
||||
clearErrors: (state) => {
|
||||
state.tasksError = null;
|
||||
state.completeError = null;
|
||||
state.skipError = null;
|
||||
state.statsError = null;
|
||||
},
|
||||
|
||||
// 更新筛选条件
|
||||
updateFilters: (state, action: PayloadAction<Partial<GetTasksQuery>>) => {
|
||||
state.filters = { ...state.filters, ...action.payload };
|
||||
},
|
||||
|
||||
// 重置筛选条件
|
||||
resetFilters: (state) => {
|
||||
state.filters = {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
};
|
||||
},
|
||||
|
||||
// 乐观更新任务完成状态
|
||||
optimisticCompleteTask: (state, action: PayloadAction<{ taskId: string; count?: number }>) => {
|
||||
const { taskId, count = 1 } = action.payload;
|
||||
const task = state.tasks.find(t => t.id === taskId);
|
||||
if (task) {
|
||||
const newCount = Math.min(task.currentCount + count, task.targetCount);
|
||||
task.currentCount = newCount;
|
||||
task.progressPercentage = Math.round((newCount / task.targetCount) * 100);
|
||||
|
||||
if (newCount >= task.targetCount) {
|
||||
task.status = 'completed';
|
||||
task.completedAt = new Date().toISOString();
|
||||
} else if (newCount > 0) {
|
||||
task.status = 'in_progress';
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
// 获取任务列表
|
||||
.addCase(fetchTasks.pending, (state) => {
|
||||
state.tasksLoading = true;
|
||||
state.tasksError = null;
|
||||
})
|
||||
.addCase(fetchTasks.fulfilled, (state, action) => {
|
||||
state.tasksLoading = false;
|
||||
const { query, response } = action.payload;
|
||||
|
||||
// 如果是第一页,替换数据;否则追加数据
|
||||
state.tasks = response.list;
|
||||
console.log('state.tasks', state.tasks);
|
||||
|
||||
state.tasksPagination = {
|
||||
page: response.page,
|
||||
pageSize: response.pageSize,
|
||||
total: response.total,
|
||||
hasMore: response.page * response.pageSize < response.total,
|
||||
};
|
||||
})
|
||||
.addCase(fetchTasks.rejected, (state, action) => {
|
||||
state.tasksLoading = false;
|
||||
state.tasksError = action.payload as string;
|
||||
})
|
||||
|
||||
// 加载更多任务
|
||||
.addCase(loadMoreTasks.pending, (state) => {
|
||||
state.tasksLoading = true;
|
||||
})
|
||||
.addCase(loadMoreTasks.fulfilled, (state, action) => {
|
||||
state.tasksLoading = false;
|
||||
const { response } = action.payload;
|
||||
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.tasks = [...state.tasks, ...response.list];
|
||||
state.tasksPagination = {
|
||||
page: response.page,
|
||||
pageSize: response.pageSize,
|
||||
total: response.total,
|
||||
hasMore: response.page * response.pageSize < response.total,
|
||||
};
|
||||
})
|
||||
.addCase(loadMoreTasks.rejected, (state, action) => {
|
||||
state.tasksLoading = false;
|
||||
state.tasksError = action.payload as string;
|
||||
})
|
||||
|
||||
// 完成任务
|
||||
.addCase(completeTask.pending, (state) => {
|
||||
state.completeLoading = true;
|
||||
state.completeError = null;
|
||||
})
|
||||
.addCase(completeTask.fulfilled, (state, action) => {
|
||||
state.completeLoading = false;
|
||||
// 更新任务列表中的对应任务
|
||||
const updatedTask = action.payload;
|
||||
const index = state.tasks.findIndex(t => t.id === updatedTask.id);
|
||||
if (index !== -1) {
|
||||
state.tasks[index] = updatedTask;
|
||||
}
|
||||
})
|
||||
.addCase(completeTask.rejected, (state, action) => {
|
||||
state.completeLoading = false;
|
||||
state.completeError = action.payload as string;
|
||||
})
|
||||
|
||||
// 跳过任务
|
||||
.addCase(skipTask.pending, (state) => {
|
||||
state.skipLoading = true;
|
||||
state.skipError = null;
|
||||
})
|
||||
.addCase(skipTask.fulfilled, (state, action) => {
|
||||
state.skipLoading = false;
|
||||
// 更新任务列表中的对应任务
|
||||
const updatedTask = action.payload;
|
||||
const index = state.tasks.findIndex(t => t.id === updatedTask.id);
|
||||
if (index !== -1) {
|
||||
state.tasks[index] = updatedTask;
|
||||
}
|
||||
})
|
||||
.addCase(skipTask.rejected, (state, action) => {
|
||||
state.skipLoading = false;
|
||||
state.skipError = action.payload as string;
|
||||
})
|
||||
|
||||
// 获取任务统计
|
||||
.addCase(fetchTaskStats.pending, (state) => {
|
||||
state.statsLoading = true;
|
||||
state.statsError = null;
|
||||
})
|
||||
.addCase(fetchTaskStats.fulfilled, (state, action) => {
|
||||
state.statsLoading = false;
|
||||
state.stats = action.payload;
|
||||
})
|
||||
.addCase(fetchTaskStats.rejected, (state, action) => {
|
||||
state.statsLoading = false;
|
||||
state.statsError = action.payload as string;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
clearErrors,
|
||||
updateFilters,
|
||||
resetFilters,
|
||||
optimisticCompleteTask,
|
||||
} = tasksSlice.actions;
|
||||
|
||||
export default tasksSlice.reducer;
|
||||
Reference in New Issue
Block a user