feat: Bug 图片上传 + 转交 + 操作日志 + 开发中含自测
1. 开发中统计包含 in_progress + testing(自测) 2. Bug 支持上传图片: - BugCreateModal 加宽为 max-w-2xl - 新增拖拽/点击上传图片区域,转 base64 存储 - 上传后可预览缩略图、可删除 - BugDetailDrawer 展示截图列表,点击可放大 3. Bug 支持转交: - BugDetailDrawer 操作栏新增"转交"按钮 - 选人下拉 + 备注输入 → 确认转交 - transferBug store 方法:更新 assigneeId + 追加日志 4. Bug 操作日志: - BugLog interface: create/status_change/transfer/resolve - createBug 时记录创建日志 - changeStatus 时记录状态变更/修复日志 - transferBug 时记录转交日志 - BugDetailDrawer 底部展示操作时间线 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
import { create } from 'zustand';
|
||||
import type { Bug, BugStatus } from '@/lib/bug';
|
||||
import type { Bug, BugStatus, BugLog } from '@/lib/bug';
|
||||
import { canBugTransition, generateBugNo } from '@/lib/bug';
|
||||
|
||||
const STORAGE_KEY = 'ftb_bugs_v1';
|
||||
@@ -17,13 +17,18 @@ function loadLocal(): Bug[] | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function makeLog(action: BugLog['action'], operator: string, from?: string, to?: string, remark?: string): BugLog {
|
||||
return { id: `log-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, action, fromValue: from, toValue: to, operator, remark, createdAt: new Date().toISOString() };
|
||||
}
|
||||
|
||||
interface BugState {
|
||||
bugs: Bug[];
|
||||
fetchBugs: () => void;
|
||||
createBug: (data: Omit<Bug, 'id' | 'bugNo' | 'createdAt' | 'updatedAt' | 'status'>) => Bug;
|
||||
createBug: (data: Omit<Bug, 'id' | 'bugNo' | 'createdAt' | 'updatedAt' | 'status' | 'logs'>, operator: string) => Bug;
|
||||
updateBug: (id: string, data: Partial<Bug>) => void;
|
||||
deleteBug: (id: string) => void;
|
||||
changeStatus: (id: string, to: BugStatus, extra?: { resolution?: string }) => { ok: boolean; message?: string };
|
||||
changeStatus: (id: string, to: BugStatus, operator: string, extra?: { resolution?: string }) => { ok: boolean; message?: string };
|
||||
transferBug: (id: string, newAssigneeId: string, operator: string, remark?: string) => { ok: boolean; message?: string };
|
||||
getByTestCase: (caseId: string) => Bug[];
|
||||
getByVersion: (versionId: string) => Bug[];
|
||||
getByAssignee: (assigneeId: string) => Bug[];
|
||||
@@ -37,14 +42,16 @@ export const useBugStore = create<BugState>((set, get) => ({
|
||||
if (cached) set({ bugs: cached });
|
||||
},
|
||||
|
||||
createBug: (data) => {
|
||||
createBug: (data, operator) => {
|
||||
const list = get().bugs;
|
||||
const now = new Date().toISOString();
|
||||
const log = makeLog('create', operator);
|
||||
const bug: Bug = {
|
||||
...data,
|
||||
id: `bug-${Date.now()}`,
|
||||
bugNo: generateBugNo(list),
|
||||
status: 'open',
|
||||
logs: [log],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
@@ -68,21 +75,32 @@ export const useBugStore = create<BugState>((set, get) => ({
|
||||
saveLocal(updated);
|
||||
},
|
||||
|
||||
changeStatus: (id, to, extra) => {
|
||||
changeStatus: (id, to, operator, extra) => {
|
||||
const bug = get().bugs.find((b) => b.id === id);
|
||||
if (!bug) return { ok: false, message: 'Bug不存在' };
|
||||
if (!canBugTransition(bug.status, to)) {
|
||||
return { ok: false, message: `不允许从「${bug.status}」流转到「${to}」` };
|
||||
}
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const now = new Date().toISOString();
|
||||
const patch: Partial<Bug> = { status: to };
|
||||
if (to === 'fixed') patch.resolvedAt = today;
|
||||
if (to === 'closed') patch.closedAt = today;
|
||||
if (to === 'fixed') patch.resolvedAt = now;
|
||||
if (to === 'closed') patch.closedAt = now;
|
||||
if (extra?.resolution) patch.resolution = extra.resolution;
|
||||
const log = makeLog(to === 'fixed' ? 'resolve' : 'status_change', operator, bug.status, to, extra?.resolution);
|
||||
patch.logs = [...(bug.logs || []), log];
|
||||
get().updateBug(id, patch);
|
||||
return { ok: true };
|
||||
},
|
||||
|
||||
transferBug: (id, newAssigneeId, operator, remark) => {
|
||||
const bug = get().bugs.find((b) => b.id === id);
|
||||
if (!bug) return { ok: false, message: 'Bug不存在' };
|
||||
if (bug.assigneeId === newAssigneeId) return { ok: false, message: '已是当前负责人' };
|
||||
const log = makeLog('transfer', operator, bug.assigneeId, newAssigneeId, remark);
|
||||
get().updateBug(id, { assigneeId: newAssigneeId, logs: [...(bug.logs || []), log] });
|
||||
return { ok: true };
|
||||
},
|
||||
|
||||
getByTestCase: (caseId) => {
|
||||
return get().bugs.filter((b) => b.testCaseId === caseId);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user