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,149 @@
|
||||
import type { LoginRequest, LoginResponse, EffectivePermissions } from '@/types/auth'
|
||||
import type { Site, Track } from '@/types/map'
|
||||
import type { Car } from '@/types/car'
|
||||
import type { Mission } from '@/types/mission'
|
||||
import type { OpsAuditEntry } from '@/types/ops'
|
||||
import type { ConfigEnvelope, ConfigSection } from '@/types/config'
|
||||
import { SITES } from './data/sites'
|
||||
import { TRACKS } from './data/tracks'
|
||||
import { CARS } from './data/cars'
|
||||
import { MISSIONS } from './data/missions'
|
||||
import { CONFIG_DEFAULTS } from './data/configs'
|
||||
|
||||
const PLATFORM_OPS = ['*']
|
||||
|
||||
const RCS_OPS = [
|
||||
'ops.car.pause', 'ops.car.resume', 'ops.car.gohome', 'ops.car.resetSession',
|
||||
'ops.car.manualCharge', 'ops.task.pause', 'ops.task.cancel', 'ops.task.reassign',
|
||||
'ops.task.boostPriority', 'monitor.note.write'
|
||||
]
|
||||
|
||||
function nowIso() { return new Date().toISOString() }
|
||||
|
||||
function buildPerm(scope: 'Platform' | 'RCSMonitor', username: string): EffectivePermissions {
|
||||
if (scope === 'Platform') {
|
||||
return {
|
||||
userId: `u-${username}`,
|
||||
version: 1,
|
||||
allowedOps: PLATFORM_OPS,
|
||||
visibleWidgets: [
|
||||
{ widgetId: 'MapEditor', visibility: 'interactive' },
|
||||
{ widgetId: 'CadToolbar', visibility: 'interactive' },
|
||||
{ widgetId: 'CarPanel', visibility: 'interactive' },
|
||||
{ widgetId: 'MissionEditor', visibility: 'interactive' },
|
||||
{ widgetId: 'OpsActionPanel', visibility: 'interactive' },
|
||||
{ widgetId: 'ConfigCenter', visibility: 'interactive' }
|
||||
]
|
||||
}
|
||||
}
|
||||
return {
|
||||
userId: `u-${username}`,
|
||||
version: 1,
|
||||
allowedOps: RCS_OPS,
|
||||
visibleWidgets: [
|
||||
{ widgetId: 'MapEditor', visibility: 'readonly' },
|
||||
{ widgetId: 'CadToolbar', visibility: 'hidden' },
|
||||
{ widgetId: 'CarPanel', visibility: 'readonly' },
|
||||
{ widgetId: 'MissionEditor', visibility: 'readonly' },
|
||||
{ widgetId: 'OpsActionPanel', visibility: 'interactive' },
|
||||
{ widgetId: 'ConfigCenter', visibility: 'hidden' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export async function mockLogin(req: LoginRequest): Promise<LoginResponse> {
|
||||
await delay(150)
|
||||
if (!req.username) throw new Error('用户名不能为空')
|
||||
const token = `mock-jwt.${req.scope}.${req.username}.${Date.now()}`
|
||||
// mock 服务器无法真正拉起 SimpleLite,只把 LaunchMode 翻译为 RunMode 让前端 UI 自洽。
|
||||
// - 历史前端不传 launchMode → 退到 DesktopAndWeb → runMode=WebEnabled(与改造前一致)。
|
||||
// - launchMode=WebOnly → runMode=WebOnly。
|
||||
const runMode: LoginResponse['runMode'] = req.launchMode === 'WebOnly' ? 'WebOnly' : 'WebEnabled'
|
||||
return {
|
||||
token,
|
||||
user: {
|
||||
id: `u-${req.username}`,
|
||||
username: req.username,
|
||||
displayName: req.username === 'admin' ? '系统管理员' : (req.username === 'ops' ? '运营人员' : req.username),
|
||||
roles: req.scope === 'Platform' ? ['role-admin'] : ['role-ops']
|
||||
},
|
||||
scope: req.scope,
|
||||
effectivePermissions: buildPerm(req.scope, req.username),
|
||||
runMode
|
||||
}
|
||||
}
|
||||
|
||||
export async function mockSites(): Promise<Site[]> { await delay(80); return [...SITES] }
|
||||
export async function mockTracks(): Promise<Track[]> { await delay(80); return [...TRACKS] }
|
||||
export async function mockCars(): Promise<Car[]> { await delay(80); return [...CARS] }
|
||||
export async function mockMissions(): Promise<Mission[]> { await delay(80); return [...MISSIONS] }
|
||||
|
||||
const MOCK_CONFIG_STORAGE_KEY = 'simple-platform-mock-config'
|
||||
|
||||
const memConfig = new Map<ConfigSection, ConfigEnvelope>()
|
||||
|
||||
function loadPersistedConfig(): Partial<Record<ConfigSection, ConfigEnvelope>> {
|
||||
try {
|
||||
const raw = localStorage.getItem(MOCK_CONFIG_STORAGE_KEY)
|
||||
if (raw) return JSON.parse(raw) as Partial<Record<ConfigSection, ConfigEnvelope>>
|
||||
} catch { /* ignore */ }
|
||||
return {}
|
||||
}
|
||||
|
||||
function persistAllConfig() {
|
||||
try {
|
||||
const obj: Partial<Record<ConfigSection, ConfigEnvelope>> = {}
|
||||
for (const [k, v] of memConfig.entries()) obj[k] = v
|
||||
localStorage.setItem(MOCK_CONFIG_STORAGE_KEY, JSON.stringify(obj))
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function ensureSection(section: ConfigSection): ConfigEnvelope {
|
||||
let cur = memConfig.get(section)
|
||||
if (!cur) {
|
||||
const persisted = loadPersistedConfig()[section]
|
||||
cur = persisted ?? {
|
||||
section,
|
||||
version: 1,
|
||||
updatedAt: nowIso(),
|
||||
payload: CONFIG_DEFAULTS[section]
|
||||
}
|
||||
memConfig.set(section, cur)
|
||||
}
|
||||
return cur
|
||||
}
|
||||
|
||||
// 启动时从 localStorage 恢复,避免刷新丢配置
|
||||
for (const section of Object.keys(CONFIG_DEFAULTS) as ConfigSection[]) {
|
||||
ensureSection(section)
|
||||
}
|
||||
|
||||
export async function mockGetConfig<T>(section: ConfigSection): Promise<ConfigEnvelope<T>> {
|
||||
await delay(60)
|
||||
return ensureSection(section) as ConfigEnvelope<T>
|
||||
}
|
||||
|
||||
export async function mockPutConfig<T>(section: ConfigSection, payload: T): Promise<ConfigEnvelope<T>> {
|
||||
await delay(80)
|
||||
const cur = ensureSection(section)
|
||||
const next: ConfigEnvelope<T> = { section, version: cur.version + 1, updatedAt: nowIso(), payload }
|
||||
memConfig.set(section, next as ConfigEnvelope)
|
||||
persistAllConfig()
|
||||
return next
|
||||
}
|
||||
|
||||
const audits: OpsAuditEntry[] = [
|
||||
{ id: 'A001', ts: nowIso(), user: 'ops', scope: 'RCSMonitor', opCode: 'ops.car.pause', target: 'C01', result: 'ok' },
|
||||
{ id: 'A002', ts: nowIso(), user: 'ops', scope: 'RCSMonitor', opCode: 'ops.task.cancel', target: 'M03', result: 'ok' }
|
||||
]
|
||||
|
||||
export async function mockOpsExecute(req: { opCode: string; targetId: string; reason?: string; idempotencyKey?: string }) {
|
||||
await delay(120)
|
||||
const id = `A${String(audits.length + 1).padStart(3, '0')}`
|
||||
audits.unshift({ id, ts: nowIso(), user: 'mock-user', scope: 'mock', opCode: req.opCode, target: req.targetId, result: 'ok', message: req.reason })
|
||||
return { ok: true, auditId: id }
|
||||
}
|
||||
|
||||
export async function mockOpsAudits(): Promise<OpsAuditEntry[]> { await delay(60); return [...audits] }
|
||||
|
||||
function delay(ms: number) { return new Promise((r) => setTimeout(r, ms)) }
|
||||
Reference in New Issue
Block a user