diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx index 30240566..08561900 100644 --- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx @@ -1502,6 +1502,56 @@ export function Dashboard({ onClick: () => onOpenQuality() } ]; + const customerServiceCockpitItems = [ + { + label: '车辆总览', + value: `${formatCount(serviceSummary?.totalVehicles)} 辆`, + detail: '按 VIN、车牌、手机号或 OEM 进入车辆服务,不从协议列表开始。', + action: '车辆中心', + color: 'blue' as const, + onClick: () => onOpenVehicles({}) + }, + { + label: '实时可见', + value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`, + detail: `${commandLocatedCount.toLocaleString()} 辆有有效坐标,先回答客户车辆在哪里。`, + action: '地图看车', + color: commandLocatedCount > 0 ? 'green' as const : 'orange' as const, + onClick: () => onOpenMap({ online: 'online' }) + }, + { + label: '轨迹复盘', + value: `${formatCount(summary?.activeToday)} 今日活跃`, + detail: '同一辆车、同一时间窗回放路线、速度、停驶和里程断点。', + action: '回放轨迹', + color: 'blue' as const, + onClick: openTimeMonitorHistory + }, + { + label: '里程统计', + value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo), + detail: '核对区间里程、每日闭合、异常记录和 BI 统计口径。', + action: '统计查询', + color: 'blue' as const, + onClick: openTimeMonitorMileage + }, + { + label: '数据交付', + value: `${formatCount(summary?.frameToday)} 今日数据`, + detail: '按车辆、时间和字段裁剪历史位置、原始记录和解析证据。', + action: '导出证据', + color: 'blue' as const, + onClick: openTimeMonitorRaw + }, + { + label: '告警通知', + value: `${formatCount(summary?.issueVehicles)} 告警`, + detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无高优先级告警。', + action: '闭环处理', + color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const, + onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {}) + } + ]; const customerCockpitWorkflows = [ { title: '实时看车', @@ -2144,6 +2194,37 @@ export function Dashboard({
+
+
+ + 车辆服务驾驶舱 + 客户主入口 + 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} 告警 + + + 先看车辆,不先看协议;客户进来先知道哪些车在线、在哪里、今天跑了多少、哪些异常要通知。 + + + 32960、808 和宇通 MQTT 都沉到证据层;首页只围绕车辆地图、轨迹回放、里程统计、数据交付和告警通知组织动作。 + +
+
+ {customerServiceCockpitItems.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 902452c1..59b63473 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -1823,6 +1823,84 @@ body { align-items: stretch; } +.vp-vehicle-service-cockpit { + margin-bottom: 16px; + border: 1px solid rgba(22, 100, 255, 0.18); + border-radius: 8px; + background: #ffffff; + display: grid; + grid-template-columns: minmax(300px, 0.48fr) minmax(0, 1.52fr); + overflow: hidden; +} + +.vp-vehicle-service-cockpit-copy { + padding: 20px; + border-right: 1px solid var(--vp-border); + background: #f6f9ff; + display: grid; + gap: 12px; + align-content: center; +} + +.vp-vehicle-service-cockpit-copy .semi-typography { + line-height: 26px; +} + +.vp-vehicle-service-cockpit-grid { + padding: 14px; + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 10px; +} + +.vp-vehicle-service-cockpit-item { + min-height: 144px; + padding: 12px; + border: 1px solid rgba(22, 100, 255, 0.14); + border-radius: 8px; + background: #fbfcff; + color: inherit; + cursor: pointer; + font: inherit; + text-align: left; + display: grid; + gap: 8px; + align-content: start; + transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease; +} + +.vp-vehicle-service-cockpit-item:hover, +.vp-vehicle-service-cockpit-item:focus-visible { + border-color: rgba(22, 100, 255, 0.42); + background: #f5f9ff; + box-shadow: 0 0 0 3px rgba(22, 100, 255, 0.08); + outline: none; +} + +.vp-vehicle-service-cockpit-item strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + font-weight: 800; + word-break: break-word; +} + +.vp-vehicle-service-cockpit-item span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; + word-break: break-word; +} + +.vp-vehicle-service-cockpit-item em { + align-self: end; + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 800; + line-height: 18px; +} + .vp-customer-service-journey-copy { min-width: 0; display: grid; @@ -11284,6 +11362,19 @@ button.vp-realtime-command-item:focus-visible { } @media (max-width: 900px) { + .vp-app, + .vp-main, + .vp-page { + max-width: 100%; + overflow-x: hidden; + } + + .semi-table-wrapper, + .semi-table-container, + .semi-table-body { + max-width: 100%; + } + .vp-shell { grid-template-columns: minmax(0, 1fr); } @@ -11327,6 +11418,8 @@ button.vp-realtime-command-item:focus-visible { .vp-dispatch-operations-highlights, .vp-customer-delivery-workbench, .vp-customer-delivery-workbench-grid, + .vp-vehicle-service-cockpit, + .vp-vehicle-service-cockpit-grid, .vp-customer-service-separation, .vp-customer-primary-flow-grid, .vp-source-readiness-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 c3c946e6..503f6578 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -728,6 +728,14 @@ test('dashboard prioritizes customer vehicle service command over data sources', render(); + expect(await screen.findByText('车辆服务驾驶舱')).toBeInTheDocument(); + expect(screen.getByText('先看车辆,不先看协议;客户进来先知道哪些车在线、在哪里、今天跑了多少、哪些异常要通知。')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆服务驾驶舱 车辆总览 1,033 辆 车辆中心' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆服务驾驶舱 实时可见 208 在线 地图看车' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆服务驾驶舱 轨迹复盘 4 今日活跃 回放轨迹' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆服务驾驶舱 里程统计 1 天窗口 统计查询' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆服务驾驶舱 数据交付 1,286,320 今日数据 导出证据' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '车辆服务驾驶舱 告警通知 7 告警 闭环处理' })).toBeInTheDocument(); expect(await screen.findByText('车辆服务闭环')).toBeInTheDocument(); expect(screen.getByText('客户从首页开始,只需要按这五步完成找车、看车、复盘、统计、导出和通知。')).toBeInTheDocument(); expect(screen.getByRole('button', { name: '车辆服务闭环 1 选车 1,033 车辆中心' })).toBeInTheDocument();