refactor(ui): converge semi vehicle directory
This commit is contained in:
@@ -208,17 +208,22 @@ test('shows deduplicated Semi vehicle candidates and opens the selected VIN', as
|
|||||||
expect(screen.getByRole('heading', { name: '授权车辆目录', level: 5 })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: '授权车辆目录', level: 5 })).toBeInTheDocument();
|
||||||
const directoryMetrics = screen.getByRole('list', { name: '车辆目录摘要' });
|
const directoryMetrics = screen.getByRole('list', { name: '车辆目录摘要' });
|
||||||
expect(directoryMetrics.closest('.v2-vehicle-directory-metric-rail')).toHaveClass('semi-card', 'v2-workspace-metric-rail', 'is-queue');
|
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(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(1)')).toHaveTextContent('授权车辆2辆账号范围');
|
||||||
expect(directoryMetrics.querySelector(':scope > [role="listitem"]:nth-child(2)')).toHaveTextContent('本页在线1辆本页 1 辆');
|
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(directoryMetrics.querySelector(':scope > [role="listitem"]:nth-child(3)')).toHaveTextContent('多源车辆0辆2+ 协议来源');
|
||||||
|
expect(screen.getByText('第 1 / 1 页').closest('.semi-tag')).toBeInTheDocument();
|
||||||
expect(screen.getByRole('button', { name: '刷新车辆目录' })).toHaveClass('semi-button');
|
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-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-search-page')).toHaveClass('v2-vehicle-directory-v3');
|
||||||
expect(view.container.querySelector('.v2-vehicle-directory-profile')).toHaveTextContent('测试品牌13800000000');
|
expect(view.container.querySelector('.v2-vehicle-directory-profile')).toHaveTextContent('测试品牌13800000000');
|
||||||
expect(view.container.querySelector('.v2-vehicle-directory-protocols .v2-protocol-tag')).toHaveClass('semi-tag-cyan-light');
|
expect(view.container.querySelector('.v2-vehicle-directory-protocols .v2-protocol-tag')).toHaveClass('semi-tag-cyan-light');
|
||||||
expect(view.container.querySelector('.v2-vehicle-discovery-shell')).toContainElement(screen.getByLabelText('查车操作'));
|
expect(view.container.querySelector('.v2-vehicle-discovery-shell')).toContainElement(screen.getByLabelText('查车操作'));
|
||||||
expect(screen.getByRole('button', { name: `打开 ${initialRealtime.vin} 车辆档案` })).toBeInTheDocument();
|
expect(screen.getByTestId(`vehicle-directory-row-${initialRealtime.vin}`)).toHaveAttribute('role', 'button');
|
||||||
|
expect(screen.getByTestId(`vehicle-directory-row-${initialRealtime.vin}`)).toHaveClass('is-online');
|
||||||
|
expect(screen.getByTestId(`vehicle-directory-row-${initialRealtime.vin}`)).toHaveAttribute('aria-label', `打开 ${initialRealtime.plate} 车辆档案`);
|
||||||
|
expect(screen.getByTestId(`vehicle-directory-row-${initialRealtime.vin}`).querySelector('button')).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId(`vehicle-directory-row-${initialRealtime.vin}`)).toHaveTextContent('查看档案');
|
||||||
fireEvent.focus(input);
|
fireEvent.focus(input);
|
||||||
expect(input).toHaveAttribute('aria-expanded', 'false');
|
expect(input).toHaveAttribute('aria-expanded', 'false');
|
||||||
expect(view.container.querySelector('.v2-vehicle-search-options')).not.toBeInTheDocument();
|
expect(view.container.querySelector('.v2-vehicle-search-options')).not.toBeInTheDocument();
|
||||||
|
|||||||
@@ -171,13 +171,6 @@ export default function VehicleSearchWorkspace() {
|
|||||||
note: '2+ 协议来源',
|
note: '2+ 协议来源',
|
||||||
tone: 'primary',
|
tone: 'primary',
|
||||||
emphasis: 'secondary'
|
emphasis: 'secondary'
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '当前页',
|
|
||||||
value: candidates.data ? `${page} / ${totalPages}` : '—',
|
|
||||||
note: `每页 ${pageSize} 辆`,
|
|
||||||
tone: 'neutral',
|
|
||||||
emphasis: 'secondary'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const directoryTitle = deferredKeyword ? '匹配车辆' : mobileLayout ? '授权车辆' : '授权车辆目录';
|
const directoryTitle = deferredKeyword ? '匹配车辆' : mobileLayout ? '授权车辆' : '授权车辆目录';
|
||||||
@@ -306,7 +299,10 @@ export default function VehicleSearchWorkspace() {
|
|||||||
items={directoryMetrics}
|
items={directoryMetrics}
|
||||||
context={<div className="v2-vehicle-directory-context">
|
context={<div className="v2-vehicle-directory-context">
|
||||||
<span><h5>{directoryTitle}</h5><small>{directoryDescription}</small></span>
|
<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>
|
<span className="v2-vehicle-directory-context-actions">
|
||||||
|
<Tag color="blue" type="light" size="small">第 {page} / {totalPages} 页</Tag>
|
||||||
|
<Button theme="borderless" type="tertiary" size="small" icon={<IconRefresh />} loading={candidates.isFetching} aria-label="刷新车辆目录" onClick={() => candidates.refetch()}>刷新</Button>
|
||||||
|
</span>
|
||||||
</div>}
|
</div>}
|
||||||
/>
|
/>
|
||||||
{candidates.isFetching && !candidates.data
|
{candidates.isFetching && !candidates.data
|
||||||
@@ -315,7 +311,7 @@ export default function VehicleSearchWorkspace() {
|
|||||||
? <div className="v2-vehicle-recent-state is-error" role="alert"><span>{candidates.error instanceof Error ? candidates.error.message : '授权车辆加载失败'}</span><Button size="small" theme="light" onClick={() => candidates.refetch()}>重新加载</Button></div>
|
? <div className="v2-vehicle-recent-state is-error" role="alert"><span>{candidates.error instanceof Error ? candidates.error.message : '授权车辆加载失败'}</span><Button size="small" theme="light" onClick={() => candidates.refetch()}>重新加载</Button></div>
|
||||||
: pageVehicles.length
|
: pageVehicles.length
|
||||||
? mobileLayout ? <div className="v2-vehicle-recent-grid" role="list" aria-label={deferredKeyword ? '匹配车辆' : '授权车辆目录'}>
|
? mobileLayout ? <div className="v2-vehicle-recent-grid" role="list" aria-label={deferredKeyword ? '匹配车辆' : '授权车辆目录'}>
|
||||||
{pageVehicles.map((vehicle) => <div key={vehicle.vin} className="v2-vehicle-recent-item-shell" role="listitem">
|
{pageVehicles.map((vehicle) => <div key={vehicle.vin} className={`v2-vehicle-recent-item-shell ${vehicle.online ? 'is-online' : 'is-offline'}`} role="listitem">
|
||||||
<Button
|
<Button
|
||||||
className="v2-vehicle-recent-item"
|
className="v2-vehicle-recent-item"
|
||||||
theme="borderless"
|
theme="borderless"
|
||||||
@@ -345,7 +341,7 @@ export default function VehicleSearchWorkspace() {
|
|||||||
render: (_value: string, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-identity"><strong>{vehicle.plate || '未绑定车牌'}</strong><small>{vehicle.vin}</small></span>
|
render: (_value: string, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-identity"><strong>{vehicle.plate || '未绑定车牌'}</strong><small>{vehicle.vin}</small></span>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '车辆信息',
|
title: '品牌 / 终端',
|
||||||
dataIndex: 'oem',
|
dataIndex: 'oem',
|
||||||
width: 260,
|
width: 260,
|
||||||
render: (_value: string, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-profile"><strong>{vehicle.oem || '品牌待维护'}</strong><small>{vehicle.phone || '暂无终端手机号'}</small></span>
|
render: (_value: string, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-profile"><strong>{vehicle.oem || '品牌待维护'}</strong><small>{vehicle.phone || '暂无终端手机号'}</small></span>
|
||||||
@@ -366,13 +362,17 @@ export default function VehicleSearchWorkspace() {
|
|||||||
dataIndex: 'vin',
|
dataIndex: 'vin',
|
||||||
width: 116,
|
width: 116,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
render: (vehicleVin: string) => <Button className="v2-vehicle-directory-open" theme="borderless" type="primary" size="small" icon={<IconChevronRight />} iconPosition="right" aria-label={`打开 ${vehicleVin} 车辆档案`} onClick={(event) => { event.stopPropagation(); openVehicle(vehicleVin); }}>查看档案</Button>
|
render: () => <span className="v2-vehicle-directory-open" aria-hidden="true">查看档案<IconChevronRight /></span>
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
dataSource={pageVehicles}
|
dataSource={pageVehicles}
|
||||||
rowKey="vin"
|
rowKey="vin"
|
||||||
onRow={(vehicle) => vehicle ? ({
|
onRow={(vehicle) => vehicle ? ({
|
||||||
|
className: vehicle.online ? 'is-online' : 'is-offline',
|
||||||
|
role: 'button',
|
||||||
tabIndex: 0,
|
tabIndex: 0,
|
||||||
|
'aria-label': `打开 ${vehicle.plate || vehicle.vin} 车辆档案`,
|
||||||
|
'data-testid': `vehicle-directory-row-${vehicle.vin}`,
|
||||||
onClick: () => openVehicle(vehicle.vin),
|
onClick: () => openVehicle(vehicle.vin),
|
||||||
onKeyDown: (event) => {
|
onKeyDown: (event) => {
|
||||||
if (event.key !== 'Enter' && event.key !== ' ') return;
|
if (event.key !== 'Enter' && event.key !== ' ') return;
|
||||||
|
|||||||
@@ -24318,3 +24318,148 @@
|
|||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Semi UI vehicle directory convergence.
|
||||||
|
* The directory remains dense, but uses one white evidence surface, explicit
|
||||||
|
* row affordances and a quieter three-signal summary.
|
||||||
|
*/
|
||||||
|
.v2-vehicle-directory-v3 > .v2-vehicle-recent-card > .semi-card-body {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-metric-rail .v2-workspace-metric-list {
|
||||||
|
grid-template-columns: 1.08fr .92fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-metric-rail.is-queue.has-context > .semi-card-body {
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(340px, .96fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-context {
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-context > .v2-vehicle-directory-context-actions {
|
||||||
|
display: inline-flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-context-actions > .semi-tag {
|
||||||
|
min-height: 22px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding-inline: 8px;
|
||||||
|
font-size: 9px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-context-actions > .semi-button {
|
||||||
|
min-width: 58px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #45627f;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-v3 .v2-vehicle-directory-table.semi-table-wrapper,
|
||||||
|
.v2-vehicle-directory-v3 .v2-vehicle-recent-grid {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-table .semi-table-thead > .semi-table-row > .semi-table-row-head {
|
||||||
|
height: 40px;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #66778e;
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: .015em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row > .semi-table-row-cell {
|
||||||
|
height: 52px;
|
||||||
|
background: #fff;
|
||||||
|
transition: background-color .16s ease, box-shadow .16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row:hover > .semi-table-row-cell,
|
||||||
|
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row:focus-visible > .semi-table-row-cell {
|
||||||
|
background: #f6f9fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row.is-online > .semi-table-row-cell:first-child {
|
||||||
|
box-shadow: inset 3px 0 #23a779;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row.is-offline > .semi-table-row-cell:first-child {
|
||||||
|
box-shadow: inset 3px 0 #c9d2de;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-table .semi-table-tbody > .semi-table-row:hover .v2-vehicle-directory-open {
|
||||||
|
background: #eaf3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-open {
|
||||||
|
display: inline-flex;
|
||||||
|
height: 28px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding-inline: 10px;
|
||||||
|
color: #155fc5;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
transition: background-color .16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-recent-card > .semi-card-body > .v2-vehicle-directory-pagination {
|
||||||
|
background: #fbfcfe;
|
||||||
|
box-shadow: 0 -8px 22px rgba(43, 62, 86, .025);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) and (min-width: 681px) {
|
||||||
|
.v2-vehicle-directory-metric-rail.is-queue.has-context > .semi-card-body {
|
||||||
|
grid-template-columns: minmax(0, 1fr) 270px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.v2-vehicle-directory-metric-rail.is-queue.has-context > .semi-card-body {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-v3 .v2-vehicle-recent-grid {
|
||||||
|
padding: 0 10px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-v3 .v2-vehicle-recent-item-shell {
|
||||||
|
border-bottom: 1px solid #e9eef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-v3 .v2-vehicle-recent-item-shell:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-v3 .v2-vehicle-recent-item.semi-button {
|
||||||
|
min-height: 64px;
|
||||||
|
border-radius: 0;
|
||||||
|
background: #fff;
|
||||||
|
transition: background-color .16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-v3 .v2-vehicle-recent-item.semi-button:active {
|
||||||
|
background: #eef5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-v3 .v2-vehicle-recent-item-shell.is-online {
|
||||||
|
box-shadow: inset 2px 0 #23a779;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-directory-v3 .v2-vehicle-recent-item-shell.is-offline {
|
||||||
|
box-shadow: inset 2px 0 #c9d2de;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user