refactor: 压平数据模型 — 版本为执行主线,需求降为语义标签
核心改动: - TestCase 主归属 versionId,requirementId 改为可选语义标签 - Bug 新增 versionId(直接挂版本)+ requirementId(可选追溯) - 新增 calcVersionExecutionStatus() 版本执行态推导函数 规则:DevTask全部提测→已提测,TestCase全部通过+Bug全关→可发布 - 概览页展示推导出的执行态标签 - BugTab 改为直接 versionId 查询(不再走 testCase→req 链路) - 统计卡片"参与人员"替换为"未关闭Bug" 心智模型: 版本(执行线)→ DevTask/TestCase/Bug 需求(语义线)→ 解释为什么做,不驱动流程 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { useRequirementStore } from '@/stores/useRequirementStore';
|
||||
import { useOvertimeStore } from '@/stores/useOvertimeStore';
|
||||
import { getVersionDetail } from '@/lib/derive';
|
||||
import { STAGES } from '@/lib/stage';
|
||||
import { VersionStatus, VERSION_STATUS_LABEL, VERSION_STATUS_BG, getVersionDisplayStatus } from '@/lib/version-status';
|
||||
import { VersionStatus, VERSION_STATUS_LABEL, VERSION_STATUS_BG, getVersionDisplayStatus, calcVersionExecutionStatus, EXECUTION_STATUS_LABEL, EXECUTION_STATUS_COLOR } from '@/lib/version-status';
|
||||
import { CapsuleStages } from '@/components/version/CapsuleStages';
|
||||
import { MemberChips } from '@/components/version/MemberChips';
|
||||
import { HealthTrend, generateMockTrend } from '@/components/version/HealthTrend';
|
||||
@@ -194,8 +194,17 @@ export default function VersionDetailPage() {
|
||||
const devTaskTodo = versionDevTasks.filter((t) => t.status === 'todo').length;
|
||||
const devTaskInProgress = versionDevTasks.filter((t) => t.status === 'in_progress').length;
|
||||
const devTaskBlocked = versionDevTasks.filter((t) => t.isBlocked).length;
|
||||
const mockBugTotal = Math.max(Math.floor(versionReqs.length * 1.5), 2);
|
||||
const mockBugOpen = Math.max(Math.floor(mockBugTotal * 0.3), 1);
|
||||
const versionBugs = bugs.filter((b) => b.versionId === version.id);
|
||||
const bugOpenCount = versionBugs.filter((b) => b.status === 'open' || b.status === 'fixing').length;
|
||||
|
||||
// 版本执行态推导
|
||||
const versionTCs = testCases.filter((c) => c.versionId === version.id);
|
||||
const executionStatus = calcVersionExecutionStatus({
|
||||
manualStatus: version.status,
|
||||
devTasks: versionDevTasks,
|
||||
testCases: versionTCs,
|
||||
bugs: versionBugs,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -209,6 +218,9 @@ export default function VersionDetailPage() {
|
||||
<span className={`text-[11px] px-2 py-0.5 rounded-full ${VERSION_STATUS_BG[version.status]}`}>
|
||||
{getVersionDisplayStatus(version.status, version.currentStage)}
|
||||
</span>
|
||||
<span className={`text-[11px] font-medium px-2 py-0.5 rounded-full ${EXECUTION_STATUS_COLOR[executionStatus]}`}>
|
||||
{EXECUTION_STATUS_LABEL[executionStatus]}
|
||||
</span>
|
||||
<span className={`inline-flex items-center gap-1.5 text-[11px] font-semibold tabular-nums px-2 py-0.5 rounded-full ${healthLevel === 'critical' ? 'bg-red-50' : healthLevel === 'risk' ? 'bg-orange-50' : healthLevel === 'attention' ? 'bg-amber-50' : 'bg-emerald-50'} ${HEALTH_LEVEL_COLOR[healthLevel]}`}>
|
||||
<span className={`h-1.5 w-1.5 rounded-full ${HEALTH_LEVEL_DOT[healthLevel]}`} />
|
||||
{healthScore} {HEALTH_LEVEL_LABEL[healthLevel]}
|
||||
@@ -230,7 +242,7 @@ export default function VersionDetailPage() {
|
||||
<StatCard label="待开发" value={devTaskTodo} />
|
||||
<StatCard label="开发中" value={devTaskInProgress} accent />
|
||||
<StatCard label="阻塞中" value={devTaskBlocked} warn={devTaskBlocked > 0} />
|
||||
<StatCard label="参与人员" value={version.members?.length ?? 0} />
|
||||
<StatCard label="未关闭Bug" value={bugOpenCount} warn={bugOpenCount > 0} />
|
||||
</div>
|
||||
|
||||
{/* 风险详情(胶囊条上方) */}
|
||||
@@ -308,7 +320,7 @@ export default function VersionDetailPage() {
|
||||
}
|
||||
|
||||
// 测试阶段进度由 TestCase 完成率驱动
|
||||
const versionTestCases = testCases.filter((c) => versionReqs.some((r) => r.id === c.requirementId));
|
||||
const versionTestCases = testCases.filter((c) => c.versionId === version.id);
|
||||
if (versionTestCases.length > 0) {
|
||||
const executed = versionTestCases.filter((c) => c.status === 'passed' || c.status === 'failed' || c.status === 'blocked').length;
|
||||
const testPercent = Math.round((executed / versionTestCases.length) * 100);
|
||||
|
||||
Reference in New Issue
Block a user