Files
Migu2.0/frontends/apps/simple-platform-vue/src/mock/server.ts
T
黄兆尉andCursor c5d5f7a726 重构页面目录与 RBAC:扁平配置入口并对齐管理/运营页映射。
下线运营总览幽灵页,补 ExpandKeysForScope / PlatformToMonitor,同步导航与角色配置 UI。

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

177 lines
6.4 KiB
TypeScript

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'
import { mockComputeAllowedPages, mockResolveLoginScope } from './rbac'
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,
allowedPages: mockComputeAllowedPages(username, 'Platform'),
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,
allowedPages: mockComputeAllowedPages(username, 'RCSMonitor'),
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 scope = mockResolveLoginScope(req.username)
const token = `mock-jwt.${scope}.${req.username}.${Date.now()}`
// mock 无法真正拉起内核;Simple3 仅 Web,默认 WebOnly。
const runMode: LoginResponse['runMode'] =
req.launchMode === 'DesktopAndWeb' ? 'WebEnabled' : 'WebOnly'
return {
token,
user: {
id: `u-${req.username}`,
username: req.username,
displayName: req.username === 'admin' ? '系统管理员' : (req.username === 'ops' ? '运营人员' : req.username),
roles: scope === 'Platform' ? ['role-admin'] : ['role-ops']
},
scope,
effectivePermissions: buildPerm(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
}
function currentMockUsername(): string {
try {
const raw = localStorage.getItem('simple.auth.user')
const u = raw ? JSON.parse(raw) as { username?: string } : null
return u?.username?.trim() || 'mock-user'
} catch {
return 'mock-user'
}
}
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' },
{ id: 'A003', ts: nowIso(), user: 'admin', scope: 'Platform', opCode: 'ops.car.resetSession', target: 'C02', result: 'failed', message: '示例:他人记录,运营账号不可见' }
]
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: currentMockUsername(),
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)
const me = currentMockUsername().toLowerCase()
return audits.filter((a) => a.user.toLowerCase() === me)
}
function delay(ms: number) { return new Promise((r) => setTimeout(r, ms)) }