feat: 与我相关加宽布局 + 任务可直接完成

1. 加宽两栏布局:
   - 左一(树):w-52 → w-64
   - 左二(环节):w-48 → w-56
   - 右侧更舒适,不再拥挤

2. 待办任务可直接在工作台完成,无需跳转:
   - 调研/产品方案/UI:开始 → 完成
   - 开发任务:开始 → 提测 → 完成
   - 测试用例:通过 / 失败
   - Bug:修复中 → 已修复 → 关闭
   - 每个状态显示对应的下一步操作按钮
   - 已完成的不显示操作按钮

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-15 17:07:04 +08:00
parent 77a9b0e827
commit 10131f9db7

View File

@@ -39,11 +39,11 @@ const PLAN_STATUS_LABEL: Record<string, string> = { pending: '未开始', in_pro
export default function WorkspacePage() {
const router = useRouter();
const { overview, fetchOverview } = useProductStore();
const { plans, fetchPlans } = useVersionPlanStore();
const { plans, fetchPlans, updatePlan } = useVersionPlanStore();
const { requirements, fetchRequirements } = useRequirementStore();
const { tasks: devTasks, fetchTasks } = useDevTaskStore();
const { testCases, fetchTestCases } = useTestCaseStore();
const { bugs, fetchBugs } = useBugStore();
const { tasks: devTasks, fetchTasks, updateTask } = useDevTaskStore();
const { testCases, fetchTestCases, updateTestCase } = useTestCaseStore();
const { bugs, fetchBugs, updateBug } = useBugStore();
const user = useAuthStore((s) => s.user);
const [activeTab, setActiveTab] = useState<TabKey>('all');
const [showCompleted, setShowCompleted] = useState(true);
@@ -121,10 +121,31 @@ export default function WorkspacePage() {
const pendingCount = pendingByTab.all;
const completedCount = (selectedVersionId ? workItems.filter((i) => i.versionId === selectedVersionId) : workItems).filter((i) => i.completed).length;
const handleItemAction = (item: WorkItem, action: string) => {
if (item.type === 'plan_research' || item.type === 'plan_product' || item.type === 'plan_ui') {
if (action === 'start') updatePlan(item.id, { status: 'in_progress' });
if (action === 'complete') updatePlan(item.id, { status: 'completed', completedAt: new Date().toISOString() });
}
if (item.type === 'devTask') {
if (action === 'start') updateTask(item.id, { status: 'in_progress' });
if (action === 'testing') updateTask(item.id, { status: 'testing' });
if (action === 'submit') updateTask(item.id, { status: 'submitted', completedAt: new Date().toISOString() });
}
if (item.type === 'testCase') {
if (action === 'pass') updateTestCase(item.id, { status: 'passed', completedAt: new Date().toISOString() });
if (action === 'fail') updateTestCase(item.id, { status: 'failed' });
}
if (item.type === 'bug') {
if (action === 'fix') updateBug(item.id, { status: 'fixing' });
if (action === 'fixed') updateBug(item.id, { status: 'fixed' });
if (action === 'close') updateBug(item.id, { status: 'closed', closedAt: new Date().toISOString() });
}
};
return (
<div className="flex h-full">
{/* 左侧第一列:产品/项目/版本树 */}
<div className="w-52 shrink-0 border-r border-[var(--line)] bg-[var(--bg-card)] flex flex-col">
<div className="w-64 shrink-0 border-r border-[var(--line)] bg-[var(--bg-card)] flex flex-col">
<div className="flex h-14 items-center px-4 border-b border-[var(--line)]">
<h1 className="text-[14px] font-semibold text-[var(--ink)]"></h1>
</div>
@@ -146,7 +167,7 @@ export default function WorkspacePage() {
</div>
{/* 左侧第二列:环节分类 */}
<div className="w-48 shrink-0 border-r border-[var(--line)] bg-[var(--bg-card)] flex flex-col">
<div className="w-56 shrink-0 border-r border-[var(--line)] bg-[var(--bg-card)] flex flex-col">
<div className="flex h-14 items-center px-4 border-b border-[var(--line)]">
<div className="flex items-center gap-3 text-[11px]">
<span className="text-[var(--ink-muted)]"> <span className="font-medium text-[var(--ink)]">{pendingCount}</span></span>
@@ -201,7 +222,12 @@ export default function WorkspacePage() {
) : (
<div className="space-y-2">
{filteredItems.map((item) => (
<WorkItemCard key={item.id} item={item} onNavigate={() => item.versionId && router.push(`/versions/${item.versionId}`)} />
<WorkItemCard
key={item.id}
item={item}
onNavigate={() => item.versionId && router.push(`/versions/${item.versionId}`)}
onAction={(action) => handleItemAction(item, action)}
/>
))}
</div>
)}
@@ -261,8 +287,9 @@ function ProjectNode({ name, versions, selectedVersionId, onSelect }: {
);
}
function WorkItemCard({ item, onNavigate }: { item: WorkItem; onNavigate: () => void }) {
function WorkItemCard({ item, onNavigate, onAction }: { item: WorkItem; onNavigate: () => void; onAction: (action: string) => void }) {
const statusBadge = getStatusBadge(item);
const actions = getActions(item);
return (
<div className={`rounded-xl border border-[var(--line)] bg-[var(--bg-card)] px-4 py-3 transition-colors hover:border-[var(--accent)] ${item.completed ? 'opacity-60' : ''}`}>
@@ -282,6 +309,16 @@ function WorkItemCard({ item, onNavigate }: { item: WorkItem; onNavigate: () =>
{BUG_SEVERITY_LABEL[item.extra.severity as keyof typeof BUG_SEVERITY_LABEL] || ''}
</span>
)}
{/* 操作按钮 */}
{actions.length > 0 && (
<div className="flex items-center gap-1 shrink-0">
{actions.map((a) => (
<button key={a.key} onClick={() => onAction(a.key)} className={`h-6 px-2 rounded text-[10px] font-medium transition-colors ${a.style}`}>
{a.label}
</button>
))}
</div>
)}
<button onClick={onNavigate} className="h-6 w-6 flex items-center justify-center rounded text-[var(--ink-muted)] hover:text-[var(--accent)] hover:bg-[var(--bg-subtle)] shrink-0" title="跳转到版本详情">
<ExternalLink className="h-3.5 w-3.5" />
</button>
@@ -299,6 +336,34 @@ function WorkItemCard({ item, onNavigate }: { item: WorkItem; onNavigate: () =>
);
}
function getActions(item: WorkItem): { key: string; label: string; style: string }[] {
if (item.completed) return [];
if (item.type === 'plan_research' || item.type === 'plan_product' || item.type === 'plan_ui') {
if (item.status === 'pending') return [{ key: 'start', label: '开始', style: 'text-blue-600 hover:bg-blue-50 border border-blue-200' }];
if (item.status === 'in_progress') return [{ key: 'complete', label: '完成', style: 'text-emerald-600 hover:bg-emerald-50 border border-emerald-200' }];
}
if (item.type === 'devTask') {
if (item.status === 'todo') return [{ key: 'start', label: '开始', style: 'text-blue-600 hover:bg-blue-50 border border-blue-200' }];
if (item.status === 'in_progress') return [{ key: 'testing', label: '提测', style: 'text-purple-600 hover:bg-purple-50 border border-purple-200' }];
if (item.status === 'testing') return [{ key: 'submit', label: '完成', style: 'text-emerald-600 hover:bg-emerald-50 border border-emerald-200' }];
}
if (item.type === 'testCase') {
if (item.status === 'pending' || item.status === 'running' || item.status === 'failed') {
return [
{ key: 'pass', label: '通过', style: 'text-emerald-600 hover:bg-emerald-50 border border-emerald-200' },
{ key: 'fail', label: '失败', style: 'text-red-600 hover:bg-red-50 border border-red-200' },
];
}
}
if (item.type === 'bug') {
if (item.status === 'open') return [{ key: 'fix', label: '修复中', style: 'text-blue-600 hover:bg-blue-50 border border-blue-200' }];
if (item.status === 'fixing') return [{ key: 'fixed', label: '已修复', style: 'text-purple-600 hover:bg-purple-50 border border-purple-200' }];
if (item.status === 'fixed' || item.status === 'verifying') return [{ key: 'close', label: '关闭', style: 'text-emerald-600 hover:bg-emerald-50 border border-emerald-200' }];
}
return [];
}
function getStatusBadge(item: WorkItem) {
if (item.type === 'devTask') {
return (