import { Button, Card, Col, Form, Row, Select, Space, Spin, Table, Tag, Toast, Typography } from '@douyinfe/semi-ui'; import { IconSearch } from '@douyinfe/semi-icons'; import { useEffect, useMemo, useState } from 'react'; import { api } from '../api/client'; import type { DashboardSummary, LinkHealth, ProtocolStat, QualityIssueRow, ServiceStatusStat, 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 statusColor: Record = { ok: 'green', warning: 'orange', error: 'red' }; const serviceStatusColor: Record = { healthy: 'green', degraded: 'orange', offline: 'red', no_data: 'orange', identity_required: 'orange' }; const serviceStatusTitle: Record = { healthy: '服务正常', degraded: '来源不完整', offline: '车辆离线', no_data: '暂无数据来源', identity_required: '身份未绑定' }; const missingProtocolTitle: Record = { GB32960: '缺 GB32960', JT808: '缺 JT808', YUTONG_MQTT: '缺 YUTONG_MQTT' }; type DashboardSnapshotRow = { section: string; item: string; value: string; detail: string; }; const dashboardSnapshotColumns: CsvColumn[] = [ { 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 formatProtocolRate(row: ProtocolStat) { if (!Number.isFinite(row.total) || row.total <= 0) { return '0%'; } return `${Math.round((row.online / row.total) * 100)}%`; } 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 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 }))}`, `RAW证据:${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 sourceConsistencyAction(row: VehicleCoverageRow, onFilter: (filters: Record) => void) { const consistency = row.sourceConsistency; if (!consistency) { return 未诊断; } 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 ( ); } return {label}; } export function Dashboard({ onOpenVehicle, onOpenQuality, onOpenMap, onOpenRealtime, onOpenVehicles, onOpenHistory, onOpenMileage }: { onOpenVehicle: (vin: string, protocol?: string) => void; onOpenQuality: (filters?: Record) => void; onOpenMap: (filters?: Record) => void; onOpenRealtime: (filters?: Record) => void; onOpenVehicles: (filters?: Record) => void; onOpenHistory: (filters?: Record) => void; onOpenMileage: (filters?: Record) => void; }) { const [summary, setSummary] = useState(null); const [serviceSummary, setServiceSummary] = useState(null); const [coverage, setCoverage] = useState([]); const [locations, setLocations] = useState([]); const [qualityIssues, setQualityIssues] = useState([]); const [loading, setLoading] = useState(true); const [coverageLoading, setCoverageLoading] = useState(false); const [coverageServiceStatusTitle, setCoverageServiceStatusTitle] = useState(''); const [coverageFilters, setCoverageFilters] = useState>({}); const amapConfigured = isAMapConfigured(); const loadCoverage = (values?: Record) => { 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)) ]; 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)); }, []); const kpis: Array<{ label: string; value: string; filters: Record }> = [ { label: '总车辆', value: formatCount(serviceSummary?.totalVehicles), filters: {} }, { label: '在线车辆', value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles), filters: { online: 'online' } }, { label: '单源车辆', value: formatCount(serviceSummary?.singleSourceVehicles), filters: { coverage: 'single' } }, { label: '多源车辆', value: formatCount(serviceSummary?.multiSourceVehicles), filters: { coverage: 'multi' } }, { label: '暂无来源车辆', value: formatCount(serviceSummary?.noDataVehicles), filters: { serviceStatus: 'no_data' } }, { label: '身份未绑定', value: formatCount(serviceSummary?.identityRequiredVehicles), filters: { serviceStatus: 'identity_required' } }, { label: '档案不完整', value: formatCount(serviceSummary?.archiveIncompleteVehicles), filters: { archiveStatus: 'incomplete' } } ]; const missingSourceCounts = new Map((serviceSummary?.missingSources ?? []).map((item) => [item.protocol, item.count])); const serviceActionQueue = useMemo; detail: string }>>(() => { const items: Array<{ label: string; count: number; filters: Record; 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 highPriorityIssue = qualityIssues.find((item) => item.severity === 'error') ?? qualityIssues[0]; const highPriorityLookup = highPriorityIssue ? qualityIssueVehicleLookup(highPriorityIssue) : undefined; const highPriorityEvidenceFilters = highPriorityIssue ? priorityIssueEvidenceFilters(highPriorityIssue) : 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 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: '围绕车辆回放历史位置轨迹,结合高德线路和 RAW 证据定位异常。', action: '打开轨迹', onClick: () => onOpenHistory() }, { title: '历史数据查询', status: `今日帧 ${formatCount(summary?.frameToday)}`, color: 'blue' as const, description: '查询位置历史、RAW 帧和解析字段,为车辆问题复盘提供证据链。', 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: '检查平台转发、Kafka、Redis、MySQL、TDengine 等关键链路。', 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: '查询 RAW 帧和解析字段,形成可追溯的数据证据。', 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 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: '查 RAW 证据', onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' }) } ] }, { title: '历史数据查询', value: `${formatCount(summary?.frameToday)} 今日帧`, meta: `Kafka Lag ${formatLag(summary?.kafkaLag)}`, color: (summary?.kafkaLag ?? 0) > 0 ? 'orange' as const : 'blue' as const, detail: '查询位置历史、RAW 帧和解析字段,为车辆服务提供可追溯证据。', 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: '里程等指标先按车辆口径闭合,再回溯轨迹和 RAW 证据。', actions: [ { label: '打开统计查询', onClick: () => onOpenMileage() }, { label: '查看车辆中心', onClick: () => onOpenVehicles({}) } ] } ]; 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: 'RAW证据', onPrimary: () => onOpenHistory(), onSecondary: () => onOpenHistory({ tab: 'raw', includeFields: 'true' }) }, { title: '历史数据查询', objective: '围绕车辆查询位置、RAW、解析字段,给 BI、运维和业务复盘提供证据。', evidence: `TDengine / 今日帧 ${formatCount(summary?.frameToday)} / Kafka Lag ${formatLag(summary?.kafkaLag)}`, 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)} / 多源覆盖 ${formatCount(serviceSummary?.multiSourceVehicles)}`, sla: '统计值必须能追溯轨迹和 RAW 证据', 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)}`, `Kafka Lag:${formatLag(summary?.kafkaLag)}`, `高德地图:${amapConfigured ? '已配置' : '待配置'}`, `链路健康:${unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join(';') : '正常'}`, `优先动作:${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 }))}`, `历史RAW:${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 都只是车辆服务证据源,最终围绕一辆车提供实时、轨迹、历史、告警、统计能力。', `高德地图:${amapConfigured ? '已配置,前端只使用运行时配置和安全代理' : '待配置'}`, `车辆规模:${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 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(serviceSummary?.multiSourceVehicles), detail: '同一车辆可由多个协议交叉验证' }, { section: '车辆服务', item: '单源车辆', value: formatCount(serviceSummary?.singleSourceVehicles), detail: '仅有一个协议来源,需要持续补齐' }, { section: '车辆服务', item: '暂无来源车辆', value: formatCount(serviceSummary?.noDataVehicles), detail: '车辆档案存在但暂无实时来源' }, { section: '实时监控', item: '有效定位', value: commandLocatedCount.toLocaleString(), detail: `当前预览 ${locations.length.toLocaleString()} 条车辆实时数据` }, { section: '实时监控', item: '来源异常', value: commandDegradedCount.toLocaleString(), detail: '当前预览中离线或来源不完整车辆' }, { section: '历史数据', item: '今日帧量', value: formatCount(summary?.frameToday), detail: `Kafka Lag ${formatLag(summary?.kafkaLag)}` }, { section: '告警事件', item: '告警车辆', value: formatCount(summary?.issueVehicles), detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无最高优先级告警' }, { section: '链路健康', item: '异常链路', value: unhealthyLinks.length.toLocaleString(), detail: unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join(';') : '正常' }, { section: '地图能力', item: '高德地图', value: amapConfigured ? '已配置' : '待配置', detail: '用于实时监控和轨迹回放地图底座' }, { section: '优先动作', item: priorityAction?.label ?? '暂无待办', value: priorityAction ? priorityAction.count.toLocaleString() : '0', detail: priorityAction?.detail ?? '当前没有必须立即处理的车辆服务事项' } ]; (serviceSummary?.protocols ?? summary?.protocols ?? []).forEach((item) => { rows.push({ section: '协议来源', item: item.protocol, value: `${item.online.toLocaleString()} / ${item.total.toLocaleString()}`, detail: `在线率 ${formatProtocolRate(item)}` }); }); (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 }); }); (summary?.linkHealth ?? []).forEach((item) => { rows.push({ section: '链路健康', item: item.name, value: item.status, detail: item.detail ?? '' }); }); 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()} 条驾驶舱摘要`); }; return (
{kpis.map((item) => ( ))}
{vehicleServiceOnlineText(serviceSummary, summary)} {formatCount(serviceSummary?.multiSourceVehicles)} 多源覆盖 0 ? 'orange' : 'green'}> {formatCount(summary?.issueVehicles)} 告警事件 0 ? 'orange' : 'green'}>Kafka Lag {formatLag(summary?.kafkaLag)}
{operationWorkbench.map((item) => (
{item.title} {item.meta}
{item.value}
{item.detail} {item.actions.map((action) => ( ))}
))}
{vehicleServicePrinciples.map((item) => (
{item.title}
{item.value}
{item.detail}
))}
车联网场景导航} style={{ marginBottom: 16 }} >
{scenarioNavigation.map((item) => (
{item.title} {item.objective}
{item.evidence}
{item.sla}
))}
{workflowSteps.map((item, index) => (
{index + 1}
{item.title} {item.value} {item.detail}
))}
{highPriorityIssue ? (
{highPriorityIssue.severity === 'error' ? 'P0' : 'P1'} {qualityIssueLabel(highPriorityIssue.issueType)} {qualityProtocolLabel(highPriorityIssue.protocol)} {highPriorityIssue.lastSeen || '-'} {priorityIssueVehicleLabel(highPriorityIssue)} {highPriorityIssue.detail || '暂无告警详情'}
) : null}
{capabilities.map((item) => (
{item.title} {item.description}
{item.status}
))}
{amapConfigured ? '高德地图配置就绪' : '高德地图待配置'} 在线 {commandOnlineCount.toLocaleString()} / {locations.length.toLocaleString()} 有效定位 {commandLocatedCount.toLocaleString()} 0 ? 'orange' : 'green'}>降级/离线 {commandDegradedCount.toLocaleString()}
实时作业
{commandOnlineCount.toLocaleString()}
当前预览车辆在线数,进入实时监控可按车辆、来源和服务状态筛选。
0 ? 'orange' : 'green'}>处置优先级
{commandDegradedCount.toLocaleString()}
优先处理离线、无来源、身份未绑定和来源不完整车辆。
{serviceActionQueue.length > 0 ? (
{serviceActionQueue.map((item) => (
{item.label} {item.count.toLocaleString()} {item.detail}
))}
) : null} {row.title} }, { title: '车辆数', dataIndex: 'count' }, { title: '操作', width: 90, render: (_: unknown, row: ServiceStatusStat) => (
formatProtocolRate(row) }, { title: '缺失车辆', render: (_: unknown, row: ProtocolStat) => { const missingCount = missingSourceCounts.get(row.protocol); if (missingCount == null) { return '-'; } return ( ); } } ]} />
{row.status} }, { title: '说明', dataIndex: 'detail' } ]} /> Kafka 当前消费积压:{formatLag(summary?.kafkaLag)} 质量问题预览} bordered style={{ marginTop: 16 }} >
`${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) => {row.severity} }, { title: '最后时间', dataIndex: 'lastSeen', width: 170 }, { title: '操作', width: 130, render: (_: unknown, row: QualityIssueRow) => { const lookup = qualityIssueVehicleLookup(row); return ; } } ]} /> 车辆服务覆盖} bordered style={{ marginTop: 16 }} > {coverageServiceStatusTitle ? (
当前筛选:{coverageServiceStatusTitle}
) : null}
loadCoverage(values as Record)} style={{ marginBottom: 12 }}> 单源 多源 缺 GB32960 缺 JT808 缺 YUTONG_MQTT 在线 离线 已绑定 未绑定 服务正常 来源不完整 车辆离线 暂无数据来源 身份未绑定
( ) }, { title: '证据覆盖', width: 130, render: (_: unknown, row: VehicleCoverageRow) => sourceEvidenceText(row) }, { title: '服务状态', width: 130, render: (_: unknown, row: VehicleCoverageRow) => { const status = rowServiceStatus(row); return {status.label}; } }, { title: '来源一致性', width: 140, render: (_: unknown, row: VehicleCoverageRow) => sourceConsistencyAction(row, loadCoverage) }, { title: '在线', width: 90, render: (_: unknown, row: VehicleCoverageRow) => }, { title: '绑定', width: 90, render: (_: unknown, row: VehicleCoverageRow) => {row.bindingStatus === 'bound' ? '已绑定' : '未绑定'} }, { title: '最后时间', dataIndex: 'lastSeen', width: 170 }, { title: '操作', width: 110, render: (_: unknown, row: VehicleCoverageRow) => } ]} />
{locations.map((row, index) => ( ))}
( {row.protocols.map((protocol) => {protocol})} ) }, { title: '服务状态', render: (_: unknown, row: VehicleRealtimeRow) => { const status = rowServiceStatus(row); return {status.label}; } }, { title: '最后时间', dataIndex: 'lastSeen' }, { title: '操作', width: 110, render: (_: unknown, row: VehicleRealtimeRow) => } ]} /> ); }