+
-
+
提示:以下已分配的车辆 ID 不在当前在册车辆中:{{ unknownCarIds.join('、') }}
@@ -85,15 +92,19 @@
diff --git a/frontends/apps/simple-platform-vue/src/composables/useClipboard.ts b/frontends/apps/simple-platform-vue/src/composables/useClipboard.ts
deleted file mode 100644
index c584d6e..0000000
--- a/frontends/apps/simple-platform-vue/src/composables/useClipboard.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { ref } from 'vue'
-import type { SelectionItem } from './useSelection'
-
-/**
- * 编辑器剪贴板:保存最近一次「复制」操作的对象快照(含字段值),
- * 用于「粘贴 (Ctrl+V)」与「复制字段 (Copy Fields)」。
- *
- * 粘贴策略:调用方在拿到目标坐标后,用 mapEditApi.batch 创建副本(带偏移)。
- * 复制字段:调用方调 mapEditApi.copyFieldsTo 把指定字段名写到目标对象(们)。
- *
- * 注意:剪贴板里保存的是对象的"逻辑快照",不是 DOM 文本剪贴板。
- */
-
-export interface ClipboardSnapshot {
- items: Array<{
- kind: string
- sourceId: number
- typeName: string
- /** 对象的几何 / 样式字段(含 x, y 用于偏移粘贴)。 */
- fields: Record
- }>
- fieldNames: string[]
-}
-
-export function useClipboard() {
- const data = ref(null)
-
- function copy(items: SelectionItem[], allFields: Record>) {
- if (items.length === 0) {
- data.value = null
- return
- }
- const fieldNamesSet = new Set()
- const snapshot: ClipboardSnapshot = {
- items: items.map((it) => {
- const f = allFields[it.id] ?? {}
- Object.keys(f).forEach((k) => fieldNamesSet.add(k))
- return {
- kind: it.kind,
- sourceId: it.id,
- typeName: it.typeName,
- fields: f
- }
- }),
- fieldNames: []
- }
- snapshot.fieldNames = [...fieldNamesSet]
- data.value = snapshot
- }
-
- function clear() {
- data.value = null
- }
-
- return { data, copy, clear }
-}
diff --git a/frontends/apps/simple-platform-vue/src/composables/useVehicleHub.ts b/frontends/apps/simple-platform-vue/src/composables/useVehicleHub.ts
index 2b123d3..eb63631 100644
--- a/frontends/apps/simple-platform-vue/src/composables/useVehicleHub.ts
+++ b/frontends/apps/simple-platform-vue/src/composables/useVehicleHub.ts
@@ -1,8 +1,9 @@
import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue'
-import { listCars } from '@/api/projection'
+import { listCars, listMissions } from '@/api/projection'
import { fetchFleetHealth } from '@/api/fleetHealth'
import { useProjectionStream } from '@/composables/useProjectionStream'
import type { Car, FleetHealthRow, VehicleCardModel } from '@/types/car'
+import type { Mission, MissionStatus } from '@/types/mission'
const CAR_POLL_MS = 5000
const HEALTH_POLL_MS = 20000
@@ -15,9 +16,41 @@ function inferMaintenanceMode(car: Car): VehicleCardModel['maintenanceMode'] {
return 'online'
}
-function mergeCarHealth(car: Car, health?: FleetHealthRow): VehicleCardModel {
+function missionStatusOrder(status: MissionStatus): number {
+ if (status === 'running') return 0
+ if (status === 'paused') return 1
+ if (status === 'assigned') return 2
+ return 3
+}
+
+function resolveMissionId(car: Car, missions: Mission[]): string | undefined {
+ if (car.missionId) {
+ const id = String(car.missionId).trim()
+ if (id && id !== '0') return id
+ }
+
+ const rawKey = car.rawId != null ? String(car.rawId) : undefined
+ let best: Mission | undefined
+
+ for (const mission of missions) {
+ if (!mission.carId) continue
+ if (mission.carId !== car.id && mission.carId !== rawKey) continue
+ if (mission.status !== 'running' && mission.status !== 'paused' && mission.status !== 'assigned') {
+ continue
+ }
+ if (!best || missionStatusOrder(mission.status) < missionStatusOrder(best.status)) {
+ best = mission
+ }
+ }
+
+ return best?.id
+}
+
+function mergeCarHealth(car: Car, health?: FleetHealthRow, missions: Mission[] = []): VehicleCardModel {
+ const missionId = resolveMissionId(car, missions)
return {
...car,
+ missionId,
ip: health?.ip ?? car.ip,
onboardUrl: health?.onboardUrl ?? car.onboardUrl ?? (car.ip ? `http://${car.ip}:8081` : undefined),
latencyMs: health?.latencyMs,
@@ -32,6 +65,7 @@ function mergeCarHealth(car: Car, health?: FleetHealthRow): VehicleCardModel {
export function useVehicleHub() {
const cars = shallowRef([])
+ const missions = shallowRef([])
const healthRows = shallowRef([])
const loading = ref(false)
const healthLoading = ref(false)
@@ -52,7 +86,7 @@ export function useVehicleHub() {
cars.value.map((car) => {
const rawId = car.rawId ?? parseInt(car.id.replace(/\D/g, ''), 10)
const health = Number.isFinite(rawId) ? healthByCarId.value.get(rawId) : undefined
- return mergeCarHealth(car, health)
+ return mergeCarHealth(car, health, missions.value)
})
)
@@ -67,7 +101,9 @@ export function useVehicleHub() {
async function loadCars() {
loading.value = true
try {
- cars.value = await listCars()
+ const [carList, missionList] = await Promise.all([listCars(), listMissions()])
+ cars.value = carList
+ missions.value = missionList
} finally {
loading.value = false
}
@@ -101,12 +137,13 @@ export function useVehicleHub() {
stream.on((evt) => {
if (evt.kind === 'car-state' && evt.payload && typeof evt.payload === 'object') {
- const p = evt.payload as { rawId?: number; id?: string; state?: string; lstatus?: string }
+ const p = evt.payload as { rawId?: number; id?: string; state?: string; lstatus?: string; missionId?: string }
const rawId = p.rawId
if (rawId != null) {
patchCarFromStream(rawId, {
state: p.state as Car['state'],
- lstatus: p.lstatus
+ lstatus: p.lstatus,
+ missionId: p.missionId
})
}
}
diff --git a/frontends/apps/simple-platform-vue/src/layouts/AppShell.vue b/frontends/apps/simple-platform-vue/src/layouts/AppShell.vue
index 7cf0cf2..26ba643 100644
--- a/frontends/apps/simple-platform-vue/src/layouts/AppShell.vue
+++ b/frontends/apps/simple-platform-vue/src/layouts/AppShell.vue
@@ -107,10 +107,8 @@
-
-
diff --git a/frontends/apps/simple-platform-vue/src/views/admin/DashboardView.vue b/frontends/apps/simple-platform-vue/src/views/admin/DashboardView.vue
index 17108d3..bc4a952 100644
--- a/frontends/apps/simple-platform-vue/src/views/admin/DashboardView.vue
+++ b/frontends/apps/simple-platform-vue/src/views/admin/DashboardView.vue
@@ -44,22 +44,44 @@
智能调度 · 一站式平台