feat(platform): add vehicle data management console
This commit is contained in:
124
vehicle-data-platform/apps/web/src/pages/Dashboard.tsx
Normal file
124
vehicle-data-platform/apps/web/src/pages/Dashboard.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Card, Col, Row, Spin, Table, Tag, Toast, Typography } from '@douyinfe/semi-ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import type { DashboardSummary, LinkHealth, ProtocolStat, RealtimeLocationRow, VehicleRow } from '../api/types';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
|
||||
const statusColor: Record<string, 'green' | 'orange' | 'red' | 'grey'> = {
|
||||
ok: 'green',
|
||||
warning: 'orange',
|
||||
error: 'red'
|
||||
};
|
||||
|
||||
export function Dashboard() {
|
||||
const [summary, setSummary] = useState<DashboardSummary | null>(null);
|
||||
const [vehicles, setVehicles] = useState<VehicleRow[]>([]);
|
||||
const [locations, setLocations] = useState<RealtimeLocationRow[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
api.dashboardSummary(),
|
||||
api.vehicles(new URLSearchParams({ limit: '5' })),
|
||||
api.realtimeLocations(new URLSearchParams({ limit: '8' }))
|
||||
])
|
||||
.then(([nextSummary, vehiclePage, locationPage]) => {
|
||||
setSummary(nextSummary);
|
||||
setVehicles(vehiclePage.items);
|
||||
setLocations(locationPage.items);
|
||||
})
|
||||
.catch((error: Error) => Toast.error(error.message))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const kpis = [
|
||||
{ label: '在线车辆', value: summary?.onlineVehicles ?? 0 },
|
||||
{ label: '今日活跃', value: summary?.activeToday ?? 0 },
|
||||
{ label: '今日帧数', value: summary?.frameToday.toLocaleString() ?? '0' },
|
||||
{ label: '问题车辆', value: summary?.issueVehicles ?? 0 }
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="vp-page">
|
||||
<PageHeader title="总览工作台" description="车辆在线、协议分布、数据质量和链路健康的统一入口" />
|
||||
<Spin spinning={loading}>
|
||||
<div className="vp-kpi-grid">
|
||||
{kpis.map((item) => (
|
||||
<Card key={item.label} bordered>
|
||||
<div className="vp-kpi-value">{item.value}</div>
|
||||
<div className="vp-kpi-label">{item.label}</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<Row gutter={16}>
|
||||
<Col span={14}>
|
||||
<Card title="协议在线分布" bordered>
|
||||
<Table
|
||||
pagination={false}
|
||||
dataSource={summary?.protocols ?? []}
|
||||
columns={[
|
||||
{ title: '协议', dataIndex: 'protocol' },
|
||||
{ title: '在线', dataIndex: 'online' },
|
||||
{ title: '总数', dataIndex: 'total' },
|
||||
{
|
||||
title: '在线率',
|
||||
render: (_: unknown, row: ProtocolStat) => `${Math.round((row.online / row.total) * 100)}%`
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<Card title="链路健康" bordered>
|
||||
<Table
|
||||
pagination={false}
|
||||
dataSource={summary?.linkHealth ?? []}
|
||||
columns={[
|
||||
{ title: '链路', dataIndex: 'name' },
|
||||
{
|
||||
title: '状态',
|
||||
render: (_: unknown, row: LinkHealth) => <Tag color={statusColor[row.status] ?? 'grey'}>{row.status}</Tag>
|
||||
},
|
||||
{ title: '说明', dataIndex: 'detail' }
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Card title="实时积压" bordered style={{ marginTop: 16 }}>
|
||||
<Typography.Text>Kafka 当前消费积压:{summary?.kafkaLag ?? 0}</Typography.Text>
|
||||
</Card>
|
||||
<Row gutter={16} style={{ marginTop: 16 }}>
|
||||
<Col span={12}>
|
||||
<Card title="实时位置预览" bordered>
|
||||
<div className="vp-map" style={{ height: 260 }}>
|
||||
{locations.map((row, index) => (
|
||||
<span
|
||||
key={row.vin}
|
||||
className="vp-map-dot"
|
||||
title={`${row.plate} ${row.protocol}`}
|
||||
style={{ left: `${18 + index * 13}%`, top: `${24 + (index % 4) * 15}%` }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card title="最新车辆" bordered>
|
||||
<Table
|
||||
pagination={false}
|
||||
dataSource={vehicles}
|
||||
columns={[
|
||||
{ title: '车牌', dataIndex: 'plate' },
|
||||
{ title: 'VIN', dataIndex: 'vin' },
|
||||
{ title: '协议', dataIndex: 'protocol' },
|
||||
{ title: '最后时间', dataIndex: 'lastSeen' }
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user