diff --git a/client/src/components/DemoFlowOverlay.tsx b/client/src/components/DemoFlowOverlay.tsx index 12a9ca9..41ccf5d 100644 --- a/client/src/components/DemoFlowOverlay.tsx +++ b/client/src/components/DemoFlowOverlay.tsx @@ -1,6 +1,11 @@ import { useMemo, useState } from 'react'; import { Check, Loader2, Circle, X, AlertTriangle, Cpu, Scan, Minimize2, Maximize2 } from 'lucide-react'; -import type { DemoFlowState, DemoFlowStep, DemoFlowStepStatus } from '@/hooks/useDemoFlowState'; +import { + formatElapsedTime, + type DemoFlowState, + type DemoFlowStep, + type DemoFlowStepStatus, +} from '@/hooks/useDemoFlowState'; interface DemoFlowOverlayProps { state: DemoFlowState; @@ -64,16 +69,13 @@ function overallStatusLabel(status: DemoFlowState['overallStatus']): string { export default function DemoFlowOverlay({ state, pluginNameById }: DemoFlowOverlayProps) { const [collapsedRunIds, setCollapsedRunIds] = useState>(() => new Set()); - const title = useMemo(() => { - switch (state.overallStatus) { - case 'processing': return '一键全流程演示运行中'; - case 'pending': return '一键全流程演示排队中'; - case 'completed': return '一键全流程演示已完成'; - case 'error': return '一键全流程演示失败'; - case 'cancelled': return '一键全流程演示已停止'; - default: return '一键全流程演示'; - } - }, [state.overallStatus]); + const title = useMemo( + () => state.completedPointCount == null + ? '点云数量暂不可用' + : `已完成 ${(state.completedPointCount / 1e8).toFixed(2)} 亿点云的测量`, + [state.completedPointCount], + ); + const elapsedTime = formatElapsedTime(state.elapsedMs); if (!state.active && state.overallStatus === 'idle') return null; @@ -91,11 +93,12 @@ export default function DemoFlowOverlay({ state, pluginNameById }: DemoFlowOverl if (isCollapsed) { return (
-
+
-
- 一键全流程 · {overallStatusLabel(state.overallStatus)} - {state.overallProgress}% +
{title}
+
+ {overallStatusLabel(state.overallStatus)} · {elapsedTime} + {state.overallProgress}%
-
+
{state.overallStatus === 'processing' ? ( @@ -130,9 +133,9 @@ export default function DemoFlowOverlay({ state, pluginNameById }: DemoFlowOverl )}
-

{title}

+

{title}

- {state.completed}/{state.total} 步骤已完成 · 总进度 {state.overallProgress}% + {state.completed}/{state.total} 步骤已完成 · 总进度 {state.overallProgress}% · 算法运行时间 {elapsedTime}

diff --git a/server/algorithmJobRunner.ts b/server/algorithmJobRunner.ts index 61c1b2a..311db66 100644 --- a/server/algorithmJobRunner.ts +++ b/server/algorithmJobRunner.ts @@ -187,6 +187,7 @@ const DEMO_FLOW_RESULT_DATA_KEYS = [ 'demoFlowTotal', 'demoFlowSourcePointCloudFileId', 'demoFlowCadFileId', + 'demoFlowCancelledAt', ] as const; async function readDemoFlowResultData(resultId: number): Promise> { diff --git a/server/routers.ts b/server/routers.ts index bde0cf1..fe65c3f 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -1262,6 +1262,26 @@ export const appRouter = router({ .mutation(async ({ input }) => { markDemoFlowCancelled(input.taskId); const result = await cancelTaskAnalysisResultsByTask(input.taskId, "用户停止一键全流程演示"); + const rows = await db.listTaskAnalysisResults(input.taskId); + const latestDemoRow = rows.find((row) => { + const data = row.resultData as Record | null; + return typeof data?.demoFlowRunId === "string"; + }); + if (latestDemoRow) { + const latestDemoData = latestDemoRow.resultData as Record; + const latestRunId = latestDemoData.demoFlowRunId; + const cancelledAt = new Date().toISOString(); + // 即使停止发生在“配准完成、测量记录尚未创建”的短暂窗口, + // 也在本次链路的全部现有记录上留下结束标记,避免刷新后重新显示为排队中。 + await Promise.all( + rows + .filter((row) => { + const data = row.resultData as Record | null; + return data?.demoFlowRunId === latestRunId; + }) + .map((row) => db.patchTaskAnalysisResultData(row.id, { demoFlowCancelledAt: cancelledAt })), + ); + } return { ...result, scheduler: algorithmJobStats(),