feat(platform): add vehicle service decision board
This commit is contained in:
@@ -599,6 +599,53 @@ export function Vehicles({
|
||||
onClick: exportVehicles
|
||||
}
|
||||
], [exportVehicles, filters.keyword, onOpenHistory, onOpenMap, onOpenMileage, onOpenRealtime, rows.length, serviceScopeFilters, summary?.onlineVehicles]);
|
||||
const primaryAction = actionQueue[0];
|
||||
const vehicleDecisionItems = [
|
||||
{
|
||||
title: '在线车辆',
|
||||
value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 辆`,
|
||||
detail: '先看当前仍在上报的车辆,直接进入地图和实时状态。',
|
||||
color: 'green' as const,
|
||||
primaryAction: '实时监控',
|
||||
secondaryAction: '车辆地图',
|
||||
onPrimary: () => onOpenRealtime?.({ ...serviceScopeFilters, online: 'online' }),
|
||||
onSecondary: () => onOpenMap?.({ ...serviceScopeFilters, online: 'online' }),
|
||||
disabled: !onOpenRealtime
|
||||
},
|
||||
{
|
||||
title: '异常车辆',
|
||||
value: `${(summary?.noDataVehicles ?? 0).toLocaleString()} 无来源`,
|
||||
detail: primaryAction ? primaryAction.detail : '当前没有必须立即处理的车辆服务事项。',
|
||||
color: (summary?.noDataVehicles ?? 0) > 0 || primaryAction ? 'orange' as const : 'green' as const,
|
||||
primaryAction: primaryAction?.label ?? '查看结果',
|
||||
secondaryAction: '告警事件',
|
||||
onPrimary: () => primaryAction ? applyFilters({ ...filters, ...primaryAction.filters }) : applyFilters(filters),
|
||||
onSecondary: () => onOpenQuality?.(serviceScopeFilters),
|
||||
disabled: !primaryAction
|
||||
},
|
||||
{
|
||||
title: '单车服务',
|
||||
value: filters.keyword?.trim() || '输入车辆',
|
||||
detail: filters.keyword?.trim() ? '当前已进入单车范围,可查看车辆详情、轨迹和里程。' : '输入 VIN、车牌或手机号后进入完整车辆服务。',
|
||||
color: filters.keyword?.trim() ? 'blue' as const : 'grey' as const,
|
||||
primaryAction: '车辆详情',
|
||||
secondaryAction: '轨迹回放',
|
||||
onPrimary: () => filters.keyword?.trim() && onOpenVehicle(filters.keyword.trim(), filters.protocol),
|
||||
onSecondary: () => onOpenHistory?.({ ...serviceScopeFilters, tab: 'location' }),
|
||||
disabled: !filters.keyword?.trim()
|
||||
},
|
||||
{
|
||||
title: '数据交付',
|
||||
value: `${rows.length.toLocaleString()} 当前页`,
|
||||
detail: '导出当前车辆清单,继续进入历史页交付轨迹、RAW 和字段证据。',
|
||||
color: rows.length > 0 ? 'blue' as const : 'grey' as const,
|
||||
primaryAction: '导出清单',
|
||||
secondaryAction: '历史导出',
|
||||
onPrimary: exportVehicles,
|
||||
onSecondary: () => onOpenHistory?.({ ...serviceScopeFilters, tab: 'raw', includeFields: 'true' }),
|
||||
disabled: rows.length === 0
|
||||
}
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
setFilters(initialFilters);
|
||||
@@ -742,6 +789,41 @@ export function Vehicles({
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered title="车辆服务决策板" style={{ marginTop: 16 }}>
|
||||
<div className="vp-vehicle-decision-grid">
|
||||
{vehicleDecisionItems.map((item) => (
|
||||
<div key={item.title} className="vp-vehicle-decision-item">
|
||||
<div className="vp-vehicle-decision-head">
|
||||
<Tag color={item.color}>{item.title}</Tag>
|
||||
<strong>{item.value}</strong>
|
||||
</div>
|
||||
<span>{item.detail}</span>
|
||||
<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.title === '单车服务' && item.disabled}
|
||||
aria-label={`车辆服务决策 ${item.title} ${item.secondaryAction}`}
|
||||
onClick={item.onSecondary}
|
||||
>
|
||||
{item.secondaryAction}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered>
|
||||
<Form key={JSON.stringify(filters)} initValues={filters} layout="horizontal" onSubmit={(values) => {
|
||||
const nextFilters = values as Record<string, string>;
|
||||
|
||||
@@ -874,6 +874,43 @@ button.vp-realtime-command-item:focus-visible {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.vp-vehicle-decision-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vp-vehicle-decision-item {
|
||||
min-height: 172px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--vp-border);
|
||||
border-radius: 8px;
|
||||
background: #fbfcff;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.vp-vehicle-decision-head {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vp-vehicle-decision-head strong {
|
||||
color: var(--vp-text);
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
font-weight: 700;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.vp-vehicle-decision-item span {
|
||||
color: var(--vp-text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.vp-result-summary-item {
|
||||
min-height: 72px;
|
||||
padding: 12px;
|
||||
@@ -3456,6 +3493,7 @@ button.vp-realtime-command-item:focus-visible {
|
||||
.vp-current-service-grid,
|
||||
.vp-vehicle-service-desk,
|
||||
.vp-vehicle-service-grid,
|
||||
.vp-vehicle-decision-grid,
|
||||
.vp-realtime-command-board,
|
||||
.vp-realtime-command-grid,
|
||||
.vp-realtime-impact-board,
|
||||
|
||||
@@ -1979,7 +1979,15 @@ test('shows vehicle service result summary on vehicle list filters', async () =>
|
||||
expect(await screen.findByText('当前车辆结果')).toBeInTheDocument();
|
||||
expect(screen.getByText('车辆服务台')).toBeInTheDocument();
|
||||
expect(screen.getByText('客户视角')).toBeInTheDocument();
|
||||
expect(screen.getByText('车辆地图')).toBeInTheDocument();
|
||||
expect(screen.getByText('车辆服务决策板')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('异常车辆').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('4 无来源')).toBeInTheDocument();
|
||||
expect(screen.getByText('单车服务')).toBeInTheDocument();
|
||||
expect(screen.getByText('输入车辆')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆服务决策 在线车辆 实时监控' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆服务决策 异常车辆 维护身份绑定' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '车辆服务决策 数据交付 导出清单' })).toBeInTheDocument();
|
||||
expect(screen.getAllByText('车辆地图').length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText('里程统计').length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText('数据导出').length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText('当前页服务判读')).toBeInTheDocument();
|
||||
@@ -1990,7 +1998,7 @@ test('shows vehicle service result summary on vehicle list filters', async () =>
|
||||
expect(screen.getAllByText('181').length).toBeGreaterThanOrEqual(2);
|
||||
expect(screen.getByText('过滤车辆')).toBeInTheDocument();
|
||||
expect(screen.getByText('73')).toBeInTheDocument();
|
||||
expect(screen.getByText('在线车辆')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('在线车辆').length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText('单源车辆')).toBeInTheDocument();
|
||||
expect(screen.getByText('暂无来源车辆')).toBeInTheDocument();
|
||||
expect(screen.getByText('待绑定')).toBeInTheDocument();
|
||||
@@ -1999,7 +2007,7 @@ test('shows vehicle service result summary on vehicle list filters', async () =>
|
||||
expect(screen.getByText('处置队列')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('P0').length).toBeGreaterThanOrEqual(2);
|
||||
expect(screen.getAllByText('P1').length).toBeGreaterThanOrEqual(2);
|
||||
expect(screen.getByText('已有数据但无法归并到 VIN,先补绑定再看跨来源服务。')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('已有数据但无法归并到 VIN,先补绑定再看跨来源服务。').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('车辆没有任何来源证据,优先确认平台转发、端口和订阅配置。')).toBeInTheDocument();
|
||||
expect(screen.getByText('缺手机号会影响车辆档案检索、绑定确认和运营侧筛选。')).toBeInTheDocument();
|
||||
expect(screen.getByText('YUTONG_MQTT 来源缺失会降低跨来源定位、里程和实时判断可信度。')).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user