'use client'; import { create } from 'zustand'; import type { TestCase, TestCaseStatus } from '@/lib/test-case'; import { canTcTransition, generateCaseNo } from '@/lib/test-case'; const STORAGE_KEY = 'ftb_test_cases_v1'; function saveLocal(items: TestCase[]) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(items)); } catch {} } function loadLocal(): TestCase[] | null { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) return JSON.parse(raw); } catch {} return null; } interface TestCaseState { testCases: TestCase[]; fetchTestCases: () => void; createTestCase: (data: Omit) => TestCase; updateTestCase: (id: string, data: Partial) => void; deleteTestCase: (id: string) => void; changeStatus: (id: string, to: TestCaseStatus, extra?: { failReason?: string; blockReason?: string }) => { ok: boolean; message?: string }; getByRequirement: (reqId: string) => TestCase[]; getByVersion: (versionId: string) => TestCase[]; getByAssignee: (assigneeId: string) => TestCase[]; } export const useTestCaseStore = create((set, get) => ({ testCases: [], fetchTestCases: () => { const cached = loadLocal(); if (cached) set({ testCases: cached }); }, createTestCase: (data) => { const list = get().testCases; const now = new Date().toISOString(); const tc: TestCase = { ...data, id: `tc-${Date.now()}`, caseNo: generateCaseNo(list), status: 'pending', createdAt: now, updatedAt: now, }; const updated = [...list, tc]; set({ testCases: updated }); saveLocal(updated); return tc; }, updateTestCase: (id, data) => { const updated = get().testCases.map((c) => c.id === id ? { ...c, ...data, updatedAt: new Date().toISOString() } : c, ); set({ testCases: updated }); saveLocal(updated); }, deleteTestCase: (id) => { const updated = get().testCases.filter((c) => c.id !== id); set({ testCases: updated }); saveLocal(updated); }, changeStatus: (id, to, extra) => { const tc = get().testCases.find((c) => c.id === id); if (!tc) return { ok: false, message: '用例不存在' }; if (!canTcTransition(tc.status, to)) { return { ok: false, message: `不允许从「${tc.status}」流转到「${to}」` }; } const today = new Date().toISOString().slice(0, 10); const patch: Partial = { status: to }; // 开始执行时间 if (to === 'running' && !tc.startedAt) patch.startedAt = today; // 执行完成时间(通过/失败/阻塞都算执行完成) if (to === 'passed' || to === 'failed' || to === 'blocked') patch.completedAt = today; if (to === 'running' || to === 'passed' || to === 'failed' || to === 'blocked') { patch.executedAt = today; } // 回退到执行中时清除完成时间 if (to === 'running') { patch.failReason = undefined; patch.blockReason = undefined; patch.completedAt = undefined; } if (to === 'failed' && extra?.failReason) patch.failReason = extra.failReason; if (to === 'blocked' && extra?.blockReason) patch.blockReason = extra.blockReason; get().updateTestCase(id, patch); return { ok: true }; }, getByRequirement: (reqId) => { return get().testCases.filter((c) => c.requirementId === reqId); }, getByVersion: (versionId) => { return get().testCases.filter((c) => c.versionId === versionId); }, getByAssignee: (assigneeId) => { return get().testCases.filter((c) => c.assigneeId === assigneeId); }, }));