diff --git a/apps/web/app/versions/[id]/page.tsx b/apps/web/app/versions/[id]/page.tsx index c8dcbcb..6310050 100644 --- a/apps/web/app/versions/[id]/page.tsx +++ b/apps/web/app/versions/[id]/page.tsx @@ -104,7 +104,10 @@ export default function VersionDetailPage() { const vTCs = testCases.filter((c) => c.versionId === version.id); const startDates: string[] = []; - vPlans.forEach((p) => { if (p.actualStartAt) startDates.push(p.actualStartAt); }); + vPlans.forEach((p) => { + if (p.actualStartAt) startDates.push(p.actualStartAt); + else if (p.status === 'pending' && p.startTime && new Date(p.startTime) <= new Date()) startDates.push(p.startTime); + }); vDevTasks.forEach((t) => { if (t.startDate) startDates.push(t.startDate); }); vTCs.forEach((c) => { if (c.startedAt) startDates.push(c.startedAt); }); @@ -387,7 +390,10 @@ export default function VersionDetailPage() { const vBugsAll = bugs.filter((b) => b.versionId === version.id); const startDates: string[] = []; - vPlansAll.forEach((p) => { if (p.actualStartAt) startDates.push(p.actualStartAt); }); + vPlansAll.forEach((p) => { + if (p.actualStartAt) startDates.push(p.actualStartAt); + else if (p.status === 'pending' && p.startTime && new Date(p.startTime) <= new Date()) startDates.push(p.startTime); + }); vDTs.forEach((t) => { if (t.startDate) startDates.push(t.startDate); }); vTCsAll.forEach((c) => { if (c.startedAt) startDates.push(c.startedAt); }); const actualStart = startDates.length > 0 ? startDates.sort()[0].slice(0, 10) : (version.startDate ?? null); diff --git a/apps/web/components/version/PlanTab.tsx b/apps/web/components/version/PlanTab.tsx index 17c3d62..ffb9890 100644 --- a/apps/web/components/version/PlanTab.tsx +++ b/apps/web/components/version/PlanTab.tsx @@ -56,25 +56,33 @@ export function PlanTab({ plans, versionId, versionDeadline, currentUserName, pl
{typePlans.map((plan) => { const now = new Date().toISOString(); + // 自动开始:如果到了计划开始日期且状态还是 pending,视为已开始 + const autoStarted = plan.status === 'pending' && plan.startTime && new Date(plan.startTime) <= new Date(); + const effectiveStatus = autoStarted ? 'in_progress' : plan.status; + const effectiveStartAt = plan.actualStartAt || (autoStarted ? plan.startTime : null); // 耗时用实际时间戳计算 const dur = plan.status === 'completed' && plan.completedAt && plan.actualStartAt ? calcPlanDuration(plan.actualStartAt, plan.completedAt) - : plan.status === 'in_progress' && plan.actualStartAt - ? calcPlanDuration(plan.actualStartAt, now) + : effectiveStatus === 'in_progress' && effectiveStartAt + ? calcPlanDuration(effectiveStartAt, now) : { days: 0, hours: 0 }; const durText = dur.days > 0 || dur.hours > 0 ? formatDuration(dur.days, dur.hours) : '-'; + // 如果自动开始了,触发 store 更新(副作用) + if (autoStarted && !plan.actualStartAt) { + onUpdate(plan.id, { status: 'in_progress' }); + } return (
{plan.title} - - {STATUS_LABEL[plan.status]} + + {STATUS_LABEL[effectiveStatus]}
- 计划:{plan.startTime.slice(0, 10)} → {plan.endTime.slice(0, 10)} + 计划:{plan.startTime.slice(0, 16).replace('T', ' ')} → {plan.endTime.slice(0, 16).replace('T', ' ')} {plan.actualStartAt && 实际开始:{plan.actualStartAt.slice(0, 16).replace('T', ' ')}} {plan.completedAt && 完成:{plan.completedAt.slice(0, 16).replace('T', ' ')}} 已耗时 {durText} @@ -106,14 +114,14 @@ export function PlanTab({ plans, versionId, versionDeadline, currentUserName, pl {plan.tasks.map((task) => (
@@ -142,14 +150,14 @@ export function PlanTab({ plans, versionId, versionDeadline, currentUserName, pl return req ? (
@@ -169,8 +177,8 @@ export function PlanTab({ plans, versionId, versionDeadline, currentUserName, pl )}
- {plan.status === 'pending' && ( - )} @@ -235,11 +243,11 @@ function PlanFormModal({ initial, planType, versionId, versionDeadline, currentU onClose: () => void; onSubmit: (data: any) => void; }) { - const now = new Date().toISOString().slice(0, 10); + const now = new Date().toISOString().slice(0, 16); const [title, setTitle] = useState(initial?.title ?? ''); const [owner] = useState(initial?.owner ?? currentUserName); - const [startTime, setStartTime] = useState(initial?.startTime?.slice(0, 10) ?? now); - const [endTime, setEndTime] = useState(initial?.endTime?.slice(0, 10) ?? ''); + const [startTime, setStartTime] = useState(initial?.startTime?.slice(0, 16) ?? now); + const [endTime, setEndTime] = useState(initial?.endTime?.slice(0, 16) ?? ''); const [remark, setRemark] = useState(initial?.remark ?? ''); const [tasks, setTasks] = useState(initial?.tasks ?? []); const [newTaskTitle, setNewTaskTitle] = useState(''); @@ -287,12 +295,12 @@ function PlanFormModal({ initial, planType, versionId, versionDeadline, currentU
- - setStartTime(e.target.value)} className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none" /> + + setStartTime(e.target.value)} className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none" />
- - setEndTime(e.target.value)} required className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none" /> + + setEndTime(e.target.value)} required className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 text-[13px] focus:border-[var(--accent)] focus:outline-none" />
{versionDeadline && (