diff --git a/apps/web/app/versions/[id]/page.tsx b/apps/web/app/versions/[id]/page.tsx index b51e332..c8dcbcb 100644 --- a/apps/web/app/versions/[id]/page.tsx +++ b/apps/web/app/versions/[id]/page.tsx @@ -303,27 +303,33 @@ export default function VersionDetailPage() { const calcGroupProgress = (group: typeof vPlans, type: 'research' | 'product' | 'ui') => { if (group.length === 0) return 0; - // 每个 plan 按权重贡献进度:completed=1, in_progress=子任务完成率, pending=0 - let totalWeight = 0; + // 分子=已完成子任务数,分母=所有plan的子任务总数 + // completed 的 plan:其子任务全算已完成 + let totalItems = 0; + let doneItems = 0; for (const p of group) { - if (p.status === 'completed') { - totalWeight += 1; - } else if (p.status === 'in_progress') { - if (type === 'research') { - const tasks = p.tasks || []; - if (tasks.length > 0) { - totalWeight += tasks.filter((t) => t.status === 'completed').length / tasks.length; - } + if (type === 'research') { + const tasks = p.tasks || []; + const count = Math.max(tasks.length, 1); + totalItems += count; + if (p.status === 'completed') { + doneItems += count; + } else { + doneItems += tasks.filter((t) => t.status === 'completed').length; + } + } else { + const linked = p.linkedRequirementIds || []; + const count = Math.max(linked.length, 1); + totalItems += count; + if (p.status === 'completed') { + doneItems += count; } else { - const linked = p.linkedRequirementIds || []; const completed = p.completedRequirementIds || []; - if (linked.length > 0) { - totalWeight += completed.filter((id) => linked.includes(id)).length / linked.length; - } + doneItems += completed.filter((id) => linked.includes(id)).length; } } } - return Math.round((totalWeight / group.length) * 100); + return totalItems > 0 ? Math.round((doneItems / totalItems) * 100) : 0; }; const getPlanStatus = (group: typeof vPlans): 'idle' | 'active' | 'done' => { diff --git a/apps/web/components/version/PlanTab.tsx b/apps/web/components/version/PlanTab.tsx index 9a9ad2b..17c3d62 100644 --- a/apps/web/components/version/PlanTab.tsx +++ b/apps/web/components/version/PlanTab.tsx @@ -252,6 +252,7 @@ function PlanFormModal({ initial, planType, versionId, versionDeadline, currentU e.preventDefault(); if (!title.trim() || !endTime) return; if (isOverdue && !overdueReason.trim()) return; + if (planType === 'research' && tasks.length === 0) return; onSubmit({ versionId, type: planType, @@ -326,7 +327,25 @@ function PlanFormModal({ initial, planType, versionId, versionDeadline, currentU )} {planType === 'research' && (
- + + {/* 预设选项 */} + {tasks.length === 0 && ( +
+ {['竞品分析', '用户访谈', '数据调研', '技术可行性分析', '市场调研', '需求分析'].map((preset) => ( + + ))} +
+ )}
{tasks.map((task, i) => (
@@ -340,11 +359,14 @@ function PlanFormModal({ initial, planType, versionId, versionDeadline, currentU value={newTaskTitle} onChange={(e) => setNewTaskTitle(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); if (newTaskTitle.trim()) { setTasks([...tasks, { id: `task-${Date.now()}`, title: newTaskTitle.trim(), status: 'pending' }]); setNewTaskTitle(''); } } }} - placeholder="输入任务名称,回车添加" + placeholder="自定义任务名称,回车添加" className="flex-1 h-8 rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[12px] focus:border-[var(--accent)] focus:outline-none" />
+ {tasks.length === 0 && ( +
请至少添加一项任务
+ )}
)}