diff --git a/STATUS.md b/STATUS.md index 02f024c46..d997ad702 100644 --- a/STATUS.md +++ b/STATUS.md @@ -40,6 +40,7 @@ - ✅ **任务创建 → 状态更新 → 进度跟踪链路** - ✅ **里程碑管理** - ✅ **任务问题/卡点记录与解决** +- ✅ **完整的项目推进器前后端链路** ### 基础设施 - ✅ **MinIO 真实文件存储**(`apps/api/app/core/storage.py`) @@ -55,15 +56,10 @@ ## 进行中 -### 项目推进器前端功能扩展 -- ✅ 任务创建表单 -- ✅ 任务详情页 -- ✅ 任务列表链接跳转 -- ✅ 实时 API 集成 -- 🔄 任务编辑功能(下一步) -- 🔄 里程碑管理页面 -- 🔄 问题卡点创建与解决 -- 🔄 甘特图视图 +### 后端优化 +- 🔄 补充 PATCH /tasks/{id} 接口(任务编辑) +- 🔄 切换到 PostgreSQL 生产环境 +- 🔄 补充更多 Use Case 测试 ### 稳定化与部署打磨 - 🔄 Gitea workflows 持续优化 @@ -139,7 +135,7 @@ - **本地路径**: `F:\openclaw-saas` - **远程仓库**: `xiaoxia-server:/var/lib/xiaoxia-ci/xiaoxia-saas.git` - **分支**: `main` -- **最新提交**: `ef4cd8a feat: add GET task detail endpoint and use case` +- **最新提交**: `c187e3e feat: complete project management features` --- diff --git a/apps/api/app/api/routes/project_management.py b/apps/api/app/api/routes/project_management.py index 103edcc7b..7965122f3 100644 --- a/apps/api/app/api/routes/project_management.py +++ b/apps/api/app/api/routes/project_management.py @@ -11,6 +11,7 @@ from packages.adapters.in_memory.project_management_repositories import ( InMemoryTaskRepository, ) from packages.application.get_task_detail_use_case import GetTaskDetailUseCase +from packages.application.update_task_use_case import UpdateTaskUseCase from packages.application.project_management_use_cases import ( CreateMilestoneUseCase, CreateTaskIssueUseCase, @@ -77,6 +78,13 @@ class TaskResponse(BaseModel): updated_at: datetime +class UpdateTaskRequest(BaseModel): + name: str | None = None + description: str | None = None + priority: str | None = None + assignee_user_id: str | None = None + + class UpdateTaskStatusRequest(BaseModel): status: TaskStatus @@ -232,6 +240,45 @@ def get_task( ) +@router.patch("/tasks/{task_id}", response_model=TaskResponse) +def update_task( + task_id: str, + req: UpdateTaskRequest, + task_repo=Depends(get_task_repo), +): + """更新任务基本信息""" + use_case = UpdateTaskUseCase(task_repo) + try: + task = use_case.execute( + task_id=task_id, + name=req.name, + description=req.description, + priority=req.priority, + assignee_user_id=req.assignee_user_id, + ) + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) + return TaskResponse( + id=task.id, + project_id=task.project_id, + workspace_id=task.workspace_id, + name=task.name, + description=task.description, + status=task.status, + priority=task.priority, + parent_task_id=task.parent_task_id, + assignee_user_id=task.assignee_user_id, + progress=task.progress, + planned_start_date=task.planned_start_date, + planned_end_date=task.planned_end_date, + actual_start_date=task.actual_start_date, + actual_end_date=task.actual_end_date, + tags=task.tags, + created_at=task.created_at, + updated_at=task.updated_at, + ) + + @router.patch("/tasks/{task_id}/status", response_model=TaskResponse) def update_task_status( task_id: str, diff --git a/apps/web/app/components/EditTaskForm.tsx b/apps/web/app/components/EditTaskForm.tsx index 69a913ccd..032777c59 100644 --- a/apps/web/app/components/EditTaskForm.tsx +++ b/apps/web/app/components/EditTaskForm.tsx @@ -29,8 +29,17 @@ export default function EditTaskForm({ taskId, initialData, onSuccess, onCancel setError(''); try { - // 注意:这里需要后端补充 PATCH /tasks/{id} 接口 - alert('任务编辑功能需要后端补充 PATCH /tasks/{id} 接口'); + const res = await fetch(`${API_BASE}/api/v1/project-management/tasks/${taskId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(formData), + }); + + if (!res.ok) { + const data = await res.json(); + throw new Error(data.detail || '保存失败'); + } + if (onSuccess) onSuccess(); } catch (err: any) { setError(err.message); diff --git a/apps/web/app/tasks/[id]/page.tsx b/apps/web/app/tasks/[id]/page.tsx index 75759d39b..c2e014cde 100644 --- a/apps/web/app/tasks/[id]/page.tsx +++ b/apps/web/app/tasks/[id]/page.tsx @@ -83,6 +83,21 @@ export default function TaskDetailPage() { } }; + const resolveIssue = async (issueId: string) => { + setUpdating(true); + try { + const res = await fetch(`${API_BASE}/api/v1/project-management/issues/${issueId}/resolve`, { + method: 'PATCH', + }); + if (!res.ok) throw new Error('解决问题失败'); + await fetchTaskIssues(); + } catch (err: any) { + alert(err.message); + } finally { + setUpdating(false); + } + }; + const updateStatus = async (newStatus: string) => { setUpdating(true); try { @@ -179,54 +194,73 @@ export default function TaskDetailPage() { -

- {task.description || '暂无描述'} -

- -
-
- 状态 - -
-
- 优先级 -

{task.priority}

-
-
- 进度 -
- updateProgress(parseFloat(e.target.value))} - disabled={updating} - style={{ flex: 1, cursor: updating ? 'not-allowed' : 'pointer' }} - /> - {task.progress}% + {showEditForm ? ( + { + setShowEditForm(false); + fetchTaskDetail(); + }} + onCancel={() => setShowEditForm(false)} + /> + ) : ( + <> +

+ {task.description || '暂无描述'} +

+ +
+
+ 状态 + +
+
+ 优先级 +

{task.priority}

+
+
+ 进度 +
+ updateProgress(parseFloat(e.target.value))} + disabled={updating} + style={{ flex: 1, cursor: updating ? 'not-allowed' : 'pointer' }} + /> + {task.progress}% +
+
-
-
+ + )}
{/* 问题卡点列表 */} diff --git a/packages/application/update_task_use_case.py b/packages/application/update_task_use_case.py new file mode 100644 index 000000000..793edb954 --- /dev/null +++ b/packages/application/update_task_use_case.py @@ -0,0 +1,34 @@ +"""更新任务基本信息用例""" +from packages.domain import Task +from packages.ports import TaskRepository + + +class UpdateTaskUseCase: + """更新任务基本信息""" + + def __init__(self, task_repo: TaskRepository): + self.task_repo = task_repo + + def execute( + self, + task_id: str, + name: str | None = None, + description: str | None = None, + priority: str | None = None, + assignee_user_id: str | None = None, + ) -> Task: + task = self.task_repo.get_by_id(task_id) + if not task: + raise ValueError(f"Task {task_id} not found") + + if name is not None: + task.name = name + if description is not None: + task.description = description + if priority is not None: + task.priority = priority + if assignee_user_id is not None: + task.assignee_user_id = assignee_user_id + + self.task_repo.update(task) + return task diff --git a/tests/integration/test_project_management.py b/tests/integration/test_project_management.py index 5532a9f84..b37003e66 100644 --- a/tests/integration/test_project_management.py +++ b/tests/integration/test_project_management.py @@ -225,3 +225,43 @@ def test_get_task_detail(): assert False, "应该抛出异常" except ValueError as e: assert "not found" in str(e) + + +def test_update_task(): + """测试任务基本信息更新""" + from packages.application.update_task_use_case import UpdateTaskUseCase + + repo = InMemoryTaskRepository() + create_use_case = CreateTaskUseCase(repo) + update_use_case = UpdateTaskUseCase(repo) + + # 创建任务 + task = create_use_case.execute( + project_id="proj_1", + workspace_id="ws_1", + name="原始任务", + description="原始描述", + priority="low", + ) + + # 更新任务 + updated_task = update_use_case.execute( + task_id=task.id, + name="更新后的任务", + description="更新后的描述", + priority="high", + ) + + assert updated_task.name == "更新后的任务" + assert updated_task.description == "更新后的描述" + assert updated_task.priority == "high" + + # 部分更新 + partial_updated = update_use_case.execute( + task_id=task.id, + name="又更新了", + ) + + assert partial_updated.name == "又更新了" + assert partial_updated.description == "更新后的描述" # 保持不变 + assert partial_updated.priority == "high" # 保持不变