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,158 @@
<template>
<section class="mmcg">
<header class="mmcg-head">
<span class="mmcg-title">{{ title }}</span>
<el-tag size="small" effect="plain" type="info">{{ summaryText }}</el-tag>
<div class="spacer" />
<el-button-group v-if="available.length">
<el-button size="small" :disabled="!canSelectAll" @click="selectAll">全选</el-button>
<el-button size="small" :disabled="!selected.length" @click="clearAll">清空</el-button>
</el-button-group>
</header>
<p v-if="!available.length" class="mmcg-empty muted">{{ emptyTip }}</p>
<el-checkbox-group
v-else
:model-value="selected"
class="mmcg-grid"
@update:model-value="onChange">
<el-checkbox v-for="key in available" :key="key" :value="key" :label="key" class="mmcg-cb" />
</el-checkbox-group>
<!-- 处理"残留键"已勾选但不再出现在 available 后端实现变了 / 插件卸载了-->
<div v-if="orphanKeys.length" class="mmcg-orphans">
<el-alert
type="warning"
:closable="false"
show-icon
:title="`检测到 ${orphanKeys.length} 个残留键(后端已经不再暴露,但仍在白名单中)`">
<template #default>
<div class="orphan-list">
<el-tag
v-for="key in orphanKeys"
:key="key"
type="warning"
size="small"
closable
@close="removeOrphan(key)">
{{ key }}
</el-tag>
<el-button link type="warning" size="small" @click="clearOrphans">一键清理全部残留</el-button>
</div>
</template>
</el-alert>
</div>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { CheckboxValueType } from 'element-plus'
const props = defineProps<{
title: string
available: string[]
selected: string[]
emptyTip?: string
}>()
const emit = defineEmits<{
(e: 'update:selected', value: string[]): void
}>()
const summaryText = computed(() => {
if (!props.available.length) return '空'
if (!props.selected.length) return `白名单空 = 显示全部 (${props.available.length})`
return `${props.selected.length} / ${props.available.length}`
})
// available 是后端最新扫描结果。selected 里可能有 "残留"——之前勾选过但后端现在不再返回(插件
// 卸载 / 类型重命名)。这些键不会出现在 checkbox-group 里(避免渲染陌生项),但要单独列出来
// 让用户决定是否清理;不主动删,避免误清"暂时离线的插件键"。
const orphanKeys = computed(() =>
props.selected.filter((k) => !props.available.includes(k))
)
const canSelectAll = computed(() => props.available.length > props.selected.length)
function onChange(v: CheckboxValueType[]) {
// element-plus 在泛型推断下会拿到 CheckboxValueTypestring | number | boolean)。我们这里键
// 全是 string,强制断言;同时把可能残留的 orphan 键并回去,避免「在某 kind 全选」操作把
// orphan 默默丢掉(用户应该走"清理残留"显式确认)。
const next = (v as string[]).filter((x) => typeof x === 'string')
const merged = [...next, ...orphanKeys.value]
emit('update:selected', merged)
}
function selectAll() {
emit('update:selected', [...props.available, ...orphanKeys.value])
}
function clearAll() {
// 仅清空 available 内的;orphan 保留(防误删,要走"残留清理"流程)
emit('update:selected', [...orphanKeys.value])
}
function removeOrphan(key: string) {
emit('update:selected', props.selected.filter((k) => k !== key))
}
function clearOrphans() {
emit('update:selected', props.selected.filter((k) => props.available.includes(k)))
}
</script>
<style scoped>
.mmcg {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 14px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
}
.mmcg-head {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.mmcg-title {
font-weight: 600;
font-size: 13px;
color: #fff;
}
.mmcg-head .spacer { flex: 1; }
.mmcg-empty {
margin: 4px 0 0;
font-size: 12px;
font-style: italic;
}
.muted { color: rgba(255, 255, 255, 0.62); }
.mmcg-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 4px 12px;
align-items: center;
}
.mmcg-grid :deep(.el-checkbox) {
margin-right: 0;
height: 28px;
}
.mmcg-grid :deep(.el-checkbox__label) {
color: rgba(255, 255, 255, 0.92);
font-size: 12.5px;
font-family: ui-monospace, Menlo, Consolas, monospace;
}
.mmcg-orphans { margin-top: 4px; }
.orphan-list {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
}
</style>