feat(platform): add alert vehicle task board
This commit is contained in:
@@ -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() },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user