diff --git a/frontend/web/src/pages/Campaigns.tsx b/frontend/web/src/pages/Campaigns.tsx index 02e1d02..0a45ffa 100644 --- a/frontend/web/src/pages/Campaigns.tsx +++ b/frontend/web/src/pages/Campaigns.tsx @@ -17,6 +17,7 @@ import { import { passRateColor } from '../utils/colors' import { shortDateTime } from '../utils/date' import { deriveTimeScale, acceleratedDuration, formatScale } from '../utils/campaignTime' +import WindowTimeline, { type TimelineMarker } from '../components/WindowTimeline' import { useResource } from '../hooks/useResource' import { usePolling } from '../hooks/usePolling' import { useTabStore } from '../stores/tabStore' @@ -99,6 +100,25 @@ export default function CampaignsPage() { const campaigns = data?.campaigns ?? [] const targets = data?.targets ?? [] const scenarios = data?.scenarios ?? [] + const scenarioNames = Object.fromEntries(scenarios.map((s) => [s.id, s.name])) + + const wPlan = (Form.useWatch('plan', form) as PlanFormEntry[] | undefined) ?? [] + const planMarkers: TimelineMarker[] = wPlan + .map((e, i) => ({ e, i })) + .filter(({ e }) => e && e.scenario_id) + .map(({ e, i }) => { + const name = scenarioNames[e.scenario_id as string] ?? (e.scenario_id as string).slice(0, 8) + const count = e.count ?? 1 + const offsetHours = e.offset_hours ?? 0 + return { + key: `plan-${i}`, + offsetSeconds: offsetHours * 3600, + colorKey: e.scenario_id as string, + label: name, + badge: count > 1 ? `×${count}` : undefined, + tooltip: `${name} · +${offsetHours}h · ${count} 次`, + } + }) // Poll the list while this tab is active and a campaign is still working — // compressed dev-line campaigns change fast. Stop once all are terminal. @@ -412,6 +432,25 @@ export default function CampaignsPage() { {acceleratedDuration(wWindow, derivedScale)} +