# 前后端接口对接指南 - 创建项目功能 > **最后更新**: 2026-02-06 > **维护者**: Jointo 开发团队 --- ## ✅ 当前状态 **创建项目功能已完整实现**,前后端接口对接完成。 - ✅ **后端接口**: `POST /api/v1/projects` - ✅ **前端 API 服务**: `client/src/services/api/projects.ts` - ✅ **React Query Hook**: `useCreateProject()` - ✅ **UI 组件**: `CreateProjectModal.tsx` - ✅ **类型定义**: TypeScript 类型已对齐 --- ## 📋 架构概览 ``` ┌─────────────────────────────────────────────────────────────┐ │ 前端架构(React) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ UI 层:CreateProjectModal.tsx │ │ ↓ │ │ Hook 层:useCreateProject() (TanStack Query) │ │ ↓ │ │ API 层:projectApi.create() (Axios) │ │ ↓ │ │ HTTP:POST /api/v1/projects │ │ │ └───────────────────────────────┬─────────────────────────────┘ │ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 后端架构(FastAPI) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ API 层:projects.py → create_project() │ │ ↓ │ │ Service 层:ProjectService.create_project() │ │ ↓ │ │ Repository 层:ProjectRepository.create() │ │ ↓ │ │ Database:PostgreSQL │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 🔌 接口详情 ### 后端接口 **文件**: `server/app/api/v1/projects.py` ```python @router.post("", response_model=ApiResponse[ProjectResponse], summary="创建项目") async def create_project( project_data: ProjectCreate, current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session) ): """ 创建项目 - 支持个人项目(mine)和协作项目(collab) - 可指定所属文件夹 - 自动检查同文件夹下名称唯一性 """ ``` **请求体(ProjectCreate)**: ```python { "name": str, # 项目名称(必填) "description": Optional[str], # 项目描述 "type": "mine" | "collab", # 项目类型(默认 "mine") "folderId": Optional[UUID], # 所属文件夹ID "contentType": Optional[str], # 内容类型(ad/movie/series等) "aspectRatio": Optional[str], # 画幅比例(16:9/9:16等) "plannedDuration": Optional[int], # 计划时长(秒) "styleAndCharacters": Optional[str], # 风格和角色 "settings": Optional[dict] # 项目设置 } ``` **响应格式(ApiResponse)**: ```json { "success": true, "code": 200, "message": "Success", "data": { "id": "uuid", "name": "项目名称", "description": "项目描述", "type": "mine", "ownerId": "uuid", "folderId": "uuid", "displayOrder": 0, "thumbnailUrl": null, "contentType": "ad", "aspectRatio": "16:9", "plannedDuration": 60, "actualDuration": null, "styleAndCharacters": "风格描述", "settings": {}, "status": "active", "createdAt": "2026-02-06T12:00:00Z", "updatedAt": "2026-02-06T12:00:00Z", "trashedAt": null }, "timestamp": "2026-02-06T12:00:00Z" } ``` --- ### 前端 API 服务 **文件**: `client/src/services/api/projects.ts` ```typescript /** * 创建项目 * POST /api/v1/projects */ async create(data: CreateProjectDto): Promise { return apiClient.post('/projects', data) as Promise; } ``` **TypeScript 类型定义**: ```typescript // client/src/types/project.ts export interface CreateProjectDto { name: string; // 项目名称 description?: string; // 项目描述 type?: ProjectType; // 项目类型(1=mine, 2=collab) folderId?: string; // 所属文件夹ID contentType?: ProjectContentType; // 内容类型 aspectRatio?: AspectRatioType; // 画幅比例 plannedDuration?: number; // 计划时长(秒) styleAndCharacters?: string; // 风格和角色 settings?: Partial; // 项目设置 } export type ProjectContentType = | 'ad' // 广告片 | 'movie' // 电影 | 'series' // 剧集 | 'anime' // 动画 | 'short' // 短视频 | 'concept' // 概念片 export type AspectRatioType = | '16:9' | '9:16' | '4:3' | '21:9' | '1:1' | '2.35:1' | '2.39:1' ``` --- ### React Query Hook **文件**: `client/src/hooks/api/useProjects.ts` ```typescript /** * 创建项目 */ export function useCreateProject() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateProjectDto) => projectApi.create(data), onSuccess: () => { // 失效所有项目列表查询 queryClient.invalidateQueries({ queryKey: projectKeys.lists() }); }, }); } ``` **功能特性**: - ✅ 自动处理 loading 状态 - ✅ 自动处理错误 - ✅ 成功后自动刷新项目列表 - ✅ 支持乐观更新(可扩展) --- ## 🎯 使用示例 ### 1. 在组件中使用 ```typescript import { useCreateProject } from '@/hooks/api/useProjects'; import { useToast } from '@/hooks/use-toast'; function MyComponent() { const createProject = useCreateProject(); const { toast } = useToast(); const handleSubmit = async (formData: any) => { try { const newProject = await createProject.mutateAsync({ name: formData.name, contentType: formData.projectType, // ad/movie/series等 aspectRatio: formData.aspectRatio, // 16:9等 plannedDuration: formData.duration, // 秒 styleAndCharacters: formData.style, folderId: formData.folderId, // 可选 }); toast({ title: '项目已创建', description: `项目 "${newProject.name}" 已成功创建`, }); // 导航到新项目 navigate(`/project/${newProject.id}`); } catch (error) { toast({ title: '创建失败', description: error instanceof Error ? error.message : '创建失败,请重试', variant: 'destructive', }); } }; return (
{/* 表单字段 */}
); } ``` ### 2. 完整示例:CreateProjectModal 参考文件:`client/src/components/features/project/CreateProjectModal.tsx` ```typescript const createProject = useCreateProject(); const onSubmit = async (data: CreateProjectForm) => { try { const newProject = await createProject.mutateAsync({ name: data.name, folderId: data.folderId?.startsWith('virtual-') ? undefined : data.folderId, contentType: data.projectType, aspectRatio: data.aspectRatio, plannedDuration: durationInSeconds, styleAndCharacters: data.styleAndRoles, }); toast({ title: '项目已创建' }); navigate(`/project/${newProject.id}`); } catch (error) { toast({ title: '创建失败', description: error.message, variant: 'destructive' }); } }; ``` --- ## 🧪 测试联调 ### 方法 1:使用现有 UI(推荐) 1. **启动前后端服务**: ```bash # 终端 1:启动后端 cd server uvicorn app.main:app --reload --port 8000 # 终端 2:启动前端 cd client npm run dev ``` 2. **手动测试**: - 访问 `http://localhost:5173` - 点击"创建项目"按钮 - 填写表单字段 - 点击"创建"按钮 - 观察网络请求和响应 3. **检查点**: - ✅ 请求发送到 `POST /api/v1/projects` - ✅ 请求头包含 `Authorization: Bearer ` - ✅ 响应格式为 `{ success: true, data: {...} }` - ✅ 创建成功后跳转到项目详情页 - ✅ 项目列表自动刷新 ### 方法 2:使用 Playwright 自动化测试 ```typescript import { test, expect } from '@playwright/test'; test('创建项目功能', async ({ page }) => { // 1. 登录 await page.goto('http://localhost:5173/login'); await page.fill('input[name="email"]', 'test@example.com'); await page.fill('input[name="password"]', 'password'); await page.click('button[type="submit"]'); // 2. 打开创建项目弹窗 await page.click('button:has-text("创建项目")'); // 3. 填写表单 await page.fill('input[name="name"]', '测试项目'); await page.selectOption('select[name="projectType"]', 'ad'); await page.selectOption('select[name="aspectRatio"]', '16:9'); await page.fill('input[name="duration"]', '60'); // 4. 提交 await page.click('button[type="submit"]'); // 5. 验证 await expect(page).toHaveURL(/\/project\/[a-f0-9-]+/); await expect(page.locator('h1')).toContainText('测试项目'); }); ``` ### 方法 3:使用 API 测试工具 **Postman / Insomnia**: ```http POST http://localhost:8000/api/v1/projects Authorization: Bearer Content-Type: application/json { "name": "测试项目", "contentType": "ad", "aspectRatio": "16:9", "plannedDuration": 60, "styleAndCharacters": "现代简约风格" } ``` **cURL**: ```bash curl -X POST http://localhost:8000/api/v1/projects \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "测试项目", "contentType": "ad", "aspectRatio": "16:9", "plannedDuration": 60 }' ``` --- ## 🔍 故障排查 ### 问题 1:401 Unauthorized **原因**: Token 未传递或已过期 **解决**: ```typescript // 检查 localStorage 中的 token const token = localStorage.getItem('token'); console.log('Token:', token); // 手动设置 token(测试用) localStorage.setItem('token', 'your_valid_token'); ``` ### 问题 2:400 Bad Request **原因**: 请求数据格式错误 **解决**: - 检查必填字段:`name` 不能为空 - 检查字段类型:`plannedDuration` 必须是数字 - 检查枚举值:`contentType` 必须是 ad/movie/series 等 ### 问题 3:网络错误 **原因**: 后端服务未启动或 CORS 配置错误 **解决**: ```bash # 检查后端是否运行 curl http://localhost:8000/health # 检查环境变量 echo $VITE_API_BASE_URL # 应该输出:http://localhost:8000/api/v1 ``` ### 问题 4:响应数据类型不匹配 **原因**: 前后端字段命名不一致 **检查**: - 后端使用 `snake_case`:`folder_id`, `content_type` - 前端使用 `camelCase`:`folderId`, `contentType` - 响应拦截器已自动转换(`apiClient.interceptors.response`) --- ## 📝 快速提示词 ### 创建新功能时使用 ``` 为前端 [功能名] 功能对接后端 API 后端接口: - 路径:[METHOD] /api/v1/[path] - Schema:[SchemaName] - 响应:ApiResponse[ResponseType] 前端需求: 1. 在 client/src/services/api/[module].ts 中添加 API 调用 2. 参考后端 Schema 定义 TypeScript 类型 3. 创建 React Query Hook 4. 处理成功/失败响应 5. 添加 loading 状态 参考: - 后端接口:server/app/api/v1/projects.py - 前端实现:client/src/services/api/projects.ts - Hook 实现:client/src/hooks/api/useProjects.ts ``` ### 测试联调时使用 ``` 使用 Playwright 测试 [功能名] 的前后端联调 测试流程: 1. 启动前后端服务 2. 登录系统 3. 执行功能操作([具体步骤]) 4. 验证响应数据 5. 验证 UI 更新 检查点: - 请求路径正确 - 请求头包含 Token - 响应格式符合 ApiResponse - UI 状态正确更新 ``` --- ## 📚 相关文档 - [后端 API 设计规范](../../.claude/skills/jointo-tech-stack/references/api-design.md) - [前端技术栈规范](../../.claude/skills/jointo-tech-stack/references/frontend.md) - [测试规范](../../.claude/skills/jointo-tech-stack/references/testing.md) - [项目服务文档](../requirements/backend/04-services/project/project-service.md) --- **最后更新**: 2026-02-06 **维护者**: Jointo 开发团队