feat(platform): add vehicle coverage view

This commit is contained in:
lingniu
2026-07-03 21:54:09 +08:00
parent 6352289c25
commit 110041a9db
13 changed files with 223 additions and 5 deletions

View File

@@ -1,8 +1,9 @@
import { Card, Col, Row, Spin, Table, Tag, Toast, Typography } from '@douyinfe/semi-ui';
import { Button, Card, Col, Row, Space, 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 type { DashboardSummary, LinkHealth, ProtocolStat, RealtimeLocationRow, VehicleCoverageRow, VehicleRow } from '../api/types';
import { PageHeader } from '../components/PageHeader';
import { StatusTag } from '../components/StatusTag';
const statusColor: Record<string, 'green' | 'orange' | 'red' | 'grey'> = {
ok: 'green',
@@ -10,9 +11,10 @@ const statusColor: Record<string, 'green' | 'orange' | 'red' | 'grey'> = {
error: 'red'
};
export function Dashboard() {
export function Dashboard({ onOpenVehicle }: { onOpenVehicle: (vin: string) => void }) {
const [summary, setSummary] = useState<DashboardSummary | null>(null);
const [vehicles, setVehicles] = useState<VehicleRow[]>([]);
const [coverage, setCoverage] = useState<VehicleCoverageRow[]>([]);
const [locations, setLocations] = useState<RealtimeLocationRow[]>([]);
const [loading, setLoading] = useState(true);
@@ -20,11 +22,13 @@ export function Dashboard() {
Promise.all([
api.dashboardSummary(),
api.vehicles(new URLSearchParams({ limit: '5' })),
api.vehicleCoverage(new URLSearchParams({ limit: '8' })),
api.realtimeLocations(new URLSearchParams({ limit: '8' }))
])
.then(([nextSummary, vehiclePage, locationPage]) => {
.then(([nextSummary, vehiclePage, coveragePage, locationPage]) => {
setSummary(nextSummary);
setVehicles(vehiclePage.items);
setCoverage(coveragePage.items);
setLocations(locationPage.items);
})
.catch((error: Error) => Toast.error(error.message))
@@ -88,6 +92,35 @@ export function Dashboard() {
<Card title="实时积压" bordered style={{ marginTop: 16 }}>
<Typography.Text>Kafka {summary?.kafkaLag ?? 0}</Typography.Text>
</Card>
<Card title="车辆服务覆盖" bordered style={{ marginTop: 16 }}>
<Table
pagination={false}
rowKey="vin"
dataSource={coverage}
columns={[
{ title: '车牌', dataIndex: 'plate', width: 110 },
{ title: 'VIN', dataIndex: 'vin', width: 190 },
{
title: '来源',
width: 240,
render: (_: unknown, row: VehicleCoverageRow) => (
<Space spacing={4} wrap>
{row.protocols.map((protocol) => <Tag key={protocol} color="blue">{protocol}</Tag>)}
</Space>
)
},
{
title: '覆盖',
width: 110,
render: (_: unknown, row: VehicleCoverageRow) => `${row.onlineSourceCount}/${row.sourceCount}`
},
{ title: '在线', width: 90, render: (_: unknown, row: VehicleCoverageRow) => <StatusTag status={row.online ? 'ok' : 'offline'} /> },
{ title: '绑定', width: 90, render: (_: unknown, row: VehicleCoverageRow) => <Tag color={row.bindingStatus === 'bound' ? 'green' : 'orange'}>{row.bindingStatus === 'bound' ? '已绑定' : '未绑定'}</Tag> },
{ title: '最后时间', dataIndex: 'lastSeen', width: 170 },
{ title: '操作', width: 110, render: (_: unknown, row: VehicleCoverageRow) => <Button onClick={() => onOpenVehicle(row.vin)}></Button> }
]}
/>
</Card>
<Row gutter={16} style={{ marginTop: 16 }}>
<Col span={12}>
<Card title="实时位置预览" bordered>