refine Semi UI alert disposition flow
This commit is contained in:
@@ -167,6 +167,10 @@ test('clears old alert rows and inspector evidence when the event scope changes'
|
||||
expect(screen.getByLabelText('告警触发证据')).toHaveTextContent('触发值90 km/h阈值条件> 80 km/h,持续 60 秒超限幅度+10 km/h');
|
||||
expect(screen.getByText('1 条记录').closest('.semi-tag')).toBeInTheDocument();
|
||||
expect(screen.getByText('可操作').closest('.semi-tag')).toBeInTheDocument();
|
||||
const dispositionCard = view.container.querySelector('.v2-alert-disposition-card');
|
||||
const progressCard = view.container.querySelector('.v2-alert-progress-card');
|
||||
expect(dispositionCard).toHaveAttribute('aria-label', '告警处置操作');
|
||||
expect(dispositionCard?.compareDocumentPosition(progressCard!)).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
|
||||
expect(view.container.querySelector('.v2-alert-technical-collapse.semi-collapse')).toBeInTheDocument();
|
||||
expect(screen.getByText('技术详情')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-alert-inspector-heading')).toHaveClass('v2-workspace-panel-header');
|
||||
@@ -244,7 +248,12 @@ test('renders only selectable Semi alert cards on mobile', async () => {
|
||||
expect(action).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(await screen.findByRole('dialog', { name: '告警事件详情' })).toBeInTheDocument();
|
||||
expect(document.querySelector('.v2-alert-detail-sidesheet')).toHaveClass('semi-sidesheet-bottom');
|
||||
expect(document.querySelector('.v2-alert-detail-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(86dvh, 760px)' });
|
||||
expect(document.querySelector('.v2-alert-detail-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(90dvh, 800px)' });
|
||||
expect(document.querySelector('.v2-alert-detail-sidesheet .v2-alert-inspector-heading')).not.toBeInTheDocument();
|
||||
const mobileDisposition = document.querySelector('.v2-alert-detail-sidesheet .v2-alert-disposition-card');
|
||||
const mobileProgress = document.querySelector('.v2-alert-detail-sidesheet .v2-alert-progress-card');
|
||||
expect(mobileDisposition).toHaveAttribute('aria-label', '告警处置操作');
|
||||
expect(mobileDisposition?.compareDocumentPosition(mobileProgress!)).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
|
||||
expect(screen.getByLabelText('告警触发证据')).toHaveTextContent('超限幅度+10 km/h');
|
||||
expect(await screen.findByText('mobile-event')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭告警详情' }));
|
||||
|
||||
@@ -115,7 +115,8 @@ const AlertEventTable = memo(function AlertEventTable({ rows, selectedID, compac
|
||||
function EventInspector({ event, note, acting, actionError, editable, onNote, onAction, onClose, sheet = false }: { event?: AlertEvent; note: string; acting: boolean; actionError?: string; editable: boolean; onNote: (value: string) => void; onAction: (action: 'acknowledge' | 'close' | 'ignore') => void; onClose: () => void; sheet?: boolean }) {
|
||||
if (!event) return <Card className="v2-alert-inspector" bodyStyle={{ padding: 0 }}><Empty className="v2-alert-inspector-empty" image={<IconAlarm />} title="选择告警事件" description="查看触发证据、状态时间线和处置动作。" /></Card>;
|
||||
const actionCount = event.actions?.length ?? 0;
|
||||
return <Card className={`v2-alert-inspector${sheet ? ' is-sheet' : ''}`} bodyStyle={{ padding: 0 }}><WorkspacePanelHeader className="v2-alert-inspector-heading" title="事件处置" description={event.ruleName} actions={<><span className="v2-alert-inspector-status"><SeverityTag severity={event.severity} /><StatusTag status={event.status} /></span>{sheet ? null : <Button className="v2-alert-inspector-close" theme="borderless" icon={<IconClose />} aria-label="关闭告警详情" onClick={onClose} />}</>} />
|
||||
return <Card className={`v2-alert-inspector${sheet ? ' is-sheet' : ''}`} bodyStyle={{ padding: 0 }}>
|
||||
{!sheet ? <WorkspacePanelHeader className="v2-alert-inspector-heading" title="事件处置" description={event.ruleName} actions={<><span className="v2-alert-inspector-status"><SeverityTag severity={event.severity} /><StatusTag status={event.status} /></span><Button className="v2-alert-inspector-close" theme="borderless" icon={<IconClose />} aria-label="关闭告警详情" onClick={onClose} /></>} /> : null}
|
||||
<div className="v2-alert-inspector-content">
|
||||
<Card className="v2-alert-detail-card v2-alert-focus-card" bodyStyle={{ padding: 0 }} aria-label="告警处置摘要">
|
||||
<header className="v2-alert-focus-identity">
|
||||
@@ -134,12 +135,12 @@ function EventInspector({ event, note, acting, actionError, editable, onNote, on
|
||||
</div>
|
||||
<div className="v2-alert-focus-rule"><Tag color="grey" type="light" size="small">规则判断</Tag><span>{event.ruleName}</span></div>
|
||||
</Card>
|
||||
<Card className="v2-alert-detail-card v2-alert-disposition-card" title={<span className="v2-alert-detail-title"><strong>处置与备注</strong><Tag color={editable ? 'green' : 'grey'} type="light" size="small">{editable ? '可操作' : '只读'}</Tag></span>} headerLine aria-label="告警处置操作">
|
||||
{editable ? <><TextArea maxCount={200} autosize={{ minRows: 2, maxRows: 5 }} placeholder="请输入处置说明(选填)" value={note} onChange={onNote} />{actionError ? <p className="v2-alert-action-error">{actionError}</p> : null}<div className="v2-alert-actions"><Button theme="solid" className="is-primary" disabled={acting || !canAct(event.status, 'acknowledge')} onClick={() => onAction('acknowledge')}>确认告警</Button><Button theme="light" disabled={acting || !canAct(event.status, 'close')} onClick={() => onAction('close')}>关闭</Button><Button theme="borderless" disabled={acting || !canAct(event.status, 'ignore')} onClick={() => onAction('ignore')}>忽略</Button></div></> : <p className="v2-role-notice">当前为只读角色,可查看完整证据与处置记录。</p>}
|
||||
</Card>
|
||||
<Card className="v2-alert-detail-card v2-alert-progress-card" title={<span className="v2-alert-detail-title"><strong>处理进度</strong><Tag color={actionCount ? 'blue' : 'amber'} type="light" size="small">{actionCount ? `${actionCount} 条记录` : '待处置'}</Tag></span>} headerLine>
|
||||
{event.actions?.length ? <Timeline className="v2-alert-timeline" aria-label="告警处理进度">{event.actions.map((item, index) => <Timeline.Item key={item.id} type={index === event.actions!.length - 1 ? 'ongoing' : 'default'} time={<span>{item.actor} · <AlertTime value={item.createdAt} /></span>}><strong>{actionLabels[item.action] ?? item.action}</strong>{item.note ? <p>{item.note}</p> : null}</Timeline.Item>)}</Timeline> : <Empty className="v2-alert-timeline-empty" title="暂无处理记录" description="事件被确认、关闭或忽略后,将在这里形成审计履历。" />}
|
||||
</Card>
|
||||
<Card className="v2-alert-detail-card v2-alert-disposition-card" title={<span className="v2-alert-detail-title"><strong>处置与备注</strong><Tag color={editable ? 'green' : 'grey'} type="light" size="small">{editable ? '可操作' : '只读'}</Tag></span>} headerLine>
|
||||
{editable ? <><TextArea maxCount={200} autosize={{ minRows: 2, maxRows: 5 }} placeholder="请输入处置说明(选填)" value={note} onChange={onNote} />{actionError ? <p className="v2-alert-action-error">{actionError}</p> : null}<div className="v2-alert-actions"><Button theme="solid" className="is-primary" disabled={acting || !canAct(event.status, 'acknowledge')} onClick={() => onAction('acknowledge')}>确认告警</Button><Button theme="light" disabled={acting || !canAct(event.status, 'close')} onClick={() => onAction('close')}>关闭</Button><Button theme="borderless" disabled={acting || !canAct(event.status, 'ignore')} onClick={() => onAction('ignore')}>忽略</Button></div></> : <p className="v2-role-notice">当前为只读角色,可查看完整证据与处置记录。</p>}
|
||||
</Card>
|
||||
<Collapse className="v2-alert-technical-collapse" keepDOM>
|
||||
<Collapse.Panel
|
||||
itemKey="technical-evidence"
|
||||
@@ -294,7 +295,7 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e
|
||||
visible={mobileLayout && Boolean(selectedID)}
|
||||
aria-label="告警事件详情"
|
||||
placement="bottom"
|
||||
height="min(86dvh, 760px)"
|
||||
height="min(90dvh, 800px)"
|
||||
title={<div className="v2-alert-sheet-title"><strong>告警详情</strong><span>{selectedEvent ? `${selectedEvent.plate || selectedEvent.vin} · ${selectedEvent.ruleName}` : '证据、状态与处置履历'}</span></div>}
|
||||
onCancel={closeDetail}
|
||||
>
|
||||
|
||||
@@ -1941,6 +1941,31 @@
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector.is-sheet .v2-alert-inspector-content {
|
||||
gap: 8px;
|
||||
padding: 8px 8px 14px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector.is-sheet .v2-alert-disposition-card > .semi-card-header {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector.is-sheet .v2-alert-disposition-card > .semi-card-body {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.v2-alert-inspector.is-sheet .v2-alert-disposition-card .semi-input-textarea {
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector.is-sheet .v2-alert-disposition-card .v2-alert-actions {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector.is-sheet .v2-alert-disposition-card .v2-alert-actions > .semi-button {
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.v2-access-governance-summary > .semi-card-body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -7630,6 +7655,89 @@
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.v2-alert-disposition-card {
|
||||
border-color: #cfe0f7 !important;
|
||||
box-shadow: 0 6px 18px rgba(18, 104, 243, .06) !important;
|
||||
}
|
||||
|
||||
.v2-alert-disposition-card > .semi-card-header {
|
||||
background: linear-gradient(90deg, #f4f8ff 0%, #fff 72%);
|
||||
}
|
||||
|
||||
.v2-alert-disposition-card .v2-alert-actions {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.v2-alert-disposition-card .v2-alert-actions > .semi-button {
|
||||
min-height: 36px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-inspector-content {
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) > .semi-card-body > .v2-alert-inspector-heading {
|
||||
min-height: 64px;
|
||||
padding: 8px 11px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) > .semi-card-body > .v2-alert-inspector-heading > .v2-workspace-panel-copy {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) > .semi-card-body > .v2-alert-inspector-heading > .v2-workspace-panel-actions {
|
||||
max-width: none;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-focus-identity {
|
||||
min-height: 68px;
|
||||
padding: 9px 11px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-focus-facts > span {
|
||||
min-height: 48px;
|
||||
padding: 6px 9px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-focus-card .v2-alert-evidence {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-focus-card .v2-alert-evidence > .v2-alert-evidence-value > .semi-card-body {
|
||||
min-height: 62px;
|
||||
padding-block: 6px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-focus-rule {
|
||||
min-height: 34px;
|
||||
padding-block: 5px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-disposition-card > .semi-card-header {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-disposition-card > .semi-card-body {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-disposition-card .semi-input-textarea {
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-disposition-card .v2-alert-actions {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector:not(.is-sheet) .v2-alert-disposition-card .v2-alert-actions > .semi-button {
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
.v2-alert-inspector > .semi-card-body > .v2-alert-links {
|
||||
min-height: 50px;
|
||||
padding: 7px 10px;
|
||||
|
||||
Reference in New Issue
Block a user