refactor: 胶囊条改为纯进度展示,去掉"当前阶段"和"阶段耗时"
胶囊条 CapsuleStages 重构: - 去掉 currentStage/progress 旧 props - 每段只显示进度百分比 + 状态颜色(idle/active/done) - 支持并行阶段(多个可同时 active) - 数据由实际 PlanTask/DevTask/TestCase 状态驱动 进度数据来源: - 调研:任务完成数 / 总数 - 产品/UI:需求完成数 / 关联数 - 开发:DevTask 进度(STATUS_PROGRESS 加权) - 测试:已执行用例 / 总用例 版本列表页: - 去掉"当前阶段"和"阶段进度"两列 - 替换为"状态"列(显示版本状态标签) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,55 +1,20 @@
|
||||
import { Stage, Role, STAGES, STAGE_INDEX } from '@/lib/stage';
|
||||
import type { RoleProgress } from '@/lib/derive';
|
||||
import type { Stage } from '@/lib/stage';
|
||||
import { STAGES } from '@/lib/stage';
|
||||
|
||||
export interface StageProgressItem {
|
||||
percent: number;
|
||||
daysSpent: number;
|
||||
status: 'idle' | 'active' | 'done';
|
||||
}
|
||||
|
||||
export function CapsuleStages({ currentStage, progress, stageProgress }: {
|
||||
currentStage?: Stage;
|
||||
progress?: RoleProgress[];
|
||||
export function CapsuleStages({ stageProgress }: {
|
||||
stageProgress?: Partial<Record<Stage, StageProgressItem>>;
|
||||
}) {
|
||||
const currentIdx = currentStage !== undefined
|
||||
? (currentStage === 'released' ? STAGES.length : STAGE_INDEX[currentStage])
|
||||
: -1;
|
||||
const progressMap = (progress ?? []).reduce<Record<Role, { percent: number; daysSpent: number }>>((acc, p) => {
|
||||
acc[p.role] = { percent: p.percent, daysSpent: p.daysSpent };
|
||||
return acc;
|
||||
}, {} as Record<Role, { percent: number; daysSpent: number }>);
|
||||
|
||||
const stageRoleMap: Record<Stage, Role[]> = {
|
||||
requirement: ['product'],
|
||||
product_design: ['product'],
|
||||
ui_design: ['ui'],
|
||||
dev: ['frontend', 'backend'],
|
||||
integration: ['frontend', 'backend'],
|
||||
testing: ['testing'],
|
||||
released: [],
|
||||
};
|
||||
|
||||
function getStageInfo(stage: Stage) {
|
||||
// 优先使用 stageProgress
|
||||
if (stageProgress && stageProgress[stage]) {
|
||||
const sp = stageProgress[stage]!;
|
||||
return { percent: sp.percent, days: sp.daysSpent, hasData: true };
|
||||
}
|
||||
const roles = stageRoleMap[stage];
|
||||
if (roles.length === 0) return { percent: 0, days: 0, hasData: false };
|
||||
const items = roles.map((r) => progressMap[r]).filter(Boolean);
|
||||
if (items.length === 0) return { percent: 0, days: 0, hasData: false };
|
||||
const percent = Math.round(items.reduce((s, i) => s + i.percent, 0) / items.length);
|
||||
const days = Math.max(...items.map((i) => i.daysSpent));
|
||||
return { percent, days, hasData: true };
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex rounded-lg border border-[var(--line)] overflow-hidden bg-[var(--bg-card)]">
|
||||
{STAGES.map((stage, idx) => {
|
||||
const isCompleted = idx < currentIdx;
|
||||
const isCurrent = idx === currentIdx;
|
||||
const info = getStageInfo(stage.key);
|
||||
const info = stageProgress?.[stage.key];
|
||||
const status = info?.status || 'idle';
|
||||
const percent = info?.percent ?? 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -57,18 +22,20 @@ export function CapsuleStages({ currentStage, progress, stageProgress }: {
|
||||
className={`flex-1 flex flex-col ${idx < STAGES.length - 1 ? 'border-r border-[var(--line-soft)]' : ''}`}
|
||||
>
|
||||
<div className="flex items-center justify-between px-2 py-1.5 min-h-[28px]">
|
||||
<span className={`text-[10px] font-medium leading-tight ${isCurrent ? 'text-[var(--ink)]' : isCompleted ? 'text-[var(--ink-soft)]' : 'text-[var(--ink-muted)]'}`}>
|
||||
<span className={`text-[10px] font-medium leading-tight ${status === 'active' ? 'text-[var(--ink)]' : status === 'done' ? 'text-[var(--ink-soft)]' : 'text-[var(--ink-muted)]'}`}>
|
||||
{stage.label}
|
||||
</span>
|
||||
<span className={`text-[10px] leading-tight ${isCompleted ? 'text-emerald-600' : isCurrent ? 'text-blue-600 font-medium' : 'text-[var(--ink-muted)]'}`}>
|
||||
{isCompleted ? (info.hasData ? `${info.percent}% · ${info.days}天` : '-') : isCurrent ? (info.hasData ? `${info.percent}% · ${info.days}天` : '-') : ''}
|
||||
</span>
|
||||
{status !== 'idle' && (
|
||||
<span className={`text-[10px] font-medium leading-tight tabular-nums ${status === 'done' ? 'text-emerald-600' : 'text-blue-600'}`}>
|
||||
{percent}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-[3px] w-full bg-zinc-50">
|
||||
{isCompleted && <div className="h-full bg-zinc-700 w-full" />}
|
||||
{isCurrent && info.hasData && (
|
||||
{status === 'done' && <div className="h-full bg-emerald-500 w-full" />}
|
||||
{status === 'active' && (
|
||||
<div className="h-full bg-blue-100 w-full">
|
||||
<div className="h-full bg-blue-500 transition-all" style={{ width: `${info.percent}%` }} />
|
||||
<div className="h-full bg-blue-500 transition-all" style={{ width: `${percent}%` }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user