feat(platform): add realtime next actions

This commit is contained in:
lingniu
2026-07-05 14:07:29 +08:00
parent 2574a3bfa0
commit 4c110566f9
3 changed files with 119 additions and 0 deletions

View File

@@ -1214,6 +1214,39 @@ export function Realtime({
disabled: rows.length === 0
}
];
const realtimeNextActions = [
{
level: degradedCount > 0 || staleCount > 0 ? '优先' : '巡检',
title: degradedCount > 0 || staleCount > 0 ? '先处理异常车辆' : '先看在线车辆',
value: degradedCount > 0 || staleCount > 0 ? `${degradedCount.toLocaleString()} 降级 / ${staleCount.toLocaleString()} 超时` : `${onlineCount.toLocaleString()} 在线`,
detail: degradedCount > 0 || staleCount > 0
? '离线、更新超时、坐标无效或服务降级会直接影响客户看车体验。'
: `当前页 ${locatedCount.toLocaleString()} 辆有有效坐标,可进入地图查看分布。`,
action: degradedCount > 0 || staleCount > 0 ? '关注异常' : '打开地图',
color: degradedCount > 0 || staleCount > 0 ? 'orange' as const : 'green' as const,
onClick: () => degradedCount > 0 || staleCount > 0 ? applyFilters({ ...filters, serviceStatus: 'degraded' }) : (window.location.hash = buildAppHash({ page: 'map', protocol: filters.protocol, filters }))
},
{
level: selectedMapRow ? '单车' : '选车',
title: selectedMapRow ? '复盘选中车辆' : '先选择一辆车',
value: selectedMapRow?.plate || selectedMapRow?.vin || '等待选车',
detail: selectedMapRow ? `${vehicleServiceStatus(selectedMapRow).label}${dataFreshness(selectedMapRow).detail}` : '从地图或实时列表选择车辆后,进入轨迹、里程、历史和告警。',
action: selectedMapRow ? '轨迹回放' : '查看车辆',
color: selectedMapRow ? vehicleServiceStatus(selectedMapRow).color : 'blue' as const,
disabled: selectedMapRow ? !canOpenVehicle(selectedMapRow.vin) : false,
onClick: () => selectedMapRow && canOpenVehicle(selectedMapRow.vin) ? onOpenHistory?.(selectedMapRow.vin, selectedVehicleProtocol) : applyFilters({ ...filters, online: 'online' })
},
{
level: '交付',
title: '导出当前实时清单',
value: `${rows.length.toLocaleString()} 辆当前页`,
detail: '客户问询或运营交接时,先导出当前筛选的车辆在线、位置、速度和里程。',
action: '导出 CSV',
color: rows.length > 0 ? 'blue' as const : 'grey' as const,
disabled: rows.length === 0,
onClick: exportRealtime
}
];
const timeWindowMonitorBlock = (
<Card bordered className="vp-time-window-monitor">
<div className="vp-time-window-summary">
@@ -1634,6 +1667,24 @@ export function Realtime({
<Button size="small" theme="light" onClick={copyRealtimeSummary}></Button>
</Space>
</div>
<div className="vp-realtime-next-actions">
{realtimeNextActions.map((item) => (
<button
key={item.title}
type="button"
className="vp-realtime-next-action"
disabled={item.disabled}
onClick={item.onClick}
aria-label={`实时运营建议 ${item.title} ${item.action}`}
>
<Tag color={item.color}>{item.level}</Tag>
<strong>{item.title}</strong>
<span>{item.value}</span>
<small>{item.detail}</small>
<em>{item.action}</em>
</button>
))}
</div>
<div className="vp-realtime-customer-steps">
{realtimeCustomerJourneyItems.map((item) => (
<button

View File

@@ -1414,6 +1414,70 @@ body {
align-content: start;
}
.vp-realtime-next-actions {
padding: 16px;
border-bottom: 1px solid var(--vp-border);
background: #ffffff;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.vp-realtime-next-action {
min-height: 126px;
padding: 12px;
border: 1px solid rgba(22, 100, 255, 0.16);
border-radius: var(--vp-radius);
background: #fbfcff;
color: inherit;
text-align: left;
cursor: pointer;
font: inherit;
display: grid;
gap: 7px;
align-content: start;
}
.vp-realtime-next-action:hover,
.vp-realtime-next-action:focus-visible {
border-color: rgba(22, 100, 255, 0.42);
background: #f5f9ff;
outline: none;
}
.vp-realtime-next-action:disabled {
cursor: not-allowed;
opacity: 0.58;
}
.vp-realtime-next-action strong {
color: var(--vp-text);
font-size: 15px;
line-height: 22px;
}
.vp-realtime-next-action span {
color: var(--vp-primary);
font-size: 14px;
line-height: 20px;
font-weight: 700;
word-break: break-word;
}
.vp-realtime-next-action small {
color: var(--vp-text-muted);
font-size: 12px;
line-height: 18px;
}
.vp-realtime-next-action em {
color: var(--vp-primary);
font-size: 12px;
font-style: normal;
font-weight: 700;
line-height: 18px;
}
.vp-realtime-customer-steps {
padding: 16px;
display: grid;
@@ -5883,6 +5947,7 @@ button.vp-realtime-command-item:focus-visible {
.vp-time-audit-strip,
.vp-time-audit-grid,
.vp-realtime-customer-board .semi-card-body,
.vp-realtime-next-actions,
.vp-realtime-customer-steps,
.vp-time-window-monitor .semi-card-body,
.vp-time-window-controls,

View File

@@ -9097,6 +9097,9 @@ test('frames realtime page as one vehicle service with source evidence', async (
expect(screen.getAllByText('高德地图待配置').length).toBeGreaterThan(0);
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: '实时运营建议 导出当前实时清单 导出 CSV' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '实时监控路径 只看在线 在线车辆' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '实时监控路径 地图态势 查看地图' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '实时监控路径 异常优先 关注车辆' })).toBeInTheDocument();