feat: 提测通知 + 测试全通过自动完成开发任务

1. TestCaseTab 显示提测状态横幅:
   - 绿色:开发已全部提测,可以开始测试
   - 橙色:开发进行中,显示提测进度 (x/y)

2. TestCase 全部通过 + Bug 全关闭时,自动将版本下所有
   submitted 状态的 DevTask 流转为 done(无需手动操作)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-12 14:20:44 +08:00
parent 93f6c33109
commit e4f3817719
2 changed files with 41 additions and 2 deletions

View File

@@ -1,9 +1,10 @@
'use client';
import { useState, useMemo, useEffect } from 'react';
import { Plus, ClipboardCheck, Filter } from 'lucide-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';
@@ -22,6 +23,7 @@ interface Props {
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);
@@ -30,9 +32,13 @@ export function TestCaseTab({ versionId, requirementIds }: Props) {
const versionCases = useMemo(
() => testCases.filter((c) => c.versionId === versionId),
[testCases, requirementIds],
[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' || t.status === 'done');
const stats = calcTestProgress(versionCases);
// 筛选
@@ -73,6 +79,19 @@ export function TestCaseTab({ versionId, requirementIds }: Props) {
return (
<div className="space-y-3">
{/* 提测通知 */}
{allSubmitted && (
<div className="flex items-center gap-2 px-4 py-2.5 rounded-lg bg-emerald-50 border border-emerald-200">
<CheckCircle2 className="h-4 w-4 text-emerald-600" />
<span className="text-[12px] font-medium text-emerald-700"></span>
{versionDevTasks.length > 0 && <span className="text-[11px] text-emerald-600 ml-auto">{versionDevTasks.length} </span>}
</div>
)}
{versionDevTasks.length > 0 && !allSubmitted && (
<div className="flex items-center gap-2 px-4 py-2.5 rounded-lg bg-orange-50 border border-orange-200">
<span className="text-[12px] text-orange-700">{versionDevTasks.filter((t) => t.status === 'submitted' || t.status === 'done').length}/{versionDevTasks.length}</span>
</div>
)}
{/* 统计栏 */}
<div className="flex items-center gap-4 px-4 py-3 rounded-lg bg-[var(--bg-subtle)] border border-[var(--line)]">
<div className="flex items-center gap-2">

View File

@@ -83,6 +83,26 @@ export const useTestCaseStore = create<TestCaseState>((set, get) => ({
if (to === 'blocked' && extra?.blockReason) patch.blockReason = extra.blockReason;
if (to === 'running') { patch.failReason = undefined; patch.blockReason = undefined; }
get().updateTestCase(id, patch);
// 副作用:当版本所有用例通过 + Bug全关闭 → DevTask 自动完成
if (to === 'passed' && tc.versionId) {
setTimeout(() => {
try {
const { useDevTaskStore } = require('@/stores/useDevTaskStore');
const { useBugStore } = require('@/stores/useBugStore');
const allCases = get().testCases.filter((c) => c.versionId === tc.versionId);
const allPassed = allCases.every((c) => c.id === id ? true : c.status === 'passed');
if (!allPassed) return;
const allBugs = useBugStore.getState().bugs.filter((b: any) => b.versionId === tc.versionId);
const allBugsClosed = allBugs.every((b: any) => b.status === 'closed' || b.status === 'rejected');
if (!allBugsClosed) return;
const devStore = useDevTaskStore.getState();
const versionTasks = devStore.tasks.filter((t: any) => t.status === 'submitted');
versionTasks.forEach((t: any) => devStore.changeStatus(t.id, 'done'));
} catch {}
}, 0);
}
return { ok: true };
},