feat(platform): unify source status tags
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
import { Space, Tag } from '@douyinfe/semi-ui';
|
||||||
|
import type { VehicleSourceStatus } from '../api/types';
|
||||||
|
|
||||||
|
function sourceStatusLabel(source: Pick<VehicleSourceStatus, 'online' | 'hasRealtime' | 'lastSeen'>) {
|
||||||
|
if (source.online) {
|
||||||
|
return { text: '在线', color: 'green' as const };
|
||||||
|
}
|
||||||
|
if (source.hasRealtime || source.lastSeen) {
|
||||||
|
return { text: '离线', color: 'orange' as const };
|
||||||
|
}
|
||||||
|
return { text: '未接入', color: 'grey' as const };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SourceStatusTags({
|
||||||
|
sourceStatus,
|
||||||
|
protocols,
|
||||||
|
lastSeen
|
||||||
|
}: {
|
||||||
|
sourceStatus?: VehicleSourceStatus[];
|
||||||
|
protocols: string[];
|
||||||
|
lastSeen: string;
|
||||||
|
}) {
|
||||||
|
const sources = sourceStatus?.length
|
||||||
|
? sourceStatus
|
||||||
|
: protocols.map((protocol) => ({ protocol, online: false, hasRealtime: true, lastSeen }));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space spacing={4} wrap>
|
||||||
|
{sources.map((source) => {
|
||||||
|
const status = sourceStatusLabel(source);
|
||||||
|
return <Tag key={source.protocol} color={status.color}>{source.protocol} {status.text}</Tag>;
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { api } from '../api/client';
|
import { api } from '../api/client';
|
||||||
import type { DashboardSummary, LinkHealth, ProtocolStat, QualityIssueRow, ServiceStatusStat, VehicleCoverageRow, VehicleRealtimeRow, VehicleServiceSummary } from '../api/types';
|
import type { DashboardSummary, LinkHealth, ProtocolStat, QualityIssueRow, ServiceStatusStat, VehicleCoverageRow, VehicleRealtimeRow, VehicleServiceSummary } from '../api/types';
|
||||||
import { PageHeader } from '../components/PageHeader';
|
import { PageHeader } from '../components/PageHeader';
|
||||||
|
import { SourceStatusTags } from '../components/SourceStatusTags';
|
||||||
import { StatusTag } from '../components/StatusTag';
|
import { StatusTag } from '../components/StatusTag';
|
||||||
import { qualityIssueVehicleLookup } from '../domain/vehicleLookup';
|
import { qualityIssueVehicleLookup } from '../domain/vehicleLookup';
|
||||||
|
|
||||||
@@ -324,11 +325,9 @@ export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenVehicles }: { on
|
|||||||
{ title: 'VIN', dataIndex: 'vin', width: 190 },
|
{ title: 'VIN', dataIndex: 'vin', width: 190 },
|
||||||
{
|
{
|
||||||
title: '来源证据',
|
title: '来源证据',
|
||||||
width: 240,
|
width: 300,
|
||||||
render: (_: unknown, row: VehicleCoverageRow) => (
|
render: (_: unknown, row: VehicleCoverageRow) => (
|
||||||
<Space spacing={4} wrap>
|
<SourceStatusTags sourceStatus={row.sourceStatus} protocols={row.protocols} lastSeen={row.lastSeen} />
|
||||||
{row.protocols.map((protocol) => <Tag key={protocol} color="blue">{protocol}</Tag>)}
|
|
||||||
</Space>
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { api } from '../api/client';
|
|||||||
import type { VehicleCoverageRow, VehicleCoverageSummary } from '../api/types';
|
import type { VehicleCoverageRow, VehicleCoverageSummary } from '../api/types';
|
||||||
import { DataEmpty } from '../components/DataEmpty';
|
import { DataEmpty } from '../components/DataEmpty';
|
||||||
import { PageHeader } from '../components/PageHeader';
|
import { PageHeader } from '../components/PageHeader';
|
||||||
|
import { SourceStatusTags } from '../components/SourceStatusTags';
|
||||||
import { StatusTag } from '../components/StatusTag';
|
import { StatusTag } from '../components/StatusTag';
|
||||||
|
|
||||||
function vehicleServiceStatus(row: VehicleCoverageRow) {
|
function vehicleServiceStatus(row: VehicleCoverageRow) {
|
||||||
@@ -33,16 +34,6 @@ function sourceEvidenceText(row: VehicleCoverageRow) {
|
|||||||
return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`;
|
return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sourceStatusLabel(source: { online: boolean; hasRealtime: boolean; lastSeen: string }) {
|
|
||||||
if (source.online) {
|
|
||||||
return { text: '在线', color: 'green' as const };
|
|
||||||
}
|
|
||||||
if (source.hasRealtime || source.lastSeen) {
|
|
||||||
return { text: '离线', color: 'orange' as const };
|
|
||||||
}
|
|
||||||
return { text: '未接入', color: 'grey' as const };
|
|
||||||
}
|
|
||||||
|
|
||||||
function sourceConsistencyAction(row: VehicleCoverageRow, onFilter: (filters: Record<string, string>) => void) {
|
function sourceConsistencyAction(row: VehicleCoverageRow, onFilter: (filters: Record<string, string>) => void) {
|
||||||
const consistency = row.sourceConsistency;
|
const consistency = row.sourceConsistency;
|
||||||
if (!consistency) {
|
if (!consistency) {
|
||||||
@@ -160,12 +151,7 @@ export function Vehicles({
|
|||||||
title: '来源证据',
|
title: '来源证据',
|
||||||
width: 300,
|
width: 300,
|
||||||
render: (_: unknown, row: VehicleCoverageRow) => (
|
render: (_: unknown, row: VehicleCoverageRow) => (
|
||||||
<Space spacing={4} wrap>
|
<SourceStatusTags sourceStatus={row.sourceStatus} protocols={row.protocols} lastSeen={row.lastSeen} />
|
||||||
{(row.sourceStatus?.length ? row.sourceStatus : row.protocols.map((protocol) => ({ protocol, online: false, hasRealtime: true, lastSeen: row.lastSeen }))).map((source) => {
|
|
||||||
const status = sourceStatusLabel(source);
|
|
||||||
return <Tag key={source.protocol} color={status.color}>{source.protocol} {status.text}</Tag>;
|
|
||||||
})}
|
|
||||||
</Space>
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -714,6 +714,12 @@ test('shows row service status in dashboard vehicle previews', async () => {
|
|||||||
phone: '',
|
phone: '',
|
||||||
oem: 'G7s',
|
oem: 'G7s',
|
||||||
protocols: ['GB32960', 'JT808'],
|
protocols: ['GB32960', 'JT808'],
|
||||||
|
missingProtocols: ['YUTONG_MQTT'],
|
||||||
|
sourceStatus: [
|
||||||
|
{ protocol: 'GB32960', online: true, lastSeen: '2026-07-03 20:12:10', hasRealtime: true, hasHistory: false, hasRaw: false, hasMileage: false },
|
||||||
|
{ protocol: 'JT808', online: false, lastSeen: '2026-07-03 20:10:00', hasRealtime: true, hasHistory: false, hasRaw: false, hasMileage: false },
|
||||||
|
{ protocol: 'YUTONG_MQTT', online: false, lastSeen: '', hasRealtime: false, hasHistory: false, hasRaw: false, hasMileage: false }
|
||||||
|
],
|
||||||
sourceCount: 2,
|
sourceCount: 2,
|
||||||
onlineSourceCount: 1,
|
onlineSourceCount: 1,
|
||||||
online: true,
|
online: true,
|
||||||
@@ -791,6 +797,9 @@ test('shows row service status in dashboard vehicle previews', async () => {
|
|||||||
|
|
||||||
expect(await screen.findByText('VIN-COVERAGE-WARN')).toBeInTheDocument();
|
expect(await screen.findByText('VIN-COVERAGE-WARN')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('来源证据').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('来源证据').length).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(screen.getByText('GB32960 在线')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('JT808 离线')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('YUTONG_MQTT 未接入')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('1/2 来源在线').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('1/2 来源在线').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText('覆盖部分离线')).toBeInTheDocument();
|
expect(screen.getByText('覆盖部分离线')).toBeInTheDocument();
|
||||||
expect(screen.getByText('VIN-REALTIME-OFFLINE')).toBeInTheDocument();
|
expect(screen.getByText('VIN-REALTIME-OFFLINE')).toBeInTheDocument();
|
||||||
|
|||||||
Reference in New Issue
Block a user