feat(test-case+bug): 测试用例模块 + Bug 模块完整实现

需求验收体系:
- 测试用例:五态状态机(待执行/执行中/通过/失败/阻塞)
- Bug:六态状态机(待修复/修复中/已修复/验证中/已关闭/已拒绝)
- Bug 通过测试用例间接关联需求(不冗余存版本)
- Bug 默认修复人 = 关联需求的开发任务负责人
- 测试进度 = 已执行用例/总用例, 通过率 = 通过/已执行

UI:
- 版本详情页新增"测试用例" Tab + "BUG" Tab
- 测试用例:统计栏+筛选+按需求分组列表+详情抽屉+提BUG入口
- Bug:统计栏+筛选+列表+详情抽屉(链式跳转用例→需求)
- 概览胶囊"测试"阶段进度联动
- "与我相关"新增测试用例/Bug分组

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-12 13:11:08 +08:00
parent 75b0d12fa0
commit 7fe3a3854a
16 changed files with 1296 additions and 5 deletions

75
apps/web/lib/bug.ts Normal file
View File

@@ -0,0 +1,75 @@
import type { Priority } from './derive';
export type BugStatus = 'open' | 'fixing' | 'fixed' | 'verifying' | 'closed' | 'rejected';
export type BugSeverity = 'critical' | 'major' | 'minor' | 'trivial';
export interface Bug {
id: string;
bugNo: string;
testCaseId: string;
title: string;
description: string;
severity: BugSeverity;
priority: Priority;
reportedBy: string;
assigneeId: string;
status: BugStatus;
resolvedAt?: string;
closedAt?: string;
resolution?: string;
createdAt: string;
updatedAt: string;
}
export const BUG_STATUS_LABEL: Record<BugStatus, string> = {
open: '待修复',
fixing: '修复中',
fixed: '已修复',
verifying: '验证中',
closed: '已关闭',
rejected: '已拒绝',
};
export const BUG_STATUS_COLOR: Record<BugStatus, string> = {
open: 'bg-red-50 text-red-600',
fixing: 'bg-blue-50 text-blue-600',
fixed: 'bg-indigo-50 text-indigo-600',
verifying: 'bg-purple-50 text-purple-600',
closed: 'bg-emerald-50 text-emerald-600',
rejected: 'bg-zinc-100 text-zinc-500',
};
export const BUG_SEVERITY_LABEL: Record<BugSeverity, string> = {
critical: '致命',
major: '严重',
minor: '一般',
trivial: '轻微',
};
export const BUG_SEVERITY_COLOR: Record<BugSeverity, string> = {
critical: 'bg-red-100 text-red-700',
major: 'bg-orange-50 text-orange-700',
minor: 'bg-yellow-50 text-yellow-700',
trivial: 'bg-zinc-100 text-zinc-600',
};
export const BUG_ALLOWED_TRANSITIONS: Record<BugStatus, BugStatus[]> = {
open: ['fixing', 'rejected'],
fixing: ['fixed'],
fixed: ['verifying'],
verifying: ['closed', 'open'],
closed: [],
rejected: [],
};
export function canBugTransition(from: BugStatus, to: BugStatus): boolean {
return BUG_ALLOWED_TRANSITIONS[from].includes(to);
}
export function generateBugNo(existingBugs: Bug[]): string {
const maxNum = existingBugs.reduce((max, b) => {
const num = parseInt(b.bugNo.replace('BUG-', ''), 10);
return isNaN(num) ? max : Math.max(max, num);
}, 0);
return `BUG-${String(maxNum + 1).padStart(3, '0')}`;
}