From c86e87f95ba8ec1eb37b137b1486cf87945e14d6 Mon Sep 17 00:00:00 2001 From: Script Generator Date: Fri, 12 Jun 2026 16:11:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=9E=E9=99=85=E8=80=97=E6=97=B6?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=B2=BE=E7=A1=AE=E6=97=B6=E9=97=B4=E6=88=B3?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=EF=BC=88=E7=B2=BE=E7=A1=AE=E5=88=B00.5h?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前用日期+工作日×8h 计算耗时太粗糙,现改为: - DevTask startDate/completedAt 使用完整 ISO 时间戳(含时分秒) - TestCase startedAt/completedAt 同上 - 耗时 = (endTimestamp - startTimestamp) / 3600000,精确到0.5h - 详情抽屉显示精确到分钟的时间(YYYY-MM-DD HH:mm) 例如:08:00 开始,10:30 提测,耗时 2.5h(不再是8h) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../dev-task/DevTaskDetailDrawer.tsx | 4 ++-- .../test-case/TestCaseDetailDrawer.tsx | 4 ++-- apps/web/lib/dev-task.ts | 18 ++++++------------ apps/web/stores/useDevTaskStore.ts | 7 +++---- apps/web/stores/useTestCaseStore.ts | 10 ++++------ 5 files changed, 17 insertions(+), 26 deletions(-) diff --git a/apps/web/components/dev-task/DevTaskDetailDrawer.tsx b/apps/web/components/dev-task/DevTaskDetailDrawer.tsx index b2f9cb1..e9fc04e 100644 --- a/apps/web/components/dev-task/DevTaskDetailDrawer.tsx +++ b/apps/web/components/dev-task/DevTaskDetailDrawer.tsx @@ -161,7 +161,7 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose }: Props) {
开始开发 - {task.startDate} + {task.startDate.slice(0, 16).replace('T', ' ')}
)} {task.dueDate && ( @@ -175,7 +175,7 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose }: Props) {
提测 - {task.completedAt} + {task.completedAt.slice(0, 16).replace('T', ' ')}
)} diff --git a/apps/web/components/test-case/TestCaseDetailDrawer.tsx b/apps/web/components/test-case/TestCaseDetailDrawer.tsx index 5d87e70..8dd9cfc 100644 --- a/apps/web/components/test-case/TestCaseDetailDrawer.tsx +++ b/apps/web/components/test-case/TestCaseDetailDrawer.tsx @@ -127,8 +127,8 @@ export function TestCaseDetailDrawer({ testCaseId, onClose, onCreateBug }: Props
负责人:{tc.assigneeId || '-'}
创建人:{tc.createdBy}
创建日期:{tc.createdAt.slice(0, 10)}
- {tc.startedAt &&
开始执行:{tc.startedAt}
} - {tc.completedAt &&
执行完成:{tc.completedAt}
} + {tc.startedAt &&
开始执行:{tc.startedAt.slice(0, 16).replace('T', ' ')}
} + {tc.completedAt &&
执行完成:{tc.completedAt.slice(0, 16).replace('T', ' ')}
} {tc.startedAt &&
测试耗时:{calcActualHoursByDates(tc.startedAt, tc.completedAt)}h
} diff --git a/apps/web/lib/dev-task.ts b/apps/web/lib/dev-task.ts index 3881f9a..56757c4 100644 --- a/apps/web/lib/dev-task.ts +++ b/apps/web/lib/dev-task.ts @@ -83,18 +83,12 @@ export function formatHours(hours: number): string { export function calcActualHoursByDates(startDate?: string, completedAt?: string): number { if (!startDate) return 0; - const end = completedAt || new Date().toISOString().slice(0, 10); - const start = new Date(startDate); - const endDate = new Date(end); - if (isNaN(start.getTime()) || isNaN(endDate.getTime()) || endDate < start) return 0; - let workDays = 0; - const current = new Date(start); - while (current <= endDate) { - const day = current.getDay(); - if (day !== 0 && day !== 6) workDays++; - current.setDate(current.getDate() + 1); - } - return workDays * 8; + const end = completedAt || new Date().toISOString(); + const startMs = new Date(startDate).getTime(); + const endMs = new Date(end).getTime(); + if (isNaN(startMs) || isNaN(endMs) || endMs < startMs) return 0; + const diffHours = (endMs - startMs) / (1000 * 60 * 60); + return Math.round(diffHours * 2) / 2; // 精确到0.5h } export function generateTaskNo(existingTasks: DevTask[]): string { diff --git a/apps/web/stores/useDevTaskStore.ts b/apps/web/stores/useDevTaskStore.ts index 0f8d6cc..e842b1e 100644 --- a/apps/web/stores/useDevTaskStore.ts +++ b/apps/web/stores/useDevTaskStore.ts @@ -77,10 +77,9 @@ export const useDevTaskStore = create((set, get) => ({ if (!canTransition(task.status, to)) { return { ok: false, message: `不允许从「${task.status}」流转到「${to}」` }; } - const today = new Date().toISOString().slice(0, 10); - const startDate = (to === 'in_progress' && !task.startDate) ? today : task.startDate; - // 开发完成时间 = 提测时间(不是测试完成时间) - const completedAt = (to === 'submitted' && !task.completedAt) ? today : task.completedAt; + const now = new Date().toISOString(); + const startDate = (to === 'in_progress' && !task.startDate) ? now : task.startDate; + const completedAt = (to === 'submitted' && !task.completedAt) ? now : task.completedAt; get().updateTask(id, { status: to, startDate, completedAt }); return { ok: true }; }, diff --git a/apps/web/stores/useTestCaseStore.ts b/apps/web/stores/useTestCaseStore.ts index 5fc7d65..0948528 100644 --- a/apps/web/stores/useTestCaseStore.ts +++ b/apps/web/stores/useTestCaseStore.ts @@ -74,14 +74,12 @@ export const useTestCaseStore = create((set, get) => ({ if (!canTcTransition(tc.status, to)) { return { ok: false, message: `不允许从「${tc.status}」流转到「${to}」` }; } - const today = new Date().toISOString().slice(0, 10); + const now = new Date().toISOString(); 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' && !tc.startedAt) patch.startedAt = now; + if (to === 'passed' || to === 'failed' || to === 'blocked') patch.completedAt = now; if (to === 'running' || to === 'passed' || to === 'failed' || to === 'blocked') { - patch.executedAt = today; + patch.executedAt = now; } // 回退到执行中时清除完成时间 if (to === 'running') { patch.failReason = undefined; patch.blockReason = undefined; patch.completedAt = undefined; }