Files
digital-pilates/store/tasksSlice.ts
richarjiang 91df01bd79 feat(auth): 预加载用户数据并优化登录状态同步
- 在启动屏预加载用户 token 与资料,避免首页白屏
- 新增 rehydrateUserSync 同步注入 Redux,减少异步等待
- 登录页兼容 ERR_REQUEST_CANCELED 取消场景
- 各页面统一依赖 isLoggedIn 判断,移除冗余控制台日志
- 步数卡片与详情页改为实时拉取健康数据,不再缓存至 Redux
- 后台任务注册移至顶层,防止重复定义
- 体重记录、HeaderBar 等 UI 细节样式微调
2025-09-15 09:56:42 +08:00

323 lines
8.5 KiB
TypeScript

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;