fix: improve map editor and monitor operations

This commit is contained in:
zhaowei.huang
2026-07-26 14:14:19 +08:00
parent beb6563060
commit 7af29fe07f
23 changed files with 2786 additions and 958 deletions
@@ -12,6 +12,36 @@ function kv(rows: Kv[] | null | undefined, ...keys: string[]): string {
return ''
}
function isEmptyStatus(v: string): boolean {
const s = v.trim()
return !s || s === '/' || s === '-' || s === '[]' || s === '(0, 0)' || s === '(0,0)' || /^none$/i.test(s)
}
function compactJsonList(raw: string, maxItems = 3): string {
if (isEmptyStatus(raw)) return ''
try {
const arr = JSON.parse(raw) as unknown
if (!Array.isArray(arr) || arr.length === 0) return ''
const items = arr.slice(0, maxItems).map((x) => {
if (x == null) return ''
if (typeof x === 'number' || typeof x === 'string') return String(x)
if (typeof x === 'object') {
const o = x as Record<string, unknown>
if (o.siteId != null) return `#${o.siteId}`
if (o.siteID != null) return `#${o.siteID}`
if (o.id != null) return `#${o.id}`
if (o.carid != null) return `${o.carid}`
}
return ''
}).filter(Boolean)
if (!items.length) return ''
const more = arr.length > maxItems ? `+${arr.length - maxItems}` : ''
return items.join(',') + more
} catch {
return raw.length > 24 ? `${raw.slice(0, 24)}` : raw
}
}
/**
* SimpleLite /projection/cars 会把「正常但未初始化」「导航失联」等一律标成 fault,
* 与地图标签/车体 AlarmLevel 不一致。这里用 lstatus + 车体 status 重新推导。
@@ -28,7 +58,7 @@ export function deriveCarState(car: Pick<Car, 'state' | 'lstatus'>, statusRows?:
const hasAlarm =
(Number.isFinite(alarmLevel) && alarmLevel > 0) ||
(/true/i.test(riskAlarm)) ||
(alarmInfo.length > 0 && !/^\/$|^-$|^none$/i.test(alarmInfo))
(alarmInfo.length > 0 && !isEmptyStatus(alarmInfo))
// 明确健康:以「正常」开头(含「正常但未初始化」)→ 绝不是故障
if (/^正常/.test(lstatus)) {
@@ -62,6 +92,90 @@ export function deriveCarState(car: Pick<Car, 'state' | 'lstatus'>, statusRows?:
return 'idle'
}
/**
* 与 SimpleLite 车体浮窗 CarVisual.FormatTrafficStatus 对齐:
* 1) TCStat 含 BeforeLock → 互锁
* 2) blockedBy 有车 →「被 X、Y 车阻塞」;仅非车阻塞 →「阻塞中」
* 3) 有 holdingLocks → 正常拿锁;否则 → 畅通
*/
export function formatTrafficStatus(statusRows?: Kv[] | null): string {
if (!statusRows?.length) return '—'
const tc = kv(statusRows, 'TCStat')
if (tc && /BeforeLock/i.test(tc)) return '互锁(interlock'
const blockedIds = parseBlockedByCarIds(kv(statusRows, 'blockedBy'))
if (blockedIds != null) {
return blockedIds.length > 0
? `${blockedIds.join('、')} 车阻塞`
: '阻塞中'
}
const holding = kv(statusRows, 'holdingLocks')
if (holding && !isEmptyStatus(holding) && holding !== '[]') return '正常拿锁'
return '畅通'
}
/** @returns null=无阻塞;[]=阻塞但无车 idnumber[]=阻塞车 id */
function parseBlockedByCarIds(raw: string): number[] | null {
if (!raw || isEmptyStatus(raw)) return null
try {
const arr = JSON.parse(raw) as unknown
if (!Array.isArray(arr) || arr.length === 0) return null
const ids = arr
.map((item) => {
if (item == null) return null
if (typeof item === 'number') return item
if (typeof item === 'object') {
const o = item as Record<string, unknown>
const n = Number(o.carid ?? o.Item1 ?? o.item1)
return Number.isFinite(n) ? n : null
}
return null
})
.filter((n): n is number => n != null && n !== -1)
return [...new Set(ids)]
} catch {
// 兼容 ToString 形态:[(11, 2), (12, 3)]
const ids = [...raw.matchAll(/\(\s*(-?\d+)\s*,\s*-?\d+\s*\)/g)]
.map((m) => Number(m[1]))
.filter((n) => Number.isFinite(n) && n !== -1)
if (!ids.length && /\[\s*\]/.test(raw)) return null
if (raw.includes('(') || raw.includes('[')) return [...new Set(ids)]
return null
}
}
export function isTrafficCongested(status: string | undefined | null): boolean {
return !!status && /阻塞|互锁|拥堵/.test(status)
}
export function formatLockPoint(statusRows?: Kv[] | null, fields?: { siteId?: string }): string {
const holding = compactJsonList(kv(statusRows, 'holdingLocks'))
if (holding) return holding
const pending = compactJsonList(kv(statusRows, 'pendingLocks'))
if (pending) return `${pending}`
const siteRaw = fields?.siteId || kv(statusRows, 'siteID', 'siteId')
const siteId = Number(siteRaw)
if (Number.isFinite(siteId) && siteId > 0) return `#${siteId}`
return '—'
}
export function formatAlarmInfo(statusRows?: Kv[] | null, lstatus?: string): { text: string; level: number } {
const levelRaw = Number(kv(statusRows, '车体_AlarmLevel', 'AlarmLevel'))
const level = Number.isFinite(levelRaw) ? levelRaw : 0
const info = kv(statusRows, '车体_AlarmInfo', 'AlarmInfo').trim()
if (info && !isEmptyStatus(info) && info !== '0') {
return { text: info, level: level > 0 ? level : 1 }
}
if (lstatus && /故障|报警|异常|失联|超时|检修/i.test(lstatus)) {
return { text: lstatus, level: Math.max(level, 1) }
}
return { text: '', level }
}
export function applyRuntimeEnrichment(car: Car, statusRows?: Kv[] | null): Car {
const socFromStatus = extractSocFromKv(statusRows)
let batterySoc: number
@@ -74,9 +188,18 @@ export function applyRuntimeEnrichment(car: Car, statusRows?: Kv[] | null): Car
batterySoc = parseSocNumber(car.batterySoc) ?? 0
}
const alarm = formatAlarmInfo(statusRows, car.lstatus)
const siteRaw = kv(statusRows, 'siteID', 'siteId')
const siteIdNum = Number(siteRaw)
return {
...car,
batterySoc,
state: deriveCarState(car, statusRows)
state: deriveCarState(car, statusRows),
alarmInfo: alarm.text || undefined,
alarmLevel: alarm.level || undefined,
trafficStatus: formatTrafficStatus(statusRows),
lockPoint: formatLockPoint(statusRows, { siteId: siteRaw }),
siteId: Number.isFinite(siteIdNum) && siteIdNum > 0 ? siteIdNum : undefined
}
}