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>