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:
@@ -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>
|
||||
Reference in New Issue
Block a user