feat: 与我相关点击打开侧边详情 + 开发/测试转交功能

一、工作台交互改为侧边栏详情(点击卡片打开):
- 调研/产品/UI:新建 PlanDetailDrawer
  - 子任务勾选、进度条、上传成果(链接/文件)
  - 开始/提交完成操作、转交功能
- 开发任务:复用 DevTaskDetailDrawer
  - 状态流转、阻塞管理、前置任务等完整功能
- 测试用例:复用 TestCaseDetailDrawer
  - 测试步骤、预期结果、关联Bug、提Bug
- Bug:复用 BugDetailDrawer
  - 描述、截图、操作日志、状态转换

二、开发任务增加转交功能:
- DevTaskDetailDrawer 顶部增加转交按钮
- 从 members 列表选择新负责人
- 更新 assigneeId

三、测试用例增加转交功能:
- TestCaseDetailDrawer 顶部增加转交按钮
- 人员离职等场景可转交

四、版本详情同步:
- 所有 Drawer 改动同时生效于版本详情页
  (因为复用同一组件)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-15 17:30:16 +08:00
parent 10131f9db7
commit 98251f1c38
4 changed files with 320 additions and 73 deletions

View File

@@ -1,12 +1,13 @@
'use client';
import { useState } from 'react';
import { X, AlertTriangle, Link2, ChevronRight, Bug as BugIcon, Trash2 } from 'lucide-react';
import { X, AlertTriangle, Link2, ChevronRight, Bug as BugIcon, Trash2, ArrowRightLeft } from 'lucide-react';
import { TestCaseStatusBadge } from './TestCaseStatusBadge';
import { BugStatusBadge } from '@/components/bug/BugStatusBadge';
import { useTestCaseStore } from '@/stores/useTestCaseStore';
import { useBugStore } from '@/stores/useBugStore';
import { useRequirementStore } from '@/stores/useRequirementStore';
import { useMemberStore } from '@/stores/useMemberStore';
import { TC_ALLOWED_TRANSITIONS, TEST_CASE_STATUS_LABEL } from '@/lib/test-case';
import { calcActualHoursByDates } from '@/lib/dev-task';
import { BUG_SEVERITY_LABEL, BUG_SEVERITY_COLOR } from '@/lib/bug';
@@ -19,9 +20,12 @@ interface Props {
}
export function TestCaseDetailDrawer({ testCaseId, onClose, onCreateBug }: Props) {
const { testCases, changeStatus, deleteTestCase } = useTestCaseStore();
const { testCases, changeStatus, deleteTestCase, updateTestCase } = useTestCaseStore();
const { bugs } = useBugStore();
const { requirements } = useRequirementStore();
const { members } = useMemberStore();
const [showTransfer, setShowTransfer] = useState(false);
const [transferTo, setTransferTo] = useState('');
const tc = testCases.find((c) => c.id === testCaseId);
if (!tc) return null;
@@ -62,11 +66,27 @@ export function TestCaseDetailDrawer({ testCaseId, onClose, onCreateBug }: Props
<span className="text-[14px] font-semibold text-[var(--ink)] truncate max-w-[240px]">{tc.title}</span>
</div>
<div className="flex items-center gap-1">
{tc.status !== 'passed' && (
<button onClick={() => setShowTransfer(!showTransfer)} className="p-1.5 rounded-lg hover:bg-blue-50 text-[var(--ink-muted)] hover:text-blue-500" title="转交"><ArrowRightLeft className="h-4 w-4" /></button>
)}
<button onClick={() => { if (relatedBugs.length > 0) { alert('该用例有关联 Bug无法删除'); return; } if (confirm('确定删除此测试用例?')) { deleteTestCase(tc.id); onClose(); } }} className="p-1.5 rounded-lg hover:bg-red-50 text-[var(--ink-muted)] hover:text-red-500" title="删除"><Trash2 className="h-4 w-4" /></button>
<button onClick={onClose} className="p-1.5 rounded-lg hover:bg-[var(--bg-subtle)]"><X className="h-4 w-4 text-[var(--ink-muted)]" /></button>
</div>
</div>
{/* 转交 */}
{showTransfer && (
<div className="mx-5 mt-3 rounded-lg border border-[var(--line)] p-3 flex items-center gap-2">
<span className="text-[11px] text-[var(--ink-muted)] shrink-0"></span>
<select value={transferTo} onChange={(e) => setTransferTo(e.target.value)} className="h-7 flex-1 rounded-lg border border-[var(--line)] px-2 text-[12px] focus:border-[var(--accent)] focus:outline-none">
<option value=""></option>
{members.filter((m) => m.name !== tc.assigneeId).map((m) => <option key={m.id} value={m.name}>{m.name}</option>)}
</select>
<button onClick={() => { if (transferTo) { updateTestCase(tc.id, { assigneeId: transferTo }); setShowTransfer(false); setTransferTo(''); } }} disabled={!transferTo} className="h-7 px-2.5 rounded text-[11px] font-medium bg-blue-500 text-white disabled:opacity-50"></button>
<button onClick={() => setShowTransfer(false)} className="h-7 px-2 text-[11px] text-[var(--ink-muted)]"></button>
</div>
)}
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{/* 关联需求 */}
{requirement && (