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,93 @@
import { ElMessage, ElMessageBox } from 'element-plus'
import { mapEditApi } from '@/api/mapEdit'
import { reflectionApi, type ReflectionKind, type ReflectionMethod } from '@/api/reflection'
/** 需在 Web 端先选站点再执行的动作(后端方法内部依赖 SimpleUI.GetPointiframe 下不可靠)。 */
const SITE_PICK_METHOD_NAMES = new Set(['Goto', 'GotoSite'])
const SITE_PICK_LABEL_HINTS = ['去某地', '前往站点']
export function methodNeedsSitePick(m: ReflectionMethod): boolean {
if (m.params?.length) return false
if (SITE_PICK_METHOD_NAMES.has(m.methodName)) return true
const label = m.label ?? ''
return SITE_PICK_LABEL_HINTS.some((h) => label.includes(h))
}
export interface SitePickOption {
id: number
name: string
label: string
}
export async function loadSitePickOptions(): Promise<SitePickOption[]> {
const rows = await reflectionApi.listObjects('site')
return rows
.map((s) => ({
id: s.id,
name: s.name,
label: `#${s.id} ${s.name || '站点'}`
}))
.sort((a, b) => a.id - b.id)
}
/** 在地图上拾取目标站点(调用 SimpleLite pick 会话)。 */
export async function pickTargetSiteOnMap(): Promise<number | null> {
try {
ElMessage.info({ message: '请在 3D 地图上点击目标站点附近', duration: 4000 })
const r = await mapEditApi.pick()
if (r.siteId == null || r.siteId < 0) {
ElMessage.warning('未识别到附近站点,请靠近站点再点击')
return null
}
return r.siteId
} catch (e) {
if (e instanceof Error && /取消|cancel|499/i.test(e.message)) return null
ElMessage.error(`地图拾取失败:${e instanceof Error ? e.message : String(e)}`)
return null
}
}
export async function executeCarGotoSite(carId: number, siteId: number): Promise<boolean> {
try {
const r = await reflectionApi.gotoCarSite(carId, siteId)
ElMessage.success(r.message || `已下发前往站点 #${siteId}`)
return true
} catch (e) {
ElMessage.error(`前往站点失败:${e instanceof Error ? e.message : String(e)}`)
return false
}
}
export async function executeReflectionMethod(
kind: ReflectionKind,
objectId: number,
m: ReflectionMethod
): Promise<boolean> {
const label = m.label || m.methodName
const params: Record<string, string> = {}
for (const p of m.params) {
try {
const { value } = await ElMessageBox.prompt(
`参数 ${p.name}${p.typeName}`,
label,
{
inputValue: p.defaultValue ?? '',
confirmButtonText: '确定',
cancelButtonText: '取消'
}
)
params[p.name] = value ?? ''
} catch {
return false
}
}
try {
const r = await reflectionApi.execute(kind, objectId, m.methodName, params)
ElMessage.success(r.returnValue ? `已执行:${r.returnValue}` : `已执行 ${label}`)
return true
} catch (e) {
ElMessage.error(`执行失败:${e instanceof Error ? e.message : String(e)}`)
return false
}
}
@@ -0,0 +1,96 @@
import { reflectionApi, emptyMonitorVisibilityMap, type MonitorVisibilityMap } from '@/api/reflection'
import { DEFAULT_OPS } from '@/mock/data/configs'
import type { MonitorOpsPolicy, OpsConfig } from '@/types/config'
export function defaultMonitorPolicy(): MonitorOpsPolicy {
return JSON.parse(JSON.stringify(DEFAULT_OPS.monitor)) as MonitorOpsPolicy
}
export function normalizeOpsConfig(raw?: Partial<OpsConfig> | null): OpsConfig {
const base = JSON.parse(JSON.stringify(DEFAULT_OPS)) as OpsConfig
if (!raw) return base
const monitor = {
...defaultMonitorPolicy(),
...(raw.monitor ?? {}),
car: { ...defaultMonitorPolicy().car, ...(raw.monitor?.car ?? {}) },
site: { ...defaultMonitorPolicy().site, ...(raw.monitor?.site ?? {}) },
track: { ...defaultMonitorPolicy().track, ...(raw.monitor?.track ?? {}) },
carActionByType: { ...(raw.monitor?.carActionByType ?? {}) }
}
return {
...base,
...raw,
playback: { ...base.playback, ...(raw.playback ?? {}) },
logRetention: { ...base.logRetention, ...(raw.logRetention ?? {}) },
version: { ...base.version, ...(raw.version ?? {}) },
monitor
}
}
type MonitorConfigRoot = MonitorVisibilityMap & { carActionByType?: Record<string, string[]> }
function pickMonitorRoot(cfg: MonitorConfigRoot) {
return {
carActionByType: { ...(cfg.carActionByType ?? {}) },
siteActionKeys: [...(cfg.site?.methods ?? [])],
trackActionKeys: [...(cfg.track?.methods ?? [])]
}
}
/**
* 从 SimpleLite simple.json 加载地图监控配置(运行时唯一可信源)。
* Platform ops 里的 monitor 仅作备份;以 SimpleLite 为准覆盖表单。
*/
export async function loadMonitorSettingsIntoOps(payload: OpsConfig): Promise<OpsConfig> {
const next = normalizeOpsConfig(payload)
try {
const mc = await reflectionApi.getMonitorConfig()
const picked = pickMonitorRoot(mc.config as MonitorConfigRoot)
next.monitor.carActionByType = picked.carActionByType
next.monitor.site.actionKeys = picked.siteActionKeys
next.monitor.track.actionKeys = picked.trackActionKeys
} catch {
// SimpleLite 未连接:退回 Platform payload 中已有的 monitor(若有)
}
return next
}
/**
* 将运营维护表单中的地图监控配置写入 SimpleLitesimple.json)。
* 保留「地图监控配置」页面对 fields/status 的既有设置,只更新动作相关字段。
*/
export async function saveMonitorSettingsFromOps(payload: OpsConfig): Promise<void> {
const monitor = normalizeOpsConfig(payload).monitor
let current: MonitorConfigRoot = emptyMonitorVisibilityMap()
try {
const mc = await reflectionApi.getMonitorConfig()
current = mc.config as MonitorConfigRoot
} catch {
// 无现有配置时用空壳,仍可写入动作白名单
}
await reflectionApi.saveMonitorConfig({
car: {
fields: [...(current.car?.fields ?? [])],
status: [...(current.car?.status ?? [])],
methods: [...(current.car?.methods ?? [])]
},
site: {
fields: [...(current.site?.fields ?? [])],
status: [...(current.site?.status ?? [])],
methods: [...monitor.site.actionKeys]
},
track: {
fields: [...(current.track?.fields ?? [])],
status: [...(current.track?.status ?? [])],
methods: [...monitor.track.actionKeys]
},
carActionByType: { ...monitor.carActionByType }
})
}
/** @deprecated 使用 loadMonitorSettingsIntoOps */
export const mergeMonitorFromSimpleLite = loadMonitorSettingsIntoOps
/** @deprecated 使用 saveMonitorSettingsFromOps */
export const syncOpsMonitorToSimpleLite = saveMonitorSettingsFromOps