feat(platform): filter vehicle archive gaps

This commit is contained in:
lingniu
2026-07-04 09:50:41 +08:00
parent b127b22a4b
commit b5a72ce0ec
11 changed files with 174 additions and 26 deletions

View File

@@ -72,6 +72,7 @@ export interface VehicleCoverageSummary {
unboundVehicles: number;
archiveIncompleteVehicles: number;
missingSources: MissingSourceStat[];
archiveMissingFields: ArchiveMissingFieldStat[];
}
export interface VehicleIdentityResolution {
@@ -152,6 +153,7 @@ export interface VehicleServiceSummary {
serviceStatuses: ServiceStatusStat[];
protocols: ProtocolStat[];
missingSources: MissingSourceStat[];
archiveMissingFields: ArchiveMissingFieldStat[];
}
export interface MissingSourceStat {
@@ -159,6 +161,12 @@ export interface MissingSourceStat {
count: number;
}
export interface ArchiveMissingFieldStat {
field: string;
title: string;
count: number;
}
export interface VehicleServiceStatus {
status: string;
severity: string;

View File

@@ -23,7 +23,7 @@ describe('parseAppHash', () => {
});
test('parses vehicle list filters from hash query', () => {
expect(parseAppHash('#/vehicles?coverage=multi&serviceStatus=degraded&online=online&bindingStatus=bound&archiveStatus=incomplete&missingProtocol=YUTONG_MQTT')).toEqual({
expect(parseAppHash('#/vehicles?coverage=multi&serviceStatus=degraded&online=online&bindingStatus=bound&archiveStatus=incomplete&archiveMissing=phone&missingProtocol=YUTONG_MQTT')).toEqual({
page: 'vehicles',
filters: {
coverage: 'multi',
@@ -31,6 +31,7 @@ describe('parseAppHash', () => {
online: 'online',
bindingStatus: 'bound',
archiveStatus: 'incomplete',
archiveMissing: 'phone',
missingProtocol: 'YUTONG_MQTT'
}
});
@@ -62,7 +63,7 @@ describe('buildAppHash', () => {
});
test('builds shareable vehicle list hash with filters', () => {
expect(buildAppHash({ page: 'vehicles', filters: { coverage: 'multi', serviceStatus: 'degraded', archiveStatus: 'incomplete', missingProtocol: 'YUTONG_MQTT' } })).toBe('#/vehicles?coverage=multi&serviceStatus=degraded&archiveStatus=incomplete&missingProtocol=YUTONG_MQTT');
expect(buildAppHash({ page: 'vehicles', filters: { coverage: 'multi', serviceStatus: 'degraded', archiveStatus: 'incomplete', archiveMissing: 'phone', missingProtocol: 'YUTONG_MQTT' } })).toBe('#/vehicles?coverage=multi&serviceStatus=degraded&archiveStatus=incomplete&archiveMissing=phone&missingProtocol=YUTONG_MQTT');
});
test('builds shareable quality hash with filters', () => {

View File

@@ -15,6 +15,7 @@ const filterKeys = [
'online',
'bindingStatus',
'archiveStatus',
'archiveMissing',
'missingProtocol',
'issueType',
'tab',

View File

@@ -67,6 +67,11 @@ const archiveStatusLabel: Record<string, string> = {
complete: '完整',
incomplete: '不完整'
};
const archiveMissingLabel: Record<string, string> = {
plate: '缺车牌',
phone: '缺手机号',
oem: '缺OEM'
};
function sourceDiagnosisText(row: VehicleCoverageRow) {
const parts = [sourceEvidenceText(row)];
@@ -157,6 +162,14 @@ export function Vehicles({
{ label: '待绑定', value: (summary?.unboundVehicles ?? 0).toLocaleString(), filters: { bindingStatus: 'unbound' } },
{ label: '档案不完整', value: (summary?.archiveIncompleteVehicles ?? 0).toLocaleString(), filters: { archiveStatus: 'incomplete' } }
];
for (const field of summary?.archiveMissingFields ?? []) {
if (field.count <= 0) continue;
items.push({
label: field.title,
value: field.count.toLocaleString(),
filters: { archiveMissing: field.field }
});
}
for (const source of summary?.missingSources ?? []) {
if (source.count <= 0) continue;
items.push({
@@ -181,6 +194,10 @@ export function Vehicles({
if (archiveIncompleteCount > 0) {
items.push({ label: '完善车辆档案', count: archiveIncompleteCount, filters: { archiveStatus: 'incomplete' }, color: 'orange' });
}
for (const field of summary?.archiveMissingFields ?? []) {
if (field.count <= 0) continue;
items.push({ label: `补齐${field.title}`, count: field.count, filters: { archiveMissing: field.field }, color: 'orange' });
}
for (const source of summary?.missingSources ?? []) {
if (source.count <= 0) continue;
items.push({
@@ -200,7 +217,8 @@ export function Vehicles({
filters.serviceStatus ? `服务状态:${serviceStatusLabel[filters.serviceStatus] ?? filters.serviceStatus}` : '',
filters.online ? `在线:${onlineLabel[filters.online] ?? filters.online}` : '',
filters.bindingStatus ? `绑定:${bindingStatusLabel[filters.bindingStatus] ?? filters.bindingStatus}` : '',
filters.archiveStatus ? `档案:${archiveStatusLabel[filters.archiveStatus] ?? filters.archiveStatus}` : ''
filters.archiveStatus ? `档案:${archiveStatusLabel[filters.archiveStatus] ?? filters.archiveStatus}` : '',
filters.archiveMissing ? `档案缺项:${archiveMissingLabel[filters.archiveMissing] ?? filters.archiveMissing}` : ''
].filter(Boolean);
const load = (values: Record<string, string> = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => {
@@ -213,6 +231,7 @@ export function Vehicles({
if (values?.online) params.set('online', values.online);
if (values?.bindingStatus) params.set('bindingStatus', values.bindingStatus);
if (values?.archiveStatus) params.set('archiveStatus', values.archiveStatus);
if (values?.archiveMissing) params.set('archiveMissing', values.archiveMissing);
if (values?.serviceStatus) params.set('serviceStatus', values.serviceStatus);
const summaryParams = new URLSearchParams(params);
summaryParams.delete('limit');
@@ -360,6 +379,11 @@ export function Vehicles({
<Select.Option value="complete"></Select.Option>
<Select.Option value="incomplete"></Select.Option>
</Form.Select>
<Form.Select field="archiveMissing" label="档案缺项" placeholder="全部" style={{ width: 140 }}>
<Select.Option value="plate"></Select.Option>
<Select.Option value="phone"></Select.Option>
<Select.Option value="oem">OEM</Select.Option>
</Form.Select>
<Space>
<Button htmlType="submit" theme="solid" type="primary"></Button>
<Button icon={<IconCopy />} onClick={copyVehicleShareURL}></Button>

View File

@@ -594,6 +594,8 @@ test('shows vehicle service result summary on vehicle list filters', async () =>
multiSourceVehicles: 181,
noDataVehicles: 4,
unboundVehicles: 9,
archiveIncompleteVehicles: 12,
archiveMissingFields: [{ field: 'phone', title: '缺手机号', count: 7 }],
missingSources: [{ protocol: 'YUTONG_MQTT', count: 181 }]
},
traceId: 'trace-test',
@@ -661,9 +663,12 @@ 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.getByRole('button', { name: '维护身份绑定 9' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '确认平台转发 4' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '补齐缺手机号 7' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '补齐 YUTONG_MQTT 来源 181' })).toBeInTheDocument();
expect(screen.getByText('档案完整度')).toBeInTheDocument();
expect(screen.getByText('4/4')).toBeInTheDocument();