11.1 项目概述与架构设计
11.1.1 项目需求分析
// src/types/requirements.ts - 需求定义
export interface ProjectRequirements {
functional: {
userManagement: {
registration: boolean;
authentication: boolean;
profileManagement: boolean;
};
taskManagement: {
createTask: boolean;
editTask: boolean;
deleteTask: boolean;
taskStatus: boolean;
taskPriority: boolean;
taskCategories: boolean;
};
collaboration: {
taskAssignment: boolean;
comments: boolean;
fileAttachments: boolean;
notifications: boolean;
};
reporting: {
taskStatistics: boolean;
progressTracking: boolean;
timeTracking: boolean;
};
};
nonFunctional: {
performance: {
responseTime: string; // "< 200ms"
throughput: string; // "1000 concurrent users"
};
security: {
authentication: string; // "JWT"
authorization: string; // "RBAC"
dataEncryption: boolean;
};
scalability: {
horizontalScaling: boolean;
databaseSharding: boolean;
};
};
}
// 项目需求实例
export const projectRequirements: ProjectRequirements = {
functional: {
userManagement: {
registration: true,
authentication: true,
profileManagement: true
},
taskManagement: {
createTask: true,
editTask: true,
deleteTask: true,
taskStatus: true,
taskPriority: true,
taskCategories: true
},
collaboration: {
taskAssignment: true,
comments: true,
fileAttachments: true,
notifications: true
},
reporting: {
taskStatistics: true,
progressTracking: true,
timeTracking: true
}
},
nonFunctional: {
performance: {
responseTime: "< 200ms",
throughput: "1000 concurrent users"
},
security: {
authentication: "JWT",
authorization: "RBAC",
dataEncryption: true
},
scalability: {
horizontalScaling: true,
databaseSharding: false
}
}
};
11.1.2 系统架构设计
// src/types/architecture.ts - 架构定义
export interface SystemArchitecture {
frontend: {
framework: string;
stateManagement: string;
routing: string;
uiLibrary: string;
buildTool: string;
};
backend: {
runtime: string;
framework: string;
database: string;
cache: string;
messageQueue: string;
};
infrastructure: {
containerization: string;
orchestration: string;
monitoring: string;
logging: string;
};
}
export const systemArchitecture: SystemArchitecture = {
frontend: {
framework: "React 18",
stateManagement: "Redux Toolkit",
routing: "React Router v6",
uiLibrary: "Material-UI v5",
buildTool: "Vite"
},
backend: {
runtime: "Node.js",
framework: "Express.js",
database: "PostgreSQL",
cache: "Redis",
messageQueue: "RabbitMQ"
},
infrastructure: {
containerization: "Docker",
orchestration: "Docker Compose",
monitoring: "Prometheus + Grafana",
logging: "Winston + ELK Stack"
}
};
// src/types/domain.ts - 领域模型
export interface User {
id: string;
email: string;
username: string;
firstName: string;
lastName: string;
avatar?: string;
role: UserRole;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
export enum UserRole {
ADMIN = 'admin',
MANAGER = 'manager',
MEMBER = 'member'
}
export interface Task {
id: string;
title: string;
description: string;
status: TaskStatus;
priority: TaskPriority;
category: TaskCategory;
assigneeId?: string;
assignee?: User;
creatorId: string;
creator: User;
dueDate?: Date;
estimatedHours?: number;
actualHours?: number;
tags: string[];
attachments: Attachment[];
comments: Comment[];
createdAt: Date;
updatedAt: Date;
}
export enum TaskStatus {
TODO = 'todo',
IN_PROGRESS = 'in_progress',
IN_REVIEW = 'in_review',
DONE = 'done',
CANCELLED = 'cancelled'
}
export enum TaskPriority {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high',
URGENT = 'urgent'
}
export interface TaskCategory {
id: string;
name: string;
color: string;
description?: string;
}
export interface Comment {
id: string;
content: string;
authorId: string;
author: User;
taskId: string;
createdAt: Date;
updatedAt: Date;
}
export interface Attachment {
id: string;
filename: string;
originalName: string;
mimeType: string;
size: number;
url: string;
uploadedBy: string;
taskId: string;
createdAt: Date;
}
export interface Project {
id: string;
name: string;
description: string;
ownerId: string;
owner: User;
members: User[];
tasks: Task[];
categories: TaskCategory[];
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
11.1.3 项目结构设计
// project-structure.ts - 项目结构定义
export const projectStructure = {
frontend: {
'src/': {
'components/': {
'common/': ['Button', 'Input', 'Modal', 'Loading'],
'layout/': ['Header', 'Sidebar', 'Footer'],
'task/': ['TaskCard', 'TaskForm', 'TaskList', 'TaskDetail'],
'user/': ['UserProfile', 'UserList', 'UserForm']
},
'pages/': {
'auth/': ['Login', 'Register', 'ForgotPassword'],
'dashboard/': ['Dashboard', 'Analytics'],
'tasks/': ['TasksPage', 'TaskDetail', 'CreateTask'],
'projects/': ['ProjectsPage', 'ProjectDetail'],
'users/': ['UsersPage', 'UserProfile']
},
'hooks/': ['useAuth', 'useTasks', 'useProjects', 'useUsers'],
'store/': {
'slices/': ['authSlice', 'tasksSlice', 'projectsSlice', 'usersSlice'],
'api/': ['authApi', 'tasksApi', 'projectsApi', 'usersApi']
},
'utils/': ['api', 'validation', 'formatting', 'constants'],
'types/': ['api', 'domain', 'ui'],
'styles/': ['globals.css', 'components.css', 'themes.ts']
}
},
backend: {
'src/': {
'controllers/': ['authController', 'tasksController', 'projectsController'],
'services/': ['authService', 'tasksService', 'projectsService'],
'repositories/': ['userRepository', 'taskRepository', 'projectRepository'],
'models/': ['User', 'Task', 'Project', 'Comment'],
'middleware/': ['auth', 'validation', 'errorHandler', 'logging'],
'routes/': ['auth', 'tasks', 'projects', 'users'],
'utils/': ['database', 'jwt', 'validation', 'logger'],
'types/': ['api', 'database', 'auth']
}
},
shared: {
'types/': ['common', 'api', 'domain'],
'utils/': ['validation', 'formatting', 'constants']
}
};
11.2 核心功能实现
11.2.1 用户认证系统
// src/types/auth.ts - 认证类型定义
export interface LoginCredentials {
email: string;
password: string;
}
export interface RegisterData {
email: string;
username: string;
firstName: string;
lastName: string;
password: string;
confirmPassword: string;
}
export interface AuthResponse {
user: User;
accessToken: string;
refreshToken: string;
expiresIn: number;
}
export interface AuthState {
user: User | null;
accessToken: string | null;
refreshToken: string | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
}
// src/store/slices/authSlice.ts - 认证状态管理
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { authApi } from '../api/authApi';
import { AuthState, LoginCredentials, RegisterData, AuthResponse } from '../../types/auth';
const initialState: AuthState = {
user: null,
accessToken: localStorage.getItem('accessToken'),
refreshToken: localStorage.getItem('refreshToken'),
isAuthenticated: false,
isLoading: false,
error: null
};
// 异步 thunks
export const loginUser = createAsyncThunk<
AuthResponse,
LoginCredentials,
{ rejectValue: string }
>(
'auth/login',
async (credentials, { rejectWithValue }) => {
try {
const response = await authApi.login(credentials);
// 存储 tokens
localStorage.setItem('accessToken', response.accessToken);
localStorage.setItem('refreshToken', response.refreshToken);
return response;
} catch (error: any) {
return rejectWithValue(error.message || 'Login failed');
}
}
);
export const registerUser = createAsyncThunk<
AuthResponse,
RegisterData,
{ rejectValue: string }
>(
'auth/register',
async (userData, { rejectWithValue }) => {
try {
const response = await authApi.register(userData);
localStorage.setItem('accessToken', response.accessToken);
localStorage.setItem('refreshToken', response.refreshToken);
return response;
} catch (error: any) {
return rejectWithValue(error.message || 'Registration failed');
}
}
);
export const refreshToken = createAsyncThunk<
{ accessToken: string; expiresIn: number },
void,
{ rejectValue: string }
>(
'auth/refreshToken',
async (_, { getState, rejectWithValue }) => {
try {
const state = getState() as { auth: AuthState };
const refreshToken = state.auth.refreshToken;
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await authApi.refreshToken(refreshToken);
localStorage.setItem('accessToken', response.accessToken);
return response;
} catch (error: any) {
return rejectWithValue(error.message || 'Token refresh failed');
}
}
);
export const logoutUser = createAsyncThunk<
void,
void,
{ rejectValue: string }
>(
'auth/logout',
async (_, { getState, rejectWithValue }) => {
try {
const state = getState() as { auth: AuthState };
const refreshToken = state.auth.refreshToken;
if (refreshToken) {
await authApi.logout(refreshToken);
}
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
} catch (error: any) {
// 即使登出失败,也要清除本地存储
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
return rejectWithValue(error.message || 'Logout failed');
}
}
);
export const getCurrentUser = createAsyncThunk<
User,
void,
{ rejectValue: string }
>(
'auth/getCurrentUser',
async (_, { rejectWithValue }) => {
try {
const response = await authApi.getCurrentUser();
return response;
} catch (error: any) {
return rejectWithValue(error.message || 'Failed to get current user');
}
}
);
// Auth slice
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
clearError: (state) => {
state.error = null;
},
setCredentials: (state, action: PayloadAction<AuthResponse>) => {
const { user, accessToken, refreshToken } = action.payload;
state.user = user;
state.accessToken = accessToken;
state.refreshToken = refreshToken;
state.isAuthenticated = true;
state.error = null;
},
clearCredentials: (state) => {
state.user = null;
state.accessToken = null;
state.refreshToken = null;
state.isAuthenticated = false;
state.error = null;
}
},
extraReducers: (builder) => {
builder
// Login
.addCase(loginUser.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(loginUser.fulfilled, (state, action) => {
state.isLoading = false;
state.user = action.payload.user;
state.accessToken = action.payload.accessToken;
state.refreshToken = action.payload.refreshToken;
state.isAuthenticated = true;
state.error = null;
})
.addCase(loginUser.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload || 'Login failed';
state.isAuthenticated = false;
})
// Register
.addCase(registerUser.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(registerUser.fulfilled, (state, action) => {
state.isLoading = false;
state.user = action.payload.user;
state.accessToken = action.payload.accessToken;
state.refreshToken = action.payload.refreshToken;
state.isAuthenticated = true;
state.error = null;
})
.addCase(registerUser.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload || 'Registration failed';
state.isAuthenticated = false;
})
// Refresh Token
.addCase(refreshToken.fulfilled, (state, action) => {
state.accessToken = action.payload.accessToken;
})
.addCase(refreshToken.rejected, (state) => {
state.user = null;
state.accessToken = null;
state.refreshToken = null;
state.isAuthenticated = false;
})
// Logout
.addCase(logoutUser.fulfilled, (state) => {
state.user = null;
state.accessToken = null;
state.refreshToken = null;
state.isAuthenticated = false;
state.error = null;
})
// Get Current User
.addCase(getCurrentUser.fulfilled, (state, action) => {
state.user = action.payload;
state.isAuthenticated = true;
})
.addCase(getCurrentUser.rejected, (state) => {
state.user = null;
state.accessToken = null;
state.refreshToken = null;
state.isAuthenticated = false;
});
}
});
export const { clearError, setCredentials, clearCredentials } = authSlice.actions;
export default authSlice.reducer;
// src/hooks/useAuth.ts - 认证 Hook
import { useSelector, useDispatch } from 'react-redux';
import { useCallback, useEffect } from 'react';
import { RootState, AppDispatch } from '../store';
import {
loginUser,
registerUser,
logoutUser,
getCurrentUser,
refreshToken,
clearError
} from '../store/slices/authSlice';
import { LoginCredentials, RegisterData } from '../types/auth';
export const useAuth = () => {
const dispatch = useDispatch<AppDispatch>();
const authState = useSelector((state: RootState) => state.auth);
const login = useCallback(async (credentials: LoginCredentials) => {
const result = await dispatch(loginUser(credentials));
return result.type === loginUser.fulfilled.type;
}, [dispatch]);
const register = useCallback(async (userData: RegisterData) => {
const result = await dispatch(registerUser(userData));
return result.type === registerUser.fulfilled.type;
}, [dispatch]);
const logout = useCallback(async () => {
await dispatch(logoutUser());
}, [dispatch]);
const clearAuthError = useCallback(() => {
dispatch(clearError());
}, [dispatch]);
const checkAuth = useCallback(async () => {
if (authState.accessToken && !authState.user) {
await dispatch(getCurrentUser());
}
}, [dispatch, authState.accessToken, authState.user]);
const refreshAuthToken = useCallback(async () => {
if (authState.refreshToken) {
await dispatch(refreshToken());
}
}, [dispatch, authState.refreshToken]);
// 自动检查认证状态
useEffect(() => {
checkAuth();
}, [checkAuth]);
// 自动刷新 token
useEffect(() => {
if (!authState.accessToken || !authState.refreshToken) {
return;
}
// 设置定时刷新 token(在过期前 5 分钟刷新)
const refreshInterval = setInterval(() => {
refreshAuthToken();
}, 25 * 60 * 1000); // 25 分钟
return () => clearInterval(refreshInterval);
}, [authState.accessToken, authState.refreshToken, refreshAuthToken]);
return {
...authState,
login,
register,
logout,
clearError: clearAuthError,
checkAuth,
refreshToken: refreshAuthToken
};
};
// src/components/auth/LoginForm.tsx - 登录表单组件
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
TextField,
Button,
Typography,
Alert,
Link,
InputAdornment,
IconButton
} from '@mui/material';
import { Visibility, VisibilityOff, Email, Lock } from '@mui/icons-material';
import { useAuth } from '../../hooks/useAuth';
import { LoginCredentials } from '../../types/auth';
import { useNavigate, Link as RouterLink } from 'react-router-dom';
interface LoginFormProps {
onSuccess?: () => void;
}
export const LoginForm: React.FC<LoginFormProps> = ({ onSuccess }) => {
const navigate = useNavigate();
const { login, isLoading, error, isAuthenticated, clearError } = useAuth();
const [formData, setFormData] = useState<LoginCredentials>({
email: '',
password: ''
});
const [showPassword, setShowPassword] = useState(false);
const [formErrors, setFormErrors] = useState<Partial<LoginCredentials>>({});
useEffect(() => {
if (isAuthenticated) {
onSuccess?.();
navigate('/dashboard');
}
}, [isAuthenticated, navigate, onSuccess]);
useEffect(() => {
// 清除错误当组件卸载时
return () => {
clearError();
};
}, [clearError]);
const validateForm = (): boolean => {
const errors: Partial<LoginCredentials> = {};
if (!formData.email) {
errors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
errors.email = 'Email is invalid';
}
if (!formData.password) {
errors.password = 'Password is required';
} else if (formData.password.length < 6) {
errors.password = 'Password must be at least 6 characters';
}
setFormErrors(errors);
return Object.keys(errors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) {
return;
}
clearError();
const success = await login(formData);
if (!success) {
// 错误已经在 Redux store 中处理
console.error('Login failed');
}
};
const handleInputChange = (field: keyof LoginCredentials) => (
e: React.ChangeEvent<HTMLInputElement>
) => {
setFormData(prev => ({
...prev,
[field]: e.target.value
}));
// 清除字段错误
if (formErrors[field]) {
setFormErrors(prev => ({
...prev,
[field]: undefined
}));
}
};
return (
<Box
display="flex"
justifyContent="center"
alignItems="center"
minHeight="100vh"
bgcolor="grey.100"
>
<Card sx={{ maxWidth: 400, width: '100%', mx: 2 }}>
<CardContent sx={{ p: 4 }}>
<Typography variant="h4" component="h1" gutterBottom align="center">
Login
</Typography>
<Typography variant="body2" color="text.secondary" align="center" mb={3}>
Welcome back! Please sign in to your account.
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<Box component="form" onSubmit={handleSubmit}>
<TextField
fullWidth
label="Email"
type="email"
value={formData.email}
onChange={handleInputChange('email')}
error={!!formErrors.email}
helperText={formErrors.email}
margin="normal"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Email />
</InputAdornment>
)
}}
/>
<TextField
fullWidth
label="Password"
type={showPassword ? 'text' : 'password'}
value={formData.password}
onChange={handleInputChange('password')}
error={!!formErrors.password}
helperText={formErrors.password}
margin="normal"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Lock />
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
)
}}
/>
<Button
type="submit"
fullWidth
variant="contained"
size="large"
disabled={isLoading}
sx={{ mt: 3, mb: 2 }}
>
{isLoading ? 'Signing In...' : 'Sign In'}
</Button>
<Box textAlign="center">
<Link component={RouterLink} to="/auth/forgot-password" variant="body2">
Forgot password?
</Link>
</Box>
<Box textAlign="center" mt={2}>
<Typography variant="body2">
Don't have an account?{' '}
<Link component={RouterLink} to="/auth/register">
Sign up
</Link>
</Typography>
</Box>
</Box>
</CardContent>
</Card>
</Box>
);
};
11.2.2 任务管理功能
// src/store/slices/tasksSlice.ts - 任务状态管理
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { tasksApi } from '../api/tasksApi';
import { Task, TaskStatus, TaskPriority } from '../../types/domain';
interface TasksState {
tasks: Task[];
currentTask: Task | null;
isLoading: boolean;
error: string | null;
filters: {
status?: TaskStatus;
priority?: TaskPriority;
assigneeId?: string;
search?: string;
};
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
const initialState: TasksState = {
tasks: [],
currentTask: null,
isLoading: false,
error: null,
filters: {},
pagination: {
page: 1,
limit: 10,
total: 0,
totalPages: 0
}
};
// 异步 thunks
export const fetchTasks = createAsyncThunk<
{ tasks: Task[]; pagination: TasksState['pagination'] },
{ page?: number; limit?: number; filters?: TasksState['filters'] },
{ rejectValue: string }
>(
'tasks/fetchTasks',
async ({ page = 1, limit = 10, filters = {} }, { rejectWithValue }) => {
try {
const response = await tasksApi.getTasks({ page, limit, ...filters });
return {
tasks: response.data,
pagination: {
page: response.page,
limit: response.limit,
total: response.total,
totalPages: response.totalPages
}
};
} catch (error: any) {
return rejectWithValue(error.message || 'Failed to fetch tasks');
}
}
);
export const fetchTaskById = createAsyncThunk<
Task,
string,
{ rejectValue: string }
>(
'tasks/fetchTaskById',
async (taskId, { rejectWithValue }) => {
try {
const response = await tasksApi.getTaskById(taskId);
return response;
} catch (error: any) {
return rejectWithValue(error.message || 'Failed to fetch task');
}
}
);
export const createTask = createAsyncThunk<
Task,
Omit<Task, 'id' | 'creator' | 'createdAt' | 'updatedAt'>,
{ rejectValue: string }
>(
'tasks/createTask',
async (taskData, { rejectWithValue }) => {
try {
const response = await tasksApi.createTask(taskData);
return response;
} catch (error: any) {
return rejectWithValue(error.message || 'Failed to create task');
}
}
);
export const updateTask = createAsyncThunk<
Task,
{ id: string; updates: Partial<Task> },
{ rejectValue: string }
>(
'tasks/updateTask',
async ({ id, updates }, { rejectWithValue }) => {
try {
const response = await tasksApi.updateTask(id, updates);
return response;
} catch (error: any) {
return rejectWithValue(error.message || 'Failed to update task');
}
}
);
export const deleteTask = createAsyncThunk<
string,
string,
{ rejectValue: string }
>(
'tasks/deleteTask',
async (taskId, { rejectWithValue }) => {
try {
await tasksApi.deleteTask(taskId);
return taskId;
} catch (error: any) {
return rejectWithValue(error.message || 'Failed to delete task');
}
}
);
export const updateTaskStatus = createAsyncThunk<
Task,
{ id: string; status: TaskStatus },
{ rejectValue: string }
>(
'tasks/updateTaskStatus',
async ({ id, status }, { rejectWithValue }) => {
try {
const response = await tasksApi.updateTaskStatus(id, status);
return response;
} catch (error: any) {
return rejectWithValue(error.message || 'Failed to update task status');
}
}
);
// Tasks slice
const tasksSlice = createSlice({
name: 'tasks',
initialState,
reducers: {
setFilters: (state, action: PayloadAction<TasksState['filters']>) => {
state.filters = { ...state.filters, ...action.payload };
},
clearFilters: (state) => {
state.filters = {};
},
setCurrentTask: (state, action: PayloadAction<Task | null>) => {
state.currentTask = action.payload;
},
clearError: (state) => {
state.error = null;
},
optimisticUpdateTask: (state, action: PayloadAction<{ id: string; updates: Partial<Task> }>) => {
const { id, updates } = action.payload;
const taskIndex = state.tasks.findIndex(task => task.id === id);
if (taskIndex !== -1) {
state.tasks[taskIndex] = { ...state.tasks[taskIndex], ...updates };
}
if (state.currentTask?.id === id) {
state.currentTask = { ...state.currentTask, ...updates };
}
}
},
extraReducers: (builder) => {
builder
// Fetch Tasks
.addCase(fetchTasks.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchTasks.fulfilled, (state, action) => {
state.isLoading = false;
state.tasks = action.payload.tasks;
state.pagination = action.payload.pagination;
state.error = null;
})
.addCase(fetchTasks.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload || 'Failed to fetch tasks';
})
// Fetch Task By ID
.addCase(fetchTaskById.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchTaskById.fulfilled, (state, action) => {
state.isLoading = false;
state.currentTask = action.payload;
state.error = null;
})
.addCase(fetchTaskById.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload || 'Failed to fetch task';
})
// Create Task
.addCase(createTask.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(createTask.fulfilled, (state, action) => {
state.isLoading = false;
state.tasks.unshift(action.payload);
state.pagination.total += 1;
state.error = null;
})
.addCase(createTask.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload || 'Failed to create task';
})
// Update Task
.addCase(updateTask.fulfilled, (state, action) => {
const updatedTask = action.payload;
const taskIndex = state.tasks.findIndex(task => task.id === updatedTask.id);
if (taskIndex !== -1) {
state.tasks[taskIndex] = updatedTask;
}
if (state.currentTask?.id === updatedTask.id) {
state.currentTask = updatedTask;
}
})
.addCase(updateTask.rejected, (state, action) => {
state.error = action.payload || 'Failed to update task';
})
// Delete Task
.addCase(deleteTask.fulfilled, (state, action) => {
const taskId = action.payload;
state.tasks = state.tasks.filter(task => task.id !== taskId);
if (state.currentTask?.id === taskId) {
state.currentTask = null;
}
state.pagination.total -= 1;
})
.addCase(deleteTask.rejected, (state, action) => {
state.error = action.payload || 'Failed to delete task';
})
// Update Task Status
.addCase(updateTaskStatus.fulfilled, (state, action) => {
const updatedTask = action.payload;
const taskIndex = state.tasks.findIndex(task => task.id === updatedTask.id);
if (taskIndex !== -1) {
state.tasks[taskIndex] = updatedTask;
}
if (state.currentTask?.id === updatedTask.id) {
state.currentTask = updatedTask;
}
})
.addCase(updateTaskStatus.rejected, (state, action) => {
state.error = action.payload || 'Failed to update task status';
});
}
});
export const {
setFilters,
clearFilters,
setCurrentTask,
clearError,
optimisticUpdateTask
} = tasksSlice.actions;
export default tasksSlice.reducer;
本章练习
项目初始化:
- 创建项目结构
- 配置开发环境
- 设置代码规范
认证系统:
- 实现用户注册和登录
- 添加 JWT 认证
- 实现权限控制
任务管理:
- 创建任务 CRUD 功能
- 实现任务状态管理
- 添加任务过滤和搜索
状态管理:
- 配置 Redux Toolkit
- 实现异步数据获取
- 添加乐观更新
本章总结
本章开始了任务管理系统的实战开发:
- 项目规划:完成了需求分析、架构设计和项目结构规划
- 认证系统:实现了完整的用户认证流程,包括登录、注册、token 管理
- 任务管理:构建了任务的状态管理和基础 CRUD 操作
- 最佳实践:应用了 TypeScript 的类型安全、Redux Toolkit 的现代状态管理
下一章将继续完善项目功能,包括用户界面组件、实时通信和部署配置。