fix: 时间显示统一使用本地时区(修复UTC显示问题)
之前 ISO 时间戳直接 slice(0, 16).replace('T', ' ') 显示 UTC 时间
(北京时间晚 8 小时),现统一用 formatDateTime 转本地时区。
新增 lib/format.ts:
- formatDateTime(iso) → YYYY-MM-DD HH:mm(本地时区)
- formatDate(iso) → YYYY-MM-DD(本地时区)
- formatDateTimeShort(iso) → MM-DD HH:mm(本地时区)
替换文件:
- versions/[id]/page.tsx (实际开始/截止)
- workspace/page.tsx (计划任务时间)
- BugDetailDrawer.tsx (基本信息+操作日志)
- TestCaseDetailDrawer.tsx (执行时间)
- DevTaskDetailDrawer.tsx (开始/提测时间)
- PlanDetailDrawer.tsx (计划/实际/完成时间)
- PlanTab.tsx (计划任务列表时间)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import { useRequirementStore } from '@/stores/useRequirementStore';
|
||||
import { useMemberStore } from '@/stores/useMemberStore';
|
||||
import { useAuthStore } from '@/stores/useAuthStore';
|
||||
import { BUG_ALLOWED_TRANSITIONS, BUG_STATUS_LABEL, BUG_SEVERITY_LABEL, BUG_SEVERITY_COLOR } from '@/lib/bug';
|
||||
import { formatDateTime } from '@/lib/format';
|
||||
import type { BugStatus } from '@/lib/bug';
|
||||
|
||||
const LOG_ACTION_LABEL: Record<string, string> = {
|
||||
@@ -160,9 +161,9 @@ export function BugDetailDrawer({ bugId, onClose, contextLabel }: Props) {
|
||||
<div className="grid grid-cols-2 gap-y-3 gap-x-4 text-[12px]">
|
||||
<div><span className="text-[var(--ink-muted)]">提交人:</span><span className="text-[var(--ink)]">{bug.reportedBy}</span></div>
|
||||
<div><span className="text-[var(--ink-muted)]">修复人:</span><span className="text-[var(--ink)] font-medium">{bug.assigneeId}</span></div>
|
||||
<div><span className="text-[var(--ink-muted)]">提交:</span><span className="text-[var(--ink)]">{bug.createdAt.slice(0, 16).replace('T', ' ')}</span></div>
|
||||
{bug.resolvedAt && <div><span className="text-[var(--ink-muted)]">修复:</span><span className="text-[var(--ink)]">{bug.resolvedAt.slice(0, 16).replace('T', ' ')}</span></div>}
|
||||
{bug.closedAt && <div><span className="text-[var(--ink-muted)]">关闭:</span><span className="text-emerald-600">{bug.closedAt.slice(0, 16).replace('T', ' ')}</span></div>}
|
||||
<div><span className="text-[var(--ink-muted)]">提交:</span><span className="text-[var(--ink)]">{formatDateTime(bug.createdAt)}</span></div>
|
||||
{bug.resolvedAt && <div><span className="text-[var(--ink-muted)]">修复:</span><span className="text-[var(--ink)]">{formatDateTime(bug.resolvedAt)}</span></div>}
|
||||
{bug.closedAt && <div><span className="text-[var(--ink-muted)]">关闭:</span><span className="text-emerald-600">{formatDateTime(bug.closedAt)}</span></div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -199,7 +200,7 @@ export function BugDetailDrawer({ bugId, onClose, contextLabel }: Props) {
|
||||
<div className="space-y-2.5">
|
||||
{[...bug.logs].reverse().map((log) => (
|
||||
<div key={log.id} className="flex gap-2.5 text-[11px]">
|
||||
<span className="text-[var(--ink-muted)] tabular-nums shrink-0 w-[110px]">{log.createdAt.slice(0, 16).replace('T', ' ')}</span>
|
||||
<span className="text-[var(--ink-muted)] tabular-nums shrink-0 w-[110px]">{formatDateTime(log.createdAt)}</span>
|
||||
<div className="flex-1">
|
||||
<span className="font-medium text-[var(--ink)]">{log.operator}</span>
|
||||
<span className="text-[var(--ink-soft)]"> {LOG_ACTION_LABEL[log.action] || log.action}</span>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore';
|
||||
import { useRequirementStore } from '@/stores/useRequirementStore';
|
||||
import { useMemberStore } from '@/stores/useMemberStore';
|
||||
import { ALLOWED_TRANSITIONS, DEV_TASK_STATUS_LABEL, DEV_TASK_STATUS_COLOR, formatHours, calcActualHoursByDates } from '@/lib/dev-task';
|
||||
import { formatDateTime } from '@/lib/format';
|
||||
import type { DevTaskStatus } from '@/lib/dev-task';
|
||||
|
||||
interface Props {
|
||||
@@ -188,7 +189,7 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel
|
||||
<div className="flex items-center gap-2">
|
||||
<Play className="h-3 w-3 text-[var(--ink-muted)]" />
|
||||
<span className="text-[var(--ink-muted)]">开始开发</span>
|
||||
<span className="text-[var(--ink)]">{task.startDate.slice(0, 16).replace('T', ' ')}</span>
|
||||
<span className="text-[var(--ink)]">{formatDateTime(task.startDate)}</span>
|
||||
</div>
|
||||
)}
|
||||
{task.dueDate && (
|
||||
@@ -202,7 +203,7 @@ export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-3 w-3 text-[var(--ink-muted)]" />
|
||||
<span className="text-[var(--ink-muted)]">提测</span>
|
||||
<span className="text-emerald-600 font-medium">{task.completedAt.slice(0, 16).replace('T', ' ')}</span>
|
||||
<span className="text-emerald-600 font-medium">{formatDateTime(task.completedAt)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ 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 { formatDateTime } from '@/lib/format';
|
||||
import { BUG_SEVERITY_LABEL, BUG_SEVERITY_COLOR } from '@/lib/bug';
|
||||
import type { TestCaseStatus } from '@/lib/test-case';
|
||||
|
||||
@@ -153,8 +154,8 @@ export function TestCaseDetailDrawer({ testCaseId, onClose, onCreateBug, context
|
||||
<div><span className="text-[var(--ink-muted)]">负责人:</span><span className="text-[var(--ink)] font-medium">{tc.assigneeId || '-'}</span></div>
|
||||
<div><span className="text-[var(--ink-muted)]">创建人:</span><span className="text-[var(--ink)]">{tc.createdBy}</span></div>
|
||||
<div><span className="text-[var(--ink-muted)]">创建日期:</span><span className="text-[var(--ink)]">{tc.createdAt.slice(0, 10)}</span></div>
|
||||
{tc.startedAt && <div><span className="text-[var(--ink-muted)]">开始执行:</span><span className="text-[var(--ink)]">{tc.startedAt.slice(0, 16).replace('T', ' ')}</span></div>}
|
||||
{tc.completedAt && <div><span className="text-[var(--ink-muted)]">执行完成:</span><span className="text-[var(--ink)]">{tc.completedAt.slice(0, 16).replace('T', ' ')}</span></div>}
|
||||
{tc.startedAt && <div><span className="text-[var(--ink-muted)]">开始执行:</span><span className="text-[var(--ink)]">{formatDateTime(tc.startedAt)}</span></div>}
|
||||
{tc.completedAt && <div><span className="text-[var(--ink-muted)]">执行完成:</span><span className="text-[var(--ink)]">{formatDateTime(tc.completedAt)}</span></div>}
|
||||
{tc.startedAt && <div><span className="text-[var(--ink-muted)]">测试耗时:</span><span className="text-[var(--ink)] font-medium">{calcActualHoursByDates(tc.startedAt, tc.completedAt)}h</span></div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useVersionPlanStore } from '@/stores/useVersionPlanStore';
|
||||
import { useRequirementStore } from '@/stores/useRequirementStore';
|
||||
import { useMemberStore } from '@/stores/useMemberStore';
|
||||
import { calcPlanProgress, calcLinkedReqProgress } from '@/lib/version-plan';
|
||||
import { formatDateTime } from '@/lib/format';
|
||||
import type { PlanTask, VersionPlan } from '@/lib/version-plan';
|
||||
|
||||
interface Props {
|
||||
@@ -109,18 +110,18 @@ export function PlanDetailDrawer({ planId, onClose, contextLabel }: Props) {
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[var(--ink-muted)]">计划时间</span>
|
||||
<div className="font-medium text-[var(--ink)] mt-0.5">{plan.startTime.slice(0, 16).replace('T', ' ')} → {plan.endTime.slice(0, 16).replace('T', ' ')}</div>
|
||||
<div className="font-medium text-[var(--ink)] mt-0.5">{formatDateTime(plan.startTime)} → {formatDateTime(plan.endTime)}</div>
|
||||
</div>
|
||||
{plan.actualStartAt && (
|
||||
<div>
|
||||
<span className="text-[var(--ink-muted)]">实际开始</span>
|
||||
<div className="font-medium text-blue-600 mt-0.5">{plan.actualStartAt.slice(0, 16).replace('T', ' ')}</div>
|
||||
<div className="font-medium text-blue-600 mt-0.5">{formatDateTime(plan.actualStartAt)}</div>
|
||||
</div>
|
||||
)}
|
||||
{plan.completedAt && (
|
||||
<div>
|
||||
<span className="text-[var(--ink-muted)]">完成时间</span>
|
||||
<div className="font-medium text-emerald-600 mt-0.5">{plan.completedAt.slice(0, 16).replace('T', ' ')}</div>
|
||||
<div className="font-medium text-emerald-600 mt-0.5">{formatDateTime(plan.completedAt)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState } from 'react';
|
||||
import { Plus, Pencil, Trash2, X, Check, ExternalLink, FileUp, Link2, Play, ArrowRightLeft } from 'lucide-react';
|
||||
import type { VersionPlan, PlanTask } from '@/lib/version-plan';
|
||||
import { calcPlanDuration, formatDuration, calcTotalDuration, calcPlanProgress, calcLinkedReqProgress } from '@/lib/version-plan';
|
||||
import { formatDateTime } from '@/lib/format';
|
||||
|
||||
interface Props {
|
||||
plans: VersionPlan[];
|
||||
@@ -85,9 +86,9 @@ export function PlanTab({ plans, versionId, versionDeadline, currentUserName, pl
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-[12px] text-[var(--ink-soft)]">
|
||||
<span>计划:{plan.startTime.slice(0, 16).replace('T', ' ')} → {plan.endTime.slice(0, 16).replace('T', ' ')}</span>
|
||||
{plan.actualStartAt && <span className="text-blue-600">实际开始:{plan.actualStartAt.slice(0, 16).replace('T', ' ')}</span>}
|
||||
{plan.completedAt && <span className="text-green-600">完成:{plan.completedAt.slice(0, 16).replace('T', ' ')}</span>}
|
||||
<span>计划:{formatDateTime(plan.startTime)} → {formatDateTime(plan.endTime)}</span>
|
||||
{plan.actualStartAt && <span className="text-blue-600">实际开始:{formatDateTime(plan.actualStartAt)}</span>}
|
||||
{plan.completedAt && <span className="text-green-600">完成:{formatDateTime(plan.completedAt)}</span>}
|
||||
<span className="font-medium text-[var(--ink)]">已耗时 {durText}</span>
|
||||
</div>
|
||||
{plan.overdueReason && (
|
||||
|
||||
Reference in New Issue
Block a user