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 { 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 { await delay(80); return [...SITES] } export async function mockTracks(): Promise { await delay(80); return [...TRACKS] } export async function mockCars(): Promise { await delay(80); return [...CARS] } export async function mockMissions(): Promise { await delay(80); return [...MISSIONS] } const MOCK_CONFIG_STORAGE_KEY = 'simple-platform-mock-config' const memConfig = new Map() function loadPersistedConfig(): Partial> { try { const raw = localStorage.getItem(MOCK_CONFIG_STORAGE_KEY) if (raw) return JSON.parse(raw) as Partial> } catch { /* ignore */ } return {} } function persistAllConfig() { try { const obj: Partial> = {} 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(section: ConfigSection): Promise> { await delay(60) return ensureSection(section) as ConfigEnvelope } export async function mockPutConfig(section: ConfigSection, payload: T): Promise> { await delay(80) const cur = ensureSection(section) const next: ConfigEnvelope = { 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 { 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)) }