feat: 迁入 MiGu.Server、平台前端与车辆列表 reflection 回退

从 Simple-FR 拆出 Platform.Server 并重命名为 MiGu.Server;frontends 源码与构建脚本迁入本仓库。地图监控在 projection/cars 失败或为空时回退 reflection 车辆列表;Simple 仓库已移除旧 Platform.Server。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-05-29 18:16:34 +08:00
co-authored by Cursor
parent 804aa68ade
commit 42978930ca
280 changed files with 30046 additions and 8 deletions
@@ -0,0 +1,103 @@
import { onBeforeUnmount, onMounted } from 'vue'
import { useProjectionStream, type StreamEvent } from './useProjectionStream'
/**
* 编辑器 / 监控页对 SimpleLite SSE 的事件订阅封装,把以下 5 类事件转给业务回调:
*
* - `alarm` (来自 AlarmStreamService):报警新增/更新/解除
* - `object-created` (来自 MapEditApiController):对象创建后
* - `object-deleted` (来自 MapEditApiController):对象删除后
* - `object-patched` (来自 MapEditApiController):对象字段被 patch 后
* - `object-batch-changed`(来自 batch / ai 调用):一组对象变化,建议直接整页重拉
* - `pick-result` (来自 /pick):拾取会话完成
*
* 业务页只用关心自己感兴趣的事件回调,其它走默认忽略。
*/
export interface AlarmEvent {
action: 'raise' | 'update' | 'clear'
carId: number
carName: string
info?: string
x?: number
y?: number
timestamp: string
}
export interface ObjectChangeEvent {
kind: string
id: number
typeName?: string
source?: string
}
export interface PickResultEvent {
x: number
y: number
siteId: number
}
export interface SelectionDetailEvent {
/** 主选中 kind (site|track|car|null);当多选时取首个非空集合的 kind。 */
kind: 'site' | 'track' | 'car' | null
/** 主选中 id;多选时取首个。 */
id: number
siteIds: number[]
trackIds: number[]
carIds: number[]
/** 完整对象名列表,例如 ["UISite-12", "UITrack-7", "Car-3"]。 */
names: string[]
}
export interface MapEditStreamHandlers {
onAlarm?: (e: AlarmEvent) => void
onObjectCreated?: (e: ObjectChangeEvent) => void
onObjectDeleted?: (e: ObjectChangeEvent) => void
onObjectPatched?: (e: ObjectChangeEvent) => void
onObjectBatchChanged?: (e: { count: number; source?: string }) => void
onPickResult?: (e: PickResultEvent) => void
onSelectionDetail?: (e: SelectionDetailEvent) => void
}
export function useMapEditStream(handlers: MapEditStreamHandlers) {
const stream = useProjectionStream({ autoConnect: false })
function dispatch(ev: StreamEvent) {
const payload = ev?.payload as Record<string, unknown> | undefined
if (!payload) return
switch (ev.kind) {
case 'alarm':
handlers.onAlarm?.(payload as unknown as AlarmEvent)
break
case 'object-created':
handlers.onObjectCreated?.(payload as unknown as ObjectChangeEvent)
break
case 'object-deleted':
handlers.onObjectDeleted?.(payload as unknown as ObjectChangeEvent)
break
case 'object-patched':
handlers.onObjectPatched?.(payload as unknown as ObjectChangeEvent)
break
case 'object-batch-changed':
handlers.onObjectBatchChanged?.(payload as unknown as { count: number; source?: string })
break
case 'pick-result':
handlers.onPickResult?.(payload as unknown as PickResultEvent)
break
case 'selection-detail':
handlers.onSelectionDetail?.(payload as unknown as SelectionDetailEvent)
break
}
}
onMounted(() => {
stream.on(dispatch)
stream.connect()
})
onBeforeUnmount(() => {
stream.off(dispatch)
stream.disconnect()
})
return { connected: stream.connected }
}