feat: 新增任务管理功能及相关组件
- 将目标页面改为任务列表,支持任务的创建、完成和跳过功能 - 新增任务卡片和任务进度卡片组件,展示任务状态和进度 - 实现任务数据的获取和管理,集成Redux状态管理 - 更新API服务,支持任务相关的CRUD操作 - 编写任务管理功能实现文档,详细描述功能和组件架构
This commit is contained in:
318
store/tasksSlice.ts
Normal file
318
store/tasksSlice.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
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 {
|
||||
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;
|
||||
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