'use client'; import { useState, useMemo, useEffect } from 'react'; import { Plus, ClipboardCheck, Filter, CheckCircle2 } from 'lucide-react'; import { useTestCaseStore } from '@/stores/useTestCaseStore'; import { useBugStore } from '@/stores/useBugStore'; import { useDevTaskStore } from '@/stores/useDevTaskStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useAuthStore } from '@/stores/useAuthStore'; import { TestCaseRow } from './TestCaseRow'; import { TestCaseCreateModal } from './TestCaseCreateModal'; import { TestCaseDetailDrawer } from './TestCaseDetailDrawer'; import { BugCreateModal } from '@/components/bug/BugCreateModal'; import { calcTestProgress, TEST_CASE_STATUS_LABEL } from '@/lib/test-case'; import { Pagination, usePagination } from '@/components/Pagination'; import type { TestCaseStatus } from '@/lib/test-case'; interface Props { versionId: string; requirementIds: string[]; } export function TestCaseTab({ versionId, requirementIds }: Props) { const { testCases, fetchTestCases } = useTestCaseStore(); const { bugs, fetchBugs } = useBugStore(); const { tasks: devTasks } = useDevTaskStore(); const { requirements } = useRequirementStore(); const user = useAuthStore((s) => s.user); useEffect(() => { fetchTestCases(); }, [fetchTestCases]); useEffect(() => { fetchBugs(); }, [fetchBugs]); const versionCases = useMemo( () => testCases.filter((c) => c.versionId === versionId), [testCases, versionId], ); // 提测状态判断 const versionDevTasks = useMemo(() => devTasks.filter((t) => requirementIds.includes(t.requirementId)), [devTasks, requirementIds]); const allSubmitted = versionDevTasks.length > 0 && versionDevTasks.every((t) => t.status === 'submitted'); const stats = calcTestProgress(versionCases); // 筛选 const [filterAssignee, setFilterAssignee] = useState(''); const [filterStatus, setFilterStatus] = useState(''); const filteredCases = useMemo(() => { let result = versionCases; if (filterAssignee) result = result.filter((c) => c.assigneeId === filterAssignee); if (filterStatus) result = result.filter((c) => c.status === filterStatus); return result; }, [versionCases, filterAssignee, filterStatus]); const { paged, page, setPage, total, pageSize, setPageSize } = usePagination(filteredCases, 20); const [showCreate, setShowCreate] = useState(false); const [selectedCaseId, setSelectedCaseId] = useState(null); const [bugForCaseId, setBugForCaseId] = useState(null); // 按需求分组(无需求的归为"通用") const groupedByReq = useMemo(() => { const map = new Map(); for (const c of paged) { const key = c.requirementId || '__none__'; const list = map.get(key) || []; list.push(c); map.set(key, list); } return map; }, [paged]); const assignees = useMemo(() => { const names = new Set(versionCases.map((c) => c.assigneeId).filter(Boolean) as string[]); return Array.from(names); }, [versionCases]); const hasFilter = !!(filterAssignee || filterStatus); return (
{/* 提测通知 */} {allSubmitted && (
开发已全部提测,可以开始测试 {versionDevTasks.length > 0 && {versionDevTasks.length} 个开发任务已交付}
)} {versionDevTasks.length > 0 && !allSubmitted && (
开发进行中,尚未全部提测({versionDevTasks.filter((t) => t.status === 'submitted').length}/{versionDevTasks.length})
)} {/* 统计栏 */}
测试完成 {stats.completionRate}%
{stats.total} 用例 · 通过 {stats.passed} · 失败 {stats.failed} · 阻塞 {stats.blocked} 通过率 {stats.passRate}%
{/* 筛选 */}
{hasFilter && } {filteredCases.length} 条
{/* 列表 */} {filteredCases.length === 0 ? (

{hasFilter ? '没有匹配的用例' : '暂无测试用例'}

{!hasFilter && }
) : ( Array.from(groupedByReq.entries()).map(([reqId, cases]) => { const req = requirements.find((r) => r.id === reqId); const reqPassed = cases.filter((c) => c.status === 'passed').length; return (
{req?.code} {req?.title} {reqPassed}/{cases.length} 通过
{cases.map((c) => ( b.testCaseId === c.id).length} onClick={() => setSelectedCaseId(c.id)} /> ))}
); }) )} {total > 20 && } {showCreate && setShowCreate(false)} />} {selectedCaseId && setSelectedCaseId(null)} onCreateBug={(id) => { setBugForCaseId(id); }} />} {bugForCaseId && setBugForCaseId(null)} />}
); }