Merge remote-tracking branch 'origin/main'

This commit is contained in:
黄兆尉
2026-08-26 13:56:20 +08:00
57 changed files with 5028 additions and 76 deletions
@@ -0,0 +1,98 @@
import { listCars, listSites, listTracks } from '@/api/projection'
import { reflectionApi } from '@/api/reflection'
import { getWizardProfile } from '@/api/wizard'
import type { SetupCarParamRow, SetupStatus } from '@/types/setup'
const IP_KEYS = ['address', 'ip']
const PORT_KEYS = ['port', 'magport']
function pick(rows: Array<{ key: string; value: string }>, keys: string[]): string {
const set = new Set(keys)
const hit = rows.find((r) => set.has(r.key.toLowerCase()))
return (hit?.value ?? '').trim()
}
function ipOk(v: string): boolean {
return v.length > 0 && v !== '0.0.0.0'
}
function portOk(v: string): boolean {
const n = Number(v)
return Number.isFinite(n) && n > 0 && n <= 65535
}
async function inspectCarParams(rawId: number, name: string): Promise<SetupCarParamRow> {
try {
const fields = await reflectionApi.getFields('car', rawId)
const address = pick(fields, IP_KEYS)
const port = pick(fields, PORT_KEYS)
return { id: rawId, name, address, port, paramsReady: ipOk(address) && portOk(port) }
} catch {
return { id: rawId, name, address: '', port: '', paramsReady: false }
}
}
export async function loadSetupStatus(): Promise<SetupStatus> {
const empty: SetupStatus = {
carCount: 0,
carsWithParams: 0,
siteCount: 0,
trackCount: 0,
carsReady: false,
mapsReady: false,
incomplete: true,
cars: [],
navigationKinds: [],
scenarios: [],
modules: []
}
try {
const [cars, sites, tracks, profile] = await Promise.all([
listCars().catch(() => []),
listSites().catch(() => []),
listTracks().catch(() => []),
getWizardProfile().catch(() => null)
])
const inspected = await Promise.all(
cars.slice(0, 40).map((c) => {
const id = c.rawId ?? Number(String(c.id).replace(/^C/i, ''))
if (!Number.isFinite(id) || id <= 0) {
return Promise.resolve({
id: 0,
name: c.name,
address: c.address ?? c.ip ?? '',
port: '',
paramsReady: ipOk(c.address ?? c.ip ?? '')
} satisfies SetupCarParamRow)
}
return inspectCarParams(id, c.name)
})
)
const carsWithParams = inspected.filter((c) => c.paramsReady).length
const couldReadParams = inspected.some((c) => c.address || c.port || c.paramsReady)
const carsReady = cars.length >= 1 && (!couldReadParams || carsWithParams >= 1)
const mapsReady = sites.length >= 1 && tracks.length >= 1
return {
carCount: cars.length,
carsWithParams,
siteCount: sites.length,
trackCount: tracks.length,
carsReady,
mapsReady,
incomplete: !(carsReady && mapsReady),
cars: inspected,
navigationKinds: profile?.navigationKinds ?? [],
scenarios: profile?.scenarios ?? [],
modules: profile?.modules ?? []
}
} catch (e) {
return {
...empty,
error: e instanceof Error ? e.message : String(e)
}
}
}
@@ -0,0 +1,69 @@
import http from './http'
const MOCK = import.meta.env.VITE_USE_MOCK === 'true'
export interface SignalColumn {
key: string
label: string
type: 'string' | 'int' | 'bool' | 'enum' | string
options?: string[] | null
group?: string | null
}
export interface SignalTableSummary {
id: string
title: string
category: string
fileName: string
}
export interface SignalTableDto extends SignalTableSummary {
exists: boolean
error?: string | null
columns: SignalColumn[]
rows: Record<string, unknown>[]
}
export interface SignalDataListDto {
signalEnabled: boolean
workingDirectory?: string | null
tables: SignalTableSummary[]
}
export async function listSignalTables(): Promise<SignalDataListDto> {
if (MOCK) {
return {
signalEnabled: true,
workingDirectory: 'D:\\工作\\stand\\SimpleLite',
tables: [
{ id: 'stations', title: 'PLC机构', category: 'PLC数据管理', fileName: 'stations.json' },
{ id: 'docks', title: '机构工位', category: 'PLC数据管理', fileName: 'station-docks.json' },
{ id: 'handshake', title: '握手点', category: '握手点数据管理', fileName: 'handshake-points.json' },
{ id: 'release', title: '放行点', category: '放行点数据管理', fileName: 'release-points.json' },
{ id: 'mag-control', title: '磁条管控区', category: '磁条交管', fileName: 'mag-control-areas.json' }
]
}
}
const { data } = await http.get<SignalDataListDto>('/signal-data', { params: { summary: true } })
return data
}
export async function getSignalTable(id: string): Promise<SignalTableDto> {
if (MOCK) {
const list = await listSignalTables()
const meta = list.tables.find((t) => t.id === id)
if (!meta) throw new Error(`未知数据表:${id}`)
return { ...meta, exists: true, columns: [], rows: [] }
}
const { data } = await http.get<SignalTableDto>(`/signal-data/${id}`)
return data
}
export async function saveSignalTable(id: string, rows: Record<string, unknown>[]): Promise<SignalTableDto> {
if (MOCK) {
const t = await getSignalTable(id)
return { ...t, rows: JSON.parse(JSON.stringify(rows)) }
}
const { data } = await http.put<SignalTableDto>(`/signal-data/${id}`, { rows })
return data
}
@@ -29,6 +29,24 @@ const MOCK_OPTIONS: WizardOptions = {
}
}
const NAV_SCENE: Record<string, string> = {
magnetic: 'scene.mag',
qrcode: 'scene.qrlidar',
laser: 'scene.qrlidar'
}
function toLauncherSceneIds(kinds: string[], scenarios: string[] = []): string[] {
const result: string[] = []
for (const k of kinds) {
const id = NAV_SCENE[k] ?? `scene.${k}`
if (!result.includes(id)) result.push(id)
}
const wantSignal = scenarios.some((s) => s === 'tpl-sps' || s === 'tpl-pack')
if (wantSignal && !result.includes('scene.signal')) result.push('scene.signal')
if (result.length > 0 && !result.includes('scene.device')) result.push('scene.device')
return result
}
let mockProfile: DeploymentProfileDto = {
configured: false,
platformType: 'standard',
@@ -61,7 +79,7 @@ export async function saveWizardProfile(req: SaveWizardRequest): Promise<Deploym
navigationKinds: req.navigationKinds ?? [],
scenarios: req.scenarios ?? [],
configured: true,
activeSceneIds: (req.navigationKinds ?? []).map((k) => `scene.${k}`)
activeSceneIds: toLauncherSceneIds(req.navigationKinds ?? [], req.scenarios ?? [])
}
return mockProfile
}