Files
lingniu-vehicle-ingest/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx

4564 lines
218 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Button, Card, Col, Form, Row, Select, Space, Spin, Table, Tag, Toast, Typography } from '@douyinfe/semi-ui';
import { useEffect, useMemo, useState } from 'react';
import { api } from '../api/client';
import type { DashboardSummary, OpsHealth, QualityIssueRow, VehicleCoverageRow, VehicleRealtimeRow, VehicleServiceSummary } from '../api/types';
import { PageHeader } from '../components/PageHeader';
import { SourceStatusTags } from '../components/SourceStatusTags';
import { StatusTag } from '../components/StatusTag';
import { VehicleMap, type VehicleMapPoint } from '../components/VehicleMap';
import { isAMapConfigured } from '../config/appConfig';
import { buildAppHash } from '../domain/appRoute';
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
import { qualityIssueLabel, qualityProtocolLabel } from '../domain/qualityIssue';
import { qualityIssueVehicleLookup } from '../domain/vehicleLookup';
const serviceStatusTitle: Record<string, string> = {
healthy: '服务正常',
degraded: '来源不完整',
offline: '车辆离线',
no_data: '暂无数据来源',
identity_required: '身份未绑定'
};
const missingProtocolTitle: Record<string, string> = {
GB32960: '缺 GB32960',
JT808: '缺 JT808',
YUTONG_MQTT: '缺 YUTONG_MQTT'
};
type DashboardSnapshotRow = {
section: string;
item: string;
value: string;
detail: string;
};
type FocusVehicleService = {
label: string;
lookupKey: string;
protocol?: string;
reason: string;
statusLabel: string;
statusColor: 'green' | 'orange' | 'red' | 'grey' | 'blue';
realtimeEvidence: string;
historyEvidence: string;
alertEvidence: string;
statisticEvidence: string;
issueType?: string;
};
const dashboardSnapshotColumns: CsvColumn<DashboardSnapshotRow>[] = [
{ title: '模块', value: (row) => row.section },
{ title: '指标', value: (row) => row.item },
{ title: '值', value: (row) => row.value },
{ title: '说明', value: (row) => row.detail }
];
function formatLag(value?: number | null) {
return value == null ? '未接入' : value.toLocaleString();
}
function formatCount(value?: number | null) {
return value == null ? '0' : value.toLocaleString();
}
function vehicleServiceOnlineText(serviceSummary: VehicleServiceSummary | null, summary: DashboardSummary | null) {
const onlineVehicles = serviceSummary?.onlineVehicles ?? summary?.onlineVehicles;
const totalVehicles = serviceSummary?.totalVehicles;
if (onlineVehicles == null || totalVehicles == null) {
return '未接入';
}
return `${onlineVehicles.toLocaleString()} / ${totalVehicles.toLocaleString()} 在线`;
}
function rowServiceStatus(row: { serviceStatus?: { title: string; severity: string }; onlineSourceCount: number; sourceCount: number }) {
if (row.serviceStatus) {
return {
label: row.serviceStatus.title,
color: row.serviceStatus.severity === 'ok' ? 'green' as const : row.serviceStatus.severity === 'error' ? 'red' as const : 'orange' as const
};
}
if (row.onlineSourceCount <= 0) {
return { label: '车辆离线', color: 'red' as const };
}
if (row.onlineSourceCount < row.sourceCount) {
return { label: '来源不完整', color: 'orange' as const };
}
return { label: '服务正常', color: 'green' as const };
}
function sourceEvidenceText(row: { onlineSourceCount: number; sourceCount: number }) {
return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`;
}
function hasValidCoordinate(row: VehicleRealtimeRow) {
return Number.isFinite(row.longitude) && Number.isFinite(row.latitude) && row.longitude !== 0 && row.latitude !== 0;
}
function issueEvidenceDate(value?: string) {
const match = String(value ?? '').match(/^(\d{4})-(\d{2})-(\d{2})/);
return match ? `${match[1]}-${match[2]}-${match[3]}` : '';
}
function nextDate(value: string) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
if (!match) return '';
const date = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]) + 1));
return date.toISOString().slice(0, 10);
}
function todayDateString() {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function previousDateString(value: string) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
if (!match) return value;
const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]) - 1);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function offsetDateString(value: string, offsetDays: number) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
if (!match) return value;
const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]) + offsetDays);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function weekStartDateString(value: string) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
if (!match) return value;
const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
const day = date.getDay();
const mondayOffset = day === 0 ? -6 : 1 - day;
return offsetDateString(value, mondayOffset);
}
function dayRangeText(dateFrom?: string, dateTo?: string) {
if (!dateFrom && !dateTo) return '未限定时间';
if (!dateFrom || !dateTo) return '单边时间窗';
const start = new Date(`${dateFrom}T00:00:00+08:00`).getTime();
const end = new Date(`${dateTo}T00:00:00+08:00`).getTime();
if (!Number.isFinite(start) || !Number.isFinite(end)) return '时间格式待确认';
const days = Math.max(0, Math.round((end - start) / 86400000));
return days <= 0 ? '同日或倒置时间窗' : `${days} 天窗口`;
}
function naturalDayRangeText(dateFrom?: string, dateTo?: string) {
if (!dateFrom && !dateTo) return '未限定时间';
if (!dateFrom || !dateTo) return '单边时间窗';
const start = new Date(`${dateFrom}T00:00:00+08:00`).getTime();
const end = new Date(`${dateTo}T00:00:00+08:00`).getTime();
if (!Number.isFinite(start) || !Number.isFinite(end)) return '时间格式待确认';
const days = Math.max(0, Math.round((end - start) / 86400000)) + 1;
return days <= 0 ? '同日或倒置时间窗' : `${days} 天窗口`;
}
function priorityIssueVehicleLabel(row: QualityIssueRow) {
const identity = row.vin?.trim() && row.vin !== 'unknown' ? row.vin.trim() : row.phone?.trim();
return [row.plate?.trim(), identity].filter(Boolean).join(' / ') || row.sourceEndpoint || '-';
}
function priorityIssueEvidenceFilters(row: QualityIssueRow) {
const lookup = qualityIssueVehicleLookup(row);
const dateFrom = issueEvidenceDate(row.lastSeen);
return {
keyword: lookup.key,
protocol: row.protocol,
...(dateFrom ? { dateFrom, dateTo: nextDate(dateFrom) } : {})
};
}
function appURL(hash: string) {
return `${window.location.origin}${window.location.pathname}${hash}`;
}
async function copyText(value: string, label: string) {
try {
await navigator.clipboard.writeText(value);
Toast.success(`已复制${label}`);
} catch {
Toast.error(`复制${label}失败`);
}
}
function priorityIssueNotificationText(row: QualityIssueRow) {
const lookup = qualityIssueVehicleLookup(row);
const filters = priorityIssueEvidenceFilters(row);
const rawFilters = { ...filters, tab: 'raw', includeFields: 'true' };
const priority = row.severity === 'error' ? 'P0' : 'P1';
return [
`${priority} 告警通知】${qualityIssueLabel(row.issueType)}`,
`车辆:${priorityIssueVehicleLabel(row)}`,
`数据来源:${qualityProtocolLabel(row.protocol)}`,
`问题:${qualityIssueLabel(row.issueType)}`,
`最后时间:${row.lastSeen || '-'}`,
`详情:${row.detail || '-'}`,
`实时定位:${appURL(buildAppHash({ page: 'realtime', keyword: lookup.key, protocol: row.protocol }))}`,
`轨迹证据:${appURL(buildAppHash({ page: 'history', keyword: filters.keyword, protocol: filters.protocol, filters }))}`,
`原始记录:${appURL(buildAppHash({ page: 'history-query', keyword: rawFilters.keyword, protocol: rawFilters.protocol, filters: rawFilters }))}`,
`车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: lookup.key, protocol: row.protocol }))}`,
`告警筛选:${appURL(buildAppHash({ page: 'alert-events', protocol: row.protocol, filters: { issueType: row.issueType } }))}`
].join('\n');
}
function focusVehicleFromIssue(row: QualityIssueRow): FocusVehicleService {
const lookup = qualityIssueVehicleLookup(row);
const filters = priorityIssueEvidenceFilters(row);
const label = priorityIssueVehicleLabel(row);
return {
label,
lookupKey: lookup.key,
protocol: row.protocol,
reason: `${row.severity === 'error' ? 'P0' : 'P1'} ${qualityIssueLabel(row.issueType)}`,
statusLabel: row.severity === 'error' ? '优先处置' : '持续跟踪',
statusColor: row.severity === 'error' ? 'red' : 'orange',
realtimeEvidence: `来源 ${qualityProtocolLabel(row.protocol)} / 最后 ${row.lastSeen || '-'}`,
historyEvidence: filters.dateFrom ? `${filters.dateFrom}${filters.dateTo}` : '待确认时间窗',
alertEvidence: row.detail || qualityIssueLabel(row.issueType),
statisticEvidence: '统计查询需回溯车辆口径',
issueType: row.issueType
};
}
function focusVehicleFromRealtime(row: VehicleRealtimeRow): FocusVehicleService {
const status = rowServiceStatus(row);
const label = [row.plate?.trim(), row.vin?.trim()].filter(Boolean).join(' / ') || row.phone || '-';
const location = hasValidCoordinate(row) ? `${row.longitude.toFixed(6)}, ${row.latitude.toFixed(6)}` : '无有效坐标';
return {
label,
lookupKey: row.vin || row.phone || row.plate,
protocol: row.primaryProtocol,
reason: row.online ? '最新在线车辆' : '最新车辆',
statusLabel: status.label,
statusColor: status.color,
realtimeEvidence: `${row.onlineSourceCount}/${row.sourceCount} 来源在线 / ${row.lastSeen || '-'}`,
historyEvidence: location,
alertEvidence: row.onlineSourceCount < row.sourceCount ? '来源不完整,建议检查缺失协议' : '暂无高优先级告警',
statisticEvidence: `速度 ${formatCount(row.speedKmh)} km/h / 里程 ${formatCount(row.totalMileageKm)} km`
};
}
function sourceConsistencyAction(row: VehicleCoverageRow, onFilter: (filters: Record<string, string>) => void) {
const consistency = row.sourceConsistency;
if (!consistency) {
return <Tag color="grey"></Tag>;
}
const color = consistency.severity === 'ok' ? 'green' as const : consistency.severity === 'error' ? 'red' as const : 'orange' as const;
const label = consistency.title || consistency.status;
if ((consistency.missingProtocols ?? []).length > 0) {
return (
<Button
size="small"
theme="light"
type={color === 'red' ? 'danger' : color === 'orange' ? 'warning' : 'primary'}
onClick={() => onFilter({ serviceStatus: 'degraded', missingProtocol: consistency.missingProtocols[0] })}
>
{label}
</Button>
);
}
return <Tag color={color}>{label}</Tag>;
}
function defaultTimeMonitorFilters() {
const today = todayDateString();
return {
dateFrom: previousDateString(today),
dateTo: today
};
}
export function Dashboard({
onOpenVehicle,
onOpenQuality,
onOpenMap,
onOpenRealtime,
onOpenVehicles,
onOpenHistory,
onOpenMileage,
customerTimeWindow,
onCopyCustomerScope,
onSaveCustomerView,
focusMode = 'overview',
initialTimeMonitorFilters
}: {
onOpenVehicle: (vin: string, protocol?: string) => void;
onOpenQuality: (filters?: Record<string, string>) => void;
onOpenMap: (filters?: Record<string, string>) => void;
onOpenRealtime: (filters?: Record<string, string>) => void;
onOpenVehicles: (filters?: Record<string, string>) => void;
onOpenHistory: (filters?: Record<string, string>) => void;
onOpenMileage: (filters?: Record<string, string>) => void;
customerTimeWindow?: Record<string, string>;
onCopyCustomerScope?: () => void;
onSaveCustomerView?: () => void;
focusMode?: 'overview' | 'time-window';
initialTimeMonitorFilters?: Record<string, string>;
}) {
const [summary, setSummary] = useState<DashboardSummary | null>(null);
const [serviceSummary, setServiceSummary] = useState<VehicleServiceSummary | null>(null);
const [coverage, setCoverage] = useState<VehicleCoverageRow[]>([]);
const [locations, setLocations] = useState<VehicleRealtimeRow[]>([]);
const [qualityIssues, setQualityIssues] = useState<QualityIssueRow[]>([]);
const [opsHealth, setOpsHealth] = useState<OpsHealth | null>(null);
const [timeMonitorFilters, setTimeMonitorFilters] = useState<Record<string, string>>({
...defaultTimeMonitorFilters(),
...(initialTimeMonitorFilters ?? {})
});
const [loading, setLoading] = useState(true);
const [coverageLoading, setCoverageLoading] = useState(false);
const [coverageServiceStatusTitle, setCoverageServiceStatusTitle] = useState('');
const [coverageFilters, setCoverageFilters] = useState<Record<string, string>>({});
const localAMapConfigured = isAMapConfigured();
const amapConfigured = opsHealth?.runtime?.amapWebJsConfigured ?? localAMapConfigured;
const amapApiConfigured = opsHealth?.runtime?.amapApiConfigured ?? false;
const amapSecurityProxyEnabled = opsHealth?.runtime?.amapSecurityProxyEnabled ?? false;
const amapSecurityCodeExposed = opsHealth?.runtime?.amapSecurityCodeExposed ?? false;
const loadCoverage = (values?: Record<string, string>) => {
const nextValues = values ?? {};
setCoverageLoading(true);
setCoverageFilters(nextValues);
const scopeTitle = [
nextValues.serviceStatus ? serviceStatusTitle[nextValues.serviceStatus] ?? nextValues.serviceStatus : '',
nextValues.missingProtocol ? missingProtocolTitle[nextValues.missingProtocol] ?? `${nextValues.missingProtocol}` : ''
].filter(Boolean).join(' / ');
setCoverageServiceStatusTitle(scopeTitle);
const params = new URLSearchParams({ limit: '8' });
if (nextValues.keyword) params.set('keyword', nextValues.keyword);
if (nextValues.coverage) params.set('coverage', nextValues.coverage);
if (nextValues.missingProtocol) params.set('missingProtocol', nextValues.missingProtocol);
if (nextValues.online) params.set('online', nextValues.online);
if (nextValues.bindingStatus) params.set('bindingStatus', nextValues.bindingStatus);
if (nextValues.serviceStatus) params.set('serviceStatus', nextValues.serviceStatus);
api.vehicleCoverage(params)
.then((page) => setCoverage(page.items))
.catch((error: Error) => Toast.error(error.message))
.finally(() => setCoverageLoading(false));
};
useEffect(() => {
const tasks = [
api.dashboardSummary().then(setSummary),
api.vehicleServiceSummary().then(setServiceSummary),
api.vehicleCoverage(new URLSearchParams({ limit: '8' })).then((page) => setCoverage(page.items)),
api.vehicleRealtime(new URLSearchParams({ limit: '8' })).then((page) => setLocations(page.items)),
api.alertEvents(new URLSearchParams({ limit: '5' })).then((page) => setQualityIssues(page.items)),
api.opsHealth().then(setOpsHealth)
];
Promise.allSettled(tasks)
.then((results) => {
const failed = results.find((result) => result.status === 'rejected');
if (failed?.status === 'rejected') {
const reason = failed.reason;
Toast.error(reason instanceof Error ? reason.message : '总览数据加载失败');
}
})
.finally(() => setLoading(false));
}, []);
useEffect(() => {
const dateFrom = customerTimeWindow?.dateFrom?.trim();
const dateTo = customerTimeWindow?.dateTo?.trim();
if (!dateFrom && !dateTo) {
return;
}
setTimeMonitorFilters((previous) => {
const next = {
...previous,
dateFrom: dateFrom ?? '',
dateTo: dateTo ?? ''
};
if (previous.dateFrom === next.dateFrom && previous.dateTo === next.dateTo) {
return previous;
}
return next;
});
}, [customerTimeWindow?.dateFrom, customerTimeWindow?.dateTo]);
const serviceActionQueue = useMemo<Array<{ label: string; count: number; filters: Record<string, string>; detail: string }>>(() => {
const items: Array<{ label: string; count: number; filters: Record<string, string>; detail: string }> = [];
if ((serviceSummary?.noDataVehicles ?? 0) > 0) {
items.push({
label: '确认平台转发',
count: serviceSummary?.noDataVehicles ?? 0,
filters: { serviceStatus: 'no_data' },
detail: '车辆没有形成任何来源证据,优先确认上游平台是否持续转发。'
});
}
if ((serviceSummary?.identityRequiredVehicles ?? 0) > 0) {
items.push({
label: '维护身份绑定',
count: serviceSummary?.identityRequiredVehicles ?? 0,
filters: { serviceStatus: 'identity_required' },
detail: '已有数据但无法稳定归并到 VIN会影响车辆服务聚合。'
});
}
if ((serviceSummary?.archiveIncompleteVehicles ?? 0) > 0) {
items.push({
label: '完善车辆档案',
count: serviceSummary?.archiveIncompleteVehicles ?? 0,
filters: { archiveStatus: 'incomplete' },
detail: '车辆缺少车牌、手机号或 OEM 等基础档案,影响后续运营查询和治理。'
});
}
for (const field of serviceSummary?.archiveMissingFields ?? []) {
if (field.count <= 0) continue;
items.push({
label: `补齐${field.title}`,
count: field.count,
filters: { archiveMissing: field.field },
detail: `${field.title}会影响车辆档案检索、绑定确认和运营侧筛选。`
});
}
for (const source of serviceSummary?.missingSources ?? []) {
if (source.count <= 0) continue;
items.push({
label: `补齐 ${source.protocol} 来源`,
count: source.count,
filters: { serviceStatus: 'degraded', missingProtocol: source.protocol },
detail: `${source.protocol} 来源缺失会降低跨来源定位、里程和实时判断可信度。`
});
}
return items;
}, [serviceSummary]);
const commandOnlineCount = locations.filter((row) => row.online).length;
const commandLocatedCount = locations.filter(hasValidCoordinate).length;
const commandDegradedCount = locations.filter((row) => row.onlineSourceCount <= 0 || row.onlineSourceCount < row.sourceCount).length;
const kpis: Array<{ label: string; value: string; filters: Record<string, string> }> = [
{ label: '总车辆', value: formatCount(serviceSummary?.totalVehicles), filters: {} },
{ label: '在线车辆', value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles), filters: { online: 'online' } },
{ label: '今日活跃', value: formatCount(summary?.activeToday), filters: { online: 'online' } },
{ label: '有效定位', value: commandLocatedCount.toLocaleString(), filters: { online: 'online' } },
{ label: '告警车辆', value: formatCount(summary?.issueVehicles), filters: { serviceStatus: 'degraded' } },
{ label: '今日数据', value: formatCount(summary?.frameToday), filters: {} },
{ label: '档案不完整', value: formatCount(serviceSummary?.archiveIncompleteVehicles), filters: { archiveStatus: 'incomplete' } }
];
const highPriorityIssue = qualityIssues.find((item) => item.severity === 'error') ?? qualityIssues[0];
const highPriorityLookup = highPriorityIssue ? qualityIssueVehicleLookup(highPriorityIssue) : undefined;
const highPriorityEvidenceFilters = highPriorityIssue ? priorityIssueEvidenceFilters(highPriorityIssue) : undefined;
const focusVehicle = highPriorityIssue ? focusVehicleFromIssue(highPriorityIssue) : locations[0] ? focusVehicleFromRealtime(locations[0]) : undefined;
const unhealthyLinkCount = (summary?.linkHealth ?? []).filter((item) => item.status !== 'ok').length;
const commandMapPoints: VehicleMapPoint[] = locations.map((row, index) => ({
id: row.vin || `${row.primaryProtocol || 'source'}-${index}`,
label: row.plate || row.vin || 'unknown',
longitude: row.longitude,
latitude: row.latitude,
online: row.online,
title: `${row.plate || row.vin || '-'} ${row.primaryProtocol || ''} ${row.lastSeen || ''}`
}));
const customerHeroActions = [
{
title: '车辆地图',
value: `${commandLocatedCount.toLocaleString()} 辆有定位`,
detail: '按车查看实时位置、在线状态、速度和最新上报时间。',
action: '进入地图',
onClick: () => onOpenMap({ online: 'online' })
},
{
title: '轨迹回放',
value: `${formatCount(summary?.activeToday)} 今日活跃`,
detail: '选择车辆和时间范围,回放历史路线并核对断点。',
action: '回放轨迹',
onClick: () => onOpenHistory()
},
{
title: '统计查询',
value: `${formatCount(serviceSummary?.totalVehicles)} 辆可统计`,
detail: '按自定义时间查询区间里程、日里程和异常记录。',
action: '查看统计',
onClick: () => onOpenMileage({})
},
{
title: '历史数据',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '按车辆、时间和字段裁剪查询历史位置与原始记录。',
action: '查询导出',
onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' })
}
];
const customerJourneySteps = [
{
step: '01',
title: '定位车辆',
detail: '先在地图上确认车辆是否在线、坐标是否可信。',
action: '车辆地图',
onClick: () => onOpenMap({ online: 'online' })
},
{
step: '02',
title: '查看状态',
detail: '进入实时监控查看速度、SOC、里程和最新上报。',
action: '实时监控',
onClick: () => onOpenRealtime({ online: 'online' })
},
{
step: '03',
title: '复盘轨迹',
detail: '按自定义时间窗回放位置、速度和里程断点。',
action: '轨迹回放',
onClick: () => onOpenHistory()
},
{
step: '04',
title: '统计查询',
detail: '用同一时间窗核对区间里程和每日统计。',
action: '统计查询',
onClick: () => onOpenMileage({})
},
{
step: '05',
title: '查询导出',
detail: '导出历史位置、原始记录、字段明细和告警证据。',
action: '历史数据',
onClick: () => openTimeMonitorRaw()
}
];
const customerHealthItems = [
{
label: '在线车辆',
value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles),
detail: vehicleServiceOnlineText(serviceSummary, summary),
color: 'green' as const,
onClick: () => onOpenVehicles({ online: 'online' })
},
{
label: '有效定位',
value: commandLocatedCount.toLocaleString(),
detail: `当前抽样 ${locations.length.toLocaleString()} 辆,定位可用于地图和回放。`,
color: commandLocatedCount > 0 ? 'blue' as const : 'orange' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
label: '需要关注',
value: formatCount(summary?.issueVehicles),
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无高优先级告警。',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenQuality()
},
{
label: '时间范围监控',
value: formatCount(summary?.activeToday),
detail: '支持按任意时间窗查询轨迹、历史数据、里程和导出。',
color: 'blue' as const,
onClick: () => onOpenHistory()
}
];
const taskTimeMonitorSummary = (() => {
const vehicle = String(timeMonitorFilters.keyword ?? '').trim() || '全部车辆';
const source = String(timeMonitorFilters.protocol ?? '').trim() || '全部数据通道';
const dateFrom = String(timeMonitorFilters.dateFrom ?? '').trim();
const dateTo = String(timeMonitorFilters.dateTo ?? '').trim();
const range = dateFrom || dateTo ? `${dateFrom || '-'}${dateTo || '-'}` : '全部时间';
return `${vehicle} / ${source} / ${range}`;
})();
const customerTaskBoard = [
{
title: '先看全域在线',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: `地图有效定位 ${commandLocatedCount.toLocaleString()} 辆,优先确认车辆是否仍在上报。`,
color: 'green' as const,
primaryAction: '打开实时地图',
secondaryAction: '查看在线车辆',
onPrimary: () => onOpenMap({ online: 'online' }),
onSecondary: () => onOpenRealtime({ online: 'online' })
},
{
title: '再看重点车辆',
value: focusVehicle?.label ?? '暂无重点车辆',
detail: focusVehicle ? `${focusVehicle.reason}${focusVehicle.realtimeEvidence}` : '当前没有高优先级车辆,保持日常巡检。',
color: focusVehicle?.statusColor ?? 'green' as const,
primaryAction: '车辆服务',
secondaryAction: '复制处置卡',
onPrimary: () => focusVehicle?.lookupKey && onOpenVehicle(focusVehicle.lookupKey, focusVehicle.protocol),
onSecondary: () => copyFocusVehicleService(),
disabled: !focusVehicle?.lookupKey
},
{
title: '按时间窗复盘',
value: taskTimeMonitorSummary,
detail: '同一时间窗贯穿轨迹、统计、原始记录和告警,不需要跨页面重复输入。',
color: 'blue' as const,
primaryAction: '轨迹回放',
secondaryAction: '统计查询',
onPrimary: () => openTimeMonitorHistory(),
onSecondary: () => openTimeMonitorMileage()
},
{
title: '导出证据数据',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '面向 BI、客户复盘和异常定位按车辆、时间、字段裁剪导出。',
color: 'blue' as const,
primaryAction: '历史导出',
secondaryAction: '导出概览',
onPrimary: () => openTimeMonitorRaw(),
onSecondary: () => exportDashboardSnapshot()
},
{
title: '闭环告警通知',
value: `${formatCount(summary?.issueVehicles)} 告警车辆`,
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无高优先级告警。',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
primaryAction: '告警事件',
secondaryAction: '复制交接',
onPrimary: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {}),
onSecondary: () => copyOperationsHandoff()
}
];
const capabilities = [
{
title: '实时地图',
status: `在线态势 ${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)}`,
color: 'green' as const,
description: '以高德地图为主视角查看车辆在线分布、定位有效性、来源一致性和地图接入状态。',
action: '打开地图',
onClick: () => onOpenMap({ online: 'online' })
},
{
title: '实时监控',
status: `在线 ${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)}`,
color: 'green' as const,
description: '按车辆聚合最新位置、在线状态和多来源实时字段,支持实时地图查看。',
action: '查看在线车辆',
onClick: () => onOpenRealtime({ online: 'online' })
},
{
title: '轨迹回放',
status: `有效定位 ${formatCount(commandLocatedCount)}`,
color: commandLocatedCount > 0 ? 'blue' as const : 'grey' as const,
description: '围绕车辆回放历史位置轨迹,结合高德线路和原始记录定位异常。',
action: '打开轨迹',
onClick: () => onOpenHistory()
},
{
title: '历史数据查询',
status: `今日帧 ${formatCount(summary?.frameToday)}`,
color: 'blue' as const,
description: '查询位置历史和原始记录字段,为车辆问题复盘提供依据。',
action: '查询历史',
onClick: () => onOpenHistory({ tab: 'raw' })
},
{
title: '告警事件触发与通知',
status: `告警 ${formatCount(summary?.issueVehicles)}`,
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
description: '围绕断链、无来源、VIN 缺失、字段缺失形成告警和通知闭环。',
action: '查看告警',
onClick: () => onOpenQuality()
},
{
title: '统计查询',
status: `统计车辆 ${formatCount(serviceSummary?.totalVehicles)}`,
color: 'green' as const,
description: '提供里程等运营统计查询,保证区间总值与每日统计口径闭合。',
action: '查看统计',
onClick: () => onOpenMileage()
}
];
const vehicleServicePrinciples = [
{
title: '一车一档',
value: formatCount(serviceSummary?.totalVehicles),
detail: '以 VIN 为主对象沉淀车牌、手机号、OEM、绑定状态和来源覆盖。'
},
{
title: '一车一实时',
value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles),
detail: '实时状态合并位置、在线、速度、SOC、里程和来源新鲜度。'
},
{
title: '一车一轨迹',
value: formatCount(commandLocatedCount),
detail: '轨迹回放围绕车辆展开,协议来源只作为证据过滤条件。'
},
{
title: '一车一统计',
value: formatCount(serviceSummary?.totalVehicles),
detail: '统计指标按车辆口径闭合,再回溯到来源证据。'
}
];
const workflowSteps = [
{
title: '接入巡检',
value: unhealthyLinkCount > 0 ? `${unhealthyLinkCount.toLocaleString()} 项关注` : '链路正常',
color: unhealthyLinkCount > 0 ? 'orange' as const : 'green' as const,
detail: '检查平台转发、消息队列、缓存和存储等关键链路。',
action: '查看告警',
onClick: () => onOpenQuality()
},
{
title: '实时态势',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
color: 'green' as const,
detail: '确认车辆是否在线、是否有有效坐标以及来源是否完整。',
action: '实时监控',
onClick: () => onOpenRealtime({ online: 'online' })
},
{
title: '轨迹复盘',
value: `${formatCount(summary?.activeToday)} 活跃`,
color: 'blue' as const,
detail: '按车辆进入历史轨迹,复盘位置、速度、里程和断点。',
action: '轨迹回放',
onClick: () => onOpenHistory()
},
{
title: '历史证据',
value: `${formatCount(summary?.frameToday)}`,
color: 'blue' as const,
detail: '查询原始记录和字段明细,形成可追溯的数据依据。',
action: '历史查询',
onClick: () => onOpenHistory({ tab: 'raw' })
},
{
title: '告警事件',
value: `${formatCount(summary?.issueVehicles)} 车辆`,
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
detail: '把断链、缺 VIN、字段缺失等问题进入通知和处置队列。',
action: '告警事件',
onClick: () => onOpenQuality()
},
{
title: '统计复核',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
color: 'green' as const,
detail: '核对统计查询口径,保证区间统计和每日统计闭合。',
action: '统计查询',
onClick: () => onOpenMileage()
}
];
const mapReadiness = [
{
title: '高德 Web JS',
value: amapConfigured ? '已配置' : '待配置',
color: amapConfigured ? 'green' as const : 'orange' as const,
detail: amapConfigured ? '实时监控和轨迹回放可加载高德底图。' : '缺少 Web JS Key 时降级为坐标预览。'
},
{
title: '安全代理',
value: amapSecurityProxyEnabled ? '已启用' : '未启用',
color: amapSecurityProxyEnabled ? 'green' as const : 'orange' as const,
detail: amapSecurityProxyEnabled ? '安全码由服务端代理追加,不下发到浏览器。' : '生产环境建议启用 /_AMapService。'
},
{
title: '服务端 API',
value: amapApiConfigured ? '已配置' : '待配置',
color: amapApiConfigured ? 'green' as const : 'orange' as const,
detail: amapApiConfigured ? '逆地理、路线、围栏等服务端地图能力可扩展。' : '后端地图服务会降级。'
},
{
title: '安全码暴露',
value: amapSecurityCodeExposed ? '需处理' : '未暴露',
color: amapSecurityCodeExposed ? 'red' as const : 'green' as const,
detail: amapSecurityCodeExposed ? '浏览器端可见安全码,需要切换到安全代理。' : '符合生产密钥收敛要求。'
}
];
const mapWorkItems = [
{
title: '实时监控',
value: `${commandLocatedCount.toLocaleString()} 个有效坐标`,
detail: '在线车辆进入地图态势,点击车辆后继续查看车辆服务和实时字段。',
action: '实时地图',
onClick: () => onOpenMap({ online: 'online' })
},
{
title: '轨迹回放',
value: `${formatCount(summary?.activeToday)} 今日活跃`,
detail: '按车辆和时间窗回放历史位置,支持打开高德线路和原始记录。',
action: '轨迹回放',
onClick: () => onOpenHistory()
},
{
title: '历史证据',
value: `${formatCount(summary?.frameToday)} 今日帧`,
detail: '地图点位异常时直接回查位置历史、解析字段和原始帧。',
action: '历史查询',
onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' })
},
{
title: '告警通知',
value: `${formatCount(summary?.issueVehicles)} 告警车辆`,
detail: '断链、无来源、坐标异常进入告警队列并触发通知闭环。',
action: '告警事件',
onClick: () => onOpenQuality()
}
];
const operationWorkbench = [
{
title: '实时监控',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
meta: `有效定位 ${commandLocatedCount.toLocaleString()}`,
color: 'green' as const,
detail: '面向值班人员确认车辆是否在线、坐标是否有效、来源是否完整。',
actions: [
{ label: '打开实时地图', onClick: () => onOpenMap({ online: 'online' }) },
{ label: '查看在线车辆', onClick: () => onOpenRealtime({ online: 'online' }) }
]
},
{
title: '轨迹回放',
value: `${formatCount(summary?.activeToday)} 今日活跃`,
meta: '高德轨迹证据',
color: 'blue' as const,
detail: '按车辆回看位置、速度、里程和断点,用于定位异常复盘。',
actions: [
{ label: '打开轨迹回放', onClick: () => onOpenHistory() },
{ label: '查历史明细', onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' }) }
]
},
{
title: '历史数据查询',
value: `${formatCount(summary?.frameToday)} 今日帧`,
meta: '字段裁剪与导出',
color: 'blue' as const,
detail: '查询位置历史、历史明细和字段明细,为车辆服务提供可追溯依据。',
actions: [
{ label: '查询历史数据', onClick: () => onOpenHistory({ tab: 'location' }) },
{ label: '查询解析字段', onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' }) }
]
},
{
title: '告警事件与通知',
value: `${formatCount(summary?.issueVehicles)} 告警车辆`,
meta: highPriorityIssue ? `${highPriorityIssue.severity === 'error' ? 'P0' : 'P1'} ${qualityIssueLabel(highPriorityIssue.issueType)}` : '暂无高优先级',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
detail: '把断链、无来源、VIN 缺失、字段缺失转成可通知、可闭环的事件。',
actions: [
{ label: '查看告警事件', onClick: () => onOpenQuality() },
{ label: '处理最高优先级', onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {}) }
]
},
{
title: '统计查询',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆口径`,
meta: '区间闭合复核',
color: 'green' as const,
detail: '统计指标先按车辆口径闭合,再回溯轨迹和原始记录。',
actions: [
{ label: '打开统计查询', onClick: () => onOpenMileage() },
{ label: '查看车辆中心', onClick: () => onOpenVehicles({}) }
]
}
];
const timeMonitorScope = (values = timeMonitorFilters) => {
const normalized: Record<string, string> = {};
for (const key of ['keyword', 'protocol', 'dateFrom', 'dateTo'] as const) {
const value = String(values[key] ?? '').trim();
if (value) {
normalized[key] = value;
}
}
return normalized;
};
const openTimeMonitorHistory = () => {
onOpenHistory(timeMonitorScope());
};
const openTimeMonitorMileage = () => {
onOpenMileage(timeMonitorScope());
};
const openTimeMonitorRaw = () => {
onOpenHistory({ ...timeMonitorScope(), tab: 'raw', includeFields: 'true' });
};
const openTimeMonitorAlerts = () => {
const scope = timeMonitorScope();
onOpenQuality({
...(scope.keyword ? { keyword: scope.keyword } : {}),
...(scope.protocol ? { protocol: scope.protocol } : {})
});
};
const timeMonitorSummary = (() => {
const scope = timeMonitorScope();
const vehicle = scope.keyword || '全部车辆';
const source = scope.protocol || '全部数据通道';
const range = scope.dateFrom || scope.dateTo ? `${scope.dateFrom || '-'}${scope.dateTo || '-'}` : '全部时间';
return `${vehicle} / ${source} / ${range}`;
})();
const timeMonitorReviewItems = [
{
label: '车辆范围',
value: timeMonitorScope().keyword || '全部车辆',
detail: timeMonitorScope().protocol || '全部数据通道',
color: timeMonitorScope().keyword ? 'green' as const : 'blue' as const
},
{
label: '复盘时间',
value: timeMonitorScope().dateFrom || timeMonitorScope().dateTo ? `${timeMonitorScope().dateFrom || '-'}${timeMonitorScope().dateTo || '-'}` : '全部时间',
detail: '同步带入轨迹、统计、历史证据和告警',
color: 'blue' as const
},
{
label: '轨迹证据',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: '位置、速度、里程断点同窗复核',
color: 'blue' as const
},
{
label: '统计证据',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '区间里程、日统计和异常点闭合',
color: 'green' as const
},
{
label: '风险事件',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: highPriorityIssue ? qualityIssueLabel(highPriorityIssue.issueType) : '暂无高优先级',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const
}
];
const timeMonitorScopeValue = timeMonitorScope();
const customerServiceScopeLabel = `${timeMonitorScopeValue.keyword || '全部车辆'} / ${timeMonitorScopeValue.protocol || '全部数据通道'} / ${timeMonitorScopeValue.dateFrom || '-'}${timeMonitorScopeValue.dateTo || '-'}`;
const usesCustomerTimeWindow = Boolean(
(customerTimeWindow?.dateFrom || customerTimeWindow?.dateTo)
&& customerTimeWindow?.dateFrom === timeMonitorScopeValue.dateFrom
&& customerTimeWindow?.dateTo === timeMonitorScopeValue.dateTo
);
const timeMonitorWindowLabel = usesCustomerTimeWindow
? naturalDayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)
: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo);
const timeMonitorChecklist = [
{
label: '范围可解释',
value: timeMonitorScopeValue.keyword ? '单车聚焦' : '全域巡检',
detail: `${timeMonitorScopeValue.protocol || '全部数据通道'} / ${timeMonitorWindowLabel}`,
color: timeMonitorScopeValue.dateFrom && timeMonitorScopeValue.dateTo ? 'green' as const : 'orange' as const
},
{
label: '轨迹可复盘',
value: `${formatCount(summary?.activeToday)} 活跃车辆`,
detail: '可进入轨迹页核对位置、速度、里程断点和停驶片段。',
color: (summary?.activeToday ?? 0) > 0 ? 'green' as const : 'orange' as const
},
{
label: '数据可导出',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '历史查询默认带原始记录与解析字段,支持按字段缩小导出体积。',
color: (summary?.frameToday ?? 0) > 0 ? 'blue' as const : 'orange' as const
},
{
label: '异常可闭环',
value: `${formatCount(summary?.issueVehicles)} 告警车辆`,
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '当前没有高优先级告警。',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const
}
];
const timeMonitorHasRange = Boolean(timeMonitorScopeValue.dateFrom && timeMonitorScopeValue.dateTo);
const timeMonitorHasTrajectory = (summary?.activeToday ?? 0) > 0;
const timeMonitorHasHistory = (summary?.frameToday ?? 0) > 0;
const timeMonitorHasAlerts = (summary?.issueVehicles ?? 0) > 0;
const timeMonitorDeliveryStatus = !timeMonitorHasRange
? { label: '待补时间', color: 'orange' as const, detail: '开始和结束时间不完整,交付前先锁定复盘窗口。' }
: !timeMonitorHasHistory
? { label: '证据不足', color: 'red' as const, detail: '当前没有历史证据,无法支撑客户复盘或导出。' }
: timeMonitorHasAlerts
? { label: '带风险交付', color: 'orange' as const, detail: '存在告警车辆,交付时需要附带告警闭环和原始证据。' }
: { label: '可交付', color: 'green' as const, detail: '时间窗、轨迹、历史证据和统计入口已准备好。' };
const timeMonitorDeliveryActions = [
{
label: '客户结论',
value: timeMonitorDeliveryStatus.label,
detail: timeMonitorDeliveryStatus.detail,
action: '复制复盘包',
color: timeMonitorDeliveryStatus.color,
onClick: () => copyTimeMonitorReviewPackage()
},
{
label: '证据准备',
value: timeMonitorHasTrajectory && timeMonitorHasHistory ? '证据可用' : '需要补证',
detail: `轨迹 ${formatCount(summary?.activeToday)} 活跃 / 历史 ${formatCount(summary?.frameToday)} 帧。`,
action: timeMonitorHasHistory ? '历史导出' : '查历史',
color: timeMonitorHasHistory ? 'blue' as const : 'orange' as const,
onClick: openTimeMonitorRaw
},
{
label: '异常说明',
value: timeMonitorHasAlerts ? `${formatCount(summary?.issueVehicles)} 告警` : '无高风险',
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '当前没有需要额外说明的高优先级告警。',
action: '告警复盘',
color: timeMonitorHasAlerts ? 'orange' as const : 'green' as const,
onClick: openTimeMonitorAlerts
}
];
const copyTimeMonitorReviewPackage = () => {
const scope = timeMonitorScope();
const rawFilters = { ...scope, tab: 'raw', includeFields: 'true' };
const lines = [
'【客户时间窗复盘包】',
`范围:${timeMonitorSummary}`,
`交付结论:${timeMonitorDeliveryStatus.label}${timeMonitorDeliveryStatus.detail}`,
`时间窗判定:${dayRangeText(scope.dateFrom, scope.dateTo)}`,
'平台能力:实时地图 / 轨迹回放 / 统计查询 / 历史数据查询 / 告警通知',
`车辆范围:${scope.keyword || '全部车辆'}`,
`数据通道:${scope.protocol || '全部数据通道'}`,
`时间范围:${scope.dateFrom || '-'}${scope.dateTo || '-'}`,
`轨迹证据:今日活跃 ${formatCount(summary?.activeToday)},用于回放位置、速度、里程断点`,
`统计证据:车辆口径 ${formatCount(serviceSummary?.totalVehicles)},用于区间里程和日统计闭合`,
`历史证据:今日数据 ${formatCount(summary?.frameToday)},用于原始记录与解析字段复核`,
`告警事件:${formatCount(summary?.issueVehicles)} 辆车存在告警`,
'交付核对:',
...timeMonitorChecklist.map((item, index) => `${index + 1}. ${item.label}${item.value}${item.detail}`),
'下一步动作:',
...timeMonitorDeliveryActions.map((item, index) => `${index + 1}. ${item.label}${item.value}${item.detail}`),
`轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
`统计查询:${appURL(buildAppHash({ page: 'mileage', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
`历史数据查询:${appURL(buildAppHash({ page: 'history-query', keyword: scope.keyword, protocol: scope.protocol, filters: rawFilters }))}`,
`告警通知:${appURL(buildAppHash({ page: 'alert-events', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
`实时地图:${appURL(buildAppHash({ page: 'map', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`
];
copyText(lines.join('\n'), '时间窗复盘包');
};
const timeMonitorWorkbench = [
{
label: '轨迹回放',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: '查看时间窗内车辆位置、速度、里程和断点。',
action: '打开轨迹',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
label: '统计查询',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '用同一范围核对区间里程和日统计闭合。',
action: '查看统计',
color: 'green' as const,
onClick: openTimeMonitorMileage
},
{
label: '历史证据',
value: `${formatCount(summary?.frameToday)}`,
detail: '按车辆、时间和字段裁剪导出原始记录与解析字段。',
action: '导出证据',
color: 'blue' as const,
onClick: openTimeMonitorRaw
},
{
label: '告警复盘',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: '查看该车辆或来源在当前范围内的风险事件。',
action: '查看告警',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: openTimeMonitorAlerts
}
];
const timeWindowServicePathItems = [
{
step: '1',
label: '锁定范围',
value: timeMonitorWindowLabel,
detail: timeMonitorScopeValue.keyword ? `单车 ${timeMonitorScopeValue.keyword}` : '先确认车辆或车辆池,再应用同一时间窗。',
action: '应用时间窗',
color: timeMonitorHasRange ? 'green' as const : 'orange' as const,
onClick: () => setTimeMonitorFilters(timeMonitorScopeValue)
},
{
step: '2',
label: '回放轨迹',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: '查看时间窗内位置、速度、停驶和里程断点。',
action: '轨迹回放',
color: timeMonitorHasTrajectory ? 'blue' as const : 'orange' as const,
onClick: openTimeMonitorHistory
},
{
step: '3',
label: '核对里程',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '用同一范围核对区间里程、日统计和 BI 口径。',
action: '统计查询',
color: 'green' as const,
onClick: openTimeMonitorMileage
},
{
step: '4',
label: '导出证据',
value: `${formatCount(summary?.frameToday)}`,
detail: '导出轨迹、原始记录和解析字段,形成客户证据包。',
action: '数据导出',
color: timeMonitorHasHistory ? 'blue' as const : 'orange' as const,
onClick: openTimeMonitorRaw
},
{
step: '5',
label: '告警说明',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '没有高优先级告警时,交付时说明当前无异常。',
action: '告警通知',
color: timeMonitorHasAlerts ? 'orange' as const : 'green' as const,
onClick: openTimeMonitorAlerts
}
];
const timeWindowNextPrimary = !timeMonitorHasRange
? '锁定范围'
: timeMonitorHasAlerts
? '告警复盘'
: timeMonitorHasHistory
? '历史导出'
: timeMonitorHasTrajectory
? '轨迹回放'
: '应用时间窗';
const runTimeWindowPrimaryAction = () => {
if (!timeMonitorHasRange) {
setTimeMonitorFilters(timeMonitorScopeValue);
return;
}
if (timeMonitorHasAlerts) {
openTimeMonitorAlerts();
return;
}
if (timeMonitorHasHistory) {
openTimeMonitorRaw();
return;
}
if (timeMonitorHasTrajectory) {
openTimeMonitorHistory();
return;
}
setTimeMonitorFilters(timeMonitorScopeValue);
};
const timeWindowNextItems = [
{
label: '首要动作',
value: timeWindowNextPrimary,
detail: timeMonitorHasAlerts ? '存在告警车辆,先给客户解释风险与处置闭环。' : timeMonitorHasHistory ? '历史证据可用,优先导出原始记录与解析字段。' : '先锁定车辆和时间范围。',
color: timeMonitorHasAlerts ? 'orange' as const : timeMonitorHasHistory ? 'blue' as const : 'orange' as const,
onClick: runTimeWindowPrimaryAction
},
{
label: '交付状态',
value: timeMonitorDeliveryStatus.label,
detail: timeMonitorDeliveryStatus.detail,
color: timeMonitorDeliveryStatus.color,
onClick: () => copyTimeMonitorReviewPackage()
},
{
label: '证据状态',
value: `${formatCount(summary?.frameToday)}`,
detail: `${formatCount(summary?.activeToday)} 活跃车辆,可核对轨迹与历史明细。`,
color: timeMonitorHasHistory ? 'blue' as const : 'orange' as const,
onClick: openTimeMonitorRaw
},
{
label: '服务范围',
value: timeMonitorScopeValue.keyword ? '单车聚焦' : '全域巡检',
detail: `${timeMonitorScopeValue.keyword || '全部车辆'} / ${timeMonitorScopeValue.protocol || '全部数据通道'} / ${timeMonitorWindowLabel}`,
color: timeMonitorScopeValue.keyword ? 'green' as const : 'blue' as const,
onClick: () => setTimeMonitorFilters(timeMonitorScopeValue)
}
];
const timeWindowNextActions = [
{
label: '处理告警',
action: '告警复盘',
color: timeMonitorHasAlerts ? 'orange' as const : 'green' as const,
disabled: false,
onClick: openTimeMonitorAlerts
},
{
label: '导出证据',
action: '历史导出',
color: timeMonitorHasHistory ? 'blue' as const : 'orange' as const,
disabled: false,
onClick: openTimeMonitorRaw
},
{
label: '核对里程',
action: '统计查询',
color: 'green' as const,
disabled: false,
onClick: openTimeMonitorMileage
},
{
label: '复制复盘',
action: '交付包',
color: timeMonitorDeliveryStatus.color,
disabled: false,
onClick: () => copyTimeMonitorReviewPackage()
}
];
const dedicatedTimeMonitorActions = [
{
label: '轨迹回放',
value: timeMonitorWindowLabel,
detail: '按同一辆车和时间窗回放位置、速度、停留和里程断点。',
color: timeMonitorHasTrajectory ? 'blue' as const : 'orange' as const,
onClick: openTimeMonitorHistory
},
{
label: '统计查询',
value: timeMonitorWindowLabel,
detail: '用同一时间窗核对区间里程、日报闭合和异常点。',
color: 'green' as const,
onClick: openTimeMonitorMileage
},
{
label: '历史导出',
value: `${formatCount(summary?.frameToday)}`,
detail: '导出历史位置、原始记录和字段证据,形成客户可复核数据包。',
color: timeMonitorHasHistory ? 'blue' as const : 'orange' as const,
onClick: openTimeMonitorRaw
},
{
label: '告警复盘',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: '用同一时间窗解释异常车辆、通知策略和恢复验收。',
color: timeMonitorHasAlerts ? 'orange' as const : 'green' as const,
onClick: openTimeMonitorAlerts
}
];
const dataFlowStages = [
{
stage: '01',
title: '车辆接入',
owner: '接入网关',
evidence: `32960 / 808 / MQTT连接 ${formatCount(opsHealth?.activeConnections)}`,
detail: '只接收真实实时数据帧和必要鉴权注册帧,接入来源最终归并到车辆服务。',
action: '运维质量',
onClick: () => onOpenQuality()
},
{
stage: '02',
title: '统一解析',
owner: '字段映射',
evidence: `今日帧 ${formatCount(summary?.frameToday)}Kafka Lag ${formatLag(summary?.kafkaLag)}`,
detail: '一次解析生成扁平字段、原始证据和车辆身份关联,避免三处重复解析。',
action: '历史字段',
onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' })
},
{
stage: '03',
title: '实时投影',
owner: 'Redis KV',
evidence: `在线 Key ${formatCount(opsHealth?.redisOnlineKeys)},在线车辆 ${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)}`,
detail: '快速写入车辆最新状态、在线 TTL 和全量协议字段,支撑 0-1 秒监控。',
action: '实时监控',
onClick: () => onOpenRealtime({ online: 'online' })
},
{
stage: '04',
title: '车辆归并',
owner: 'Vehicle Service',
evidence: `多源 ${formatCount(serviceSummary?.multiSourceVehicles)},单源 ${formatCount(serviceSummary?.singleSourceVehicles)}`,
detail: '以 VIN 合并来源证据,车牌、手机号和 OEM 只是车辆服务的检索键。',
action: '车辆中心',
onClick: () => onOpenVehicles({})
},
{
stage: '05',
title: '历史证据',
owner: 'TDengine / MySQL',
evidence: `轨迹 ${formatCount(summary?.activeToday)} 活跃,原始记录 ${formatCount(summary?.frameToday)}`,
detail: '位置、原始记录和解析字段可分页查询,并能回跳轨迹、车辆服务和告警。',
action: '轨迹回放',
onClick: () => onOpenHistory()
},
{
stage: '06',
title: '运营闭环',
owner: '告警 / 统计',
evidence: `告警 ${formatCount(summary?.issueVehicles)},统计车辆 ${formatCount(serviceSummary?.totalVehicles)}`,
detail: '断链、缺字段、里程异常和离线时长进入通知、统计和复核工作流。',
action: '统计查询',
onClick: () => onOpenMileage()
}
];
const scenarioNavigation = [
{
title: '实时监控',
objective: '确认车辆是否在线、位置是否可信、必要接入来源是否齐全。',
evidence: `Redis 在线态 / 最新定位 / 有效坐标 ${commandLocatedCount.toLocaleString()}`,
sla: '目标 0-1 秒内进入实时视图',
primaryAction: '实时监控',
secondaryAction: '地图态势',
onPrimary: () => onOpenRealtime({ online: 'online' }),
onSecondary: () => onOpenMap({ online: 'online' })
},
{
title: '轨迹回放',
objective: '按车辆复盘位置、速度、里程和断点,定位平台转发或车辆异常。',
evidence: `高德轨迹底座 / 今日活跃 ${formatCount(summary?.activeToday)}`,
sla: '支持按车辆和时间窗快速回放',
primaryAction: '轨迹回放',
secondaryAction: '原始记录',
onPrimary: () => onOpenHistory(),
onSecondary: () => onOpenHistory({ tab: 'raw', includeFields: 'true' })
},
{
title: '历史数据查询',
objective: '围绕车辆查询位置、原始记录和字段明细,给 BI、运维和业务复盘提供依据。',
evidence: `今日数据 ${formatCount(summary?.frameToday)} / 支持字段裁剪与导出`,
sla: '优先返回必要字段,避免大 JSON 拖慢查询',
primaryAction: '历史查询',
secondaryAction: '字段证据',
onPrimary: () => onOpenHistory({ tab: 'location' }),
onSecondary: () => onOpenHistory({ tab: 'raw', includeFields: 'true' })
},
{
title: '告警事件触发和通知',
objective: '把断链、无来源、VIN 缺失、字段缺失转换为可通知、可闭环事件。',
evidence: `告警车辆 ${formatCount(summary?.issueVehicles)} / 最高优先级 ${highPriorityIssue ? qualityIssueLabel(highPriorityIssue.issueType) : '暂无'}`,
sla: 'P0 进入通知队列,超时升级',
primaryAction: '告警事件',
secondaryAction: '通知规则',
onPrimary: () => onOpenQuality(),
onSecondary: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
},
{
title: '统计查询',
objective: '按车辆口径查询里程等指标,保证区间统计和日统计可闭合。',
evidence: `车辆口径 ${formatCount(serviceSummary?.totalVehicles)} / 区间统计可追溯轨迹证据`,
sla: '统计值必须能追溯轨迹和原始记录',
primaryAction: '统计查询',
secondaryAction: '车辆中心',
onPrimary: () => onOpenMileage(),
onSecondary: () => onOpenVehicles({})
}
];
const copyOperationsHandoff = () => {
const unhealthyLinks = (summary?.linkHealth ?? []).filter((item) => item.status !== 'ok');
const priorityAction = serviceActionQueue[0];
const highPriorityLookupKey = highPriorityLookup?.key;
const highPriorityFilters = highPriorityEvidenceFilters;
const lines = [
'【车辆服务中台客户交付摘要】',
`在线车辆:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} / ${formatCount(serviceSummary?.totalVehicles)}`,
`今日活跃:${formatCount(summary?.activeToday)}`,
`今日帧量:${formatCount(summary?.frameToday)}`,
`实时地图有效定位:${commandLocatedCount.toLocaleString()}`,
`告警车辆:${formatCount(summary?.issueVehicles)}`,
`高德地图Web JS ${amapConfigured ? '已配置' : '待配置'} / 服务端 API ${amapApiConfigured ? '已配置' : '待配置'} / 安全代理 ${amapSecurityProxyEnabled ? '已启用' : '未启用'}`,
`优先动作:${priorityAction ? `${priorityAction.label} ${priorityAction.count.toLocaleString()}辆 - ${priorityAction.detail}` : '暂无待办'}`,
highPriorityIssue ? `最高告警:${highPriorityIssue.severity === 'error' ? 'P0' : 'P1'} ${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)} / ${highPriorityIssue.lastSeen || '-'}` : '最高告警:暂无',
highPriorityIssue ? `告警详情:${highPriorityIssue.detail || '-'}` : '',
`实时监控:${appURL(buildAppHash({ page: 'realtime', filters: { online: 'online' } }))}`,
`实时地图:${appURL(buildAppHash({ page: 'map', filters: { online: 'online' } }))}`,
`轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: highPriorityFilters?.keyword, protocol: highPriorityFilters?.protocol, filters: highPriorityFilters }))}`,
`原始记录:${appURL(buildAppHash({ page: 'history-query', keyword: highPriorityFilters?.keyword, protocol: highPriorityFilters?.protocol, filters: { ...highPriorityFilters, tab: 'raw', includeFields: 'true' } }))}`,
`告警事件:${appURL(buildAppHash({ page: 'alert-events', filters: highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {} }))}`,
`统计查询:${appURL(buildAppHash({ page: 'mileage', keyword: highPriorityLookupKey, protocol: highPriorityIssue?.protocol }))}`,
`车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: highPriorityLookupKey, protocol: highPriorityIssue?.protocol }))}`
].filter(Boolean);
copyText(lines.join('\n'), '客户交付摘要');
};
const copyScenarioBlueprint = () => {
const lines = [
'【车辆服务中台功能蓝图】',
'定位32960 / 808 / 宇通 MQTT 都只是车辆接入来源,最终围绕一辆车提供实时、轨迹、历史、告警、统计能力。',
`高德地图Web JS ${amapConfigured ? '已配置' : '待配置'};服务端 API ${amapApiConfigured ? '已配置' : '待配置'};安全代理 ${amapSecurityProxyEnabled ? '已启用' : '未启用'};安全码${amapSecurityCodeExposed ? '已暴露' : '未暴露'}`,
`车辆规模:${formatCount(serviceSummary?.totalVehicles)};在线:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)};告警:${formatCount(summary?.issueVehicles)}`,
'',
...scenarioNavigation.map((item, index) => [
`${index + 1}. ${item.title}`,
` 目标:${item.objective}`,
` 证据:${item.evidence}`,
` SLA${item.sla}`
].join('\n')),
'',
`入口:${appURL(buildAppHash({ page: 'dashboard' }))}`
];
copyText(lines.join('\n'), '功能蓝图');
};
const copyDataFlowBlueprint = () => {
const lines = [
'【车辆服务中台数据流转图】',
'原则32960 / 808 / 宇通 MQTT 是车辆接入来源,最终都归并为同一辆车的车辆服务。',
`运行态:连接 ${formatCount(opsHealth?.activeConnections)};在线 Key ${formatCount(opsHealth?.redisOnlineKeys)}Kafka Lag ${formatLag(summary?.kafkaLag)};今日帧 ${formatCount(summary?.frameToday)}`,
'',
...dataFlowStages.map((item) => [
`${item.stage}. ${item.title} / ${item.owner}`,
` 证据:${item.evidence}`,
` 说明:${item.detail}`
].join('\n')),
'',
`实时监控:${appURL(buildAppHash({ page: 'realtime', filters: { online: 'online' } }))}`,
`车辆中心:${appURL(buildAppHash({ page: 'vehicles' }))}`,
`轨迹回放:${appURL(buildAppHash({ page: 'history' }))}`,
`历史查询:${appURL(buildAppHash({ page: 'history-query', filters: { tab: 'raw', includeFields: 'true' } }))}`,
`告警事件:${appURL(buildAppHash({ page: 'alert-events' }))}`,
`统计查询:${appURL(buildAppHash({ page: 'mileage' }))}`,
`运维质量:${appURL(buildAppHash({ page: 'ops-quality' }))}`
];
copyText(lines.join('\n'), '数据流转图');
};
const copyFocusVehicleService = () => {
if (!focusVehicle) {
Toast.warning('当前没有重点车辆服务可复制');
return;
}
const commonFilters = { keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol ?? '' };
const lines = [
'【重点车辆服务处置卡】',
`车辆:${focusVehicle.label}`,
`原因:${focusVehicle.reason}`,
`服务状态:${focusVehicle.statusLabel}`,
`实时证据:${focusVehicle.realtimeEvidence}`,
`轨迹证据:${focusVehicle.historyEvidence}`,
`告警证据:${focusVehicle.alertEvidence}`,
`统计证据:${focusVehicle.statisticEvidence}`,
`车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol }))}`,
`实时监控:${appURL(buildAppHash({ page: 'realtime', keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol }))}`,
`轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol }))}`,
`原始记录:${appURL(buildAppHash({ page: 'history-query', keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol, filters: { ...commonFilters, tab: 'raw', includeFields: 'true' } }))}`,
`统计查询:${appURL(buildAppHash({ page: 'mileage', keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol }))}`,
`告警事件:${appURL(buildAppHash({ page: 'alert-events', keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol, filters: focusVehicle.issueType ? { issueType: focusVehicle.issueType } : {} }))}`
];
copyText(lines.join('\n'), '重点车辆服务处置卡');
};
const buildDashboardSnapshotRows = () => {
const unhealthyLinks = (summary?.linkHealth ?? []).filter((item) => item.status !== 'ok');
const priorityAction = serviceActionQueue[0];
const rows: DashboardSnapshotRow[] = [
{
section: '车辆服务',
item: '在线车辆',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} / ${formatCount(serviceSummary?.totalVehicles)}`,
detail: '统一车辆服务在线态势'
},
{
section: '车辆服务',
item: '今日活跃',
value: formatCount(summary?.activeToday),
detail: '当天有有效上报或历史记录的车辆服务对象'
},
{
section: '实时监控',
item: '有效定位',
value: commandLocatedCount.toLocaleString(),
detail: `当前预览 ${locations.length.toLocaleString()} 条车辆实时数据`
},
{
section: '实时监控',
item: '来源异常',
value: commandDegradedCount.toLocaleString(),
detail: '当前预览中离线或来源不完整车辆'
},
{
section: '历史数据',
item: '今日帧量',
value: formatCount(summary?.frameToday),
detail: '历史查询和导出可按车辆、时间、字段裁剪'
},
{
section: '告警事件',
item: '告警车辆',
value: formatCount(summary?.issueVehicles),
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无最高优先级告警'
},
{
section: '地图能力',
item: '高德地图',
value: amapConfigured ? '已配置' : '待配置',
detail: '用于实时监控和轨迹回放地图底座'
},
{
section: '优先动作',
item: priorityAction?.label ?? '暂无待办',
value: priorityAction ? priorityAction.count.toLocaleString() : '0',
detail: priorityAction?.detail ?? '当前没有必须立即处理的车辆服务事项'
}
];
(serviceSummary?.serviceStatuses ?? summary?.serviceStatuses ?? []).forEach((item) => {
rows.push({
section: '服务状态',
item: item.title || serviceStatusTitle[item.status] || item.status,
value: item.count.toLocaleString(),
detail: serviceStatusTitle[item.status] || item.status
});
});
return rows;
};
const exportDashboardSnapshot = () => {
const rows = buildDashboardSnapshotRows();
if (rows.length === 0) {
Toast.warning('当前没有可导出的驾驶舱摘要');
return;
}
downloadCsv('dashboard-snapshot.csv', buildCsv(dashboardSnapshotColumns, rows));
Toast.success(`已导出 ${rows.length.toLocaleString()} 条驾驶舱摘要`);
};
const commandCenterItems = [
{
title: '全域车辆监控',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: `有效定位 ${commandLocatedCount.toLocaleString()} 辆,先从地图确认车辆分布、在线状态和最新位置。`,
color: 'green' as const,
primaryAction: '打开车辆地图',
secondaryAction: '在线车辆',
onPrimary: () => onOpenMap({ online: 'online' }),
onSecondary: () => onOpenVehicles({ online: 'online' })
},
{
title: '异常车辆闭环',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: highPriorityIssue ? `最高优先级:${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '当前没有高优先级告警,保持实时巡检。',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
primaryAction: '告警事件',
secondaryAction: '通知规则',
onPrimary: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {}),
onSecondary: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
},
{
title: '自定义时间复盘',
value: taskTimeMonitorSummary,
detail: '一个时间窗贯穿轨迹回放、统计查询、历史查询和告警复盘,适合客户问询与 BI 核对。',
color: 'blue' as const,
primaryAction: '轨迹复盘',
secondaryAction: '统计查询',
onPrimary: () => openTimeMonitorHistory(),
onSecondary: () => openTimeMonitorMileage()
},
{
title: '数据交付与导出',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '按车辆、时间、字段裁剪导出位置、原始记录和字段证据,交付给业务或客户复盘。',
color: 'blue' as const,
primaryAction: '历史导出',
secondaryAction: '导出概览',
onPrimary: () => openTimeMonitorRaw(),
onSecondary: () => exportDashboardSnapshot()
}
];
const customerServicePathItems = [
{
title: '找车',
value: formatCount(serviceSummary?.totalVehicles),
detail: '按 VIN、车牌、手机号或 OEM 进入车辆服务,协议来源只作为证据。',
action: '车辆中心',
color: 'blue' as const,
onClick: () => onOpenVehicles({})
},
{
title: '看位置',
value: `${commandLocatedCount.toLocaleString()} 有定位`,
detail: '优先打开实时地图,判断车辆是否在线、坐标是否有效、是否存在断链。',
action: '实时地图',
color: commandLocatedCount > 0 ? 'green' as const : 'orange' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
title: '复盘时间',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '同一个时间窗贯穿轨迹回放、统计查询、原始记录和告警复盘。',
action: '轨迹回放',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
title: '交付数据',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '按车辆、时间、字段裁剪导出位置历史、原始记录和字段明细。',
action: '查询导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
},
{
title: '闭环异常',
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()
}
];
const customerServiceJourneySteps = [
{
step: '1',
title: '选车',
value: formatCount(serviceSummary?.totalVehicles),
detail: '先用 VIN、车牌、手机号或 OEM 锁定服务对象。',
action: '车辆中心',
color: 'blue' as const,
onClick: () => onOpenVehicles({})
},
{
step: '2',
title: '实时看车',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: '打开地图看在线、坐标、最后上报和区域态势。',
action: '实时地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
step: '3',
title: '锁定时间窗',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '同一范围同步带入轨迹、统计、历史导出和告警。',
action: '自定义时间',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
step: '4',
title: '复盘统计',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: '用轨迹、位置历史和里程统计回答这段时间发生了什么。',
action: '轨迹统计',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
step: '5',
title: '交付闭环',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: '导出证据数据,并把异常车辆推到告警通知闭环。',
action: '导出通知',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: openTimeMonitorRaw
}
];
const copyCustomerServiceGuide = () => {
const scope = timeMonitorScope();
const rawFilters = { ...scope, tab: 'raw', includeFields: 'true' };
const lines = [
'【车辆客户服务说明】',
'服务对象:车辆,不是接入来源。',
`车辆规模:${formatCount(serviceSummary?.totalVehicles)},在线车辆:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)},有效定位:${commandLocatedCount.toLocaleString()}`,
`当前时间窗:${timeMonitorSummary}${dayRangeText(scope.dateFrom, scope.dateTo)}`,
`告警车辆:${formatCount(summary?.issueVehicles)},最高优先级:${highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无'}`,
'',
'客户服务路径:',
'1. 找车:先按 VIN、车牌、手机号或 OEM 锁定车辆。',
'2. 看位置:打开实时地图确认车辆在线、坐标和最新上报。',
'3. 复盘时间:同一时间窗查看轨迹、统计和告警。',
'4. 交付数据:按车辆、时间、字段导出位置历史、原始记录和字段明细。',
'5. 闭环异常:告警事件进入通知和处置流程。',
`车辆中心:${appURL(buildAppHash({ page: 'vehicles' }))}`,
`实时地图:${appURL(buildAppHash({ page: 'map', filters: { online: 'online' } }))}`,
`轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
`统计查询:${appURL(buildAppHash({ page: 'mileage', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
`历史数据:${appURL(buildAppHash({ page: 'history-query', keyword: scope.keyword, protocol: scope.protocol, filters: rawFilters }))}`,
`告警事件:${appURL(buildAppHash({ page: 'alert-events', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`
];
copyText(lines.join('\n'), '客户服务说明');
};
const customerCockpitMetrics = [
{
label: '车辆总数',
value: formatCount(serviceSummary?.totalVehicles),
detail: '以 VIN 聚合车牌、手机号、OEM 和来源证据',
color: 'blue' as const,
onClick: () => onOpenVehicles({})
},
{
label: '在线车辆',
value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles),
detail: vehicleServiceOnlineText(serviceSummary, summary),
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
label: '有效定位',
value: commandLocatedCount.toLocaleString(),
detail: '用于实时地图、轨迹回放和客户位置问询',
color: commandLocatedCount > 0 ? 'green' as const : 'orange' as const,
onClick: () => onOpenRealtime({ online: 'online' })
},
{
label: '告警车辆',
value: formatCount(summary?.issueVehicles),
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无高优先级告警',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
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: '实时看车',
description: '打开地图确认车辆在哪里、是否在线、最后上报是否新鲜。',
action: '实时地图',
evidence: `${commandLocatedCount.toLocaleString()} 辆有定位`,
onClick: () => onOpenMap({ online: 'online' })
},
{
title: '轨迹复盘',
description: '用同一辆车和同一时间窗回放位置、速度、里程断点。',
action: '轨迹回放',
evidence: `${formatCount(summary?.activeToday)} 今日活跃`,
onClick: openTimeMonitorHistory
},
{
title: '统计查询',
description: '按车辆口径核对区间里程、日统计和异常来源。',
action: '统计查询',
evidence: `${formatCount(serviceSummary?.totalVehicles)} 辆可统计`,
onClick: openTimeMonitorMileage
},
{
title: '数据交付',
description: '按车辆、时间和字段导出位置历史、原始记录和解析字段。',
action: '历史导出',
evidence: `${formatCount(summary?.frameToday)} 今日数据`,
onClick: openTimeMonitorRaw
},
{
title: '告警通知',
description: '将断链、离线、定位异常和字段缺失变成可通知事件。',
action: '告警事件',
evidence: `${formatCount(summary?.issueVehicles)} 告警车辆`,
onClick: () => onOpenQuality()
}
];
const modernFleetOperations = [
{
label: 'Live Map',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: `有效定位 ${commandLocatedCount.toLocaleString()} 辆,先确认车辆在哪里。`,
action: '打开地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
label: 'Route Replay',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: `时间窗 ${dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)},用于客户复盘。`,
action: '回放轨迹',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
label: 'Mileage',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '按车辆口径查询区间里程、日统计和异常断点。',
action: '查统计',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
label: 'Export',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '位置、原始记录和字段证据按车辆与时间裁剪交付。',
action: '导出数据',
color: 'blue' as const,
onClick: openTimeMonitorRaw
},
{
label: 'Alerts',
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 customerPlatformBlueprintItems = [
{
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: `${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 } : {})
},
{
label: '报表导出',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '按车辆、时间、字段裁剪轨迹、历史明细、统计和告警说明。',
action: '历史导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
},
{
label: '车辆健康',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '围绕车辆档案、在线状态、能耗字段和异常维护建立服务视图。',
action: '车辆中心',
color: 'blue' as const,
onClick: () => onOpenVehicles({})
}
];
const customerSavedViewItems = [
{
label: '在线车队视图',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: '默认进入车辆地图,按在线车辆、最新位置和定位有效性巡检。',
action: '打开地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
label: '轨迹日报模板',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: '按当前时间窗复用轨迹回放和历史位置查询,处理每日客户问询。',
action: '历史导出',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
label: '里程周报模板',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '按车辆口径复用里程统计,支撑周报、对账和 BI 复核。',
action: '统计查询',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
label: '告警订阅视图',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: '围绕断链、离线、定位异常和字段缺失形成通知闭环。',
action: '通知闭环',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
}
];
const tenSecondTriageItems = [
{
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 } : {})
},
{
label: '无数据车辆',
value: `${formatCount(serviceSummary?.noDataVehicles)}`,
detail: '优先确认平台转发、车辆是否在线以及是否有任何来源证据。',
action: '查车辆',
color: (serviceSummary?.noDataVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenVehicles({ serviceStatus: 'no_data' })
},
{
label: '身份未绑定',
value: `${formatCount(serviceSummary?.identityRequiredVehicles)}`,
detail: '已有上报但无法稳定归并到 VIN会影响地图、轨迹和统计聚合。',
action: '维护绑定',
color: (serviceSummary?.identityRequiredVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenVehicles({ serviceStatus: 'identity_required' })
}
];
const amapVehicleServiceItems = [
{
label: '实时看车',
value: `${commandLocatedCount.toLocaleString()} 有定位`,
detail: '高德底图承接车辆在线、最新位置、速度和最后上报时间。',
action: '打开地图',
color: commandLocatedCount > 0 ? 'green' as const : 'orange' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
label: '路线回放',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '同一车辆和时间窗回放路径,核对速度、停留、里程和断点。',
action: '轨迹回放',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
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 } : {})
},
{
label: '时间窗导出',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '按车辆、时间和字段导出位置、原始记录、解析字段和统计证据。',
action: '历史导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
}
];
const amapCapabilityItems = [
{
label: '实时底图',
value: amapConfigured ? '可用' : '降级',
detail: amapConfigured ? '车辆在线分布、最后位置和地图选车可直接使用高德底图。' : '未配置 Web JS Key 时保留坐标预览。',
color: amapConfigured ? 'green' as const : 'orange' as const
},
{
label: '轨迹路径',
value: amapConfigured ? '可回放' : '坐标线',
detail: amapConfigured ? '轨迹回放可叠加道路底图,辅助判断停留、绕行和断点。' : '缺少底图时只展示历史坐标证据。',
color: amapConfigured ? 'green' as const : 'orange' as const
},
{
label: '区域围栏',
value: amapApiConfigured ? '可扩展' : '待接入',
detail: amapApiConfigured ? '服务端 API 可承接围栏、区域、路线和位置服务扩展。' : '围栏和区域能力需要服务端地图 API。',
color: amapApiConfigured ? 'blue' as const : 'orange' as const
},
{
label: '地址解析',
value: amapSecurityProxyEnabled ? '代理可用' : '待代理',
detail: amapSecurityProxyEnabled ? '逆地理和路线请求可通过安全代理收敛密钥。' : '生产环境建议通过 /_AMapService 代理服务端能力。',
color: amapSecurityProxyEnabled ? 'green' as const : 'orange' as const
}
];
const customerQuestionActions = [
{
question: '这辆车现在在哪里?',
answer: `${commandLocatedCount.toLocaleString()} 辆有可用坐标`,
action: '打开地图',
color: commandLocatedCount > 0 ? 'green' as const : 'orange' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
question: '这段时间跑了多少公里?',
answer: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
action: '查里程',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
question: '能不能回放轨迹?',
answer: `${formatCount(summary?.activeToday)} 辆今日活跃`,
action: '轨迹回放',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
question: '数据能导出给客户吗?',
answer: `${formatCount(summary?.frameToday)} 条今日证据`,
action: '查询导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
},
{
question: '哪些车需要马上处理?',
answer: 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 customerProblemDesk = [
{
question: '现在车辆在哪里',
answer: `${commandLocatedCount.toLocaleString()} 辆有可用坐标,先进入实时地图确认分布和最新上报。`,
metric: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
action: '打开实时地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
question: '这段时间跑了多少',
answer: `${dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)},用同一时间窗核对区间里程和每日统计。`,
metric: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
action: '查统计查询',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
question: '轨迹能不能回放',
answer: `今日活跃 ${formatCount(summary?.activeToday)},可回放位置、速度、停驶和里程断点。`,
metric: `${formatCount(summary?.activeToday)} 活跃`,
action: '打开轨迹回放',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
question: '需要导出哪些证据',
answer: `今日数据 ${formatCount(summary?.frameToday)},按车辆、时间、字段裁剪导出位置和原始证据。`,
metric: `${formatCount(summary?.frameToday)} 数据`,
action: '查询历史导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
},
{
question: '哪些车需要通知处理',
answer: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无高优先级告警,保持日常巡检。',
metric: `${formatCount(summary?.issueVehicles)} 告警`,
action: '查看告警事件',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
}
];
const onlineVehicleCount = serviceSummary?.onlineVehicles ?? summary?.onlineVehicles ?? 0;
const missingLocationCount = Math.max(onlineVehicleCount - commandLocatedCount, 0);
const vehiclePriorityQueue = [
{
title: '告警待处理',
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 } : {})
},
{
title: '无有效定位',
value: `${missingLocationCount.toLocaleString()}`,
detail: '在线车辆缺少有效坐标时,客户无法完成地图看车和轨迹复盘。',
action: '打开地图',
color: missingLocationCount > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
title: '身份未绑定',
value: `${formatCount(serviceSummary?.identityRequiredVehicles)}`,
detail: '已有数据但无法归并到 VIN会影响车辆服务、统计和导出。',
action: '维护绑定',
color: (serviceSummary?.identityRequiredVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenVehicles({ serviceStatus: 'identity_required' })
},
{
title: '暂无数据',
value: `${formatCount(serviceSummary?.noDataVehicles)}`,
detail: '车辆没有形成实时、轨迹或历史证据,优先确认平台转发。',
action: '车辆列表',
color: (serviceSummary?.noDataVehicles ?? 0) > 0 ? 'red' as const : 'green' as const,
onClick: () => onOpenVehicles({ serviceStatus: 'no_data' })
}
];
const customerNextActions = [
{
level: (summary?.issueVehicles ?? 0) > 0 ? '优先' : '巡检',
title: (summary?.issueVehicles ?? 0) > 0 ? '先处理告警车辆' : '先查看在线车辆地图',
detail: (summary?.issueVehicles ?? 0) > 0
? (highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '存在告警车辆,建议先进入告警事件。')
: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 辆在线,${commandLocatedCount.toLocaleString()} 辆有定位。`,
action: (summary?.issueVehicles ?? 0) > 0 ? '进入告警' : '打开地图',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => (summary?.issueVehicles ?? 0) > 0 ? onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {}) : onOpenMap({ online: 'online' })
},
{
level: focusVehicle ? '单车' : '范围',
title: focusVehicle ? '复盘重点车辆' : '选择一辆车复盘',
detail: focusVehicle ? `${focusVehicle.label}${focusVehicle.reason}` : '从车辆中心或地图选择车辆后,进入轨迹、统计和历史证据。',
action: focusVehicle ? '车辆服务' : '车辆中心',
color: focusVehicle?.statusColor ?? 'blue' as const,
onClick: () => focusVehicle?.lookupKey ? onOpenVehicle(focusVehicle.lookupKey, focusVehicle.protocol) : onOpenVehicles({})
},
{
level: '时间窗',
title: '按同一范围交付数据',
detail: `${dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)},同步轨迹、统计、历史和告警。`,
action: '复制复盘包',
color: 'blue' as const,
onClick: copyTimeMonitorReviewPackage
}
];
const customerTrustSignals = [
{
label: '数据来源',
value: `${formatCount(serviceSummary?.multiSourceVehicles)} 多源 / ${formatCount(serviceSummary?.singleSourceVehicles)} 单源`,
detail: '来源只作为可信度证据,不作为客户主导航。'
},
{
label: '地图能力',
value: amapConfigured ? '高德地图可用' : '坐标预览',
detail: amapConfigured ? '支持实时看车和轨迹底图。' : '地图配置缺失时仍可查看坐标证据。'
},
{
label: '时间窗',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '同一时间范围贯穿轨迹、统计、历史和告警。'
}
];
const customerTaskNavigation = [
{
title: '看车在哪',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: `${commandLocatedCount.toLocaleString()} 辆有有效定位`,
action: '实时地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
title: '查这段时间',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '同窗查看轨迹和里程统计',
action: '轨迹统计',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
title: '导出数据',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '位置、原始记录和字段证据',
action: '历史导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
},
{
title: '处理告警',
value: `${formatCount(summary?.issueVehicles)} 告警车辆`,
detail: highPriorityIssue ? qualityIssueLabel(highPriorityIssue.issueType) : '当前无高优先级',
action: '告警事件',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
}
];
const customerDeliveryItems = [
{
title: '在线车辆交付',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: `地图有效定位 ${commandLocatedCount.toLocaleString()} 辆,先交付客户最关心的车辆当前位置和在线态势。`,
action: '实时地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
title: '时间窗复盘',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '同一时间范围同步到轨迹、统计、历史证据和告警复盘,避免客户反复给条件。',
action: '轨迹回放',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
title: '数据证据交付',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '按车辆、时间和字段裁剪导出位置、原始记录、解析字段和统计依据。',
action: '历史导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
},
{
title: '告警通知交付',
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 customerVehicleMonitorItems = [
{
label: '车辆在哪里',
value: `${commandLocatedCount.toLocaleString()} 辆有定位`,
detail: '打开地图查看在线车辆、最后上报、速度和坐标可信度。',
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: `${formatCount(serviceSummary?.totalVehicles)} 辆可统计`,
detail: '按车辆口径核对区间里程、日里程和异常记录。',
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 customerServiceCommandItems = [
{
label: '实时地图',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: `${commandLocatedCount.toLocaleString()} 辆有有效定位,优先回答车辆在哪里。`,
action: '进入地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
label: '轨迹回放',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: `${dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)},按车辆复盘路线、速度和里程断点。`,
action: '回放轨迹',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
label: '统计查询',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '按车辆口径核对区间里程、日统计和异常来源。',
action: '查统计',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
label: '时间窗监控',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '同一时间窗贯穿轨迹、统计、历史导出和告警复盘。',
action: '自定义时间',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
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 customerFleetGroupItems = [
{
label: '在线运营组',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: '给调度和客服复用:默认进入在线车辆地图,先回答客户车辆在哪里。',
action: '打开地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
label: '告警处置组',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: '给运维和业务负责人复用:只看需要通知、升级和验收的车辆。',
action: '告警通知',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
},
{
label: '身份维护组',
value: `${formatCount(serviceSummary?.identityRequiredVehicles)} 待绑定`,
detail: '给数据维护人员复用:补齐 VIN、车牌、手机号和客户可查身份。',
action: '维护车辆',
color: (serviceSummary?.identityRequiredVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenVehicles({ serviceStatus: 'identity_required' })
},
{
label: '报表交付组',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '给客户交付人员复用:按同一车辆范围导出历史、轨迹、字段和统计证据。',
action: '历史导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
}
];
const customerSlaItems = [
{
label: '断链发现',
value: '1 分钟内',
detail: 'Redis 在线状态按 1 分钟 TTL 形成准实时断链判断,优先确认车辆是否仍在上报。',
action: '在线状态',
color: 'green' as const,
onClick: () => onOpenRealtime({ online: 'offline' })
},
{
label: '异常通知',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} 进入通知和升级闭环。` : '暂无高优先级告警,保持例行巡检。',
action: '通知闭环',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
},
{
label: '恢复验收',
value: '轨迹/统计可查',
detail: '恢复不是只看链路 active还要确认实时、轨迹、里程统计和历史证据都能打开。',
action: '验收证据',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
label: '数据交付',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '按客户车辆和时间窗导出历史位置、原始记录、解析字段和统计证据。',
action: '导出证据',
color: 'blue' as const,
onClick: openTimeMonitorRaw
}
];
const vehicleHealthEnergyItems = [
{
label: '实时工况',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: '查看车辆最新速度、SOC、总里程、在线状态和多来源实时字段。',
action: '实时监控',
color: 'green' as const,
onClick: () => onOpenRealtime({ online: 'online' })
},
{
label: 'SOC/氢能字段',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '从历史字段中回查 SOC、燃料电池、氢耗和关键工况字段。',
action: '字段证据',
color: 'blue' as const,
onClick: () => onOpenHistory({ tab: 'fields', includeFields: 'true' })
},
{
label: '异常维护',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} 需要进入告警闭环。` : '暂无高优先级异常,继续观察车辆健康状态。',
action: '告警事件',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
},
{
label: '里程能耗',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '用同一时间窗核对里程、运行状态和能耗相关统计口径。',
action: '统计查询',
color: 'blue' as const,
onClick: openTimeMonitorMileage
}
];
const dispatchOperationsHighlights = [
{
label: '实时地图',
value: `${commandLocatedCount.toLocaleString()} 有定位`,
detail: '先看车辆空间分布、在线态势和最新位置。',
action: '打开地图',
color: commandLocatedCount > 0 ? 'blue' as const : 'orange' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
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 dispatchOperationsTasks = [
{
title: '看全域位置',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: '打开实时地图,确认在线车辆、有效坐标和重点区域。',
action: '实时地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
title: '回放重点轨迹',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: '按车辆和时间窗回放路线,核对停驶、偏航和里程断点。',
action: '轨迹回放',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
title: '核对时间窗里程',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '按同一时间范围核对区间里程、每日统计和 BI 口径。',
action: '统计查询',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
title: '导出客户证据',
value: `${formatCount(summary?.frameToday)}`,
detail: '导出历史位置、原始记录、解析字段和告警说明。',
action: '历史导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
}
];
const deliveryToday = timeMonitorScopeValue.dateTo || todayDateString();
const customerTimeRangePresets = [
{
label: '最近一天',
range: `${previousDateString(deliveryToday)}${deliveryToday}`,
filters: { ...timeMonitorScopeValue, dateFrom: previousDateString(deliveryToday), dateTo: deliveryToday }
},
{
label: '最近三天',
range: `${offsetDateString(deliveryToday, -3)}${deliveryToday}`,
filters: { ...timeMonitorScopeValue, dateFrom: offsetDateString(deliveryToday, -3), dateTo: deliveryToday }
},
{
label: '本周',
range: `${weekStartDateString(deliveryToday)}${deliveryToday}`,
filters: { ...timeMonitorScopeValue, dateFrom: weekStartDateString(deliveryToday), dateTo: deliveryToday }
},
{
label: '自定义复盘',
range: '当前范围',
filters: timeMonitorScopeValue
}
];
const deliveryScopeText = `${timeMonitorScopeValue.keyword || '全部车辆'} / ${timeMonitorScopeValue.protocol || '全部数据通道'} / ${timeMonitorScopeValue.dateFrom || '-'}${timeMonitorScopeValue.dateTo || '-'}`;
const customerDeliveryWorkbenchItems = [
{
label: '轨迹回放',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: '回放同一时间窗内的路线、速度、停驶和里程断点。',
action: '回放轨迹',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
label: '里程统计',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '核对区间里程、每日统计和 BI 展示口径。',
action: '统计查询',
color: 'green' 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: openTimeMonitorAlerts
}
];
const customerPrimaryFlowItems = [
{
label: '实时地图',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: '先确认车辆在哪里、是否在线、是否有有效坐标。',
action: '打开地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
label: '时间窗复盘',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '用同一时间范围串起轨迹、位置、里程、历史明细和导出。',
action: '查询导出',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
label: '里程统计',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '以车辆为对象核对区间里程、每日统计和 BI 口径。',
action: '统计查询',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
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: openTimeMonitorAlerts
}
];
const customerVehicleServiceHubItems = [
{
label: '实时地图',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: '先确认车辆在哪里、是否在线、坐标是否可信。',
action: '看车在哪',
color: 'green' 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 operationsEvidenceItems = [
{
label: '链路健康',
value: unhealthyLinkCount > 0 ? `${formatCount(unhealthyLinkCount)} 异常` : '正常',
detail: '解释车辆实时、轨迹、统计或导出不可用时的链路证据。',
action: '查看告警',
color: unhealthyLinkCount > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
},
{
label: '消息积压',
value: formatLag(summary?.kafkaLag),
detail: '判断接收、解析和历史落地是否存在排队。',
action: '查看运维',
color: (summary?.kafkaLag ?? 0) > 0 ? 'orange' as const : 'green' as const,
onClick: () => onOpenQuality()
},
{
label: '缓存在线',
value: formatCount(opsHealth?.redisOnlineKeys),
detail: '辅助解释在线车辆数和实时状态是否可信。',
action: '缓存证据',
color: (opsHealth?.redisOnlineKeys ?? 0) > 0 ? 'blue' as const : 'orange' as const,
onClick: () => onOpenRealtime({ online: 'online' })
}
];
const customerVehicleServiceDashboardItems = [
{
question: '车辆在哪里',
value: `${commandLocatedCount.toLocaleString()} 有定位`,
detail: '进入实时地图查看车辆分布、在线状态、最后上报和当前位置。',
action: '打开地图',
color: commandLocatedCount > 0 ? 'green' as const : 'orange' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
question: '轨迹能否回放',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: '按车辆和自定义时间窗回放路线、速度、停驶和里程断点。',
action: '轨迹回放',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
question: '里程怎么算',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '查询区间里程、每日里程和统计闭合情况,支撑 BI 口径复核。',
action: '统计查询',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
question: '数据怎么导出',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '按车辆、时间和字段裁剪导出位置历史、原始记录和解析证据。',
action: '历史导出',
color: 'blue' as const,
onClick: openTimeMonitorRaw
},
{
question: '异常谁处理',
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 customerVehicleServicePathItems = [
{
label: '地图监控',
question: '在线在哪里',
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
detail: `${commandLocatedCount.toLocaleString()} 辆有有效坐标,先让客户看到车辆位置和在线状态。`,
action: '打开地图',
color: 'green' as const,
onClick: () => onOpenMap({ online: 'online' })
},
{
label: '时间窗',
question: '这段时间怎么跑',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '用同一时间窗串起轨迹、位置、里程、告警和导出证据。',
action: '开始复盘',
color: 'blue' as const,
onClick: openTimeMonitorHistory
},
{
label: '里程统计',
question: '里程是否可信',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '面向客户核对区间里程、每日汇总和 BI 统计口径。',
action: '核对统计',
color: 'blue' as const,
onClick: openTimeMonitorMileage
},
{
label: '告警通知',
question: '异常是否通知',
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 } : {})
},
{
label: '数据导出',
question: '数据能否交付',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '按车辆、时间窗和字段裁剪导出位置、原始记录和解析证据。',
action: '导出证据',
color: 'blue' as const,
onClick: openTimeMonitorRaw
}
];
return (
<div className="vp-page">
<PageHeader
title={focusMode === 'time-window' ? '自定义时间监控' : '车辆服务工作台'}
description={focusMode === 'time-window'
? '把客户选择的一辆车和一个时间窗固定下来,同时进入实时地图、轨迹回放、里程统计、历史导出和告警复盘。'
: '面向客户的车辆地图、实时监控、轨迹回放、统计查询、自定义时间窗复盘、历史查询、告警通知和数据导出'}
/>
<Spin spinning={loading}>
{focusMode === 'overview' ? (
<section className="vp-customer-service-path" aria-label="客户车辆服务路径">
<div className="vp-customer-service-path-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color="green">{vehicleServiceOnlineText(serviceSummary, summary)}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={3} style={{ margin: 0 }}>
</Typography.Title>
<Typography.Text type="secondary">
线
</Typography.Text>
<Typography.Text type="secondary" className="vp-customer-service-path-evidence">
32960 / 808 / MQTT
</Typography.Text>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-customer-service-path-grid">
{customerVehicleServicePathItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-service-path-item"
onClick={item.onClick}
aria-label={`客户车辆服务路径 ${item.label} ${item.question} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.question}</strong>
<span>{item.value}</span>
<p>{item.detail}</p>
<em>{item.action}</em>
</button>
))}
</div>
<div className="vp-customer-service-overview" aria-label="客户服务总览">
<div className="vp-customer-service-overview-copy">
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary">
使
</Typography.Text>
</div>
<div className="vp-customer-service-overview-grid">
{customerHeroActions.map((item) => (
<button
key={item.title}
type="button"
className="vp-customer-service-overview-item"
onClick={item.onClick}
aria-label={`客户服务总览 ${item.title} ${item.action}`}
>
<strong>{item.title}</strong>
<span>{item.value}</span>
<p>{item.detail}</p>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-customer-service-next" aria-label="建议下一步">
<div className="vp-customer-service-next-copy">
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary">
线
</Typography.Text>
</div>
<div className="vp-customer-service-next-grid">
{customerNextActions.map((item) => (
<button
key={`${item.level}-${item.title}`}
type="button"
className="vp-customer-service-next-item"
onClick={item.onClick}
aria-label={`建议下一步 ${item.level} ${item.title} ${item.action}`}
>
<Tag color={item.color}>{item.level}</Tag>
<strong>{item.title}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-customer-service-scope" aria-label="客户服务范围">
<div className="vp-customer-service-scope-copy">
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary">
{customerServiceScopeLabel}
</Typography.Text>
</div>
<div className="vp-customer-service-scope-actions">
<Button size="small" theme="solid" type="primary" aria-label="客户服务范围 复制范围" onClick={onCopyCustomerScope}>
</Button>
<Button size="small" theme="light" type="primary" aria-label="客户服务范围 保存视图" onClick={onSaveCustomerView}>
</Button>
</div>
</div>
</section>
) : null}
{focusMode === 'time-window' ? (
<section className="vp-time-monitor-dedicated" aria-label="时间窗监控">
<div className="vp-time-monitor-dedicated-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={timeMonitorHasRange ? 'green' : 'orange'}>{timeMonitorWindowLabel}</Tag>
<Tag color={timeMonitorDeliveryStatus.color}>{timeMonitorDeliveryStatus.label}</Tag>
</Space>
<Typography.Title heading={4} style={{ margin: 0 }}>
{timeMonitorScopeValue.keyword || '全部车辆'} / {timeMonitorScopeValue.protocol || '全部数据通道'} / {timeMonitorScopeValue.dateFrom || '-'} {timeMonitorScopeValue.dateTo || '-'}
</Typography.Title>
<Typography.Text type="secondary">
穿
</Typography.Text>
</div>
<div className="vp-time-monitor-dedicated-grid">
{dedicatedTimeMonitorActions.map((item) => (
<button
key={item.label}
type="button"
className="vp-time-monitor-dedicated-item"
onClick={item.onClick}
aria-label={`时间窗监控入口 ${item.label} ${item.value}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</button>
))}
</div>
</section>
) : null}
<details className="vp-dashboard-secondary-details vp-dashboard-customer-advanced">
<summary>
<span></span>
<small></small>
</summary>
<div className="vp-dashboard-secondary-body">
<section className="vp-vehicle-command-center" aria-label="车辆服务指挥台">
<div className="vp-vehicle-command-map">
<VehicleMap
points={commandMapPoints}
maxFallbackPoints={90}
heightClassName="vp-vehicle-command-map-canvas"
fallbackLabel="显示车辆服务指挥台坐标预览"
/>
<div className="vp-vehicle-command-map-status">
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</div>
</div>
<div className="vp-vehicle-command-main">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color="green"></Tag>
</Space>
<Typography.Title heading={3} style={{ margin: 0 }}>
10
</Typography.Title>
<div className="vp-vehicle-command-actions">
{customerTaskNavigation.map((item) => (
<button
key={item.title}
type="button"
className="vp-vehicle-command-action"
onClick={item.onClick}
aria-label={`车辆服务指挥台 ${item.title} ${item.action}`}
>
<Tag color={item.color}>{item.title}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-vehicle-command-queue">
<div className="vp-vehicle-command-queue-head">
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary"></Typography.Text>
</div>
<div className="vp-vehicle-command-queue-grid">
{vehiclePriorityQueue.map((item) => (
<button
key={item.title}
type="button"
className="vp-vehicle-command-queue-item"
onClick={item.onClick}
aria-label={`车辆服务指挥台优先队列 ${item.title} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.title}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
</section>
<section className="vp-customer-vehicle-service-hub" aria-label="客户车辆服务中枢">
<div className="vp-customer-vehicle-service-summary">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={4} style={{ margin: 0 }}>
线
</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-customer-vehicle-service-grid">
{customerVehicleServiceHubItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-vehicle-service-item"
onClick={item.onClick}
aria-label={`客户车辆服务中枢 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-customer-map-situation" aria-label="车辆地图态势">
<div className="vp-customer-map-situation-map">
<VehicleMap
points={commandMapPoints}
maxFallbackPoints={120}
heightClassName="vp-customer-map-situation-canvas"
fallbackLabel="显示车辆实时坐标预览"
/>
</div>
<div className="vp-customer-map-situation-panel">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>线</Typography.Title>
<div className="vp-customer-map-situation-metrics">
{customerCockpitMetrics.slice(1, 4).map((item) => (
<button key={item.label} type="button" onClick={item.onClick} aria-label={`车辆地图态势 ${item.label} ${item.value}`}>
<span>{item.label}</span>
<strong>{item.value}</strong>
</button>
))}
</div>
<Space wrap>
<Button size="small" theme="solid" type="primary" aria-label="车辆地图态势 打开实时地图" onClick={() => onOpenMap({ online: 'online' })}></Button>
<Button size="small" theme="light" type="primary" aria-label="车辆地图态势 回放轨迹" onClick={openTimeMonitorHistory}></Button>
<Button size="small" theme="light" type="primary" onClick={openTimeMonitorMileage}></Button>
<Button size="small" theme="light" type="warning" onClick={() => onOpenQuality()}></Button>
</Space>
</div>
</section>
<section className="vp-customer-vehicle-service-dashboard" aria-label="客户车辆服务驾驶舱">
<div className="vp-customer-vehicle-service-dashboard-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={3} style={{ margin: 0 }}>
</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
<div className="vp-customer-vehicle-service-dashboard-status" aria-label="客户车辆服务驾驶舱状态">
<span>
<strong>{formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)}</strong>
线
</span>
<span>
<strong>{dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)}</strong>
</span>
<span>
<strong>{focusVehicle?.label ?? '暂无重点车辆'}</strong>
</span>
</div>
</div>
<div className="vp-customer-vehicle-service-dashboard-grid">
{customerVehicleServiceDashboardItems.map((item) => (
<button
key={item.question}
type="button"
className="vp-customer-vehicle-service-dashboard-item"
onClick={item.onClick}
aria-label={`客户车辆服务驾驶舱 ${item.question} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.question}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
</div>
</details>
<section className="vp-vehicle-service-cockpit" aria-label="车辆服务驾驶舱">
<div className="vp-vehicle-service-cockpit-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color="green"></Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={4} style={{ margin: 0 }}>
线
</Typography.Title>
<Typography.Text type="secondary">
32960808 MQTT
</Typography.Text>
</div>
<div className="vp-vehicle-service-cockpit-grid">
{customerServiceCockpitItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-vehicle-service-cockpit-item"
onClick={item.onClick}
aria-label={`车辆服务驾驶舱 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<details className="vp-dashboard-secondary-details vp-dashboard-customer-service-playbook">
<summary>
<span></span>
<small></small>
</summary>
<div className="vp-dashboard-secondary-body">
<section className="vp-customer-platform-blueprint" aria-label="客户车辆服务中台蓝图">
<div className="vp-customer-platform-blueprint-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>
Live MapRoute ReplayGeofence AlertsReports Export Vehicle Health 使
</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-customer-platform-blueprint-grid">
{customerPlatformBlueprintItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-platform-blueprint-item"
onClick={item.onClick}
aria-label={`客户车辆服务中台蓝图 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-customer-saved-view-library" aria-label="客户常用视图库">
<div className="vp-customer-saved-view-library-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color="green"></Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>
</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-customer-saved-view-library-grid">
{customerSavedViewItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-saved-view-library-item"
onClick={item.onClick}
aria-label={`客户常用视图库 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-ten-second-triage" aria-label="十秒车辆处置台">
<div className="vp-ten-second-triage-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 || (serviceSummary?.noDataVehicles ?? 0) > 0 ? 'orange' : 'green'}></Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>
</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-ten-second-triage-grid">
{tenSecondTriageItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-ten-second-triage-item"
onClick={item.onClick}
aria-label={`十秒车辆处置台 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-customer-service-journey" aria-label="车辆服务闭环">
<div className="vp-customer-service-journey-copy">
<Tag color="blue"></Tag>
<Typography.Title heading={5} style={{ margin: 0 }}></Typography.Title>
</div>
<div className="vp-customer-service-journey-steps">
{customerServiceJourneySteps.map((item) => (
<button
key={item.step}
type="button"
className="vp-customer-service-journey-step"
onClick={item.onClick}
aria-label={`车辆服务闭环 ${item.step} ${item.title} ${item.value} ${item.action}`}
>
<span>{item.step}</span>
<div>
<strong>{item.title}</strong>
<small>{item.value}</small>
<em>{item.detail}</em>
</div>
<Tag color={item.color}>{item.action}</Tag>
</button>
))}
</div>
</section>
</div>
</details>
<section className="vp-dispatch-operations-home" aria-label="客户调度运营首页">
<div className="vp-dispatch-operations-map">
<div className="vp-dispatch-operations-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={4} style={{ margin: 0 }}></Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<VehicleMap
points={commandMapPoints}
maxFallbackPoints={120}
heightClassName="vp-dispatch-operations-map-canvas"
fallbackLabel="显示车辆调度坐标预览"
/>
<div className="vp-dispatch-operations-highlights">
{dispatchOperationsHighlights.map((item) => (
<button
key={item.label}
type="button"
className="vp-dispatch-operations-highlight"
onClick={item.onClick}
aria-label={`客户调度运营首页 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-dispatch-operations-panel">
<div className="vp-dispatch-operations-panel-head">
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary"></Typography.Text>
</div>
<div className="vp-dispatch-operations-task-grid">
{dispatchOperationsTasks.map((item) => (
<button
key={item.title}
type="button"
className="vp-dispatch-operations-task"
onClick={item.onClick}
aria-label={`调度运营任务 ${item.title} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.title}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
</section>
<section className="vp-customer-delivery-workbench" aria-label="客户查询导出交付台">
<div className="vp-customer-delivery-workbench-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={timeMonitorHasRange ? 'green' : 'orange'}>{dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)}</Tag>
<Tag color={timeMonitorDeliveryStatus.color}>{timeMonitorDeliveryStatus.label}</Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>
</Typography.Title>
<div className="vp-customer-delivery-scope">
<span></span>
<strong>{deliveryScopeText}</strong>
</div>
<div className="vp-customer-time-presets">
<span></span>
<div>
{customerTimeRangePresets.map((item) => (
<button
key={item.label}
type="button"
onClick={() => setTimeMonitorFilters(item.filters)}
aria-label={`客户常用时间范围 ${item.label} ${item.range} 应用`}
>
<strong>{item.label}</strong>
<small>{item.range}</small>
</button>
))}
</div>
</div>
</div>
<div className="vp-customer-delivery-workbench-grid">
{customerDeliveryWorkbenchItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-delivery-workbench-item"
onClick={item.onClick}
aria-label={`客户查询导出交付台 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-customer-service-separation" aria-label="客户主流程与运维证据层">
<div className="vp-customer-primary-flow">
<div className="vp-customer-primary-flow-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color="green"></Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}></Typography.Title>
</div>
<div className="vp-customer-primary-flow-grid">
{customerPrimaryFlowItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-primary-flow-item"
onClick={item.onClick}
aria-label={`客户主流程 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-operations-evidence-layer">
<div className="vp-operations-evidence-layer-copy">
<Space wrap>
<Tag color="grey"></Tag>
<Tag color={unhealthyLinkCount > 0 ? 'orange' : 'green'}>{unhealthyLinkCount > 0 ? `${formatCount(unhealthyLinkCount)} 异常` : '链路正常'}</Tag>
</Space>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-operations-evidence-layer-grid">
{operationsEvidenceItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-operations-evidence-layer-item"
onClick={item.onClick}
aria-label={`运维证据层 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
</section>
<section className="vp-customer-service-command" aria-label="客户车辆服务总控台">
<div className="vp-customer-service-command-main">
<div className="vp-customer-service-command-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color="grey"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={4} style={{ margin: 0 }}></Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-customer-service-command-evidence">
<span>GB32960 / JT808 / YUTONG_MQTT</span>
<strong>{vehicleServiceOnlineText(serviceSummary, summary)}</strong>
<small>{dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo)}</small>
</div>
</div>
<div className="vp-customer-service-command-grid">
{customerServiceCommandItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-service-command-item"
onClick={item.onClick}
aria-label={`客户车辆服务总控台 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-customer-fleet-groups" aria-label="客户车队分组工作台">
<div className="vp-customer-fleet-groups-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color="green">{formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 线</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>线</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-customer-fleet-groups-grid">
{customerFleetGroupItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-fleet-group-item"
onClick={item.onClick}
aria-label={`客户车队分组工作台 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-customer-service-sla" aria-label="客户服务 SLA 承诺">
<div className="vp-customer-service-sla-copy">
<Space wrap>
<Tag color="blue"> SLA </Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
<Tag color={unhealthyLinkCount > 0 ? 'orange' : 'green'}>{unhealthyLinkCount.toLocaleString()} </Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}></Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-customer-service-sla-grid">
{customerSlaItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-service-sla-item"
onClick={item.onClick}
aria-label={`客户服务 SLA 承诺 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-vehicle-health-energy" aria-label="车辆健康与能耗工作台">
<div className="vp-vehicle-health-energy-copy">
<Space wrap>
<Tag color="green"></Tag>
<Tag color="blue">{formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 线</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>SOC/</Typography.Title>
<Typography.Text type="secondary">
32960808 MQTT
</Typography.Text>
</div>
<div className="vp-vehicle-health-energy-grid">
{vehicleHealthEnergyItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-vehicle-health-energy-item"
onClick={item.onClick}
aria-label={`车辆健康与能耗工作台 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-customer-cockpit" aria-label="现代车队运营台">
<div className="vp-customer-cockpit-summary">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={4} style={{ margin: 0 }}></Typography.Title>
</div>
<div className="vp-customer-cockpit-actions">
{modernFleetOperations.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-cockpit-action"
onClick={item.onClick}
aria-label={`现代车队运营台 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-amap-service-foundation" aria-label="高德地图车辆服务底座">
<div className="vp-amap-service-foundation-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? 'Web JS 已配置' : '坐标预览'}</Tag>
<Tag color={amapApiConfigured ? 'green' : 'orange'}>{amapApiConfigured ? '服务端 API 已配置' : '服务端 API 待配置'}</Tag>
<Tag color={amapSecurityProxyEnabled ? 'green' : 'orange'}>{amapSecurityProxyEnabled ? '安全代理已启用' : '安全代理未启用'}</Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>线</Typography.Title>
<Typography.Text type="secondary">
32960808MQTT
</Typography.Text>
<div className="vp-amap-capability-list" aria-label="地图服务能力清单">
<div className="vp-amap-capability-head">
<strong></strong>
<span> Web JS API 使</span>
</div>
<div className="vp-amap-capability-grid">
{amapCapabilityItems.map((item) => (
<div key={item.label} className="vp-amap-capability-item">
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</div>
))}
</div>
</div>
</div>
<div className="vp-amap-service-foundation-grid">
{amapVehicleServiceItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-amap-service-foundation-item"
onClick={item.onClick}
aria-label={`高德地图车辆服务底座 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<section className="vp-customer-vehicle-monitor" aria-label="客户车辆监控台">
<div className="vp-customer-vehicle-monitor-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<strong></strong>
<span>32960808MQTT </span>
</div>
<div className="vp-customer-vehicle-monitor-grid">
{customerVehicleMonitorItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-customer-vehicle-monitor-item"
onClick={item.onClick}
aria-label={`客户车辆监控台 ${item.label} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</section>
<Card bordered className="vp-fleet-monitor" bodyStyle={{ padding: 0 }}>
<div className="vp-fleet-monitor-map">
<div className="vp-fleet-monitor-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图可用' : '坐标预览'}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={3} style={{ margin: 0 }}></Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<VehicleMap
points={commandMapPoints}
maxFallbackPoints={120}
heightClassName="vp-fleet-monitor-map-canvas"
fallbackLabel="显示车辆实时坐标预览"
/>
<div className="vp-fleet-monitor-stats">
{customerCockpitMetrics.map((item) => (
<button key={item.label} type="button" className="vp-fleet-monitor-stat" onClick={item.onClick} aria-label={`车辆监控指标 ${item.label} ${item.value}`}>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</button>
))}
</div>
</div>
<div className="vp-fleet-monitor-panel">
<div className="vp-fleet-task-router">
<div className="vp-fleet-task-router-head">
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary"></Typography.Text>
</div>
<div className="vp-fleet-task-grid">
{customerTaskNavigation.map((item) => (
<button
key={item.title}
type="button"
className="vp-fleet-task-item"
onClick={item.onClick}
aria-label={`客户任务导航 ${item.title} ${item.action}`}
>
<Tag color={item.color}>{item.title}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-customer-problem-desk">
<div className="vp-customer-problem-head">
<div>
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary"></Typography.Text>
</div>
<Tag color="blue"></Tag>
</div>
<Typography.Text type="secondary" className="vp-customer-problem-principle">
GB32960JT808MQTT
</Typography.Text>
<div className="vp-customer-problem-grid">
{customerProblemDesk.map((item) => (
<button
key={item.question}
type="button"
className="vp-customer-problem-item"
onClick={item.onClick}
aria-label={`客户问题处理台 ${item.question} ${item.action}`}
>
<div>
<strong>{item.question}</strong>
<span>{item.answer}</span>
</div>
<Tag color={item.color}>{item.metric}</Tag>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-vehicle-priority-queue">
<div className="vp-vehicle-priority-queue-head">
<div>
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary"></Typography.Text>
</div>
<Tag color="blue"></Tag>
</div>
<div className="vp-vehicle-priority-queue-grid">
{vehiclePriorityQueue.map((item) => (
<button
key={item.title}
type="button"
className="vp-vehicle-priority-queue-item"
onClick={item.onClick}
aria-label={`车辆服务优先级队列 ${item.title} ${item.value} ${item.action}`}
>
<Tag color={item.color}>{item.title}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-fleet-monitor-panel-head">
<Typography.Text strong></Typography.Text>
<Button size="small" theme="light" type="primary" onClick={copyCustomerServiceGuide}></Button>
</div>
<div className="vp-fleet-next-actions">
{customerNextActions.map((item) => (
<button
key={item.title}
type="button"
className="vp-fleet-next-action"
onClick={item.onClick}
aria-label={`车辆服务建议 ${item.title} ${item.action}`}
>
<Tag color={item.color}>{item.level}</Tag>
<strong>{item.title}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
<Typography.Text strong></Typography.Text>
<div className="vp-fleet-monitor-actions">
{customerCockpitWorkflows.map((item) => (
<button key={item.title} type="button" className="vp-fleet-monitor-action" onClick={item.onClick} aria-label={`客户车辆工作流 ${item.title} ${item.action}`}>
<span>{item.title}</span>
<strong>{item.action}</strong>
<small>{item.description}</small>
<em>{item.evidence}</em>
</button>
))}
</div>
<Typography.Text strong></Typography.Text>
<div className="vp-fleet-question-grid">
{customerQuestionActions.map((item) => (
<button
key={item.question}
type="button"
className="vp-fleet-question-item"
onClick={item.onClick}
aria-label={`客户常问 ${item.question} ${item.action}`}
>
<strong>{item.question}</strong>
<span>{item.answer}</span>
<Tag color={item.color}>{item.action}</Tag>
</button>
))}
</div>
<div className="vp-fleet-monitor-time">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={timeMonitorScope().keyword ? 'green' : 'grey'}>{timeMonitorScope().keyword ? '单车' : '全域'}</Tag>
</Space>
<strong>{timeMonitorSummary}</strong>
<span></span>
<Space wrap>
<Button size="small" theme="solid" type="primary" onClick={openTimeMonitorHistory}></Button>
<Button size="small" theme="light" type="primary" onClick={openTimeMonitorMileage}></Button>
<Button size="small" theme="light" type="primary" onClick={openTimeMonitorRaw}></Button>
</Space>
</div>
<div className="vp-fleet-monitor-trust">
{customerTrustSignals.map((item) => (
<div key={item.label} className="vp-fleet-monitor-trust-item">
<span>{item.label}</span>
<strong>{item.value}</strong>
<small>{item.detail}</small>
</div>
))}
</div>
</div>
</Card>
<Card bordered className="vp-customer-delivery-board" bodyStyle={{ padding: 0 }}>
<div className="vp-customer-delivery-summary">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图可交付' : '坐标可交付'}</Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>使</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
<Space wrap>
<Button size="small" theme="solid" type="primary" onClick={copyCustomerServiceGuide}></Button>
<Button size="small" theme="light" type="primary" aria-label="客户交付驾驶舱 导出驾驶舱 CSV" onClick={exportDashboardSnapshot}> CSV</Button>
</Space>
</div>
<div className="vp-customer-delivery-grid">
{customerDeliveryItems.map((item) => (
<button
key={item.title}
type="button"
className="vp-customer-delivery-item"
onClick={item.onClick}
aria-label={`客户交付驾驶舱 ${item.title} ${item.action}`}
>
<Tag color={item.color}>{item.title}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</Card>
<Card
bordered
title={<Space><span></span><Tag color="blue">{timeMonitorSummary}</Tag></Space>}
style={{ marginBottom: 16 }}
>
<Form
initValues={timeMonitorFilters}
layout="horizontal"
onSubmit={(values) => setTimeMonitorFilters(timeMonitorScope(values as Record<string, string>))}
style={{ marginBottom: 12 }}
>
<Form.Input field="keyword" label="车辆" placeholder="VIN / 车牌 / 手机号" style={{ width: 240 }} />
<Form.Select field="protocol" label="数据通道" placeholder="全部通道" style={{ width: 150 }}>
<Select.Option value="GB32960">GB32960</Select.Option>
<Select.Option value="JT808">JT808</Select.Option>
<Select.Option value="YUTONG_MQTT">YUTONG_MQTT</Select.Option>
</Form.Select>
<Form.Input field="dateFrom" label="开始日期" placeholder="2026-07-01" style={{ width: 150 }} />
<Form.Input field="dateTo" label="结束日期" placeholder="2026-07-05" style={{ width: 150 }} />
<Space>
<Button htmlType="submit" theme="solid" type="primary"></Button>
<Button onClick={() => {
const today = todayDateString();
setTimeMonitorFilters({ dateFrom: previousDateString(today), dateTo: today });
}}></Button>
</Space>
</Form>
<div className="vp-time-monitor-actions">
<Button theme="solid" type="primary" onClick={openTimeMonitorHistory}></Button>
<Button theme="light" type="primary" onClick={openTimeMonitorMileage}></Button>
<Button theme="light" type="primary" onClick={openTimeMonitorRaw}></Button>
<Button theme="light" type="warning" onClick={openTimeMonitorAlerts}></Button>
</div>
<Typography.Text type="secondary">
</Typography.Text>
<div className="vp-time-review-package">
<div className="vp-time-review-summary">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={timeMonitorScope().keyword ? 'green' : 'grey'}>{timeMonitorScope().keyword ? '单车复盘' : '全域复盘'}</Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>{timeMonitorSummary}</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
<Space wrap>
<Button size="small" theme="solid" type="primary" onClick={copyTimeMonitorReviewPackage}></Button>
<Button size="small" theme="light" type="primary" onClick={openTimeMonitorHistory}></Button>
<Button size="small" theme="light" type="primary" onClick={openTimeMonitorMileage}></Button>
<Button size="small" theme="light" type="primary" onClick={openTimeMonitorRaw}></Button>
<Button size="small" theme="light" type="warning" onClick={openTimeMonitorAlerts}></Button>
</Space>
</div>
<div className="vp-time-review-grid">
{timeMonitorReviewItems.map((item) => (
<div key={item.label} className="vp-time-review-item">
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</div>
))}
</div>
</div>
<div className="vp-time-service-path">
<div className="vp-time-service-path-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={timeMonitorDeliveryStatus.color}>{timeMonitorDeliveryStatus.label}</Tag>
</Space>
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-time-service-path-grid">
{timeWindowServicePathItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-time-service-path-item"
onClick={item.onClick}
aria-label={`时间窗服务路径 ${item.step} ${item.label} ${item.value} ${item.action}`}
>
<span>{item.step}</span>
<div>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<small>{item.detail}</small>
<em>{item.action}</em>
</div>
</button>
))}
</div>
</div>
<div className="vp-time-next-action-strip" aria-label="时间窗下一步行动条">
<div className="vp-time-next-action-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={timeMonitorDeliveryStatus.color}>{timeMonitorDeliveryStatus.label}</Tag>
<Tag color={timeMonitorHasAlerts ? 'orange' : 'green'}>{timeMonitorHasAlerts ? `${formatCount(summary?.issueVehicles)} 告警` : '无高风险'}</Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>
</Typography.Title>
<Typography.Text type="secondary">
{timeMonitorSummary}{timeMonitorWindowLabel}
</Typography.Text>
</div>
<div className="vp-time-next-action-grid">
{timeWindowNextItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-time-next-action-item"
onClick={item.onClick}
aria-label={`时间窗下一步 ${item.label} ${item.value}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</button>
))}
</div>
<div className="vp-time-next-action-actions">
{timeWindowNextActions.map((item) => (
<button
key={item.label}
type="button"
className="vp-time-next-action-button"
disabled={item.disabled}
onClick={item.onClick}
aria-label={`时间窗下一步动作 ${item.label} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<span>{item.action}</span>
</button>
))}
</div>
</div>
<div className="vp-time-delivery-strip">
<div className="vp-time-delivery-summary">
<Space wrap>
<Tag color={timeMonitorDeliveryStatus.color}>{timeMonitorDeliveryStatus.label}</Tag>
<Tag color={timeMonitorScopeValue.keyword ? 'green' : 'blue'}>{timeMonitorScopeValue.keyword ? '单车时间窗' : '车辆池时间窗'}</Tag>
</Space>
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary">{timeMonitorDeliveryStatus.detail}</Typography.Text>
</div>
<div className="vp-time-delivery-grid">
{timeMonitorDeliveryActions.map((item) => (
<button
key={item.label}
type="button"
className="vp-time-delivery-item"
onClick={item.onClick}
aria-label={`时间窗交付 ${item.label} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-time-audit-strip">
<div className="vp-time-audit-head">
<Typography.Text strong></Typography.Text>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-time-audit-grid">
{timeMonitorChecklist.map((item) => (
<div key={item.label} className="vp-time-audit-item">
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</div>
))}
</div>
</div>
<div className="vp-time-monitor-workbench">
<div className="vp-time-monitor-scope">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={timeMonitorScope().keyword ? 'green' : 'grey'}>{timeMonitorScope().keyword ? '单车' : '全域'}</Tag>
</Space>
<strong>{timeMonitorSummary}</strong>
<Typography.Text type="secondary">
BI
</Typography.Text>
</div>
<div className="vp-time-monitor-work-grid">
{timeMonitorWorkbench.map((item) => (
<button
key={item.label}
type="button"
className="vp-time-monitor-work-item"
onClick={item.onClick}
aria-label={`时间窗作业 ${item.label} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
<div className="vp-time-monitor-grid">
{[
{ title: '今天车辆轨迹', detail: '查看当天活跃车辆的轨迹、速度和里程断点。', action: '查今天', onClick: openTimeMonitorHistory },
{ title: '历史时间窗', detail: '按任意起止时间查询位置历史、原始记录和字段明细。', action: '自定义查询', onClick: openTimeMonitorRaw },
{ title: '统计查询', detail: '按车辆和时间范围核对区间里程、日统计和异常点。', action: '统计查询', onClick: openTimeMonitorMileage },
{ title: '告警复盘', detail: '按车辆或事件回放告警发生前后的轨迹和数据证据。', action: '告警事件', onClick: openTimeMonitorAlerts }
].map((item) => (
<button key={item.title} type="button" className="vp-time-monitor-item" onClick={item.onClick} aria-label={`时间监控 ${item.title}`}>
<div>
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
</div>
<Tag color="blue">{item.action}</Tag>
</button>
))}
</div>
</Card>
<details className="vp-dashboard-secondary-details">
<summary>
<span></span>
<small>KPI </small>
</summary>
<div className="vp-dashboard-secondary-body">
<Card bordered title="车辆服务指挥台" style={{ marginBottom: 16 }}>
<div className="vp-command-center-grid">
{commandCenterItems.map((item) => (
<div key={item.title} className="vp-command-center-item">
<div className="vp-command-center-head">
<Tag color={item.color}>{item.title}</Tag>
<strong>{item.value}</strong>
</div>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
<Space wrap>
<Button
size="small"
theme="solid"
type="primary"
aria-label={`车辆服务指挥台 ${item.title} ${item.primaryAction}`}
onClick={item.onPrimary}
>
{item.primaryAction}
</Button>
<Button
size="small"
theme="light"
type="primary"
aria-label={`车辆服务指挥台 ${item.title} ${item.secondaryAction}`}
onClick={item.onSecondary}
>
{item.secondaryAction}
</Button>
</Space>
</div>
))}
</div>
</Card>
<Card bordered title="客户常用服务" style={{ marginBottom: 16 }}>
<div className="vp-customer-workflow-grid">
{customerHeroActions.map((item) => (
<button key={item.title} type="button" className="vp-customer-workflow-item" onClick={item.onClick} aria-label={`客户工作流 ${item.title}`}>
<div>
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
</div>
<strong>{item.value}</strong>
<Tag color="blue">{item.action}</Tag>
</button>
))}
</div>
</Card>
<Card bordered title="今日车辆服务任务板" style={{ marginBottom: 16 }}>
<div className="vp-customer-task-grid">
{customerTaskBoard.map((item) => (
<div key={item.title} className="vp-customer-task-item">
<div className="vp-customer-task-head">
<Tag color={item.color}>{item.title}</Tag>
<strong>{item.value}</strong>
</div>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
<Space wrap>
<Button
size="small"
theme="solid"
type="primary"
disabled={item.disabled}
aria-label={`今日任务 ${item.title} ${item.primaryAction}`}
onClick={item.onPrimary}
>
{item.primaryAction}
</Button>
<Button
size="small"
theme="light"
type="primary"
disabled={item.disabled && item.secondaryAction !== '复制处置卡'}
aria-label={`今日任务 ${item.title} ${item.secondaryAction}`}
onClick={item.onSecondary}
>
{item.secondaryAction}
</Button>
</Space>
</div>
))}
</div>
</Card>
<div className="vp-kpi-grid">
{kpis.map((item) => (
<Card key={item.label} bordered className="vp-kpi-card" bodyStyle={{ padding: 0 }}>
<button className="vp-kpi-button" type="button" onClick={() => onOpenVehicles(item.filters)} aria-label={`${item.label} ${item.value}`}>
<div className="vp-kpi-value">{item.value}</div>
<div className="vp-kpi-label">{item.label}</div>
</button>
</Card>
))}
</div>
<Card bordered title="车辆服务入口" style={{ marginBottom: 16 }}>
<div className="vp-service-entry-board">
<div className="vp-service-entry-summary">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color="green">{vehicleServiceOnlineText(serviceSummary, summary)}</Tag>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} </Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}></Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
<Space wrap>
<Button size="small" theme="solid" type="primary" onClick={() => onOpenVehicles({ online: 'online' })}>线</Button>
<Button size="small" theme="light" type="primary" onClick={copyCustomerServiceGuide}></Button>
<Button size="small" theme="light" type="primary" onClick={exportDashboardSnapshot}> CSV</Button>
<Button size="small" theme="light" type="tertiary" onClick={copyOperationsHandoff}></Button>
</Space>
</div>
<div className="vp-service-entry-grid">
{customerServicePathItems.map((item) => (
<button
key={item.title}
type="button"
className="vp-service-entry-item"
onClick={item.onClick}
aria-label={`车辆服务入口 ${item.title} ${item.action}`}
>
<Tag color={item.color}>{item.title}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<em>{item.action}</em>
</button>
))}
</div>
</div>
</Card>
</div>
</details>
<details className="vp-dashboard-evidence-details">
<summary>
<span></span>
<small></small>
</summary>
<div className="vp-dashboard-evidence-body">
<Card bordered title="地图监控能力" style={{ marginBottom: 16 }}>
<div className="vp-map-ops-board">
<div className="vp-map-ops-readiness">
{mapReadiness.map((item) => (
<div key={item.title} className="vp-map-ops-readiness-item">
<Tag color={item.color}>{item.title}</Tag>
<div className="vp-map-ops-value">{item.value}</div>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
</div>
))}
</div>
<div className="vp-map-ops-work">
{mapWorkItems.map((item) => (
<button key={item.title} className="vp-map-ops-work-item" type="button" onClick={item.onClick} aria-label={`地图运营 ${item.title}`}>
<div className="vp-map-ops-work-head">
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
<Tag color="blue">{item.action}</Tag>
</div>
<div className="vp-map-ops-work-value">{item.value}</div>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
</button>
))}
</div>
</div>
</Card>
{focusVehicle ? (
<Card
bordered
title={<Space><span></span><Tag color={focusVehicle.statusColor}>{focusVehicle.statusLabel}</Tag><Button size="small" onClick={copyFocusVehicleService}></Button></Space>}
style={{ marginBottom: 16 }}
>
<div className="vp-focus-service">
<div className="vp-focus-service-main">
<Tag color="blue">{focusVehicle.reason}</Tag>
<Typography.Title heading={5} style={{ margin: '8px 0 4px' }}>{focusVehicle.label}</Typography.Title>
<Typography.Text type="secondary"></Typography.Text>
</div>
<div className="vp-focus-service-evidence">
{[
{ label: '实时证据', value: focusVehicle.realtimeEvidence },
{ label: '轨迹证据', value: focusVehicle.historyEvidence },
{ label: '告警证据', value: focusVehicle.alertEvidence },
{ label: '统计证据', value: focusVehicle.statisticEvidence }
].map((item) => (
<div key={item.label} className="vp-focus-service-evidence-item">
<span>{item.label}</span>
<strong>{item.value}</strong>
</div>
))}
</div>
<Space wrap>
<Button size="small" theme="solid" type="primary" aria-label="重点车辆 车辆服务" disabled={!focusVehicle.lookupKey} onClick={() => focusVehicle.lookupKey && onOpenVehicle(focusVehicle.lookupKey, focusVehicle.protocol)}></Button>
<Button size="small" theme="light" aria-label="重点车辆 实时监控" disabled={!focusVehicle.lookupKey} onClick={() => onOpenRealtime({ keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol ?? '' })}></Button>
<Button size="small" theme="light" aria-label="重点车辆 轨迹回放" disabled={!focusVehicle.lookupKey} onClick={() => onOpenHistory({ keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol ?? '' })}></Button>
<Button size="small" theme="light" aria-label="重点车辆 原始记录" disabled={!focusVehicle.lookupKey} onClick={() => onOpenHistory({ keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol ?? '', tab: 'raw', includeFields: 'true' })}></Button>
<Button size="small" theme="light" aria-label="重点车辆 统计查询" disabled={!focusVehicle.lookupKey} onClick={() => onOpenMileage({ keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol ?? '' })}></Button>
<Button size="small" theme="light" type="warning" aria-label="重点车辆 告警事件" onClick={() => onOpenQuality({ keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol ?? '', ...(focusVehicle.issueType ? { issueType: focusVehicle.issueType } : {}) })}></Button>
</Space>
</div>
</Card>
) : null}
<Card bordered title="车辆服务工作台" style={{ marginBottom: 16 }}>
<div className="vp-workbench-grid">
{operationWorkbench.map((item) => (
<div key={item.title} className="vp-workbench-item">
<div className="vp-workbench-head">
<Tag color={item.color}>{item.title}</Tag>
<Typography.Text type="secondary">{item.meta}</Typography.Text>
</div>
<div className="vp-workbench-value">{item.value}</div>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
<Space wrap>
{item.actions.map((action) => (
<Button
key={action.label}
size="small"
theme="light"
type="primary"
aria-label={`作业台 ${item.title} ${action.label}`}
onClick={action.onClick}
>
{action.label}
</Button>
))}
</Space>
</div>
))}
</div>
</Card>
<Card bordered title="一车一服务模型" style={{ marginBottom: 16 }}>
<div className="vp-service-model-grid">
{vehicleServicePrinciples.map((item) => (
<div key={item.title} className="vp-service-model-item">
<Tag color="blue">{item.title}</Tag>
<div className="vp-service-model-value">{item.value}</div>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
</div>
))}
</div>
</Card>
<Card
bordered
title={<Space><span></span><Button size="small" onClick={copyScenarioBlueprint}></Button></Space>}
style={{ marginBottom: 16 }}
>
<div className="vp-scenario-grid">
{scenarioNavigation.map((item) => (
<div key={item.title} className="vp-scenario-item">
<div className="vp-scenario-main">
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
<Typography.Text type="secondary">{item.objective}</Typography.Text>
</div>
<div className="vp-scenario-evidence">{item.evidence}</div>
<Tag color="blue">{item.sla}</Tag>
<Space wrap>
<Button size="small" theme="solid" type="primary" aria-label={`场景导航 ${item.title} ${item.primaryAction}`} onClick={item.onPrimary}>
{item.primaryAction}
</Button>
<Button size="small" theme="light" aria-label={`场景导航 ${item.title} ${item.secondaryAction}`} onClick={item.onSecondary}>
{item.secondaryAction}
</Button>
</Space>
</div>
))}
</div>
</Card>
{highPriorityIssue ? (
<Card bordered title="最高优先级告警" style={{ marginBottom: 16 }}>
<div className="vp-priority-alert">
<div className="vp-priority-alert-main">
<Space wrap>
<Tag color={highPriorityIssue.severity === 'error' ? 'red' : 'orange'}>
{highPriorityIssue.severity === 'error' ? 'P0' : 'P1'}
</Tag>
<Tag color="orange">{qualityIssueLabel(highPriorityIssue.issueType)}</Tag>
<Tag color="blue">{qualityProtocolLabel(highPriorityIssue.protocol)}</Tag>
<Tag color="grey">{highPriorityIssue.lastSeen || '-'}</Tag>
</Space>
<Typography.Title heading={6} style={{ margin: '10px 0 4px' }}>
{priorityIssueVehicleLabel(highPriorityIssue)}
</Typography.Title>
<Typography.Text type="secondary">{highPriorityIssue.detail || '暂无告警详情'}</Typography.Text>
</div>
<Space wrap>
<Button
size="small"
theme="solid"
type="primary"
disabled={!highPriorityLookup?.key}
onClick={() => highPriorityLookup?.key && onOpenVehicle(highPriorityLookup.key, highPriorityIssue.protocol)}
>
</Button>
<Button
size="small"
theme="light"
aria-label="首页告警轨迹证据"
disabled={!highPriorityEvidenceFilters?.keyword}
onClick={() => highPriorityEvidenceFilters && onOpenHistory(highPriorityEvidenceFilters)}
>
</Button>
<Button
size="small"
theme="light"
disabled={!highPriorityEvidenceFilters?.keyword}
onClick={() => highPriorityEvidenceFilters && onOpenHistory({ ...highPriorityEvidenceFilters, tab: 'raw', includeFields: 'true' })}
>
</Button>
<Button
size="small"
theme="light"
aria-label="首页复制告警通知"
onClick={() => copyText(priorityIssueNotificationText(highPriorityIssue), '告警通知')}
>
</Button>
<Button size="small" theme="light" type="warning" onClick={() => onOpenQuality({ issueType: highPriorityIssue.issueType })}>
</Button>
</Space>
</div>
</Card>
) : null}
<Card bordered title="车辆服务能力矩阵" style={{ marginBottom: 16 }}>
<div className="vp-capability-grid">
{capabilities.map((item) => (
<div key={item.title} className="vp-capability-item">
<div>
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
<Typography.Text type="secondary">{item.description}</Typography.Text>
</div>
<Tag className="vp-capability-status" color={item.color}>{item.status}</Tag>
<Button size="small" theme="light" type="primary" aria-label={`能力入口 ${item.title}`} onClick={item.onClick}>
{item.action}
</Button>
</div>
))}
</div>
</Card>
<Card
bordered
title="实时态势地图"
style={{ marginBottom: 16 }}
>
<div className="vp-monitor-layout">
<div className="vp-monitor-map">
<div className="vp-monitor-map-header">
<Space wrap>
<Tag color={amapConfigured ? 'green' : 'orange'}>
{amapConfigured ? '高德地图配置就绪' : '高德地图待配置'}
</Tag>
<Tag color="green">线 {commandOnlineCount.toLocaleString()} / {locations.length.toLocaleString()}</Tag>
<Tag color="blue"> {commandLocatedCount.toLocaleString()}</Tag>
<Tag color={commandDegradedCount > 0 ? 'orange' : 'green'}>/线 {commandDegradedCount.toLocaleString()}</Tag>
</Space>
</div>
<VehicleMap
points={commandMapPoints}
maxFallbackPoints={80}
fallbackLabel="高德地图未配置,显示车辆态势坐标预览"
/>
</div>
<div className="vp-monitor-side">
<div className="vp-monitor-metric">
<Tag color="green"></Tag>
<div className="vp-monitor-metric-value">{commandOnlineCount.toLocaleString()}</div>
<Typography.Text type="secondary">线</Typography.Text>
</div>
<div className="vp-monitor-metric">
<Tag color={commandDegradedCount > 0 ? 'orange' : 'green'}></Tag>
<div className="vp-monitor-metric-value">{commandDegradedCount.toLocaleString()}</div>
<Typography.Text type="secondary">线</Typography.Text>
</div>
<Space vertical align="start">
<Button theme="solid" type="primary" onClick={() => onOpenMap({ online: 'online' })}></Button>
<Button
disabled={!highPriorityIssue?.issueType}
theme="light"
type="warning"
onClick={() => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})}
>
</Button>
<Button theme="light" onClick={() => onOpenVehicles({ serviceStatus: 'degraded' })}></Button>
</Space>
</div>
</div>
</Card>
{serviceActionQueue.length > 0 ? (
<Card bordered title="车辆服务处置队列" style={{ marginBottom: 16 }}>
<div className="vp-action-grid">
{serviceActionQueue.map((item) => (
<div key={`${item.label}-${item.count}`} className="vp-action-item">
<div>
<Tag color="orange">{item.label} {item.count.toLocaleString()}</Tag>
<Typography.Text type="secondary" style={{ display: 'block', marginTop: 8 }}>{item.detail}</Typography.Text>
</div>
<Button size="small" theme="light" type="warning" onClick={() => onOpenVehicles(item.filters)}>
{item.label} {item.count.toLocaleString()}
</Button>
</div>
))}
</div>
</Card>
) : null}
<Card
title={<Space><span></span><Button size="small" onClick={() => onOpenQuality()}></Button></Space>}
bordered
style={{ marginTop: 16 }}
>
<Table
pagination={false}
rowKey={(row?: QualityIssueRow) => `${row?.protocol ?? ''}-${row?.issueType ?? ''}-${row?.lastSeen ?? ''}-${row?.detail ?? ''}`}
dataSource={qualityIssues}
columns={[
{ title: 'VIN', dataIndex: 'vin', width: 160 },
{ title: '车牌', dataIndex: 'plate', width: 110 },
{ title: '手机号', dataIndex: 'phone', width: 130 },
{ title: '来源地址', dataIndex: 'sourceEndpoint', width: 180 },
{ title: '来源', dataIndex: 'protocol', width: 110 },
{ title: '问题', dataIndex: 'issueType', width: 140 },
{ title: '级别', width: 90, render: (_: unknown, row: QualityIssueRow) => <Tag color={row.severity === 'error' ? 'red' : 'orange'}>{row.severity}</Tag> },
{ title: '最后时间', dataIndex: 'lastSeen', width: 170 },
{
title: '操作',
width: 130,
render: (_: unknown, row: QualityIssueRow) => {
const lookup = qualityIssueVehicleLookup(row);
return <Button disabled={!lookup.key} onClick={() => onOpenVehicle(lookup.key, row.protocol)}>{lookup.label}</Button>;
}
}
]}
/>
</Card>
<Card
title={<Space><span></span><Button size="small" onClick={() => onOpenVehicles(coverageFilters)}></Button></Space>}
bordered
style={{ marginTop: 16 }}
>
{coverageServiceStatusTitle ? (
<div className="vp-scope-bar" style={{ marginBottom: 12 }}>
<Tag color="blue">{coverageServiceStatusTitle}</Tag>
</div>
) : null}
<Form layout="horizontal" onSubmit={(values) => loadCoverage(values as Record<string, string>)} style={{ marginBottom: 12 }}>
<Form.Input field="keyword" label="关键词" placeholder="VIN / 车牌 / 手机号 / OEM" style={{ width: 240 }} />
<Form.Select field="coverage" label="来源覆盖" placeholder="全部" style={{ width: 130 }}>
<Select.Option value="single"></Select.Option>
<Select.Option value="multi"></Select.Option>
</Form.Select>
<Form.Select field="missingProtocol" label="缺失来源" placeholder="全部" style={{ width: 170 }} data-testid="dashboard-missing-protocol-filter">
<Select.Option value="GB32960"> GB32960</Select.Option>
<Select.Option value="JT808"> JT808</Select.Option>
<Select.Option value="YUTONG_MQTT"> YUTONG_MQTT</Select.Option>
</Form.Select>
<Form.Select field="online" label="在线" placeholder="全部" style={{ width: 130 }}>
<Select.Option value="online">线</Select.Option>
<Select.Option value="offline">线</Select.Option>
</Form.Select>
<Form.Select field="bindingStatus" label="绑定" placeholder="全部" style={{ width: 130 }}>
<Select.Option value="bound"></Select.Option>
<Select.Option value="unbound"></Select.Option>
</Form.Select>
<Form.Select field="serviceStatus" label="服务状态" placeholder="全部" style={{ width: 150 }} data-testid="dashboard-service-status-filter">
<Select.Option value="healthy"></Select.Option>
<Select.Option value="degraded"></Select.Option>
<Select.Option value="offline">线</Select.Option>
<Select.Option value="no_data"></Select.Option>
<Select.Option value="identity_required"></Select.Option>
</Form.Select>
<Space>
<Button htmlType="submit" theme="solid" type="primary"></Button>
<Button onClick={() => loadCoverage({})}></Button>
</Space>
</Form>
<Table
loading={coverageLoading}
pagination={false}
rowKey="vin"
dataSource={coverage}
columns={[
{ title: '车牌', dataIndex: 'plate', width: 110 },
{ title: 'VIN', dataIndex: 'vin', width: 190 },
{
title: '来源证据',
width: 300,
render: (_: unknown, row: VehicleCoverageRow) => (
<SourceStatusTags sourceStatus={row.sourceStatus} protocols={row.protocols} lastSeen={row.lastSeen} />
)
},
{
title: '证据覆盖',
width: 130,
render: (_: unknown, row: VehicleCoverageRow) => sourceEvidenceText(row)
},
{
title: '服务状态',
width: 130,
render: (_: unknown, row: VehicleCoverageRow) => {
const status = rowServiceStatus(row);
return <Tag color={status.color}>{status.label}</Tag>;
}
},
{
title: '来源一致性',
width: 140,
render: (_: unknown, row: VehicleCoverageRow) => sourceConsistencyAction(row, loadCoverage)
},
{ title: '在线', width: 90, render: (_: unknown, row: VehicleCoverageRow) => <StatusTag status={row.online ? 'ok' : 'offline'} /> },
{ title: '绑定', width: 90, render: (_: unknown, row: VehicleCoverageRow) => <Tag color={row.bindingStatus === 'bound' ? 'green' : 'orange'}>{row.bindingStatus === 'bound' ? '已绑定' : '未绑定'}</Tag> },
{ title: '最后时间', dataIndex: 'lastSeen', width: 170 },
{ title: '操作', width: 110, render: (_: unknown, row: VehicleCoverageRow) => <Button onClick={() => onOpenVehicle(row.vin)}></Button> }
]}
/>
</Card>
<Row gutter={16} style={{ marginTop: 16 }}>
<Col span={12}>
<Card title="实时位置预览" bordered>
<div className="vp-map" style={{ height: 260 }}>
{locations.map((row, index) => (
<span
key={row.vin}
className="vp-map-dot"
title={`${row.plate} ${row.primaryProtocol}`}
style={{ left: `${18 + index * 13}%`, top: `${24 + (index % 4) * 15}%` }}
/>
))}
</div>
</Card>
</Col>
<Col span={12}>
<Card title="最新车辆" bordered>
<Table
pagination={false}
rowKey="vin"
dataSource={locations}
columns={[
{ title: '车牌', dataIndex: 'plate' },
{ title: 'VIN', dataIndex: 'vin' },
{
title: '来源证据',
render: (_: unknown, row: VehicleRealtimeRow) => (
<Space spacing={4} wrap>
{row.protocols.map((protocol) => <Tag key={protocol} color={protocol === row.primaryProtocol ? 'blue' : 'grey'}>{protocol}</Tag>)}
</Space>
)
},
{
title: '服务状态',
render: (_: unknown, row: VehicleRealtimeRow) => {
const status = rowServiceStatus(row);
return <Tag color={status.color}>{status.label}</Tag>;
}
},
{ title: '最后时间', dataIndex: 'lastSeen' },
{ title: '操作', width: 110, render: (_: unknown, row: VehicleRealtimeRow) => <Button onClick={() => onOpenVehicle(row.vin, row.primaryProtocol)}></Button> }
]}
/>
</Card>
</Col>
</Row>
</div>
</details>
</Spin>
</div>
);
}