feat(app): 任务生命周期操作 UI(编辑/取消/重投/删除 + 复杂度落库)

补齐前端缺失的任务管理闭环(均接现有后端端点,无需后端改动):

- api.js:patchTask / cancelTask / requeueTask / deleteTask
- app.jsx:taskOps{patch,cancel,requeue,del},统一带刷新 + toast
- TaskDetail 操作工具条:✎编辑 / ⌨接管 / ↻重投 / ⊘取消 / 🗑删除(删除带确认;
  重投/取消按状态显隐)
- 编辑表单:标题 + 优先级 + 依赖(点选其他任务)→ PATCH /tasks/:id
- 修复:行内复杂度选择器(CplxPicker)从「仅本地 state」改为真正 PATCH 落库

验证:build 通过;建临时任务实测——工具条/编辑表单渲染 0 错误、DELETE 成功
(deleted:1)、PATCH 在途守卫正确拒绝;测试残留已清理。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-24 17:26:29 +08:00
parent 638f2f64f0
commit 7a6c826610
3 changed files with 87 additions and 18 deletions
+4
View File
@@ -32,6 +32,10 @@ export const api = {
patchProject: (pid, body) => http('PATCH', `/api/projects/${pid}`, body),
createProject: (body) => http('POST', '/api/projects', body),
takeover: (taskId) => http('POST', `/api/tasks/${taskId}/takeover`),
patchTask: (taskId, body) => http('PATCH', `/api/tasks/${taskId}`, body),
cancelTask: (taskId) => http('POST', `/api/tasks/${taskId}/cancel`),
requeueTask: (taskId) => http('POST', `/api/tasks/${taskId}/requeue`),
deleteTask: (taskId) => http('DELETE', `/api/tasks/${taskId}`),
// 附件上传走 multipart(不能用 JSON helpercontent-type 由浏览器带 boundary
uploadAttachments: async (taskId, files) => {
const fd = new FormData();
+20 -1
View File
@@ -311,6 +311,25 @@ export function App() {
setTakeoverInfo({ ...info, title: task.title });
} catch (e) { toast('warn', '接管失败:' + e.message); }
};
// 任务生命周期操作(编辑落库 / 取消 / 重投 / 删除),统一带刷新
const taskOps = {
patch: async (taskId, body) => {
try { await api.patchTask(taskId, body); toast('ok', '已更新'); loadProject(currentId); }
catch (e) { toast('warn', '更新失败:' + e.message); }
},
cancel: async (taskId) => {
try { await api.cancelTask(taskId); toast('warn', '已取消'); loadProject(currentId); loadGlobal(); }
catch (e) { toast('warn', '取消失败:' + e.message); }
},
requeue: async (taskId) => {
try { await api.requeueTask(taskId); toast('ok', '已重投'); loadProject(currentId); loadGlobal(); }
catch (e) { toast('warn', '重投失败:' + e.message); }
},
del: async (taskId) => {
try { await api.deleteTask(taskId); toast('warn', '已删除'); loadProject(currentId); loadGlobal(); }
catch (e) { toast('warn', '删除失败:' + e.message); }
},
};
const [showNewProject, setShowNewProject] = React.useState(false);
const createProject = async (body) => {
try {
@@ -342,7 +361,7 @@ export function App() {
<window.MaestroKitAgentSection agents={agents} quota={quota} t={t} onOpenStream={setStreamAgent} />
<window.MaestroKitGateSection approvals={gates} onDecide={decide} t={t} />
{tasks.length ? (
<window.MaestroKitTaskSection tasks={tasks} onToast={toast} onCreate={createTask} onTakeover={takeover} t={t} />
<window.MaestroKitTaskSection tasks={tasks} onToast={toast} onCreate={createTask} onTakeover={takeover} taskOps={taskOps} t={t} />
) : (
<div style={{ padding: '46px 0', textAlign: 'center', color: 'var(--faint)', border: '1px dashed var(--line)', borderRadius: 6, marginTop: 18 }}>
<b style={{ display: 'block', fontSize: 15, color: 'var(--muted)', marginBottom: 6, letterSpacing: '.2em' }}>{t.empty}</b>