From 6986370e9df33eb0eb8ae0c08ab0b505deb3ab77 Mon Sep 17 00:00:00 2001 From: "zhaowei.huang" <228127304@qq.com> Date: Sat, 25 Jul 2026 18:44:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BB=BB=E5=8A=A1=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E4=B8=8E=E6=8A=A5=E8=AD=A6=E7=AE=A1=E7=90=86=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=EF=BC=8C=E5=B9=B6=E6=8E=A5=E5=85=A5=E8=BD=A6=E9=98=9F?= =?UTF-8?q?=E5=81=A5=E5=BA=B7=E6=95=B0=E6=8D=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 运营菜单下挂地图监控/任务/报警入口,车辆卡片与任务列表同步展示运行态。 Co-authored-by: Cursor --- MiGu.Server/Controllers/HealthController.cs | 20 + .../apps/simple-platform-vue/src/api/alarm.ts | 18 + .../simple-platform-vue/src/api/delivery.ts | 73 +- .../src/api/fleetHealth.ts | 8 + .../simple-platform-vue/src/api/health.ts | 58 ++ .../src/components/PlaybackProgressBar.vue | 161 ++++ .../components/workbench/MissionListPanel.vue | 26 +- .../workbench/VehicleMonitorPanel.vue | 276 ++++++- .../src/composables/useVehicleCardState.ts | 6 +- .../simple-platform-vue/src/config/navMenu.ts | 56 +- .../src/config/quickEntries.ts | 8 +- .../src/mock/data/alarms.ts | 27 + .../src/mock/data/deliveries.ts | 103 ++- .../apps/simple-platform-vue/src/mock/rbac.ts | 2 + .../simple-platform-vue/src/router/index.ts | 12 +- .../simple-platform-vue/src/types/alarm.ts | 24 + .../apps/simple-platform-vue/src/types/car.ts | 4 +- .../simple-platform-vue/src/types/delivery.ts | 25 +- .../src/utils/batterySoc.ts | 58 ++ .../src/utils/carRuntime.ts | 82 ++ .../simple-platform-vue/src/utils/dateTime.ts | 43 ++ .../src/views/admin/AlarmManagementView.vue | 373 +++++++++ .../src/views/admin/TaskManagementView.vue | 712 ++++++++++++++++++ 23 files changed, 2097 insertions(+), 78 deletions(-) create mode 100644 frontends/apps/simple-platform-vue/src/api/alarm.ts create mode 100644 frontends/apps/simple-platform-vue/src/api/health.ts create mode 100644 frontends/apps/simple-platform-vue/src/components/PlaybackProgressBar.vue create mode 100644 frontends/apps/simple-platform-vue/src/mock/data/alarms.ts create mode 100644 frontends/apps/simple-platform-vue/src/types/alarm.ts create mode 100644 frontends/apps/simple-platform-vue/src/utils/batterySoc.ts create mode 100644 frontends/apps/simple-platform-vue/src/utils/carRuntime.ts create mode 100644 frontends/apps/simple-platform-vue/src/utils/dateTime.ts create mode 100644 frontends/apps/simple-platform-vue/src/views/admin/AlarmManagementView.vue create mode 100644 frontends/apps/simple-platform-vue/src/views/admin/TaskManagementView.vue diff --git a/MiGu.Server/Controllers/HealthController.cs b/MiGu.Server/Controllers/HealthController.cs index 425b0ef..de256c5 100644 --- a/MiGu.Server/Controllers/HealthController.cs +++ b/MiGu.Server/Controllers/HealthController.cs @@ -34,6 +34,26 @@ public class HealthController : ControllerBase [Authorize] public IActionResult GetSimpleLiteDiagnostics() => Ok(_launcher.GetDiagnostics()); + /// 关闭本机全部 SimpleLite 进程。仅 Platform 管理端可调。 + [HttpPost("simplelite/stop")] + [Authorize(Policy = "PlatformScope")] + public IActionResult StopSimpleLite() + { + var killed = _launcher.StopAll(); + var diag = _launcher.GetDiagnostics(); + return Ok(new { killed, diagnostics = diag }); + } + + /// 关闭并重新拉起 SimpleLite(不同步 DLL)。仅 Platform 管理端可调。 + [HttpPost("simplelite/restart")] + [Authorize(Policy = "PlatformScope")] + public IActionResult RestartSimpleLite([FromQuery] string launchMode = "webonly") + { + var result = _launcher.Restart(launchMode); + var diag = _launcher.GetDiagnostics(); + return Ok(new { restart = result, diagnostics = diag }); + } + /// /// 关闭 SimpleLite、同步最新 DLL、重新拉起。用于「前往站点」API 缺失时一键更新。 /// 会终止本机全部 SimpleLite 进程并重启,仅 Platform 管理端可调。 diff --git a/frontends/apps/simple-platform-vue/src/api/alarm.ts b/frontends/apps/simple-platform-vue/src/api/alarm.ts new file mode 100644 index 0000000..564bf06 --- /dev/null +++ b/frontends/apps/simple-platform-vue/src/api/alarm.ts @@ -0,0 +1,18 @@ +import http from './http' +import type { AlarmFeed } from '@/types/alarm' + +const MOCK = import.meta.env.VITE_USE_MOCK === 'true' + +/** 报警管理数据源:平台记录库 /fleet/alarms(离线可读 + 历史保留)。 */ +export async function fetchAlarmFeed(limit = 2000): Promise { + if (MOCK) { + const { mockAlarms } = await import('@/mock/data/alarms') + return { online: true, lastSyncAt: new Date().toISOString(), alarms: await mockAlarms() } + } + const { data } = await http.get('/fleet/alarms', { params: { limit } }) + return { + online: !!data?.online, + lastSyncAt: data?.lastSyncAt ?? null, + alarms: Array.isArray(data?.alarms) ? data.alarms : [] + } +} diff --git a/frontends/apps/simple-platform-vue/src/api/delivery.ts b/frontends/apps/simple-platform-vue/src/api/delivery.ts index be3da13..5aa37a2 100644 --- a/frontends/apps/simple-platform-vue/src/api/delivery.ts +++ b/frontends/apps/simple-platform-vue/src/api/delivery.ts @@ -1,5 +1,5 @@ import http from './http' -import type { DeliveryTask } from '@/types/delivery' +import type { CreateDeliveryPayload, DeliveryTask } from '@/types/delivery' const MOCK = import.meta.env.VITE_USE_MOCK === 'true' @@ -20,17 +20,72 @@ export async function listDeliveries(opts?: { return Array.isArray(data) ? data : [] } -export async function cancelDelivery(id: number): Promise { - if (MOCK) return - await http.post(`${BASE}/${id}/cancel`) +/** CDM 任务快照订阅结果:来自平台库 cdm_tasks(SimpleLite 关闭时仍可读,含完整历史)。 */ +export interface CdmTaskFeed { + online: boolean + lastSyncAt: string | null + tasks: DeliveryTask[] } -export async function resendDelivery(id: number): Promise { - if (MOCK) return - await http.post(`${BASE}/${id}/resend`) +/** + * 任务页数据源:优先读平台快照库 /fleet/tasks(离线可读 + 历史保留); + * 若平台端点不可用则回退到实时投影 /sl/projection/deliveries。 + */ +export async function fetchCdmTaskFeed(limit = 1000): Promise { + if (MOCK) { + const { mockDeliveries } = await import('@/mock/data/deliveries') + return { online: true, lastSyncAt: new Date().toISOString(), tasks: await mockDeliveries() } + } + try { + const { data } = await http.get('/fleet/tasks', { params: { limit } }) + return { + online: !!data?.online, + lastSyncAt: data?.lastSyncAt ?? null, + tasks: Array.isArray(data?.tasks) ? data.tasks : [] + } + } catch { + const tasks = await listDeliveries({ includeFinished: true, includeAborted: true }) + return { online: true, lastSyncAt: new Date().toISOString(), tasks } + } } -export async function forceCompleteDelivery(id: number): Promise { +export async function cancelDelivery(id: string): Promise { if (MOCK) return - await http.post(`${BASE}/${id}/force-complete`) + await http.post(`${BASE}/${encodeURIComponent(id)}/cancel`) +} + +export async function resendDelivery(id: string): Promise { + if (MOCK) return + await http.post(`${BASE}/${encodeURIComponent(id)}/resend`) +} + +export async function forceCompleteDelivery(id: string): Promise { + if (MOCK) return + await http.post(`${BASE}/${encodeURIComponent(id)}/force-complete`) +} + +export async function pauseDelivery(id: string): Promise { + if (MOCK) return + await http.post(`${BASE}/${encodeURIComponent(id)}/pause`) +} + +export async function resumeDelivery(id: string): Promise { + if (MOCK) return + await http.post(`${BASE}/${encodeURIComponent(id)}/resume`) +} + +export async function changeCarDelivery(id: string): Promise { + if (MOCK) return + await http.post(`${BASE}/${encodeURIComponent(id)}/change-car`) +} + +export async function setDeliveryPriority(id: string, value: number): Promise { + if (MOCK) return + await http.post(`${BASE}/${encodeURIComponent(id)}/priority`, { value }) +} + +export async function createDelivery(payload: CreateDeliveryPayload): Promise<{ id: string }> { + if (MOCK) return { id: `MOCK-${Date.now()}` } + const { data } = await http.post<{ success: boolean; id: string }>(BASE, payload) + return { id: data?.id ?? '' } } diff --git a/frontends/apps/simple-platform-vue/src/api/fleetHealth.ts b/frontends/apps/simple-platform-vue/src/api/fleetHealth.ts index 7488b73..5a052f4 100644 --- a/frontends/apps/simple-platform-vue/src/api/fleetHealth.ts +++ b/frontends/apps/simple-platform-vue/src/api/fleetHealth.ts @@ -32,6 +32,14 @@ export async function fetchFleetHealth(): Promise { await new Promise((r) => setTimeout(r, 120)) return mockFleetHealth() } + // 平台侧聚合:SimpleLite 指标 + WatchDog(:9776) TCP RTT。 + // 不再直打 /sl/projection/fleet/health(其探测车载 :8081,现场多数未开导致假超时 2000ms)。 + try { + const { data } = await http.get('/fleet/health') + if (Array.isArray(data) && data.length > 0) return data + } catch { + /* fall through */ + } const { data } = await http.get('/sl/projection/fleet/health') return Array.isArray(data) ? data : [] } diff --git a/frontends/apps/simple-platform-vue/src/api/health.ts b/frontends/apps/simple-platform-vue/src/api/health.ts new file mode 100644 index 0000000..7ab1b88 --- /dev/null +++ b/frontends/apps/simple-platform-vue/src/api/health.ts @@ -0,0 +1,58 @@ +import http from '@/api/http' +import type { LaunchMode, RunMode } from '@/types/auth' + +export interface HealthInfo { + status: string + startTime: string + uptimeSec: number +} + +export interface SimpleLiteDiagnostics { + enabled: boolean + isRunning: boolean + lastLaunchMode?: string | null + projectionPort: number + projectionPortReachable: boolean + gotoSiteApiAvailable?: boolean | null + executableExists: boolean + deployHint?: string | null +} + +export interface SimpleLiteLaunchResult { + started: boolean + status: string + detail: string + displayMode?: string | null + warning?: string | null +} + +export function getHealth() { + return http.get('/health') +} + +export function getSimpleLiteDiagnostics() { + return http.get('/health/simplelite') +} + +export function stopSimpleLite() { + return http.post<{ killed: number; diagnostics: SimpleLiteDiagnostics }>('/health/simplelite/stop') +} + +export function restartSimpleLite(launchMode: LaunchMode) { + return http.post<{ restart: SimpleLiteLaunchResult; diagnostics: SimpleLiteDiagnostics }>( + '/health/simplelite/restart', + null, + { params: { launchMode }, timeout: 60_000 } + ) +} + +/** 从诊断/会话 runMode 推断重启时使用的 launchMode。 */ +export function resolveRestartLaunchMode( + lastLaunchMode: string | null | undefined, + runMode: RunMode | null | undefined +): LaunchMode { + const mode = (lastLaunchMode ?? '').toLowerCase() + if (mode === 'web') return 'WebOnly' + if (mode === 'web+local') return 'DesktopAndWeb' + return runMode === 'WebOnly' ? 'WebOnly' : 'DesktopAndWeb' +} diff --git a/frontends/apps/simple-platform-vue/src/components/PlaybackProgressBar.vue b/frontends/apps/simple-platform-vue/src/components/PlaybackProgressBar.vue new file mode 100644 index 0000000..9cd60d7 --- /dev/null +++ b/frontends/apps/simple-platform-vue/src/components/PlaybackProgressBar.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/frontends/apps/simple-platform-vue/src/components/workbench/MissionListPanel.vue b/frontends/apps/simple-platform-vue/src/components/workbench/MissionListPanel.vue index 330e391..d888cca 100644 --- a/frontends/apps/simple-platform-vue/src/components/workbench/MissionListPanel.vue +++ b/frontends/apps/simple-platform-vue/src/components/workbench/MissionListPanel.vue @@ -28,7 +28,7 @@ @row-click="onRowClick" @row-contextmenu="onRowContextMenu" > - +