# 🚀 前端开发快速启动指南 - Day 18 **日期**: 2025-11-05 **状态**: ✅ 后端 API 就绪,前端可以立即开始开发 **预计工作量**: 16-22 小时(2-3 天) --- ## 📋 前提条件检查清单 在开始开发前,确保以下条件已满足: - [ ] **后端 API 正在运行** ```bash # 如果未运行,执行: cd colaflow-api/src/ColaFlow.Api dotnet run ``` - [ ] **可以访问 Scalar UI** 打开浏览器:http://localhost:5167/scalar/v1 - [ ] **已阅读 API 文档** 位置:`docs/api/FRONTEND_HANDOFF_DAY16.md` - [ ] **前端项目可以运行** ```bash cd colaflow-web npm install npm run dev ``` --- ## 🎯 Day 18 开发目标 **核心目标**: 完成 ProjectManagement API 集成,替换旧的 Issue Management API **必须完成的功能** (P0): 1. ✅ Projects 列表和详情页面 2. ✅ Epics 列表和详情页面 3. ✅ Stories 列表和详情页面 4. ✅ Tasks 列表和详情页面 5. ✅ 更新 Kanban Board 使用新 API **可选功能** (P1): - Sprint 管理基础功能 - User 管理界面 - SignalR 实时更新 --- ## 🚀 快速开始(5分钟) ### Step 1: 验证后端 API 打开浏览器访问:http://localhost:5167/scalar/v1 你应该看到 Scalar API 文档界面,包含以下模块: - 🔐 Authentication - 📦 ProjectManagement - 👤 Identity & Tenants - 📡 Real-time (SignalR) ### Step 2: 测试 API(使用 Scalar UI) 1. 点击 **"Authorize"** 按钮 2. 获取 JWT token(从登录接口或使用测试 token) 3. 输入:`Bearer ` 4. 测试几个端点: - `GET /api/v1/projects` - 获取项目列表 - `GET /api/v1/epics` - 获取 Epic 列表 ### Step 3: 生成 TypeScript 类型(推荐) ```bash cd colaflow-web # 安装类型生成工具 npm install --save-dev openapi-typescript # 生成类型 npx openapi-typescript http://localhost:5167/openapi/v1.json --output ./src/types/api.ts # 查看生成的类型 cat src/types/api.ts | head -50 ``` --- ## 📚 关键文档位置 | 文档 | 位置 | 用途 | |------|------|------| | **API 完整参考** | `docs/api/ProjectManagement-API-Reference.md` | 所有端点详细说明 | | **API 端点清单** | `docs/api/API-Endpoints-Summary.md` | 快速查找端点 | | **前端集成指南** | `docs/api/FRONTEND_HANDOFF_DAY16.md` | 代码示例和最佳实践 | | **OpenAPI Spec** | `docs/api/openapi.json` | 标准 OpenAPI 3.0 规范 | | **Scalar UI** | http://localhost:5167/scalar/v1 | 交互式 API 文档 | --- ## 🔧 开发工作流 ### Phase 1: API Client 设置(1-2小时) #### 1.1 创建 API Client 基础配置 **文件**: `colaflow-web/lib/api/client.ts` ```typescript import axios, { AxiosInstance } from 'axios'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5167'; class ApiClient { private client: AxiosInstance; constructor() { this.client = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }); // 请求拦截器 - 添加 JWT token this.client.interceptors.request.use((config) => { const token = localStorage.getItem('jwt_token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // 响应拦截器 - 处理错误 this.client.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { // Token 过期,跳转到登录页 localStorage.removeItem('jwt_token'); window.location.href = '/login'; } return Promise.reject(error); } ); } public get(url: string, params?: any) { return this.client.get(url, { params }); } public post(url: string, data?: any) { return this.client.post(url, data); } public put(url: string, data?: any) { return this.client.put(url, data); } public delete(url: string) { return this.client.delete(url); } } export const apiClient = new ApiClient(); ``` #### 1.2 创建 ProjectManagement API 模块 **文件**: `colaflow-web/lib/api/pm.ts` ```typescript import { apiClient } from './client'; // Types (可以从 openapi-typescript 生成的文件导入) export interface Project { id: string; name: string; key: string; description?: string; tenantId: string; createdAt: string; updatedAt: string; } export interface Epic { id: string; title: string; description?: string; projectId: string; status: 'Backlog' | 'Todo' | 'InProgress' | 'Done'; priority: 'Low' | 'Medium' | 'High' | 'Critical'; estimatedHours?: number; actualHours?: number; assigneeId?: string; tenantId: string; createdAt: string; updatedAt: string; } export interface Story { id: string; title: string; description?: string; epicId: string; projectId: string; status: 'Backlog' | 'Todo' | 'InProgress' | 'Done'; priority: 'Low' | 'Medium' | 'High' | 'Critical'; estimatedHours?: number; actualHours?: number; assigneeId?: string; tenantId: string; createdAt: string; updatedAt: string; } export interface Task { id: string; title: string; description?: string; storyId: string; projectId: string; status: 'Backlog' | 'Todo' | 'InProgress' | 'Done'; priority: 'Low' | 'Medium' | 'High' | 'Critical'; estimatedHours?: number; actualHours?: number; assigneeId?: string; tenantId: string; createdAt: string; updatedAt: string; } // API 方法 export const projectsApi = { list: () => apiClient.get('/api/v1/projects'), get: (id: string) => apiClient.get(`/api/v1/projects/${id}`), create: (data: { name: string; key: string; description?: string }) => apiClient.post('/api/v1/projects', data), update: (id: string, data: { name: string; key: string; description?: string }) => apiClient.put(`/api/v1/projects/${id}`, data), delete: (id: string) => apiClient.delete(`/api/v1/projects/${id}`), }; export const epicsApi = { list: (projectId?: string) => apiClient.get('/api/v1/epics', { projectId }), get: (id: string) => apiClient.get(`/api/v1/epics/${id}`), create: (data: { projectId: string; title: string; description?: string; priority: Epic['priority']; estimatedHours?: number; }) => apiClient.post('/api/v1/epics', data), update: (id: string, data: Partial) => apiClient.put(`/api/v1/epics/${id}`, data), changeStatus: (id: string, status: Epic['status']) => apiClient.put(`/api/v1/epics/${id}/status`, { status }), assign: (id: string, assigneeId: string) => apiClient.put(`/api/v1/epics/${id}/assign`, { assigneeId }), }; export const storiesApi = { list: (epicId?: string) => apiClient.get('/api/v1/stories', { epicId }), get: (id: string) => apiClient.get(`/api/v1/stories/${id}`), create: (data: { epicId: string; title: string; description?: string; priority: Story['priority']; estimatedHours?: number; }) => apiClient.post('/api/v1/stories', data), update: (id: string, data: Partial) => apiClient.put(`/api/v1/stories/${id}`, data), assign: (id: string, assigneeId: string) => apiClient.put(`/api/v1/stories/${id}/assign`, { assigneeId }), }; export const tasksApi = { list: (storyId?: string) => apiClient.get('/api/v1/tasks', { storyId }), get: (id: string) => apiClient.get(`/api/v1/tasks/${id}`), create: (data: { storyId: string; title: string; description?: string; priority: Task['priority']; estimatedHours?: number; }) => apiClient.post('/api/v1/tasks', data), update: (id: string, data: Partial) => apiClient.put(`/api/v1/tasks/${id}`, data), changeStatus: (id: string, status: Task['status']) => apiClient.put(`/api/v1/tasks/${id}/status`, { status }), assign: (id: string, assigneeId: string) => apiClient.put(`/api/v1/tasks/${id}/assign`, { assigneeId }), }; ``` #### 1.3 创建 React Query Hooks **文件**: `colaflow-web/lib/hooks/use-projects.ts` ```typescript import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { projectsApi, Project } from '@/lib/api/pm'; import { toast } from 'sonner'; export function useProjects() { return useQuery({ queryKey: ['projects'], queryFn: async () => { const response = await projectsApi.list(); return response.data; }, }); } export function useProject(id: string) { return useQuery({ queryKey: ['projects', id], queryFn: async () => { const response = await projectsApi.get(id); return response.data; }, enabled: !!id, }); } export function useCreateProject() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: { name: string; key: string; description?: string }) => projectsApi.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['projects'] }); toast.success('Project created successfully!'); }, onError: (error: any) => { toast.error(error.response?.data?.detail || 'Failed to create project'); }, }); } export function useUpdateProject() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => projectsApi.update(id, data), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ['projects'] }); queryClient.invalidateQueries({ queryKey: ['projects', variables.id] }); toast.success('Project updated successfully!'); }, onError: (error: any) => { toast.error(error.response?.data?.detail || 'Failed to update project'); }, }); } export function useDeleteProject() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => projectsApi.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['projects'] }); toast.success('Project deleted successfully!'); }, onError: (error: any) => { toast.error(error.response?.data?.detail || 'Failed to delete project'); }, }); } ``` **类似地创建**: - `use-epics.ts` - `use-stories.ts` - `use-tasks.ts` --- ### Phase 2: Projects UI(3-4小时) #### 2.1 Projects 列表页面 **文件**: `colaflow-web/app/(dashboard)/projects/page.tsx` ```typescript 'use client'; import { useProjects, useDeleteProject } from '@/lib/hooks/use-projects'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; import Link from 'next/link'; import { PlusIcon, TrashIcon } from 'lucide-react'; export default function ProjectsPage() { const { data: projects, isLoading, error } = useProjects(); const deleteProject = useDeleteProject(); if (isLoading) { return (
); } if (error) { return (
Error loading projects: {error.message}
); } return (

Projects

{projects?.map((project) => (

{project.name}

{project.key}

{project.description && (

{project.description}

)}
))}
{projects?.length === 0 && (

No projects yet. Create your first project!

)}
); } ``` #### 2.2 Project 详情页面 **文件**: `colaflow-web/app/(dashboard)/projects/[id]/page.tsx` ```typescript 'use client'; import { useProject } from '@/lib/hooks/use-projects'; import { useEpics } from '@/lib/hooks/use-epics'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import Link from 'next/link'; export default function ProjectDetailPage({ params }: { params: { id: string } }) { const { data: project, isLoading: projectLoading } = useProject(params.id); const { data: epics, isLoading: epicsLoading } = useEpics(params.id); if (projectLoading) return
Loading project...
; if (!project) return
Project not found
; return (

{project.name}

{project.key}

{project.description && (

{project.description}

)} Epics Kanban Board Settings {epicsLoading ? (
Loading epics...
) : (
{epics?.map((epic) => (

{epic.title}

{epic.description}

{epic.status} {epic.priority}
))}
)}
Project settings coming soon...
); } ``` --- ### Phase 3: Epics/Stories/Tasks UI(4-5小时) 按照类似的模式实现: - Epic 列表和详情页 - Story 列表和详情页 - Task 列表和详情页 **参考 Phase 2 的代码结构**。 --- ### Phase 4: 更新 Kanban Board(5-6小时) #### 4.1 更新 Kanban Board 使用新 API **文件**: `colaflow-web/app/(dashboard)/projects/[id]/kanban/page.tsx` ```typescript 'use client'; import { useEpics } from '@/lib/hooks/use-epics'; import { useStories } from '@/lib/hooks/use-stories'; import { useTasks } from '@/lib/hooks/use-tasks'; import { KanbanBoard } from '@/components/kanban/KanbanBoard'; export default function KanbanPage({ params }: { params: { id: string } }) { // 获取项目的所有 epics, stories, tasks const { data: epics } = useEpics(params.id); const { data: stories } = useStories(); // 可能需要按 project 过滤 const { data: tasks } = useTasks(); // 将数据转换为 Kanban Board 需要的格式 const kanbanData = useMemo(() => { // 合并 epics, stories, tasks 到统一的工作项列表 const workItems = [ ...(epics || []).map(epic => ({ ...epic, type: 'epic' as const })), ...(stories || []).map(story => ({ ...story, type: 'story' as const })), ...(tasks || []).map(task => ({ ...task, type: 'task' as const })), ]; // 按状态分组 return { Backlog: workItems.filter(item => item.status === 'Backlog'), Todo: workItems.filter(item => item.status === 'Todo'), InProgress: workItems.filter(item => item.status === 'InProgress'), Done: workItems.filter(item => item.status === 'Done'), }; }, [epics, stories, tasks]); return (

Kanban Board

); } ``` --- ## 🧪 测试清单 在提交代码前,请确保以下测试通过: ### 基础功能测试 - [ ] Projects 列表加载成功 - [ ] 创建新项目 - [ ] 编辑项目 - [ ] 删除项目 - [ ] 查看项目详情 ### Epics/Stories/Tasks 测试 - [ ] 创建 Epic - [ ] 创建 Story(在 Epic 下) - [ ] 创建 Task(在 Story 下) - [ ] 更新状态(Backlog → Todo → InProgress → Done) - [ ] 分配任务给用户 ### Kanban Board 测试 - [ ] 加载 Kanban Board - [ ] 拖拽卡片更改状态 - [ ] 显示 Epic/Story/Task 层级关系 - [ ] 显示工时信息 ### 错误处理测试 - [ ] 401 Unauthorized - 跳转到登录页 - [ ] 404 Not Found - 显示友好错误消息 - [ ] 网络错误 - 显示错误提示 --- ## 🐛 常见问题与解决方案 ### 问题 1: CORS 错误 **症状**: `Access-Control-Allow-Origin` 错误 **解决方案**: ```typescript // 确保 API 已配置 CORS(后端已配置) // 前端无需额外处理 ``` ### 问题 2: 401 Unauthorized **症状**: 所有请求返回 401 **解决方案**: ```typescript // 检查 JWT token 是否正确设置 const token = localStorage.getItem('jwt_token'); console.log('Token:', token); // 检查 token 格式 // 应该是: Bearer ``` ### 问题 3: 404 Not Found(但资源存在) **症状**: 可以看到资源,但 API 返回 404 **原因**: 多租户隔离 - 资源属于其他租户 **解决方案**: ```typescript // 确保 JWT token 包含正确的 tenant_id // 检查 JWT payload: const payload = JSON.parse(atob(token.split('.')[1])); console.log('Tenant ID:', payload.tenant_id); ``` ### 问题 4: TypeScript 类型错误 **症状**: `Property 'xxx' does not exist on type` **解决方案**: ```bash # 重新生成类型 npx openapi-typescript http://localhost:5167/openapi/v1.json --output ./src/types/api.ts # 或者手动定义类型 # 参考 docs/api/ProjectManagement-API-Reference.md 中的 Data Models ``` --- ## 📞 获取帮助 ### 文档资源 - **API 文档**: `docs/api/ProjectManagement-API-Reference.md` - **Scalar UI**: http://localhost:5167/scalar/v1 - **交接指南**: `docs/api/FRONTEND_HANDOFF_DAY16.md` ### 后端团队联系 - 如果遇到 API 问题,请查看后端日志 - 如果需要新的 API 端点,请联系后端团队 ### 测试 Token ``` # 使用 Scalar UI 的 "Try It" 功能测试 API # 或使用 curl: curl -H "Authorization: Bearer " http://localhost:5167/api/v1/projects ``` --- ## ✅ 完成标准 Day 18 结束时,应该完成: 1. ✅ **API 集成** - Projects CRUD 完成 - Epics CRUD 完成 - Stories CRUD 完成 - Tasks CRUD 完成 2. ✅ **UI 实现** - 项目列表页 - 项目详情页 - Epic/Story/Task 列表页 - Kanban Board 更新 3. ✅ **测试验证** - 所有基础功能测试通过 - 错误处理正确 - 多租户隔离验证 4. ✅ **代码质量** - TypeScript 类型安全 - React Query 缓存优化 - 用户体验流畅 --- ## 🎉 开始开发吧! **记住**: - 🚀 后端 API 已就绪(95% production ready) - 📚 完整文档可用(6,000+ 行) - 🛡️ 多租户安全已验证(100%) - ✅ 所有测试通过(39/39) **你已经拥有了所有需要的资源,开始编码吧!** 💪 --- **Last Updated**: 2025-11-05 (Day 16) **Status**: ✅ Frontend Development Ready **Estimated Time**: 16-22 hours (2-3 days)