From 3097fd59ebbc32c5291d41735759ef3f52db576a Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 03:25:14 +0800 Subject: [PATCH] feat(platform): add customer vehicle lookup path --- .../apps/web/src/pages/Vehicles.tsx | 90 +++++++++++++++++++ .../apps/web/src/styles/global.css | 68 ++++++++++++++ .../apps/web/src/test/App.test.tsx | 9 ++ 3 files changed, 167 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx index b9466839..8a668f84 100644 --- a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx @@ -273,6 +273,43 @@ function vehicleIdentityMaintenanceText({ return lines.join('\n'); } +function vehicleCustomerLookupText({ + filters, + rows, + total, + summary +}: { + filters: Record; + rows: VehicleCoverageRow[]; + total: number; + summary: VehicleCoverageSummary | null; +}) { + const serviceMode = filters.keyword?.trim() ? '单车服务' : '车辆池'; + const scopeHash = buildAppHash({ page: 'vehicles', keyword: filters.keyword, protocol: filters.protocol, filters }); + const singleVehicleFilters = filters.keyword?.trim() + ? { keyword: filters.keyword.trim(), ...(filters.protocol ? { protocol: filters.protocol } : {}) } + : {}; + const mapFilters = { ...filters, online: filters.online || 'online' }; + const lines = [ + '【客户查车路径】', + `服务模式:${serviceMode}`, + `当前筛选:${vehicleFilterSummary(filters).join(';') || '全部车辆'}`, + `当前页:${rows.length.toLocaleString()} 辆 / 当前筛选 ${total.toLocaleString()} 辆`, + `在线车辆:${(summary?.onlineVehicles ?? 0).toLocaleString()};待绑定:${(summary?.unboundVehicles ?? 0).toLocaleString()};暂无来源:${(summary?.noDataVehicles ?? 0).toLocaleString()};档案不完整:${(summary?.archiveIncompleteVehicles ?? 0).toLocaleString()}`, + '', + serviceMode === '单车服务' + ? '推荐路径:先看车辆档案和实时监控,再按需要进入轨迹回放、里程统计、历史数据导出。' + : '推荐路径:先筛在线/离线/无来源/待绑定车辆,再进入地图态势或导出处置清单。', + `车辆服务:${appURL(scopeHash)}`, + `实时监控:${appURL(buildAppHash({ page: 'realtime', ...singleVehicleFilters }))}`, + `车辆地图:${appURL(buildAppHash({ page: 'map', keyword: filters.keyword, protocol: filters.protocol, filters: mapFilters }))}`, + `轨迹回放:${appURL(buildAppHash({ page: 'history', ...singleVehicleFilters, filters: { tab: 'location' } }))}`, + `里程统计:${appURL(buildAppHash({ page: 'mileage', ...singleVehicleFilters }))}`, + `历史数据:${appURL(buildAppHash({ page: 'history', ...singleVehicleFilters, filters: { tab: 'raw', includeFields: 'true' } }))}` + ]; + return lines.join('\n'); +} + type VehicleActionRecommendation = { label: string; color: 'green' | 'orange' | 'red'; @@ -557,6 +594,31 @@ export function Vehicles({ } copyText(vehicleIdentityMaintenanceText({ filters, rows, total: pagination.total }), '车辆身份维护清单'); }; + const copyCustomerLookupPath = () => { + copyText(vehicleCustomerLookupText({ filters, rows, total: pagination.total, summary }), '客户查车路径'); + }; + const customerLookupSteps = [ + { + label: '车辆身份', + value: filters.keyword?.trim() || 'VIN / 车牌 / 手机号', + detail: '确认车辆归属、车牌、手机号和 OEM,客户问题先落到一辆车。' + }, + { + label: '在线状态', + value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`, + detail: '判断车辆是否仍在上报,离线车辆进入断链和来源证据排查。' + }, + { + label: '轨迹与位置', + value: filters.keyword?.trim() ? '可回放' : '先选车辆', + detail: '单车进入轨迹回放,车辆池进入地图态势查看位置分布。' + }, + { + label: '数据交付', + value: `${rows.length.toLocaleString()} 当前页`, + detail: '导出车辆清单,再进入历史页交付轨迹、RAW 和字段证据。' + } + ]; const serviceDeskActions = useMemo(() => [ { label: '实时监控', @@ -789,6 +851,34 @@ export function Vehicles({ + +
+
+
+ + {filters.keyword?.trim() ? '单车服务' : '车辆池服务'} + {vehicleFilterSummary(filters).join(' / ') || '全部车辆'} + + {filters.keyword?.trim() ? '围绕一辆车完成监控、回放、统计和导出' : '从车辆池筛出客户要看的车,再进入单车服务'} + 客户侧只关心车辆服务结果,32960、808、MQTT 作为证据来源参与筛选和可信度判断。 +
+ + + + + +
+
+ {customerLookupSteps.map((step) => ( +
+ {step.label} + {step.value} + {step.detail} +
+ ))} +
+
+
{vehicleDecisionItems.map((item) => ( diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index 5fe940e4..b6cdb627 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -1003,6 +1003,72 @@ button.vp-realtime-command-item:focus-visible { line-height: 20px; } +.vp-vehicle-lookup-strip { + display: grid; + gap: 14px; +} + +.vp-vehicle-lookup-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + padding: 14px; + border: 1px solid rgba(22, 100, 255, 0.16); + border-radius: 8px; + background: #f6f9ff; +} + +.vp-vehicle-lookup-head > div { + min-width: 0; + display: grid; + gap: 8px; +} + +.vp-vehicle-lookup-head strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + font-weight: 700; +} + +.vp-vehicle-lookup-head span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + +.vp-vehicle-lookup-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.vp-vehicle-lookup-item { + min-height: 128px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: 8px; + background: #fbfcff; + display: grid; + gap: 8px; + align-content: start; +} + +.vp-vehicle-lookup-item strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + font-weight: 700; + word-break: break-word; +} + +.vp-vehicle-lookup-item span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + .vp-vehicle-decision-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); @@ -4035,6 +4101,8 @@ button.vp-realtime-command-item:focus-visible { .vp-current-service-grid, .vp-vehicle-service-desk, .vp-vehicle-service-grid, + .vp-vehicle-lookup-head, + .vp-vehicle-lookup-grid, .vp-vehicle-decision-grid, .vp-time-review-package, .vp-time-review-grid, 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 17c21b14..dc056b07 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -2007,6 +2007,15 @@ test('shows vehicle service result summary on vehicle list filters', async () => expect(await screen.findByText('当前车辆结果')).toBeInTheDocument(); expect(screen.getByText('车辆服务台')).toBeInTheDocument(); expect(screen.getByText('客户视角')).toBeInTheDocument(); + expect(screen.getByText('客户查车路径')).toBeInTheDocument(); + expect(screen.getByText('车辆池服务')).toBeInTheDocument(); + expect(screen.getByText('从车辆池筛出客户要看的车,再进入单车服务')).toBeInTheDocument(); + expect(screen.getByText('客户侧只关心车辆服务结果,32960、808、MQTT 作为证据来源参与筛选和可信度判断。')).toBeInTheDocument(); + expect(screen.getByText('车辆身份')).toBeInTheDocument(); + expect(screen.getByText('VIN / 车牌 / 手机号')).toBeInTheDocument(); + expect(screen.getByText('轨迹与位置')).toBeInTheDocument(); + expect(screen.getAllByText('数据交付').length).toBeGreaterThanOrEqual(1); + expect(screen.getByRole('button', { name: /复制客户查车路径/ })).toBeInTheDocument(); expect(screen.getByText('车辆服务决策板')).toBeInTheDocument(); expect(screen.getAllByText('异常车辆').length).toBeGreaterThan(0); expect(screen.getByText('4 无来源')).toBeInTheDocument();