diff --git a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx index c4ed0e85..34b10589 100644 --- a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx @@ -807,6 +807,44 @@ export function Vehicles({ onClick: exportVehicles } ]; + const vehicleCustomerQuestions = [ + { + question: '客户要找某辆车怎么办?', + answer: filters.keyword?.trim() ? filters.keyword.trim() : '先输入 VIN/车牌/手机号', + evidence: filters.keyword?.trim() ? '已进入单车范围' : `当前筛选 ${pagination.total.toLocaleString()} 辆`, + action: '聚焦找车', + color: filters.keyword?.trim() ? 'green' as const : 'blue' as const, + disabled: false, + onClick: () => document.querySelector('input[name="keyword"]')?.focus() + }, + { + question: '现在有哪些车在线?', + answer: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 辆在线`, + evidence: filters.online === 'online' ? '已筛在线车辆' : '可切换到在线车辆池', + action: '只看在线', + color: (summary?.onlineVehicles ?? 0) > 0 ? 'green' as const : 'orange' as const, + disabled: false, + onClick: () => applyFilters({ ...filters, online: 'online' }) + }, + { + question: '哪些车暂时不能服务?', + answer: `${((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)).toLocaleString()} 辆待处理`, + evidence: `无来源 ${(summary?.noDataVehicles ?? 0).toLocaleString()} / 待绑定 ${(summary?.unboundVehicles ?? 0).toLocaleString()}`, + action: '异常车辆', + color: (summary?.noDataVehicles ?? 0) > 0 || (summary?.unboundVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const, + disabled: false, + onClick: () => primaryAction ? applyFilters({ ...filters, ...primaryAction.filters }) : applyFilters({ ...filters, serviceStatus: 'no_data' }) + }, + { + question: '车辆清单能交付吗?', + answer: rows.length > 0 ? `${rows.length.toLocaleString()} 辆当前页` : '暂无可交付车辆', + evidence: `筛选总计 ${pagination.total.toLocaleString()} 辆`, + action: '复制交付包', + color: rows.length > 0 ? 'blue' as const : 'grey' as const, + disabled: false, + onClick: () => copyCustomerServicePackage() + } + ]; useEffect(() => { setFilters(initialFilters); @@ -978,6 +1016,38 @@ export function Vehicles({ + +
+
+ + 客户问题 + {filters.keyword?.trim() ? '单车范围' : '车辆池'} + 0 || (summary?.unboundVehicles ?? 0) > 0 ? 'orange' : 'green'}> + {((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)).toLocaleString()} 辆待处理 + + + 把车辆池问题翻译成找车、在线、异常和交付动作 + 客户从车辆池开始时,不需要先理解数据源;先定位车辆、确认在线、处理不可服务车辆,再交付车辆清单。 +
+
+ {vehicleCustomerQuestions.map((item) => ( + + ))} +
+
+
{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 d940c5ad..35163237 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -2158,6 +2158,90 @@ button.vp-realtime-command-item:focus-visible { line-height: 20px; } +.vp-vehicle-question-board { + display: grid; + grid-template-columns: minmax(280px, 0.62fr) minmax(0, 1.38fr); + gap: 16px; +} + +.vp-vehicle-question-summary { + min-height: 156px; + padding: 14px; + border: 1px solid rgba(22, 100, 255, 0.22); + border-radius: var(--vp-radius); + background: #f6f9ff; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-vehicle-question-summary strong { + color: var(--vp-text); + font-size: 19px; + line-height: 25px; + word-break: break-word; +} + +.vp-vehicle-question-summary span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + +.vp-vehicle-question-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.vp-vehicle-question-item { + min-height: 156px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: 8px; + background: #fbfcff; + text-align: left; + cursor: pointer; + font: inherit; + display: grid; + gap: 9px; + align-content: start; +} + +.vp-vehicle-question-item:disabled { + cursor: not-allowed; + opacity: 0.62; +} + +.vp-vehicle-question-item:not(:disabled):hover, +.vp-vehicle-question-item:not(:disabled):focus-visible { + border-color: rgba(22, 100, 255, 0.42); + background: #f5f9ff; + outline: none; +} + +.vp-vehicle-question-item strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + word-break: break-word; +} + +.vp-vehicle-question-item span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + +.vp-vehicle-question-item em { + color: #1664ff; + font-size: 12px; + line-height: 18px; + font-style: normal; + font-weight: 600; + align-self: end; +} + .vp-vehicle-decision-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); @@ -6708,6 +6792,8 @@ button.vp-realtime-command-item:focus-visible { .vp-vehicle-service-grid, .vp-vehicle-lookup-head, .vp-vehicle-lookup-grid, + .vp-vehicle-question-board, + .vp-vehicle-question-grid, .vp-vehicle-decision-grid, .vp-vehicle-package-board, .vp-vehicle-package-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 fff89dad..95d001bc 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -2068,6 +2068,12 @@ test('shows vehicle service result summary on vehicle list filters', async () => expect(screen.getByText('轨迹与位置')).toBeInTheDocument(); expect(screen.getAllByText('数据交付').length).toBeGreaterThanOrEqual(1); expect(screen.getByRole('button', { name: /复制客户查车路径/ })).toBeInTheDocument(); + expect(screen.getByText('车辆客户常问')).toBeInTheDocument(); + expect(screen.getByText('把车辆池问题翻译成找车、在线、异常和交付动作')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆客户常问 客户要找某辆车怎么办? 聚焦找车' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆客户常问 现在有哪些车在线? 只看在线' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆客户常问 哪些车暂时不能服务? 异常车辆' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆客户常问 车辆清单能交付吗? 复制交付包' })).toBeInTheDocument(); expect(screen.getByText('车辆服务决策板')).toBeInTheDocument(); expect(screen.getAllByText('异常车辆').length).toBeGreaterThan(0); expect(screen.getAllByText('4 无来源').length).toBeGreaterThanOrEqual(1); @@ -2128,7 +2134,7 @@ test('shows vehicle service result summary on vehicle list filters', async () => expect(screen.getByText('建议动作')).toBeInTheDocument(); expect(screen.getByText('补齐 YUTONG_MQTT 来源')).toBeInTheDocument(); - fireEvent.click(screen.getByRole('button', { name: /复制交付包/ })); + fireEvent.click(screen.getAllByRole('button', { name: /复制交付包/ })[0]); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户车辆服务交付包】')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('服务范围:当前车辆池')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆地图:http://localhost:3000/#/map?'));