feat(platform): add alert vehicle task board

This commit is contained in:
lingniu
2026-07-05 02:30:27 +08:00
parent 12caed6310
commit f47475a1a9
3 changed files with 148 additions and 0 deletions

View File

@@ -723,6 +723,8 @@ export function Quality({
const dispatchOwnerRows = alertDispatchOwnerRows(rules);
const priorityP0Count = priorityRows.filter((row) => row.priority === 'P0').length;
const priorityP1Count = priorityRows.filter((row) => row.priority === 'P1').length;
const focusIssue = priorityRows[0];
const focusLookup = focusIssue ? qualityIssueVehicleLookup(focusIssue) : undefined;
const activeRulesForImpact = rules.filter((row) => row.count > 0);
const storageWritable = !health || (health.tdengineWritable && health.mysqlWritable);
const capacityFindingCount = health?.capacityFindings?.length ?? 0;
@@ -972,6 +974,81 @@ export function Quality({
</div>
</div>
</Card>
<Card bordered title="车辆风险处置任务板" style={{ marginTop: 16 }}>
<div className="vp-alert-vehicle-task-board">
<div className="vp-alert-vehicle-focus">
<Space wrap>
<Tag color={focusIssue?.priority === 'P0' ? 'red' : focusIssue ? 'orange' : 'green'}>
{focusIssue ? focusIssue.priority : '无待处置'}
</Tag>
<Tag color={focusIssue ? 'blue' : 'green'}>
{focusIssue ? qualityIssueLabel(focusIssue.issueType) : '车辆服务正常'}
</Tag>
</Space>
<strong>{focusIssue?.vehicleLabel || '暂无优先车辆'}</strong>
<span>
{focusIssue
? `${focusIssue.actionLabel}${focusIssue.actionDetail || focusIssue.detail || '请结合实时、轨迹、RAW 和里程证据闭环。'}`
: '当前没有进入优先队列的车辆,保持实时监控并定期复核告警规则。'}
</span>
<Space wrap>
<Button size="small" theme="solid" type="primary" disabled={!focusLookup?.key} onClick={() => focusIssue && onOpenVehicle(focusLookup?.key ?? '', focusIssue.protocol)}>
</Button>
<Button size="small" disabled={!focusIssue} onClick={() => focusIssue && copyText(priorityIssueNotificationText(focusIssue), '焦点车辆告警通知')}>
</Button>
</Space>
</div>
<div className="vp-alert-vehicle-task-grid">
{[
{
label: '实时确认',
value: focusIssue ? '看在线' : '待命',
detail: '先确认车辆是否仍在线、最后上报来源和实时字段是否恢复。',
action: '实时状态',
color: focusIssue ? 'blue' as const : 'grey' as const,
disabled: !focusIssue || !focusLookup?.key || !onOpenRealtime,
onClick: () => focusIssue && openIssueRealtime(focusIssue)
},
{
label: '轨迹证据',
value: focusIssue ? '看前后' : '待命',
detail: '按告警发生日期打开轨迹,判断是否影响车辆定位和回放。',
action: '轨迹回放',
color: focusIssue ? 'green' as const : 'grey' as const,
disabled: !focusIssue || !focusLookup?.key || !onOpenHistory,
onClick: () => focusIssue && openIssueHistory(focusIssue)
},
{
label: '里程复核',
value: focusIssue ? '核统计' : '待命',
detail: '对同一天里程统计与轨迹证据做交叉校验,避免影响 BI。',
action: '里程统计',
color: focusIssue ? 'orange' as const : 'grey' as const,
disabled: !focusIssue || !focusLookup?.key || !onOpenMileage,
onClick: () => focusIssue && openIssueMileage(focusIssue)
},
{
label: '原始证据',
value: focusIssue ? '查RAW' : '待命',
detail: '保留原始帧和解析字段,给平台、客户和研发确认责任边界。',
action: 'RAW证据',
color: focusIssue ? 'blue' as const : 'grey' as const,
disabled: !focusIssue || !focusLookup?.key || !onOpenRaw,
onClick: () => focusIssue && openIssueRaw(focusIssue)
}
].map((item) => (
<div key={item.label} className="vp-alert-vehicle-task-item">
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
<Button size="small" disabled={item.disabled} onClick={item.onClick}>{item.action}</Button>
</div>
))}
</div>
</div>
</Card>
<div className="vp-kpi-grid">
{[
{ label: '问题车辆', value: issueVehicleCount.toLocaleString() },

View File

@@ -1417,6 +1417,68 @@ button.vp-realtime-command-item:focus-visible {
line-height: 20px;
}
.vp-alert-vehicle-task-board {
display: grid;
grid-template-columns: minmax(280px, 0.72fr) minmax(0, 1.28fr);
gap: 16px;
}
.vp-alert-vehicle-focus {
min-height: 172px;
padding: 12px;
border: 1px solid rgba(239, 68, 68, 0.24);
border-radius: var(--vp-radius);
background: #fff7f7;
display: grid;
gap: 10px;
align-content: start;
}
.vp-alert-vehicle-focus strong {
color: var(--vp-text);
font-size: 20px;
line-height: 26px;
font-weight: 700;
word-break: break-word;
}
.vp-alert-vehicle-focus span {
color: var(--vp-text-muted);
font-size: 13px;
line-height: 20px;
}
.vp-alert-vehicle-task-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.vp-alert-vehicle-task-item {
min-height: 172px;
padding: 12px;
border: 1px solid var(--vp-border);
border-radius: var(--vp-radius);
background: #fbfcff;
display: grid;
gap: 10px;
align-content: start;
}
.vp-alert-vehicle-task-item strong {
color: var(--vp-text);
font-size: 20px;
line-height: 26px;
font-weight: 700;
word-break: break-word;
}
.vp-alert-vehicle-task-item span {
color: var(--vp-text-muted);
font-size: 13px;
line-height: 20px;
}
.vp-alert-flow-item {
min-height: 96px;
padding: 12px;
@@ -3344,6 +3406,8 @@ button.vp-realtime-command-item:focus-visible {
.vp-alert-dispatch-grid,
.vp-alert-center-board,
.vp-alert-center-grid,
.vp-alert-vehicle-task-board,
.vp-alert-vehicle-task-grid,
.vp-alert-command-board,
.vp-alert-command-grid,
.vp-notification-coverage-board,

View File

@@ -3727,6 +3727,13 @@ test('shows actionable priority queue on quality page', async () => {
render(<App />);
expect(await screen.findByText('处置优先队列')).toBeInTheDocument();
expect(screen.getByText('车辆风险处置任务板')).toBeInTheDocument();
expect(screen.getAllByText('粤A优先1 / VIN-P0-001').length).toBeGreaterThan(0);
expect(screen.getByText('实时确认')).toBeInTheDocument();
expect(screen.getAllByText('轨迹证据').length).toBeGreaterThan(0);
expect(screen.getByText('里程复核')).toBeInTheDocument();
expect(screen.getAllByText('原始证据').length).toBeGreaterThan(0);
expect(screen.getByText('先确认车辆是否仍在线、最后上报来源和实时字段是否恢复。')).toBeInTheDocument();
expect(screen.getAllByText('P0').length).toBeGreaterThan(0);
expect(screen.getAllByText('粤A优先1 / VIN-P0-001').length).toBeGreaterThan(0);
expect(screen.getAllByText('确认平台转发').length).toBeGreaterThan(0);