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,163 @@
<template>
<PermissionGuard widget-id="ConfigCenter">
<el-card shadow="never">
<template #header>
<div class="cpb-header">
<span class="cpb-title">{{ title }}</span>
<el-tag size="small" effect="plain">section={{ section }}</el-tag>
<el-tag size="small" type="info" effect="plain">v{{ envelope?.version ?? '-' }}</el-tag>
<el-tag size="small" type="success" effect="plain" v-if="envelope?.updatedAt">{{ relativeTime }}</el-tag>
<div class="spacer" />
<el-button size="small" :icon="Refresh" :loading="loading" @click="reload(true)">重载</el-button>
<el-button size="small" type="primary" :icon="Check" :loading="saving" @click="save">保存</el-button>
</div>
</template>
<p v-if="description" class="cpb-desc">{{ description }}</p>
<div class="cpb-body">
<el-row :gutter="12">
<el-col :span="14">
<el-card shadow="never" body-style="padding: 12px">
<template #header>表单</template>
<slot :payload="payload" :update="update" />
</el-card>
</el-col>
<el-col :span="10">
<el-card shadow="never" body-style="padding: 0">
<template #header>JSON 预览保存即下发占位</template>
<pre class="cpb-json">{{ jsonText }}</pre>
</el-card>
</el-col>
</el-row>
</div>
</el-card>
</PermissionGuard>
</template>
<script setup lang="ts" generic="T extends object">
import { computed, onMounted, ref, watch } from 'vue'
import { Refresh, Check } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import PermissionGuard from '@/components/PermissionGuard.vue'
import { useConfigStore } from '@/stores/config'
import type { ConfigEnvelope, ConfigSection } from '@/types/config'
const props = defineProps<{
section: ConfigSection
title: string
description?: string
defaults: T
/** 加载/保存前规范化 payload(如 ops 补全 monitor 字段) */
normalizePayload?: (payload: T) => T
/** 加载完成后二次合并(如从 SimpleLite 拉 monitor */
afterLoad?: (payload: T, update: (next: T) => void) => void | Promise<void>
/** 写入 Platform 之前先执行(如先落盘 SimpleLite monitor */
beforeSave?: (payload: T) => void | Promise<void>
/** 保存成功后副作用(如再次同步 SimpleLite */
afterSave?: (payload: T) => void | Promise<void>
}>()
const store = useConfigStore()
const envelope = ref<ConfigEnvelope<T> | null>(null)
const payload = ref<T>(JSON.parse(JSON.stringify(props.defaults)) as T)
const loading = ref(false)
const saving = ref(false)
const jsonText = computed(() => JSON.stringify(payload.value, null, 2))
const relativeTime = computed(() => envelope.value?.updatedAt
? new Date(envelope.value.updatedAt).toLocaleString('zh-CN')
: '—')
function update(next: T) { payload.value = next }
function applyPayload(raw: T | undefined | null) {
let next = raw
? (JSON.parse(JSON.stringify(raw)) as T)
: (JSON.parse(JSON.stringify(props.defaults)) as T)
if (props.normalizePayload) next = props.normalizePayload(next)
payload.value = next
}
async function reloadFromServer(force: boolean, toastOnSuccess: boolean) {
loading.value = true
try {
const env = await store.load<T>(props.section, force)
envelope.value = env
applyPayload(env.payload as T)
if (props.afterLoad) await props.afterLoad(payload.value, update)
if (toastOnSuccess) ElMessage.success('已重载')
} catch (e) {
ElMessage.error(`加载失败:${e instanceof Error ? e.message : String(e)}`)
} finally {
loading.value = false
}
}
async function reload(force = false) {
await reloadFromServer(force, force)
}
async function save() {
saving.value = true
let body = payload.value
if (props.normalizePayload) body = props.normalizePayload(body)
if (props.beforeSave) {
try {
await props.beforeSave(body)
} catch (e) {
ElMessage.error(`地图监控配置保存失败:${e instanceof Error ? e.message : String(e)}`)
saving.value = false
return
}
}
let monitorSyncWarn = ''
try {
const env = await store.save<T>(props.section, body)
envelope.value = env
payload.value = body
if (props.afterSave) {
try {
await props.afterSave(body)
} catch (e) {
monitorSyncWarn = `(平台配置已保存,但 SimpleLite 同步失败:${e instanceof Error ? e.message : String(e)}`
}
}
await reloadFromServer(true, false)
const extra = props.beforeSave || props.afterSave ? ',地图监控配置已写入 SimpleLite' : ''
ElMessage.success(`已保存 v${env.version}${extra}${monitorSyncWarn}`)
} catch (e) {
ElMessage.error(`保存失败:${e instanceof Error ? e.message : String(e)}`)
} finally {
saving.value = false
}
}
watch(() => props.section, () => reload())
onMounted(() => reload())
</script>
<style scoped>
.cpb-header { display: flex; align-items: center; gap: 8px; }
.cpb-title { font-weight: 600; font-size: 15px; color: #fff; }
.cpb-header .spacer { flex: 1; }
.cpb-desc {
color: rgba(232, 215, 245, 0.7);
font-size: 12.5px;
margin: 0 0 14px;
line-height: 1.6;
}
.cpb-body { margin-top: 4px; }
.cpb-json {
margin: 0; padding: 14px;
font-size: 12px; line-height: 1.55;
font-family: ui-monospace, Menlo, Consolas, monospace;
background: rgba(15, 4, 32, 0.55);
color: #d6c5ee;
max-height: 520px;
overflow: auto;
}
</style>