feat(platform): reshape telematics operations console

This commit is contained in:
lingniu
2026-07-04 10:32:51 +08:00
parent 1f116ccaa8
commit 89c4fffd5d
6 changed files with 181 additions and 19 deletions

View File

@@ -32,6 +32,19 @@ function sourceEvidenceText(row: VehicleRealtimeRow) {
return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`;
}
function isValidCoordinate(row: VehicleRealtimeRow) {
return Number.isFinite(row.longitude) && Number.isFinite(row.latitude) && row.longitude !== 0 && row.latitude !== 0;
}
function mapPointStyle(row: VehicleRealtimeRow, index: number) {
if (!isValidCoordinate(row)) {
return { left: `${18 + (index % 4) * 18}%`, top: `${28 + (index % 3) * 17}%` };
}
const left = Math.min(88, Math.max(10, ((row.longitude + 180) / 360) * 100));
const top = Math.min(82, Math.max(12, ((90 - row.latitude) / 180) * 100));
return { left: `${left}%`, top: `${top}%` };
}
const onlineLabel: Record<string, string> = {
online: '在线',
offline: '离线'
@@ -57,6 +70,7 @@ export function Realtime({
const [loading, setLoading] = useState(true);
const [filters, setFilters] = useState<Record<string, string>>(initialFilters);
const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 50, total: 0 });
const amapConfigured = Boolean(String(import.meta.env.VITE_AMAP_WEB_JS_KEY ?? '').trim());
const load = (values: Record<string, string> = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => {
setLoading(true);
@@ -90,10 +104,14 @@ export function Realtime({
filters.online ? `在线:${onlineLabel[filters.online] ?? filters.online}` : '',
filters.serviceStatus ? `服务状态:${serviceStatusLabel[filters.serviceStatus] ?? filters.serviceStatus}` : ''
].filter(Boolean);
const onlineCount = rows.filter((row) => row.online).length;
const locatedCount = rows.filter(isValidCoordinate).length;
const degradedCount = rows.filter((row) => row.onlineSourceCount < row.sourceCount).length;
const primaryProtocols = new Set(rows.map((row) => row.primaryProtocol).filter(Boolean));
return (
<div className="vp-page">
<PageHeader title="车辆服务实时态" description="以车辆为主对象查看最新位置、在线来源核心实时数据,协议只作为来源证据" />
<PageHeader title="实时监控" description="以车辆为主对象查看最新位置、在线来源核心实时数据和地图作业状态" />
<Card bordered>
<Form key={JSON.stringify(filters)} initValues={filters} layout="horizontal" onSubmit={(values) => {
const nextFilters = values as Record<string, string>;
@@ -132,6 +150,46 @@ export function Realtime({
</Space>
</Card>
) : null}
<div className="vp-monitor-layout">
<div className="vp-monitor-map">
<div className="vp-monitor-map-header">
<Space wrap>
<Tag color={amapConfigured ? 'green' : 'orange'}>
{amapConfigured ? '高德地图配置就绪' : '高德地图待配置'}
</Tag>
<Tag color="blue">{locatedCount.toLocaleString()} </Tag>
<Tag color="green">{onlineCount.toLocaleString()} 线</Tag>
</Space>
</div>
<div className="vp-map vp-map-monitor">
{rows.slice(0, 80).map((row, index) => (
<span
key={`${row.vin}-${row.primaryProtocol}-${index}`}
className={`vp-map-dot ${row.online ? 'vp-map-dot-online' : 'vp-map-dot-offline'}`}
title={`${row.plate || row.vin} ${row.primaryProtocol || ''}`}
style={mapPointStyle(row, index)}
/>
))}
</div>
</div>
<div className="vp-monitor-side">
{[
{ label: '当前车辆', value: pagination.total.toLocaleString(), color: 'blue' as const },
{ label: '在线车辆', value: onlineCount.toLocaleString(), color: 'green' as const },
{ label: '定位有效', value: locatedCount.toLocaleString(), color: 'green' as const },
{ label: '降级服务', value: degradedCount.toLocaleString(), color: degradedCount > 0 ? 'orange' as const : 'green' as const },
{ label: '来源类型', value: primaryProtocols.size.toLocaleString(), color: 'blue' as const }
].map((item) => (
<div key={item.label} className="vp-monitor-metric">
<Tag color={item.color}>{item.label}</Tag>
<div className="vp-monitor-metric-value">{item.value}</div>
</div>
))}
<Typography.Text type="secondary">
Web JS Key Key
</Typography.Text>
</div>
</div>
<Tabs type="line">
<Tabs.TabPane tab="表格视图" itemKey="table">
{rows.length === 0 && !loading ? (
@@ -199,7 +257,7 @@ export function Realtime({
key={row.vin}
className="vp-map-dot"
title={`${row.plate} ${row.primaryProtocol}`}
style={{ left: `${18 + index * 24}%`, top: `${26 + (index % 3) * 18}%` }}
style={mapPointStyle(row, index)}
/>
))}
</div>