feat(platform): add focus vehicle service card
This commit is contained in:
@@ -48,6 +48,20 @@ type DashboardSnapshotRow = {
|
||||
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 },
|
||||
@@ -163,6 +177,43 @@ function priorityIssueNotificationText(row: QualityIssueRow) {
|
||||
].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) {
|
||||
@@ -316,6 +367,7 @@ export function Dashboard({
|
||||
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}`,
|
||||
@@ -602,6 +654,30 @@ export function Dashboard({
|
||||
];
|
||||
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 }))}`,
|
||||
`RAW证据:${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];
|
||||
@@ -738,6 +814,42 @@ export function Dashboard({
|
||||
<Button size="small" theme="light" type="primary" onClick={exportDashboardSnapshot}>导出驾驶舱 CSV</Button>
|
||||
</Space>
|
||||
</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">把协议来源作为证据,把实时、轨迹、RAW、统计和告警集中到同一辆车处理。</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="重点车辆 RAW证据" disabled={!focusVehicle.lookupKey} onClick={() => onOpenHistory({ keyword: focusVehicle.lookupKey, protocol: focusVehicle.protocol ?? '', tab: 'raw', includeFields: 'true' })}>RAW证据</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) => (
|
||||
|
||||
Reference in New Issue
Block a user