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;

本章练习

  1. 项目初始化

    • 创建项目结构
    • 配置开发环境
    • 设置代码规范
  2. 认证系统

    • 实现用户注册和登录
    • 添加 JWT 认证
    • 实现权限控制
  3. 任务管理

    • 创建任务 CRUD 功能
    • 实现任务状态管理
    • 添加任务过滤和搜索
  4. 状态管理

    • 配置 Redux Toolkit
    • 实现异步数据获取
    • 添加乐观更新

本章总结

本章开始了任务管理系统的实战开发:

  1. 项目规划:完成了需求分析、架构设计和项目结构规划
  2. 认证系统:实现了完整的用户认证流程,包括登录、注册、token 管理
  3. 任务管理:构建了任务的状态管理和基础 CRUD 操作
  4. 最佳实践:应用了 TypeScript 的类型安全、Redux Toolkit 的现代状态管理

下一章将继续完善项目功能,包括用户界面组件、实时通信和部署配置。