Files
Migu2.0/frontends/apps/simple-platform-vue/src/components/ConfigPageBase.vue
T
黄兆尉andCursor 3686abdc78 将调度内核标识从 SimpleLite 全面重命名为 Simple3。
配置段/环境变量、Launcher、健康检查 API、OpenAPI 与前后端文案同步;兼容探测旧 SimpleLite 进程名。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 17:46:52 +08:00

179 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<PermissionGuard widget-id="ConfigCenter">
<slot
v-if="$slots.chrome"
name="chrome"
:payload="payload"
:update="update"
:save="save"
:reload="reload"
:saving="saving"
:loading="loading"
:envelope="envelope"
:relative-time="relativeTime"
/>
<el-card v-else 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
/** 加载完成后二次合并(如从 Simple3 拉 monitor */
afterLoad?: (payload: T, update: (next: T) => void) => void | Promise<void>
/** 写入 Platform 之前先执行(如先落盘 Simple3 monitor */
beforeSave?: (payload: T) => void | Promise<void>
/** 保存成功后副作用(如再次同步 Simple3) */
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(opts?: { toast?: boolean }): Promise<boolean> {
const showToast = typeof opts === 'object' && opts && 'toast' in opts ? opts.toast !== false : true
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 false
}
}
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 = `(平台配置已保存,但 Simple3 同步失败:${e instanceof Error ? e.message : String(e)}`
}
}
await reloadFromServer(true, false)
const extra = props.beforeSave || props.afterSave ? ',地图监控配置已写入 Simple3' : ''
if (showToast) ElMessage.success(`已保存 v${env.version}${extra}${monitorSyncWarn}`)
return true
} catch (e) {
ElMessage.error(`保存失败:${e instanceof Error ? e.message : String(e)}`)
return false
} 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: var(--mg-text-light); }
.cpb-header .spacer { flex: 1; }
.cpb-desc {
color: var(--mg-text-muted);
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: var(--mg-veil-2);
color: var(--mg-text-light);
max-height: 520px;
overflow: auto;
}
</style>