feat(platform): show archive gaps per vehicle

This commit is contained in:
lingniu
2026-07-04 09:56:18 +08:00
parent 4791a1fd9c
commit adff22ef74
2 changed files with 64 additions and 28 deletions

View File

@@ -40,6 +40,15 @@ function vehicleArchiveCompleteness(row: VehicleCoverageRow) {
return { completed, total: fields.length, label: `${completed}/${fields.length}` }; return { completed, total: fields.length, label: `${completed}/${fields.length}` };
} }
function vehicleArchiveMissingLabels(row: VehicleCoverageRow) {
return [
{ value: row.vin, label: '缺VIN' },
{ value: row.plate, label: '缺车牌' },
{ value: row.phone, label: '缺手机号' },
{ value: row.oem, label: '缺OEM' }
].filter((item) => !String(item.value ?? '').trim()).map((item) => item.label);
}
const coverageLabel: Record<string, string> = { const coverageLabel: Record<string, string> = {
single: '单源', single: '单源',
multi: '多源' multi: '多源'
@@ -265,10 +274,18 @@ export function Vehicles({
{ title: 'OEM', dataIndex: 'oem', width: 120 }, { title: 'OEM', dataIndex: 'oem', width: 120 },
{ {
title: '档案完整度', title: '档案完整度',
width: 120, width: 190,
render: (_: unknown, row: VehicleCoverageRow) => { render: (_: unknown, row: VehicleCoverageRow) => {
const archive = vehicleArchiveCompleteness(row); const archive = vehicleArchiveCompleteness(row);
return <Tag color={archive.completed === archive.total ? 'green' : 'orange'}>{archive.label}</Tag>; const missingLabels = vehicleArchiveMissingLabels(row);
return (
<Space spacing={4} wrap>
<Tag color={archive.completed === archive.total ? 'green' : 'orange'}>{archive.label}</Tag>
{missingLabels.map((label) => (
<Tag key={label} color="orange">{label}</Tag>
))}
</Space>
);
} }
}, },
{ {

View File

@@ -611,32 +611,48 @@ test('shows vehicle service result summary on vehicle list filters', async () =>
ok: true, ok: true,
json: async () => ({ json: async () => ({
data: { data: {
items: [{ items: [
vin: 'VIN-MULTI-001', {
plate: '粤A服务1', vin: 'VIN-MULTI-001',
phone: '13307795425', plate: '粤A服务1',
oem: 'G7s', phone: '13307795425',
protocols: ['GB32960', 'JT808'], oem: 'G7s',
missingProtocols: ['YUTONG_MQTT'], protocols: ['GB32960', 'JT808'],
sourceStatus: [ missingProtocols: ['YUTONG_MQTT'],
{ protocol: 'GB32960', online: true, lastSeen: '2026-07-03 20:12:10', hasRealtime: true, hasHistory: false, hasRaw: false, hasMileage: false }, sourceStatus: [
{ protocol: 'JT808', online: false, lastSeen: '2026-07-03 20:10:00', hasRealtime: true, hasHistory: false, hasRaw: false, hasMileage: false }, { protocol: 'GB32960', online: true, lastSeen: '2026-07-03 20:12:10', hasRealtime: true, hasHistory: false, hasRaw: false, hasMileage: false },
{ protocol: 'YUTONG_MQTT', online: false, lastSeen: '', hasRealtime: false, 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, ],
onlineSourceCount: 1,
online: true,
lastSeen: '2026-07-03 20:12:10',
bindingStatus: 'bound',
serviceStatus: {
status: 'degraded',
severity: 'warning',
title: '来源不完整',
detail: '1/2 个来源在线',
sourceCount: 2, sourceCount: 2,
onlineSourceCount: 1 onlineSourceCount: 1,
online: true,
lastSeen: '2026-07-03 20:12:10',
bindingStatus: 'bound',
serviceStatus: {
status: 'degraded',
severity: 'warning',
title: '来源不完整',
detail: '1/2 个来源在线',
sourceCount: 2,
onlineSourceCount: 1
}
},
{
vin: 'VIN-MISSING-ARCHIVE',
plate: '粤A缺项',
phone: '',
oem: '',
protocols: ['JT808'],
missingProtocols: ['GB32960', 'YUTONG_MQTT'],
sourceStatus: [],
sourceCount: 1,
onlineSourceCount: 1,
online: true,
lastSeen: '2026-07-03 20:12:09',
bindingStatus: 'bound'
} }
}], ],
total: 181, total: 181,
limit: 20, limit: 20,
offset: 0 offset: 0
@@ -667,7 +683,7 @@ test('shows vehicle service result summary on vehicle list filters', async () =>
expect(screen.getByText('暂无来源车辆')).toBeInTheDocument(); expect(screen.getByText('暂无来源车辆')).toBeInTheDocument();
expect(screen.getByText('待绑定')).toBeInTheDocument(); expect(screen.getByText('待绑定')).toBeInTheDocument();
expect(screen.getByText('档案不完整')).toBeInTheDocument(); expect(screen.getByText('档案不完整')).toBeInTheDocument();
expect(screen.getByText('缺手机号')).toBeInTheDocument(); expect(screen.getAllByText('缺手机号').length).toBeGreaterThanOrEqual(2);
expect(screen.getByText('处置队列')).toBeInTheDocument(); expect(screen.getByText('处置队列')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '维护身份绑定 9' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '维护身份绑定 9' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '确认平台转发 4' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '确认平台转发 4' })).toBeInTheDocument();
@@ -675,10 +691,13 @@ test('shows vehicle service result summary on vehicle list filters', async () =>
expect(screen.getByRole('button', { name: '补齐 YUTONG_MQTT 来源 181' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '补齐 YUTONG_MQTT 来源 181' })).toBeInTheDocument();
expect(screen.getByText('档案完整度')).toBeInTheDocument(); expect(screen.getByText('档案完整度')).toBeInTheDocument();
expect(screen.getByText('4/4')).toBeInTheDocument(); expect(screen.getByText('4/4')).toBeInTheDocument();
expect(screen.getByText('2/4')).toBeInTheDocument();
expect(screen.getByText('缺OEM')).toBeInTheDocument();
expect(screen.getByText('VIN-MULTI-001')).toBeInTheDocument(); expect(screen.getByText('VIN-MULTI-001')).toBeInTheDocument();
expect(screen.getByText('VIN-MISSING-ARCHIVE')).toBeInTheDocument();
expect(screen.getAllByText('来源证据').length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText('来源证据').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('GB32960 在线')).toBeInTheDocument(); expect(screen.getByText('GB32960 在线')).toBeInTheDocument();
expect(screen.getByText('JT808 离线')).toBeInTheDocument(); expect(screen.getAllByText('JT808 离线').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('YUTONG_MQTT 未接入')).toBeInTheDocument(); expect(screen.getByText('YUTONG_MQTT 未接入')).toBeInTheDocument();
expect(screen.getByText('1/2 来源在线')).toBeInTheDocument(); expect(screen.getByText('1/2 来源在线')).toBeInTheDocument();
expect(screen.getByText('1/2 来源在线,缺 YUTONG_MQTT')).toBeInTheDocument(); expect(screen.getByText('1/2 来源在线,缺 YUTONG_MQTT')).toBeInTheDocument();