You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
13 KiB
13 KiB
前后端接口对接指南 - 创建项目功能
最后更新: 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
@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):
{
"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):
{
"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
/**
* 创建项目
* POST /api/v1/projects
*/
async create(data: CreateProjectDto): Promise<Project> {
return apiClient.post('/projects', data) as Promise<Project>;
}
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<ProjectSettings>; // 项目设置
}
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
/**
* 创建项目
*/
export function useCreateProject() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateProjectDto) => projectApi.create(data),
onSuccess: () => {
// 失效所有项目列表查询
queryClient.invalidateQueries({ queryKey: projectKeys.lists() });
},
});
}
功能特性:
- ✅ 自动处理 loading 状态
- ✅ 自动处理错误
- ✅ 成功后自动刷新项目列表
- ✅ 支持乐观更新(可扩展)
🎯 使用示例
1. 在组件中使用
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 (
<form onSubmit={handleSubmit}>
{/* 表单字段 */}
<button
type="submit"
disabled={createProject.isPending}
>
{createProject.isPending ? '创建中...' : '创建项目'}
</button>
</form>
);
}
2. 完整示例:CreateProjectModal
参考文件:client/src/components/features/project/CreateProjectModal.tsx
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:启动后端 cd server uvicorn app.main:app --reload --port 8000 # 终端 2:启动前端 cd client npm run dev -
手动测试:
- 访问
http://localhost:5173 - 点击"创建项目"按钮
- 填写表单字段
- 点击"创建"按钮
- 观察网络请求和响应
- 访问
-
检查点:
- ✅ 请求发送到
POST /api/v1/projects - ✅ 请求头包含
Authorization: Bearer <token> - ✅ 响应格式为
{ success: true, data: {...} } - ✅ 创建成功后跳转到项目详情页
- ✅ 项目列表自动刷新
- ✅ 请求发送到
方法 2:使用 Playwright 自动化测试
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:
POST http://localhost:8000/api/v1/projects
Authorization: Bearer <your_token>
Content-Type: application/json
{
"name": "测试项目",
"contentType": "ad",
"aspectRatio": "16:9",
"plannedDuration": 60,
"styleAndCharacters": "现代简约风格"
}
cURL:
curl -X POST http://localhost:8000/api/v1/projects \
-H "Authorization: Bearer <your_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "测试项目",
"contentType": "ad",
"aspectRatio": "16:9",
"plannedDuration": 60
}'
🔍 故障排查
问题 1:401 Unauthorized
原因: Token 未传递或已过期
解决:
// 检查 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 配置错误
解决:
# 检查后端是否运行
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 状态正确更新
📚 相关文档
最后更新: 2026-02-06
维护者: Jointo 开发团队