feat: unify vehicle directory summary

This commit is contained in:
lingniu
2026-07-19 11:04:34 +08:00
parent e02d8dba54
commit 1be76c1dfa
4 changed files with 211 additions and 15 deletions

View File

@@ -206,9 +206,13 @@ test('shows deduplicated Semi vehicle candidates and opens the selected VIN', as
expect(initialQuery).toBeDefined();
expect(initialQuery!.get('limit')).toBe('10');
expect(screen.getByRole('heading', { name: '授权车辆目录', level: 5 })).toBeInTheDocument();
expect(await screen.findByText('本页 1 辆')).toBeInTheDocument();
expect(screen.getByText('本页在线 1')).toBeInTheDocument();
expect(screen.getByText('多源车辆 0')).toBeInTheDocument();
const directoryMetrics = screen.getByRole('list', { name: '车辆目录摘要' });
expect(directoryMetrics.closest('.v2-vehicle-directory-metric-rail')).toHaveClass('semi-card', 'v2-workspace-metric-rail', 'is-queue');
expect(Array.from(directoryMetrics.querySelectorAll(':scope > [role="listitem"] > small')).map((item) => item.textContent)).toEqual(['授权车辆', '本页在线', '多源车辆', '当前页']);
expect(directoryMetrics.querySelector(':scope > [role="listitem"]:nth-child(1)')).toHaveTextContent('授权车辆2辆账号范围');
expect(directoryMetrics.querySelector(':scope > [role="listitem"]:nth-child(2)')).toHaveTextContent('本页在线1辆本页 1 辆');
expect(directoryMetrics.querySelector(':scope > [role="listitem"]:nth-child(3)')).toHaveTextContent('多源车辆0辆2+ 协议来源');
expect(screen.getByRole('button', { name: '刷新车辆目录' })).toHaveClass('semi-button');
expect(view.container.querySelector('.v2-vehicle-directory-table.semi-table-wrapper')).toBeInTheDocument();
expect(view.container.querySelector('.v2-vehicle-search-page')).toHaveClass('v2-vehicle-directory-v3');
expect(view.container.querySelector('.v2-vehicle-directory-profile')).toHaveTextContent('测试品牌13800000000');
@@ -261,8 +265,9 @@ test('keeps the mobile vehicle directory primary and applies search from a Semi
const view = render(<QueryClientProvider client={client}><MemoryRouter initialEntries={['/vehicles']} future={ROUTER_FUTURE}><Routes><Route path="/vehicles" element={<VehiclePage />} /></Routes></MemoryRouter></QueryClientProvider>);
expect(await screen.findByText('本页 8 辆')).toBeInTheDocument();
expect(screen.getAllByRole('listitem')).toHaveLength(8);
const mobileDirectoryMetrics = await screen.findByRole('list', { name: '车辆目录摘要' });
await waitFor(() => expect(mobileDirectoryMetrics).toHaveTextContent('本页在线6辆本页 8 辆'));
expect(view.container.querySelectorAll('.v2-vehicle-recent-grid [role="listitem"]')).toHaveLength(8);
expect(screen.getAllByRole('button', { name: /打开 .* 车辆档案/ })).toHaveLength(8);
expect(screen.getAllByText(/GB\/T 32960|JT\/T 808/).length).toBeGreaterThan(0);
expect(view.container.querySelector('.v2-vehicle-directory-table')).not.toBeInTheDocument();

View File

@@ -18,7 +18,7 @@ import { VehicleCandidateList } from '../shared/VehicleCandidateList';
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
const VehicleProfileSyncPanel = lazy(() => import('./VehicleProfileSyncPanel'));
@@ -150,6 +150,40 @@ export default function VehicleSearchWorkspace() {
const mobileFilterSummary = deferredKeyword
? `${deferredKeyword} · ${candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆匹配` : '正在查找'}`
: candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权车辆` : '正在读取授权范围';
const directoryMetrics: WorkspaceQueueMetricRailItem[] = [
{
label: deferredKeyword ? '匹配车辆' : '授权车辆',
value: <>{candidates.data ? formatZhNumber(total) : '—'}<i className="v2-vehicle-directory-unit"></i></>,
note: deferredKeyword ? '当前条件' : '账号范围',
tone: 'primary',
emphasis: 'primary'
},
{
label: '本页在线',
value: <>{candidates.data ? formatZhNumber(onlineOnPage) : '—'}<i className="v2-vehicle-directory-unit"></i></>,
note: candidates.data ? `本页 ${formatZhNumber(pageVehicles.length)}` : '正在读取',
tone: 'success',
emphasis: 'primary'
},
{
label: '多源车辆',
value: <>{candidates.data ? formatZhNumber(multiSourceOnPage) : '—'}<i className="v2-vehicle-directory-unit"></i></>,
note: '2+ 协议来源',
tone: 'primary',
emphasis: 'secondary'
},
{
label: '当前页',
value: candidates.data ? `${page} / ${totalPages}` : '—',
note: `每页 ${pageSize}`,
tone: 'neutral',
emphasis: 'secondary'
}
];
const directoryTitle = deferredKeyword ? '匹配车辆' : mobileLayout ? '授权车辆' : '授权车辆目录';
const directoryDescription = deferredKeyword
? '按当前搜索条件展示,可直接进入车辆数字档案'
: '按照最新上报时间排列,分页浏览全部授权车辆';
return <section className={`v2-vehicle-search-page v2-vehicle-directory-v3${syncOpen ? ' has-sync-panel' : ''}${!mobileLayout && candidatesOpen ? ' has-candidates' : ''}`}>
<div className="v2-vehicle-discovery-shell">
{!mobileLayout || administrator ? <WorkspaceCommandBar
@@ -265,15 +299,15 @@ export default function VehicleSearchWorkspace() {
</WorkspaceFilterPanel>}
</div>
<Card className="v2-vehicle-recent-card" bodyStyle={{ padding: 0 }}>
<WorkspacePanelHeader
title={deferredKeyword ? '匹配车辆' : mobileLayout ? '授权车辆' : '授权车辆目录'}
description={deferredKeyword ? '按当前搜索条件展示,可直接进入车辆数字档案' : '按照最新上报时间排列,分页浏览全部授权车辆'}
meta={<span className="v2-vehicle-directory-summary">
<Tag color={deferredKeyword ? 'blue' : 'grey'} type="light" size="small">{candidates.data ? `本页 ${formatZhNumber(pageVehicles.length)}` : '授权范围'}</Tag>
{candidates.data ? <Tag className="is-secondary" color="green" type="light" size="small">线 {formatZhNumber(onlineOnPage)}</Tag> : null}
{candidates.data ? <Tag className="is-secondary" color="blue" type="light" size="small"> {formatZhNumber(multiSourceOnPage)}</Tag> : null}
</span>}
actions={<Button theme="borderless" type="tertiary" size="small" icon={<IconRefresh />} loading={candidates.isFetching} onClick={() => candidates.refetch()}></Button>}
<WorkspaceMetricRail
variant="queue"
ariaLabel="车辆目录摘要"
className="v2-vehicle-directory-metric-rail"
items={directoryMetrics}
context={<div className="v2-vehicle-directory-context">
<span><h5>{directoryTitle}</h5><small>{directoryDescription}</small></span>
<Button theme="borderless" type="tertiary" size="small" icon={<IconRefresh />} loading={candidates.isFetching} aria-label="刷新车辆目录" onClick={() => candidates.refetch()}></Button>
</div>}
/>
{candidates.isFetching && !candidates.data
? <div className="v2-vehicle-recent-state" role="status"><span className="v2-spinner" /><span></span></div>

View File

@@ -76,6 +76,10 @@ describe('V2 production entry', () => {
expect(corePageSources.VehiclePage).toContain("lazy(() => import('./VehicleSearchWorkspace'))");
expect(vehicleSearchSource).toContain("from '../shared/WorkspaceCommandBar'");
expect(vehicleSearchSource).toContain('<WorkspaceCommandBar');
expect(vehicleSearchSource).toContain("from '../shared/WorkspaceMetricRail'");
expect(vehicleSearchSource).toContain('<WorkspaceMetricRail');
expect(vehicleSearchSource).toContain('className="v2-vehicle-directory-metric-rail"');
expect(vehicleSearchSource).not.toContain('className="v2-vehicle-directory-summary"');
expect(corePageSources.VehiclePage).toContain('<Select');
expect(corePageSources.VehiclePage).toContain('<Card className="v2-identity-band v2-record-card v2-live-card v2-live-overview v2-vehicle-command-card"');
expect(corePageSources.VehiclePage).toContain("from '../shared/WorkspaceMetricRail'");

View File

@@ -23009,6 +23009,159 @@
}
}
/*
* Vehicle directory summary.
* A single Semi metric rail replaces the mixed heading/tag row so directory
* scale and current-page health can be scanned before opening a vehicle.
*/
.v2-vehicle-directory-metric-rail.is-queue.semi-card {
flex: 0 0 auto;
border: 0;
border-bottom: 1px solid #e1e8f0;
border-radius: 0;
}
.v2-vehicle-directory-metric-rail.is-queue > .semi-card-body {
min-height: 62px;
grid-template-columns: minmax(0, 1fr) minmax(280px, .82fr);
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list {
grid-template-columns: 1.15fr 1.08fr .92fr .85fr;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span {
min-height: 62px;
padding-block: 8px;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span > strong {
font-size: clamp(20px, 1.55vw, 25px);
font-weight: 740;
letter-spacing: -.02em;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span.is-secondary > strong {
font-size: clamp(17px, 1.25vw, 20px);
}
.v2-vehicle-directory-unit {
margin-left: 3px;
color: #8493a5;
font-size: 7px;
font-style: normal;
font-weight: 550;
letter-spacing: 0;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span.is-primary .v2-vehicle-directory-unit {
color: #6f90bb;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-context {
padding: 7px 10px 7px 13px;
}
.v2-vehicle-directory-context {
display: grid;
width: 100%;
min-width: 0;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 9px;
}
.v2-vehicle-directory-context > span {
display: grid;
min-width: 0;
gap: 2px;
}
.v2-vehicle-directory-context h5 {
overflow: hidden;
margin: 0;
color: #263a51;
font-size: 12px;
font-weight: 740;
line-height: 17px;
text-overflow: ellipsis;
white-space: nowrap;
}
.v2-vehicle-directory-context small {
overflow: hidden;
color: #8794a5;
font-size: 8px;
line-height: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.v2-vehicle-directory-context > .semi-button {
min-width: 58px;
height: 34px;
border-radius: 8px;
color: #45627f;
font-size: 9px;
font-weight: 650;
}
@media (max-width: 980px) {
.v2-vehicle-directory-metric-rail.is-queue > .semi-card-body {
grid-template-columns: minmax(0, 1fr) 226px;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span > em {
display: none;
}
}
@media (max-width: 680px) {
.v2-vehicle-directory-metric-rail.is-queue > .semi-card-body {
min-height: 56px;
grid-template-columns: minmax(0, 1fr);
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span {
min-height: 56px;
padding: 6px 11px;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span.is-secondary {
display: none;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span > strong {
font-size: 19px;
line-height: 22px;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span > em {
display: block;
font-size: 7px;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-context {
display: none;
}
}
@media (max-width: 900px) and (max-height: 480px) and (orientation: landscape) {
.v2-vehicle-directory-metric-rail.is-queue > .semi-card-body,
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span {
min-height: 42px;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span {
padding-block: 3px;
}
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list > span > strong {
font-size: 15px;
line-height: 17px;
}
}
/*
* Monitor fleet summary.
* Operational state remains prominent; high-volume ingest evidence is kept