fix: 计划任务勾选逻辑 + 胶囊进度计算修正
1. 必须点击"开始"后才能勾选任务:
- 调研/产品方案/UI设计的 checkbox 加 disabled 判断
- plan.status !== 'in_progress' 时不可交互
2. 调研勾选改为二态(与产品/UI一致):
- 去掉三态循环(pending→in_progress→completed)
- 改为 toggle:未完成 ↔ 已完成
- 显示文字也简化为"已完成"/"未完成"
3. 胶囊进度百分比计算修正:
- 旧逻辑:所有子任务合并计算(新建空任务导致分母为0返回0%)
- 新逻辑:以 plan 为单位加权计算
- completed 的 plan = 权重 1
- in_progress 的 plan = 子任务完成率
- pending 的 plan = 权重 0
- 公式:sum(weights) / plan_count * 100
- 新建一个 pending 的 plan 不会把已完成的 100% 打回 0%
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -303,23 +303,27 @@ 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;
|
||||
for (const p of group) {
|
||||
if (p.status === 'completed') {
|
||||
totalWeight += 1;
|
||||
} else if (p.status === 'in_progress') {
|
||||
if (type === 'research') {
|
||||
const totals = group.reduce((acc, p) => {
|
||||
const tasks = p.tasks || [];
|
||||
acc.total += tasks.length;
|
||||
acc.done += tasks.filter((t) => t.status === 'completed').length;
|
||||
return acc;
|
||||
}, { total: 0, done: 0 });
|
||||
return totals.total > 0 ? Math.round((totals.done / totals.total) * 100) : 0;
|
||||
if (tasks.length > 0) {
|
||||
totalWeight += tasks.filter((t) => t.status === 'completed').length / tasks.length;
|
||||
}
|
||||
const totals = group.reduce((acc, p) => {
|
||||
} else {
|
||||
const linked = p.linkedRequirementIds || [];
|
||||
const completed = p.completedRequirementIds || [];
|
||||
acc.total += linked.length;
|
||||
acc.done += completed.filter((id) => linked.includes(id)).length;
|
||||
return acc;
|
||||
}, { total: 0, done: 0 });
|
||||
return totals.total > 0 ? Math.round((totals.done / totals.total) * 100) : 0;
|
||||
if (linked.length > 0) {
|
||||
totalWeight += completed.filter((id) => linked.includes(id)).length / linked.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.round((totalWeight / group.length) * 100);
|
||||
};
|
||||
|
||||
const getPlanStatus = (group: typeof vPlans): 'idle' | 'active' | 'done' => {
|
||||
|
||||
@@ -106,19 +106,20 @@ export function PlanTab({ plans, versionId, versionDeadline, currentUserName, pl
|
||||
{plan.tasks.map((task) => (
|
||||
<div key={task.id} className="flex items-center gap-2">
|
||||
<button
|
||||
disabled={plan.status !== 'in_progress'}
|
||||
onClick={() => {
|
||||
const nextStatus = task.status === 'pending' ? 'in_progress' : task.status === 'in_progress' ? 'completed' : 'pending';
|
||||
if (plan.status !== 'in_progress') return;
|
||||
const nextStatus = task.status === 'completed' ? 'pending' : 'completed';
|
||||
const updatedTasks = plan.tasks!.map((t) => t.id === task.id ? { ...t, status: nextStatus as PlanTask['status'] } : t);
|
||||
onUpdate(plan.id, { tasks: updatedTasks });
|
||||
}}
|
||||
className={`h-4 w-4 rounded border flex items-center justify-center shrink-0 transition-colors ${task.status === 'completed' ? 'bg-[var(--accent)] border-[var(--accent)]' : task.status === 'in_progress' ? 'border-blue-400 bg-blue-50' : 'border-[var(--line)]'}`}
|
||||
className={`h-4 w-4 rounded border flex items-center justify-center shrink-0 transition-colors ${plan.status !== 'in_progress' ? 'opacity-40 cursor-not-allowed' : ''} ${task.status === 'completed' ? 'bg-[var(--accent)] border-[var(--accent)]' : 'border-[var(--line)]'}`}
|
||||
>
|
||||
{task.status === 'completed' && <Check className="h-2.5 w-2.5 text-white" strokeWidth={3} />}
|
||||
{task.status === 'in_progress' && <div className="h-1.5 w-1.5 rounded-full bg-blue-500" />}
|
||||
</button>
|
||||
<span className={`text-[12px] ${task.status === 'completed' ? 'line-through text-[var(--ink-muted)]' : 'text-[var(--ink)]'}`}>{task.title}</span>
|
||||
<span className={`text-[10px] ${task.status === 'completed' ? 'text-green-600' : task.status === 'in_progress' ? 'text-blue-600' : 'text-[var(--ink-muted)]'}`}>
|
||||
{task.status === 'completed' ? '已完成' : task.status === 'in_progress' ? '进行中' : '未开始'}
|
||||
<span className={`text-[10px] ${task.status === 'completed' ? 'text-green-600' : 'text-[var(--ink-muted)]'}`}>
|
||||
{task.status === 'completed' ? '已完成' : '未完成'}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -141,12 +142,14 @@ export function PlanTab({ plans, versionId, versionDeadline, currentUserName, pl
|
||||
return req ? (
|
||||
<div key={rid} className="flex items-center gap-2">
|
||||
<button
|
||||
disabled={plan.status !== 'in_progress'}
|
||||
onClick={() => {
|
||||
if (plan.status !== 'in_progress') return;
|
||||
const current = plan.completedRequirementIds || [];
|
||||
const next = isDone ? current.filter((id) => id !== rid) : [...current, rid];
|
||||
onUpdate(plan.id, { completedRequirementIds: next });
|
||||
}}
|
||||
className={`h-4 w-4 rounded border flex items-center justify-center shrink-0 transition-colors ${isDone ? 'bg-[var(--accent)] border-[var(--accent)]' : 'border-[var(--line)]'}`}
|
||||
className={`h-4 w-4 rounded border flex items-center justify-center shrink-0 transition-colors ${plan.status !== 'in_progress' ? 'opacity-40 cursor-not-allowed' : ''} ${isDone ? 'bg-[var(--accent)] border-[var(--accent)]' : 'border-[var(--line)]'}`}
|
||||
>
|
||||
{isDone && <Check className="h-2.5 w-2.5 text-white" strokeWidth={3} />}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user