diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx
index 2b932efe..d8e0ad4d 100644
--- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx
+++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx
@@ -1701,6 +1701,71 @@ export function Realtime({
});
return protocols.length > 0 ? protocols.join(' / ') : '暂无来源证据';
})();
+ const realtimeServiceOverviewItems = [
+ {
+ label: '在线车辆',
+ value: `${onlineCount.toLocaleString()} 在线`,
+ detail: `${(rows.length - onlineCount).toLocaleString()} 辆离线,在线率 ${formatPercent(onlineRate)}`,
+ color: onlineCount > 0 ? 'green' as const : 'orange' as const,
+ onClick: () => applyFilters({ ...filters, online: 'online' })
+ },
+ {
+ label: '有效定位',
+ value: `${locatedCount.toLocaleString()} 辆`,
+ detail: `${missingCoordinateCount.toLocaleString()} 辆无坐标,定位率 ${formatPercent(locatedRate)}`,
+ color: locatedCount > 0 ? 'blue' as const : 'orange' as const,
+ onClick: () => {
+ window.location.hash = buildAppHash({ page: 'map', protocol: filters.protocol, filters });
+ }
+ },
+ {
+ label: '需关注',
+ value: `${mapAttentionRows.length.toLocaleString()} 辆`,
+ detail: mapAttentionRows[0] ? `${mapAttentionRows[0].plate || mapAttentionRows[0].vin}:${realtimeIssueLabels(mapAttentionRows[0]).join(';')}` : '当前没有离线、超时或定位异常车辆',
+ color: mapAttentionRows.length > 0 ? 'orange' as const : 'green' as const,
+ onClick: () => applyFilters({ ...filters, serviceStatus: 'degraded' })
+ },
+ {
+ label: '地图能力',
+ displayLabel: '高德能力',
+ value: amapConfigured ? '高德可用' : '坐标预览',
+ detail: amapConfigured ? '高德 Web JS 与安全代理就绪' : '地图降级为坐标预览',
+ color: amapConfigured ? 'green' as const : 'orange' as const,
+ onClick: () => load(filters, pagination.currentPage, pagination.pageSize)
+ }
+ ];
+ const realtimeServiceActions = [
+ {
+ label: '打开地图',
+ action: '地图态势',
+ color: 'blue' as const,
+ disabled: false,
+ onClick: () => {
+ window.location.hash = buildAppHash({ page: 'map', protocol: filters.protocol, filters });
+ }
+ },
+ {
+ label: '轨迹复盘',
+ action: '轨迹回放',
+ color: selectedMapRow ? 'blue' as const : 'grey' as const,
+ disabled: !selectedMapRow || !canOpenVehicle(selectedMapRow.vin),
+ onClick: () => selectedMapRow && onOpenHistory?.(selectedMapRow.vin, selectedVehicleProtocol)
+ },
+ {
+ label: '导出清单',
+ action: '导出CSV',
+ color: rows.length > 0 ? 'blue' as const : 'grey' as const,
+ disabled: rows.length === 0,
+ onClick: exportRealtime
+ },
+ {
+ label: '告警通知',
+ action: '关注车辆',
+ color: mapAttentionRows.length > 0 ? 'orange' as const : 'green' as const,
+ disabled: !onOpenQuality,
+ onClick: () => onOpenQuality?.({ serviceStatus: 'degraded' })
+ }
+ ];
const realtimeCustomerCommandItems = [
{
label: '实时地图',
@@ -2628,6 +2693,51 @@ export function Realtime({
return (
+
+
+
+ 实时车辆服务总览
+ {amapConfigured ? '高德可用' : '坐标预览'}
+ 0 ? 'orange' : 'green'}>{mapAttentionRows.length.toLocaleString()} 辆需关注
+
+
+ 客户先看车辆是否在线、地图是否可见、哪些车辆需要处置,再进入轨迹、统计、导出和告警。
+
+
+ 当前范围:{filterSummary.length > 0 ? filterSummary.join(' / ') : '全部车辆'},来源证据:{realtimeSourceEvidenceText}
+
+
+
+ {realtimeServiceOverviewItems.map((item) => (
+
+ ))}
+
+
+ {realtimeServiceActions.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 bb13541d..e858f40c 100644
--- a/vehicle-data-platform/apps/web/src/styles/global.css
+++ b/vehicle-data-platform/apps/web/src/styles/global.css
@@ -4112,6 +4112,109 @@ body {
gap: 8px;
}
+.vp-realtime-service-overview {
+ margin-bottom: 16px;
+ border: 1px solid rgba(22, 100, 255, 0.18);
+ border-radius: var(--vp-radius);
+ background: #ffffff;
+ display: grid;
+ grid-template-columns: minmax(260px, 0.86fr) minmax(0, 1.36fr) minmax(190px, 0.58fr);
+ overflow: hidden;
+}
+
+.vp-realtime-service-overview-copy {
+ padding: 18px;
+ border-right: 1px solid var(--vp-border);
+ background: #f7fbff;
+ display: grid;
+ gap: 10px;
+ align-content: start;
+}
+
+.vp-realtime-service-overview-metrics {
+ padding: 14px;
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.vp-realtime-service-overview-metric {
+ min-height: 116px;
+ padding: 12px;
+ border: 1px solid rgba(22, 100, 255, 0.13);
+ border-radius: 8px;
+ background: #fbfcff;
+ color: inherit;
+ cursor: pointer;
+ font: inherit;
+ text-align: left;
+ display: grid;
+ gap: 8px;
+ align-content: start;
+}
+
+.vp-realtime-service-overview-metric:hover,
+.vp-realtime-service-overview-metric:focus-visible,
+.vp-realtime-service-overview-action:hover,
+.vp-realtime-service-overview-action:focus-visible {
+ border-color: rgba(22, 100, 255, 0.42);
+ background: #f5f9ff;
+ box-shadow: var(--vp-shadow-sm);
+ outline: none;
+}
+
+.vp-realtime-service-overview-metric strong {
+ color: var(--vp-text);
+ font-size: 19px;
+ line-height: 24px;
+ word-break: break-word;
+}
+
+.vp-realtime-service-overview-metric span {
+ color: var(--vp-text-muted);
+ font-size: 12px;
+ line-height: 18px;
+ word-break: break-word;
+}
+
+.vp-realtime-service-overview-actions {
+ padding: 14px;
+ border-left: 1px solid var(--vp-border);
+ background: #fafcff;
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 8px;
+}
+
+.vp-realtime-service-overview-action {
+ min-height: 46px;
+ padding: 8px 10px;
+ border: 1px solid rgba(22, 100, 255, 0.13);
+ border-radius: 8px;
+ background: #fff;
+ color: inherit;
+ cursor: pointer;
+ font: inherit;
+ text-align: left;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+}
+
+.vp-realtime-service-overview-action:disabled {
+ cursor: not-allowed;
+ opacity: 0.62;
+}
+
+.vp-realtime-service-overview-action span {
+ color: var(--vp-primary);
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 18px;
+ white-space: nowrap;
+}
+
.vp-realtime-command-center {
margin-bottom: 16px;
border: 1px solid rgba(22, 100, 255, 0.16);
@@ -13321,6 +13424,8 @@ button.vp-realtime-command-item:focus-visible {
.vp-time-audit-grid,
.vp-time-delivery-strip,
.vp-time-delivery-grid,
+ .vp-realtime-service-overview,
+ .vp-realtime-service-overview-metrics,
.vp-realtime-command-center,
.vp-realtime-command-center-grid,
.vp-realtime-priority-board,
@@ -13560,6 +13665,13 @@ button.vp-realtime-command-item:focus-visible {
border-bottom: 1px solid var(--vp-border);
}
+ .vp-realtime-service-overview-copy,
+ .vp-realtime-service-overview-actions {
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid var(--vp-border);
+ }
+
.vp-customer-cockpit-workflow {
grid-template-columns: 1fr;
}
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 ffda5412..2910b633 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -10445,6 +10445,16 @@ test('realtime page prioritizes customer vehicle monitoring command center', asy
render();
+ expect(await screen.findByText('实时车辆服务总览')).toBeInTheDocument();
+ expect(screen.getByText('客户先看车辆是否在线、地图是否可见、哪些车辆需要处置,再进入轨迹、统计、导出和告警。')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '实时车辆服务总览 在线车辆 2 在线' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '实时车辆服务总览 有效定位 1 辆' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '实时车辆服务总览 需关注 1 辆' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '实时车辆服务总览 地图能力 高德可用' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '实时服务动作 打开地图 地图态势' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '实时服务动作 轨迹复盘 轨迹回放' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '实时服务动作 导出清单 导出CSV' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '实时服务动作 告警通知 关注车辆' })).toBeInTheDocument();
expect(await screen.findByText('客户实时监控总控台')).toBeInTheDocument();
expect(screen.getByText('实时页先回答客户能不能看到车、车辆在哪里、哪些车辆异常,再进入轨迹、统计、导出和告警。')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户实时监控总控台 实时地图 1 有定位 打开地图' })).toBeInTheDocument();