feat: unify source diagnostics with semi ui
This commit is contained in:
@@ -356,13 +356,14 @@ test('fuzzy searches a vehicle and renders all source diagnosis evidence', async
|
|||||||
expect(await screen.findByText('当前推荐 G7')).toBeInTheDocument();
|
expect(await screen.findByText('当前推荐 G7')).toBeInTheDocument();
|
||||||
expect(screen.getByText('1 个来源')).toBeInTheDocument();
|
expect(screen.getByText('1 个来源')).toBeInTheDocument();
|
||||||
expect(screen.getByRole('group', { name: '车辆来源诊断概览' })).toHaveClass('v2-source-summary-region');
|
expect(screen.getByRole('group', { name: '车辆来源诊断概览' })).toHaveClass('v2-source-summary-region');
|
||||||
expect(document.querySelector('.v2-source-summary-region > .v2-source-summary.semi-card-group')).toBeInTheDocument();
|
const sourceSummary = screen.getByRole('list', { name: '车辆来源诊断概览' });
|
||||||
expect(document.querySelectorAll('.v2-source-summary-card.semi-card')).toHaveLength(4);
|
expect(sourceSummary.closest('.v2-source-summary-rail')).toHaveClass('v2-workspace-metric-rail', 'is-queue');
|
||||||
expect(document.querySelector('.v2-source-summary-card.is-vehicle')).toHaveTextContent('粤A00001');
|
expect(sourceSummary.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(4);
|
||||||
expect(document.querySelector('.v2-source-summary-card.is-recommended')).toHaveTextContent('G7');
|
expect(sourceSummary).toHaveTextContent('来源状态1 / 1位置冲突 233m');
|
||||||
expect(document.querySelector('.v2-source-summary-card.is-source-health')).toHaveClass('is-warning');
|
expect(sourceSummary).toHaveTextContent('当前推荐G7JT808');
|
||||||
expect(document.querySelector('.v2-source-summary-card.is-policy')).toHaveTextContent('v1');
|
expect(sourceSummary).toHaveTextContent('车辆粤A00001VIN001');
|
||||||
expect(document.querySelectorAll('.v2-source-summary-card')[1]?.querySelector('.semi-tag')).toHaveTextContent('JT808');
|
expect(sourceSummary).toHaveTextContent('策略版本v1尚无人工调整');
|
||||||
|
expect(sourceSummary.querySelector(':scope > [role="listitem"]')).toHaveClass('is-warning', 'is-primary');
|
||||||
expect(screen.getByText('推荐说明').closest('.semi-card')).toHaveClass('v2-source-recommendation');
|
expect(screen.getByText('推荐说明').closest('.semi-card')).toHaveClass('v2-source-recommendation');
|
||||||
expect(screen.getByText('最近策略审计').closest('.semi-card')).toHaveClass('v2-source-audit');
|
expect(screen.getByText('最近策略审计').closest('.semi-card')).toHaveClass('v2-source-audit');
|
||||||
expect(screen.getByText('终端 133****0001')).toBeInTheDocument();
|
expect(screen.getByText('终端 133****0001')).toBeInTheDocument();
|
||||||
@@ -455,8 +456,8 @@ test('allows provider maintenance but keeps canonical source policy read only',
|
|||||||
expect(document.querySelector('.v2-source-mobile-card.semi-card')).toBeInTheDocument();
|
expect(document.querySelector('.v2-source-mobile-card.semi-card')).toBeInTheDocument();
|
||||||
expect(document.querySelector('.v2-source-mobile-descriptions.semi-descriptions')).toBeInTheDocument();
|
expect(document.querySelector('.v2-source-mobile-descriptions.semi-descriptions')).toBeInTheDocument();
|
||||||
expect(screen.getByRole('group', { name: '车辆来源诊断概览' })).toHaveClass('v2-source-summary-region');
|
expect(screen.getByRole('group', { name: '车辆来源诊断概览' })).toHaveClass('v2-source-summary-region');
|
||||||
expect(document.querySelector('.v2-source-summary-region > .v2-source-summary.semi-card-group')).toBeInTheDocument();
|
expect(screen.getByRole('list', { name: '车辆来源诊断概览' }).closest('.v2-source-summary-rail')).toHaveClass('v2-workspace-metric-rail', 'is-queue');
|
||||||
expect(document.querySelectorAll('.v2-source-summary-card.semi-card')).toHaveLength(4);
|
expect(screen.getByRole('list', { name: '车辆来源诊断概览' }).querySelectorAll(':scope > [role="listitem"]')).toHaveLength(4);
|
||||||
expect(screen.getByText('优先级 100')).toBeInTheDocument();
|
expect(screen.getByText('优先级 100')).toBeInTheDocument();
|
||||||
expect(screen.queryByLabelText('JT808 优先级')).not.toBeInTheDocument();
|
expect(screen.queryByLabelText('JT808 优先级')).not.toBeInTheDocument();
|
||||||
fireEvent.click(screen.getByRole('button', { name: '维护提供方' }));
|
fireEvent.click(screen.getByRole('button', { name: '维护提供方' }));
|
||||||
|
|||||||
@@ -315,6 +315,36 @@ function SourceDiagnosticWorkspace() {
|
|||||||
onSelect={choose}
|
onSelect={choose}
|
||||||
footer={(candidates.data?.total ?? 0) > 20 ? <><span>第 {Math.floor(candidateOffset / 20) + 1} / {Math.ceil((candidates.data?.total ?? 0) / 20)} 页</span><div><Button theme="light" size="small" disabled={candidateOffset === 0} onClick={() => setCandidateOffset(Math.max(0, candidateOffset - 20))}>上一页</Button><Button theme="light" size="small" disabled={candidateOffset + 20 >= (candidates.data?.total ?? 0)} onClick={() => setCandidateOffset(candidateOffset + 20)}>下一页</Button></div></> : undefined}
|
footer={(candidates.data?.total ?? 0) > 20 ? <><span>第 {Math.floor(candidateOffset / 20) + 1} / {Math.ceil((candidates.data?.total ?? 0) / 20)} 页</span><div><Button theme="light" size="small" disabled={candidateOffset === 0} onClick={() => setCandidateOffset(Math.max(0, candidateOffset - 20))}>上一页</Button><Button theme="light" size="small" disabled={candidateOffset + 20 >= (candidates.data?.total ?? 0)} onClick={() => setCandidateOffset(candidateOffset + 20)}>下一页</Button></div></> : undefined}
|
||||||
/> : null;
|
/> : null;
|
||||||
|
const sourceSummaryItems: WorkspaceQueueMetricRailItem[] = data ? [
|
||||||
|
{
|
||||||
|
label: '来源状态',
|
||||||
|
value: `${data.evidence.locationSources.filter((item) => item.online).length} / ${data.evidence.locationSources.length}`,
|
||||||
|
note: data.evidence.locationConflict ? `位置冲突 ${number(data.evidence.conflictDistanceM)}m` : '未发现实时位置冲突',
|
||||||
|
tone: data.evidence.locationConflict ? 'warning' : 'success',
|
||||||
|
emphasis: 'primary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '当前推荐',
|
||||||
|
value: data.evidence.recommendedLocationLabel || '暂无推荐',
|
||||||
|
note: data.evidence.recommendedLocationProtocol || '尚无推荐协议',
|
||||||
|
tone: 'primary',
|
||||||
|
emphasis: 'primary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '车辆',
|
||||||
|
value: data.evidence.plate || selected?.plate || '未绑定车牌',
|
||||||
|
note: data.evidence.vin,
|
||||||
|
tone: 'neutral',
|
||||||
|
emphasis: 'secondary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '策略版本',
|
||||||
|
value: `v${data.policy.version}`,
|
||||||
|
note: data.policy.updatedAt ? <>{data.policy.updatedBy} · <PlatformTime className="v2-ops-time" value={data.policy.updatedAt} sourceZone="utc" /></> : '尚无人工调整',
|
||||||
|
tone: 'neutral',
|
||||||
|
emphasis: 'secondary'
|
||||||
|
}
|
||||||
|
] : [];
|
||||||
return <>
|
return <>
|
||||||
{mobileLayout ? <div className="v2-source-mobile-discovery">
|
{mobileLayout ? <div className="v2-source-mobile-discovery">
|
||||||
<MobileFilterToggle
|
<MobileFilterToggle
|
||||||
@@ -379,12 +409,12 @@ function SourceDiagnosticWorkspace() {
|
|||||||
{selected && diagnostic.isPending ? <div className="v2-source-loading" role="status"><Spin size="large" /><strong>正在读取来源证据</strong><span>只查询当前车辆,不会扫描整车队。</span></div> : null}
|
{selected && diagnostic.isPending ? <div className="v2-source-loading" role="status"><Spin size="large" /><strong>正在读取来源证据</strong><span>只查询当前车辆,不会扫描整车队。</span></div> : null}
|
||||||
{data ? <>
|
{data ? <>
|
||||||
<section className="v2-source-summary-region" role="group" aria-label="车辆来源诊断概览">
|
<section className="v2-source-summary-region" role="group" aria-label="车辆来源诊断概览">
|
||||||
<CardGroup className="v2-source-summary" type="grid" spacing={10}>
|
<WorkspaceMetricRail
|
||||||
<Card className="v2-source-summary-card is-vehicle"><small>车辆</small><strong>{data.evidence.plate || selected?.plate || '未绑定车牌'}</strong><span>{data.evidence.vin}</span></Card>
|
variant="queue"
|
||||||
<Card className="v2-source-summary-card is-recommended"><small>当前推荐</small><strong>{data.evidence.recommendedLocationLabel || '暂无推荐'}</strong><Tag color="blue" type="light" size="small">{data.evidence.recommendedLocationProtocol || '—'}</Tag></Card>
|
className="v2-source-summary-rail"
|
||||||
<Card className={`v2-source-summary-card is-source-health${data.evidence.locationConflict ? ' is-warning' : ' is-ok'}`}><small>来源状态</small><strong>{data.evidence.locationSources.filter((item) => item.online).length} / {data.evidence.locationSources.length} 在线</strong><span>{data.evidence.locationConflict ? `位置冲突 ${number(data.evidence.conflictDistanceM)}m` : '未发现实时位置冲突'}</span></Card>
|
ariaLabel="车辆来源诊断概览"
|
||||||
<Card className="v2-source-summary-card is-policy"><small>策略版本</small><strong>v{data.policy.version}</strong><span>{data.policy.updatedAt ? <>{data.policy.updatedBy} · <PlatformTime className="v2-ops-time" value={data.policy.updatedAt} sourceZone="utc" /></> : '尚无人工调整'}</span></Card>
|
items={sourceSummaryItems}
|
||||||
</CardGroup>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<Card className="v2-source-recommendation"><header><Tag color="blue" type="light" size="small">推荐说明</Tag><span>{data.refreshHint}</span></header><p>{data.recommendationReason}</p></Card>
|
<Card className="v2-source-recommendation"><header><Tag color="blue" type="light" size="small">推荐说明</Tag><span>{data.refreshHint}</span></header><p>{data.recommendationReason}</p></Card>
|
||||||
{!editable ? <div className="v2-source-readonly"><Tag color="orange" type="light" size="small">只读权限</Tag><span>当前账号可查看诊断证据;只有管理员可以调整启停和优先级。</span></div> : null}
|
{!editable ? <div className="v2-source-readonly"><Tag color="orange" type="light" size="small">只读权限</Tag><span>当前账号可查看诊断证据;只有管理员可以调整启停和优先级。</span></div> : null}
|
||||||
|
|||||||
@@ -356,10 +356,10 @@ describe('V2 production entry', () => {
|
|||||||
expect(corePageSources.OperationsPage).not.toContain('<dl><div><dt>生产数据模式</dt>');
|
expect(corePageSources.OperationsPage).not.toContain('<dl><div><dt>生产数据模式</dt>');
|
||||||
expect(corePageSources.OperationsPage).toContain('<Empty className="v2-source-empty"');
|
expect(corePageSources.OperationsPage).toContain('<Empty className="v2-source-empty"');
|
||||||
expect(corePageSources.OperationsPage).toContain('<Spin size="large"');
|
expect(corePageSources.OperationsPage).toContain('<Spin size="large"');
|
||||||
expect(corePageSources.OperationsPage).toContain('<CardGroup className="v2-source-summary"');
|
expect(corePageSources.OperationsPage).toContain('className="v2-source-summary-rail"');
|
||||||
expect(corePageSources.OperationsPage).toContain('<Card className="v2-source-summary-card is-vehicle"');
|
expect(corePageSources.OperationsPage).toContain('ariaLabel="车辆来源诊断概览"');
|
||||||
expect(corePageSources.OperationsPage).toContain('<Card className="v2-source-summary-card is-recommended"');
|
expect(corePageSources.OperationsPage).not.toContain('<CardGroup className="v2-source-summary"');
|
||||||
expect(corePageSources.OperationsPage).toContain('<Card className={`v2-source-summary-card');
|
expect(corePageSources.OperationsPage).not.toContain('v2-source-summary-card');
|
||||||
expect(corePageSources.OperationsPage).toContain('<Card className="v2-source-recommendation"');
|
expect(corePageSources.OperationsPage).toContain('<Card className="v2-source-recommendation"');
|
||||||
expect(corePageSources.OperationsPage).toContain('<Card className="v2-source-audit"');
|
expect(corePageSources.OperationsPage).toContain('<Card className="v2-source-audit"');
|
||||||
expect(corePageSources.OperationsPage).toContain('className="v2-source-table"');
|
expect(corePageSources.OperationsPage).toContain('className="v2-source-table"');
|
||||||
|
|||||||
@@ -23291,3 +23291,37 @@
|
|||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The source diagnostic now follows the same Semi UI metric hierarchy as
|
||||||
|
* monitor, history and mileage workspaces. The first two facts remain visible
|
||||||
|
* on mobile; identity and audit metadata stay available on wider layouts.
|
||||||
|
*/
|
||||||
|
.v2-source-summary-region {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-source-summary-rail.v2-workspace-metric-rail.is-queue.semi-card {
|
||||||
|
border-top: 1px solid #e2e9f2;
|
||||||
|
border-bottom: 1px solid #e2e9f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-source-summary-rail .v2-workspace-metric-list {
|
||||||
|
grid-template-columns: 1.15fr 1.15fr 1fr .9fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-source-summary-rail .v2-workspace-metric-list > span.is-primary.is-warning {
|
||||||
|
background: linear-gradient(145deg, #fff7ea 0%, #fffdf8 100%);
|
||||||
|
box-shadow: inset 0 -3px #e59a2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-source-summary-rail .v2-workspace-metric-list > span.is-primary.is-success {
|
||||||
|
background: linear-gradient(145deg, #eefaf5 0%, #fbfefd 100%);
|
||||||
|
box-shadow: inset 0 -3px #20a678;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.v2-source-summary-rail .v2-workspace-metric-list {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user