diff --git a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx index 56ed4589..ecb270cb 100644 --- a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx @@ -235,6 +235,44 @@ function vehicleDispatchListText({ return lines.join('\n'); } +function vehicleIdentityMaintenanceText({ + filters, + rows, + total +}: { + filters: Record; + rows: VehicleCoverageRow[]; + total: number; +}) { + const maintenanceRows = rows.filter((row) => row.bindingStatus !== 'bound' || vehicleArchiveMissingLabels(row).length > 0); + const lines = [ + '【车辆身份维护清单】', + `当前筛选:${vehicleFilterSummary(filters).join(';') || '全部车辆'}`, + `当前页待维护:${maintenanceRows.length.toLocaleString()} 辆 / 当前筛选 ${total.toLocaleString()} 辆`, + '' + ]; + maintenanceRows.forEach((row, index) => { + const missingLabels = vehicleArchiveMissingLabels(row); + const lookupKeys = [ + row.vin ? `vin=${row.vin}` : '', + row.plate ? `plate=${row.plate}` : '', + row.phone ? `phone=${row.phone}` : '', + row.oem ? `oem=${row.oem}` : '' + ].filter(Boolean).join(';') || '暂无可用关联键'; + const action = row.bindingStatus !== 'bound' + ? '先维护 vehicle_identity_binding 的车牌/手机号到 VIN 映射,再复核实时来源是否归并到车辆服务。' + : `补齐档案字段:${missingLabels.join('、')}`; + lines.push( + `${index + 1}. ${row.plate || '-'} / ${row.vin || '-'} / ${row.phone || '-'} / ${row.oem || '-'}`, + ` 状态:${vehicleServiceStatus(row).label};绑定:${row.bindingStatus === 'bound' ? '已绑定' : '未绑定'};档案缺项:${missingLabels.join('、') || '无'}`, + ` 关联键:${lookupKeys}`, + ` 建议动作:${action}`, + ` 车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: row.vin, protocol: primaryRowProtocol(row, filters.protocol) }))}` + ); + }); + return lines.join('\n'); +} + type VehicleActionRecommendation = { label: string; color: 'green' | 'orange' | 'red'; @@ -501,6 +539,14 @@ export function Vehicles({ } copyText(vehicleDispatchListText({ filters, rows, total: pagination.total }), '车辆处置清单'); }; + const copyIdentityMaintenanceList = () => { + const maintenanceRows = rows.filter((row) => row.bindingStatus !== 'bound' || vehicleArchiveMissingLabels(row).length > 0); + if (maintenanceRows.length === 0) { + Toast.warning('当前页没有需要维护身份或档案的车辆'); + return; + } + copyText(vehicleIdentityMaintenanceText({ filters, rows, total: pagination.total }), '车辆身份维护清单'); + }; useEffect(() => { setFilters(initialFilters); @@ -679,6 +725,7 @@ export function Vehicles({ 当前车辆结果 + )} style={{ marginTop: 16 }} diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx index 0eeea742..013b6425 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -1778,6 +1778,15 @@ test('copies vehicle governance summary from vehicle center', async () => { expect(writeText).toHaveBeenCalledWith(expect.stringContaining('实时监控:http://localhost:3000/#/realtime?keyword=VIN-GOV-001&protocol=JT808')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹回放:http://localhost:3000/#/history?keyword=VIN-GOV-001&protocol=JT808')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('告警事件:http://localhost:3000/#/alert-events?keyword=VIN-GOV-001&protocol=JT808')); + + fireEvent.click(screen.getByRole('button', { name: '复制身份维护清单' })); + + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆身份维护清单】')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('当前页待维护:1 辆 / 当前筛选 12 辆')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('粤A治理1 / VIN-GOV-001 / - / G7s')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('档案缺项:缺手机号')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('关联键:vin=VIN-GOV-001;plate=粤A治理1;oem=G7s')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('建议动作:补齐档案字段:缺手机号')); }); test('filters vehicle list from recommended action', async () => {