feat: 版本详情完善关联关系处理

1. 删除版本时清理所有孤儿数据:
   - 释放关联需求回需求池
   - 删除计划任务(调研/产品/UI)
   - 删除开发任务
   - 删除测试用例
   - 删除 Bug

2. Header 面包屑链路化:
   版本 / 产品名(可点击) / 项目名 / 版本名
   点击产品名跳转产品页

3. 概览统计卡片增加完整链路数据:
   关联需求 | 计划任务 | 开发任务 | 测试用例 | 待开发 | 开发中 | 未关闭Bug | 加班时长

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-12 16:59:59 +08:00
parent e92ec4eb0e
commit 3fd07dda71

View File

@@ -54,9 +54,9 @@ export default function VersionDetailPage() {
const { requirements, fetchRequirements, updateRequirement } = useRequirementStore(); const { requirements, fetchRequirements, updateRequirement } = useRequirementStore();
const { records, fetchRecords } = useOvertimeStore(); const { records, fetchRecords } = useOvertimeStore();
const { plans, fetchPlans, createPlan, updatePlan, completePlan, deletePlan } = useVersionPlanStore(); const { plans, fetchPlans, createPlan, updatePlan, completePlan, deletePlan } = useVersionPlanStore();
const { tasks: devTasks, fetchTasks: fetchDevTasks } = useDevTaskStore(); const { tasks: devTasks, fetchTasks: fetchDevTasks, deleteTask: deleteDevTask } = useDevTaskStore();
const { testCases, fetchTestCases } = useTestCaseStore(); const { testCases, fetchTestCases, deleteTestCase } = useTestCaseStore();
const { bugs, fetchBugs } = useBugStore(); const { bugs, fetchBugs, deleteBug } = useBugStore();
const user = useAuthStore((s) => s.user); const user = useAuthStore((s) => s.user);
const [activeTab, setActiveTab] = useState('overview'); const [activeTab, setActiveTab] = useState('overview');
@@ -117,9 +117,17 @@ export default function VersionDetailPage() {
const buttons: { label: string; action: () => void; danger?: boolean }[] = []; const buttons: { label: string; action: () => void; danger?: boolean }[] = [];
if (version.status === 'planned') { if (version.status === 'planned') {
buttons.push({ label: '删除', action: () => { buttons.push({ label: '删除', action: () => {
if (confirm('确认删除该版本?关联的需求会回到需求池。')) { if (confirm('确认删除该版本?关联的需求会回到需求池版本下的计划、开发任务、测试用例、Bug 将被清除。')) {
// 释放关联需求 // 释放关联需求
requirements.filter((r) => r.versionId === version.id).forEach((r) => updateRequirement(r.id, { versionId: undefined, addedToVersionBy: undefined })); requirements.filter((r) => r.versionId === version.id).forEach((r) => updateRequirement(r.id, { versionId: undefined, addedToVersionBy: undefined }));
// 清理计划任务
plans.filter((p) => p.versionId === version.id).forEach((p) => deletePlan(p.id));
// 清理开发任务
const versionReqIds = new Set(requirements.filter((r) => r.versionId === version.id).map((r) => r.id));
devTasks.filter((t) => versionReqIds.has(t.requirementId)).forEach((t) => deleteDevTask(t.id));
// 清理测试用例和Bug
testCases.filter((c) => c.versionId === version.id).forEach((c) => deleteTestCase(c.id));
bugs.filter((b) => b.versionId === version.id).forEach((b) => deleteBug(b.id));
deleteVersion(version.productId, version.id); deleteVersion(version.productId, version.id);
router.push('/versions'); router.push('/versions');
} }
@@ -152,7 +160,15 @@ export default function VersionDetailPage() {
<ChevronLeft className="h-3.5 w-3.5" strokeWidth={2} /> <ChevronLeft className="h-3.5 w-3.5" strokeWidth={2} />
</button> </button>
<span className="ml-2 text-[var(--ink-muted)]">/</span> <span className="ml-2 text-[var(--ink-muted)]">/</span>
<span className="ml-2 text-[15px] font-semibold text-[var(--ink)]">{version.name}</span> <button onClick={() => router.push('/products')} className="ml-2 text-[12px] text-[var(--ink-muted)] hover:text-[var(--accent)] hover:underline">{version.productName}</button>
{version.projectName && version.projectName !== '未关联' && (
<>
<span className="ml-1.5 text-[var(--ink-muted)]">/</span>
<span className="ml-1.5 text-[12px] text-[var(--ink-muted)]">{version.projectName}</span>
</>
)}
<span className="ml-1.5 text-[var(--ink-muted)]">/</span>
<span className="ml-1.5 text-[15px] font-semibold text-[var(--ink)]">{version.name}</span>
</div> </div>
<div className="flex items-center gap-2">{renderActions()}</div> <div className="flex items-center gap-2">{renderActions()}</div>
</header> </header>
@@ -236,13 +252,15 @@ export default function VersionDetailPage() {
</div> </div>
{/* 统计卡片 */} {/* 统计卡片 */}
<div className="grid grid-cols-6 gap-3"> <div className="grid grid-cols-4 gap-3 sm:grid-cols-8">
<StatCard label="加班时长" value={`${totalOTHours}h`} accent />
<StatCard label="关联需求" value={versionReqs.length} /> <StatCard label="关联需求" value={versionReqs.length} />
<StatCard label="计划任务" value={plans.filter((p) => p.versionId === version.id).length} />
<StatCard label="开发任务" value={versionDevTasks.length} />
<StatCard label="测试用例" value={versionTCs.length} />
<StatCard label="待开发" value={devTaskTodo} /> <StatCard label="待开发" value={devTaskTodo} />
<StatCard label="开发中" value={devTaskInProgress} accent /> <StatCard label="开发中" value={devTaskInProgress} accent />
<StatCard label="阻塞中" value={devTaskBlocked} warn={devTaskBlocked > 0} />
<StatCard label="未关闭Bug" value={bugOpenCount} warn={bugOpenCount > 0} /> <StatCard label="未关闭Bug" value={bugOpenCount} warn={bugOpenCount > 0} />
<StatCard label="加班时长" value={`${totalOTHours}h`} accent />
</div> </div>
{/* 风险详情(胶囊条上方) */} {/* 风险详情(胶囊条上方) */}