From 7af29fe07f280918dc7c0ac97c15317de0976952 Mon Sep 17 00:00:00 2001 From: "zhaowei.huang" <228127304@qq.com> Date: Sun, 26 Jul 2026 14:14:19 +0800 Subject: [PATCH] fix: improve map editor and monitor operations --- .gitignore | 4 +- .../Persistence/PlatformPersistence.cs | 4 +- .../simple-platform-vue/src/api/vehicleOps.ts | 71 +- .../src/components/Workspace3D.vue | 10 +- .../assistant/AiAssistantDrawer.vue | 1351 +++++++++++++---- .../map-editor/AiAssistantPanel.vue | 170 ++- .../map-editor/EditPropertyPanel.vue | 72 +- .../components/map-editor/EditStatusBar.vue | 9 +- .../components/map-editor/EditToolRail.vue | 53 +- .../src/components/map-editor/EditTopBar.vue | 165 +- .../workbench/MonitorSelectionPanel.vue | 33 +- .../workbench/VehicleMonitorPanel.vue | 480 ++++-- .../src/composables/useEditTool.ts | 2 + .../src/composables/useMapEditStream.ts | 2 +- .../src/composables/useSimpleFieldCatalog.ts | 14 +- .../src/layouts/AppShell.vue | 35 - .../apps/simple-platform-vue/src/stores/ui.ts | 1 - .../simple-platform-vue/src/styles/theme.css | 116 ++ .../simple-platform-vue/src/styles/themes.ts | 4 +- .../apps/simple-platform-vue/src/types/car.ts | 10 + .../src/utils/carRuntime.ts | 127 +- .../src/views/admin/MapEditorView.vue | 939 ++++++++---- .../src/views/admin/MapMonitorView.vue | 72 +- 23 files changed, 2786 insertions(+), 958 deletions(-) diff --git a/.gitignore b/.gitignore index 751bd87..535cae5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ MiGu.Server/data/*.db MiGu.Server/data/*.db-shm MiGu.Server/data/*.db-wal MiGu.Server/data/.internal-token -# OTA 运行时产物:拉取/上传包、任务、历史、settings(子目录,*.json 上面那行不递归) +# OTA 运行时产物:拉取/上传包、任务、历史、settings(子目录 *.json 上面那行不递归) MiGu.Server/data/ota/ # 前端构建产物 @@ -32,7 +32,7 @@ frontends/apps/simple-platform-vue/auto-imports.d.ts *.swp Thumbs.db Desktop.ini -/.cursor/rules +/.cursor/ /MiGu.Server/wwwroot /MiGu.Server/data/platform.db diff --git a/MiGu.Server/Persistence/PlatformPersistence.cs b/MiGu.Server/Persistence/PlatformPersistence.cs index 5ef45b6..774c2f7 100644 --- a/MiGu.Server/Persistence/PlatformPersistence.cs +++ b/MiGu.Server/Persistence/PlatformPersistence.cs @@ -437,12 +437,12 @@ public static class PlatformPersistence private static async Task EnsureSqliteColumnAsync(PlatformDbContext db, string table, string column, string definition) { - // 仅允许内部迁移调用方传入的标识符;拒绝注入用分隔符/空白。 + // 仅允许内部迁移调用方传入的标识符;definition 可含 GUID 默认值连字符。 static bool IsSafeIdent(string s) => !string.IsNullOrEmpty(s) && s.All(ch => char.IsAsciiLetterOrDigit(ch) || ch == '_'); if (!IsSafeIdent(table) || !IsSafeIdent(column) || definition.Contains(';') || definition.Contains("--") - || !System.Text.RegularExpressions.Regex.IsMatch(definition, @"^[A-Za-z0-9_()'.,\s]+$")) + || !System.Text.RegularExpressions.Regex.IsMatch(definition, @"^[A-Za-z0-9_()'.,\s\-]+$")) throw new ArgumentException("unsafe sqlite migration identifier"); var conn = db.Database.GetDbConnection(); diff --git a/frontends/apps/simple-platform-vue/src/api/vehicleOps.ts b/frontends/apps/simple-platform-vue/src/api/vehicleOps.ts index a7cb79d..6ce6682 100644 --- a/frontends/apps/simple-platform-vue/src/api/vehicleOps.ts +++ b/frontends/apps/simple-platform-vue/src/api/vehicleOps.ts @@ -1,7 +1,15 @@ -import { reflectionApi } from './reflection' +import { ReflectionApiError, reflectionApi } from './reflection' export type VehicleMaintenanceMode = 'online' | 'offline' | 'repair' | 'blown' +/** 地图监控车辆列表快捷动作 */ +export type VehicleQuickAction = + | 'online' + | 'offline' + | 'endTask' + | 'disable' + | 'enable' + const METHOD_MAP: Record = { online: 'OnlineCar', offline: 'OfflineCar', @@ -9,6 +17,29 @@ const METHOD_MAP: Record = { blown: 'Blown' } +/** 结束任务:按车型依次尝试(Dummy=ForceStop,Clumsy=ForceStopUI,通用=UIIntercept) */ +const END_TASK_METHODS = ['ForceStopUI', 'ForceStop', 'UIIntercept'] as const + +const QUICK_METHOD_MAP: Record, string> = { + online: 'OnlineCar', + offline: 'OfflineCar', + disable: 'DisableCar', + enable: 'EnableCar' +} + +export const VEHICLE_QUICK_ACTIONS: { + value: VehicleQuickAction + label: string + danger?: boolean + confirm: string +}[] = [ + { value: 'online', label: '上线', confirm: '确认将车辆上线?' }, + { value: 'offline', label: '下线', confirm: '确认将车辆下线?下线后不再被任务选中。' }, + { value: 'endTask', label: '结束任务', danger: true, confirm: '确认结束该车当前任务?' }, + { value: 'disable', label: '禁用车辆', danger: true, confirm: '确认禁用该车?禁用后需先启用才能重新上线。' }, + { value: 'enable', label: '启用车辆', confirm: '确认启用该车?启用后仍需上线才能参与调度。' } +] + export async function setVehicleMaintenance( carId: number, mode: VehicleMaintenanceMode @@ -23,6 +54,44 @@ export async function setVehicleMaintenance( } } +async function tryExecuteMethods(carId: number, methods: readonly string[]): Promise { + let lastErr: unknown + for (const method of methods) { + try { + await reflectionApi.execute('car', carId, method) + return true + } catch (err) { + if (!isUnsupportedMethodError(err)) throw err + lastErr = err + } + } + if (lastErr) throw lastErr + return false +} + +function isUnsupportedMethodError(err: unknown): boolean { + if (err instanceof ReflectionApiError && (err.code === 404 || err.code === 405)) return true + const msg = err instanceof Error ? err.message : String(err) + return /method|not\s*found|unsupported|not\s*supported/i.test(msg) +} + +/** 执行车辆快捷动作;失败抛错,由调用方提示。 */ +export async function executeVehicleQuickAction( + carId: number, + action: VehicleQuickAction +): Promise { + if (!Number.isFinite(carId) || carId <= 0) { + throw new Error('无效车辆 ID') + } + if (action === 'endTask') { + const ok = await tryExecuteMethods(carId, END_TASK_METHODS) + if (!ok) throw new Error('当前车型不支持结束任务') + return + } + const method = QUICK_METHOD_MAP[action] + await reflectionApi.execute('car', carId, method) +} + export function openOnboardWeb(url?: string | null, ip?: string | null): void { const target = url ?? (ip ? `http://${ip}:8081` : null) if (!target) return diff --git a/frontends/apps/simple-platform-vue/src/components/Workspace3D.vue b/frontends/apps/simple-platform-vue/src/components/Workspace3D.vue index f9f5dae..19776ee 100644 --- a/frontends/apps/simple-platform-vue/src/components/Workspace3D.vue +++ b/frontends/apps/simple-platform-vue/src/components/Workspace3D.vue @@ -189,8 +189,14 @@ async function prepareAndLoad(forceReload = false) { const embedded = needsDeclare() if (embedded) { - // 并行:iframe 立即加载,declare 后台尽力发送(不挡首屏)。 - void postDeclareBestEffort() + // embed 必须先 declare:migu 默认是 canvas-only,迟到的 /declareEmbedUi 会被跳过。 + // canvas-only 与默认一致,可后台尽力发送不挡首屏。 + if (props.embedUi) { + await postDeclareBestEffort() + if (generation !== prepareGeneration) return + } else { + void postDeclareBestEffort() + } iframeSrc.value = buildIframeSrc(forceReload || !!iframeSrc.value) } else { loadingHint.value = '正在等待 SimpleLite webVRender 就绪 ...' diff --git a/frontends/apps/simple-platform-vue/src/components/assistant/AiAssistantDrawer.vue b/frontends/apps/simple-platform-vue/src/components/assistant/AiAssistantDrawer.vue index eef4e1b..b027dc6 100644 --- a/frontends/apps/simple-platform-vue/src/components/assistant/AiAssistantDrawer.vue +++ b/frontends/apps/simple-platform-vue/src/components/assistant/AiAssistantDrawer.vue @@ -1,22 +1,26 @@ diff --git a/frontends/apps/simple-platform-vue/src/components/map-editor/AiAssistantPanel.vue b/frontends/apps/simple-platform-vue/src/components/map-editor/AiAssistantPanel.vue index 38bce1c..434e88c 100644 --- a/frontends/apps/simple-platform-vue/src/components/map-editor/AiAssistantPanel.vue +++ b/frontends/apps/simple-platform-vue/src/components/map-editor/AiAssistantPanel.vue @@ -257,6 +257,7 @@ async function send() { diff --git a/frontends/apps/simple-platform-vue/src/components/map-editor/EditPropertyPanel.vue b/frontends/apps/simple-platform-vue/src/components/map-editor/EditPropertyPanel.vue index c2dca9c..2c6889a 100644 --- a/frontends/apps/simple-platform-vue/src/components/map-editor/EditPropertyPanel.vue +++ b/frontends/apps/simple-platform-vue/src/components/map-editor/EditPropertyPanel.vue @@ -166,7 +166,8 @@ @@ -81,7 +81,8 @@ :scope="auth.scope ?? 'Platform'" :token="auth.token ?? ''" :read-only="!!readOnly" - :canvas-only="true" + :canvas-only="false" + :embed-ui="true" :hide-toolbar="true" @pick="onPick" @select="onSelect" @@ -122,7 +123,7 @@ - 沉浸模式 · 底栏 + 沉浸 · 画布下方列表
@@ -171,10 +172,12 @@ { // 新开选中时若尚未拖过,回到默认右下角(沉浸模式抬高避开底栏) if (next && !prev && floatPos.value.left === undefined) { - floatPos.value = { - right: 14, - bottom: layoutMode.value === 'immersive' ? (sheetCollapsed.value ? 56 : 280) : 14 - } + floatPos.value = { right: 14, bottom: 14 } } }) @@ -949,7 +946,8 @@ onUnmounted(() => {