feat(web): add vehicle directory views
This commit is contained in:
@@ -196,8 +196,10 @@ test('shows deduplicated Semi vehicle candidates and opens the selected VIN', as
|
||||
|
||||
const view = render(<QueryClientProvider client={client}><MemoryRouter initialEntries={['/vehicles']} future={ROUTER_FUTURE}><Routes><Route path="/vehicles" element={<VehiclePage />} /><Route path="/vehicles/:vin" element={<VehiclePage />} /></Routes></MemoryRouter></QueryClientProvider>);
|
||||
|
||||
expect(await screen.findByRole('heading', { name: '车辆快速定位', level: 5 })).toBeInTheDocument();
|
||||
expect(await screen.findByRole('heading', { name: '查找车辆', level: 5 })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('查车操作')).toHaveClass('v2-workspace-command-bar', 'v2-vehicle-command-bar');
|
||||
expect(screen.getByRole('tablist', { name: '车辆目录视图' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('tab', { name: '全部车辆' })).toHaveAttribute('aria-selected', 'true');
|
||||
expect(screen.getByRole('heading', { name: '车辆定位', level: 5 }).closest('.semi-card')).toHaveClass('v2-vehicle-query-panel', 'v2-workspace-filter-panel');
|
||||
const input = screen.getByRole('textbox', { name: '搜索车辆' });
|
||||
expect(input).toHaveAttribute('aria-expanded', 'false');
|
||||
@@ -210,10 +212,10 @@ test('shows deduplicated Semi vehicle candidates and opens the selected VIN', as
|
||||
expect(screen.getByRole('heading', { name: '授权车辆目录', level: 5 })).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(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(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(view.container.querySelector('.v2-vehicle-directory-table.semi-table-wrapper')).toBeInTheDocument();
|
||||
@@ -280,6 +282,8 @@ test('keeps the mobile vehicle directory primary and applies search from a Semi
|
||||
expect(view.container.querySelector('.v2-vehicle-directory-table')).not.toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-vehicle-query-panel')).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('查车操作')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('tablist', { name: '车辆目录视图' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('tab', { name: '全部车辆' })).toHaveAttribute('aria-selected', 'true');
|
||||
const toggle = screen.getByRole('button', { name: '打开车辆搜索:10 辆授权车辆' });
|
||||
expect(toggle).toHaveAttribute('aria-expanded', 'false');
|
||||
fireEvent.click(toggle);
|
||||
@@ -310,6 +314,50 @@ test('keeps the mobile vehicle directory primary and applies search from a Semi
|
||||
expect(screen.getByRole('heading', { name: '授权车辆', level: 5 })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('filters the authorized directory with one consistent Semi view switcher', async () => {
|
||||
const candidate = {
|
||||
vin: initialRealtime.vin,
|
||||
plate: initialRealtime.plate,
|
||||
phone: '',
|
||||
oem: '测试品牌',
|
||||
protocols: ['JT808', 'GB32960'],
|
||||
missingProtocols: [],
|
||||
sourceStatus: [],
|
||||
sourceCount: 2,
|
||||
onlineSourceCount: 2,
|
||||
online: true,
|
||||
lastSeen: initialRealtime.lastSeen,
|
||||
locationText: '广东省广州市',
|
||||
bindingScore: 100,
|
||||
bindingStatus: 'bound'
|
||||
};
|
||||
const vehicles = vi.spyOn(api, 'vehicleCoverage').mockResolvedValue({ items: [candidate], total: 1, limit: 10, offset: 0 });
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
|
||||
render(<QueryClientProvider client={client}><MemoryRouter initialEntries={['/vehicles']} future={ROUTER_FUTURE}><Routes><Route path="/vehicles" element={<VehiclePage />} /></Routes></MemoryRouter></QueryClientProvider>);
|
||||
|
||||
await screen.findByText(initialRealtime.plate);
|
||||
const viewTabs = screen.getByRole('tablist', { name: '车辆目录视图' });
|
||||
expect(viewTabs).toHaveTextContent('全部车辆当前在线当前离线多源车辆');
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: '当前在线' }));
|
||||
await waitFor(() => expect(vehicles.mock.calls[vehicles.mock.calls.length - 1]?.[0]?.get('online')).toBe('online'));
|
||||
expect(screen.getByRole('tab', { name: '当前在线' })).toHaveAttribute('aria-selected', 'true');
|
||||
expect(screen.getByRole('heading', { name: '当前在线', level: 5 })).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: '当前离线' }));
|
||||
await waitFor(() => expect(vehicles.mock.calls[vehicles.mock.calls.length - 1]?.[0]?.get('online')).toBe('offline'));
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: '多源车辆' }));
|
||||
await waitFor(() => expect(vehicles.mock.calls[vehicles.mock.calls.length - 1]?.[0]?.get('coverage')).toBe('multi'));
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: '全部车辆' }));
|
||||
expect(screen.getByRole('tab', { name: '全部车辆' })).toHaveAttribute('aria-selected', 'true');
|
||||
expect(screen.getByRole('heading', { name: '授权车辆目录', level: 5 })).toBeInTheDocument();
|
||||
expect(vehicles.mock.calls[0]?.[0]?.has('online')).toBe(false);
|
||||
expect(vehicles.mock.calls[0]?.[0]?.has('coverage')).toBe(false);
|
||||
});
|
||||
|
||||
test('keeps a partial vehicle query in the directory instead of navigating to an invalid detail route', async () => {
|
||||
const candidate = {
|
||||
vin: initialRealtime.vin,
|
||||
|
||||
@@ -92,7 +92,7 @@ function Archive({ detail, editable, onUpdated }: { detail: VehicleDetail; edita
|
||||
return <Card className="v2-record-card v2-archive-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
title="车辆主档"
|
||||
description="身份、车型与运营属性"
|
||||
description="身份与运营属性"
|
||||
meta={`完整度 ${profile?.completeness ?? 0}%`}
|
||||
actions={editable && !editing ? <Button theme="borderless" size="small" onClick={startEditing}>维护档案</Button> : null}
|
||||
/>
|
||||
@@ -126,7 +126,7 @@ function Events({ detail }: { detail: VehicleDetail }) {
|
||||
return <Card className="v2-record-card v2-events-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
title="最近事件"
|
||||
description="质量提醒与协议来源状态"
|
||||
description="质量提醒与来源状态"
|
||||
meta={`${events.length} 条`}
|
||||
actions={<Link to={`/alerts?vin=${encodeURIComponent(detail.vin)}`}><Button theme="borderless" type="tertiary" size="small">查看全部</Button></Link>}
|
||||
/>
|
||||
@@ -306,7 +306,7 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
|
||||
return <Card className="v2-record-card v2-telemetry-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
title="实时遥测"
|
||||
description="按协议查看当前值、质量和来源"
|
||||
description="按协议查看值、质量与来源"
|
||||
meta={`${data?.values.length ?? 0} 项`}
|
||||
/>
|
||||
<div className="v2-telemetry-tabs">
|
||||
@@ -596,7 +596,7 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
|
||||
<Card className="v2-single-map-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
title="实时位置"
|
||||
description="定位点按车辆上报周期平滑移动"
|
||||
description="按上报周期平滑移动"
|
||||
meta={realtime?.online ? '实时跟随' : '保留最后位置'}
|
||||
/>
|
||||
<FleetMap vehicles={mapVehicles} selectedVin={hasLocation ? detail.vin : undefined} onSelect={() => undefined} />
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PanelEmpty, PanelLoading } from '../shared/AsyncState';
|
||||
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
|
||||
import { MobileFilterSheet, MobileFilterSheetSection } from '../shared/MobileFilterSheet';
|
||||
import { ProtocolTag } from '../shared/ProtocolTag';
|
||||
import { SegmentedTabs } from '../shared/SegmentedTabs';
|
||||
import { TablePagination } from '../shared/TablePagination';
|
||||
import { VehicleCandidateList } from '../shared/VehicleCandidateList';
|
||||
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
|
||||
@@ -23,6 +24,20 @@ import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../share
|
||||
|
||||
const VehicleProfileSyncPanel = lazy(() => import('./VehicleProfileSyncPanel'));
|
||||
|
||||
type VehicleDirectoryView = 'all' | 'online' | 'offline' | 'multi';
|
||||
|
||||
const vehicleDirectoryViews = [
|
||||
{ key: 'all', label: '全部车辆' },
|
||||
{ key: 'online', label: '当前在线' },
|
||||
{ key: 'offline', label: '当前离线' },
|
||||
{ key: 'multi', label: '多源车辆' }
|
||||
] satisfies { key: VehicleDirectoryView; label: string }[];
|
||||
|
||||
function applyDirectoryView(params: URLSearchParams, view: VehicleDirectoryView) {
|
||||
if (view === 'online' || view === 'offline') params.set('online', view);
|
||||
if (view === 'multi') params.set('coverage', 'multi');
|
||||
}
|
||||
|
||||
function resetWorkspaceScroll() {
|
||||
const targets = [
|
||||
document.scrollingElement,
|
||||
@@ -56,17 +71,19 @@ export default function VehicleSearchWorkspace() {
|
||||
const [syncOpen, setSyncOpen] = useState(false);
|
||||
const [candidatesOpen, setCandidatesOpen] = useState(false);
|
||||
const [filtersCollapsed, setFiltersCollapsed] = useState(mobileLayout);
|
||||
const [directoryView, setDirectoryView] = useState<VehicleDirectoryView>('all');
|
||||
const closeTimerRef = useRef<number>();
|
||||
const deferredKeyword = useDeferredValue(keyword.trim());
|
||||
const deferredDraftKeyword = useDeferredValue(draftKeyword.trim());
|
||||
const mobileFiltersOpen = mobileLayout && !filtersCollapsed;
|
||||
const pageSize = mobileLayout ? 8 : 10;
|
||||
const directoryScope = useMemo(() => queryScopeKey({ keyword: deferredKeyword }), [deferredKeyword]);
|
||||
const directoryScope = useMemo(() => queryScopeKey({ keyword: deferredKeyword, view: directoryView }), [deferredKeyword, directoryView]);
|
||||
const candidateParams = useMemo(() => {
|
||||
const params = new URLSearchParams({ limit: String(pageSize), offset: String((page - 1) * pageSize) });
|
||||
if (deferredKeyword) params.set('keyword', deferredKeyword);
|
||||
applyDirectoryView(params, directoryView);
|
||||
return params;
|
||||
}, [deferredKeyword, page, pageSize]);
|
||||
}, [deferredKeyword, directoryView, page, pageSize]);
|
||||
const candidates = useQuery<Page<VehicleCoverageRow>>({
|
||||
queryKey: ['vehicle-directory', directoryScope, pageSize, page],
|
||||
queryFn: ({ signal }) => api.vehicleCoverage(candidateParams, signal),
|
||||
@@ -77,10 +94,11 @@ export default function VehicleSearchWorkspace() {
|
||||
const mobileCandidateParams = useMemo(() => {
|
||||
const params = new URLSearchParams({ limit: '6', offset: '0' });
|
||||
if (deferredDraftKeyword) params.set('keyword', deferredDraftKeyword);
|
||||
applyDirectoryView(params, directoryView);
|
||||
return params;
|
||||
}, [deferredDraftKeyword]);
|
||||
}, [deferredDraftKeyword, directoryView]);
|
||||
const mobileCandidates = useQuery<Page<VehicleCoverageRow>>({
|
||||
queryKey: ['vehicle-mobile-candidates', queryScopeKey({ keyword: deferredDraftKeyword })],
|
||||
queryKey: ['vehicle-mobile-candidates', queryScopeKey({ keyword: deferredDraftKeyword, view: directoryView })],
|
||||
queryFn: ({ signal }) => api.vehicleCoverage(mobileCandidateParams, signal),
|
||||
enabled: mobileFiltersOpen && Boolean(deferredDraftKeyword),
|
||||
staleTime: 30_000,
|
||||
@@ -147,15 +165,23 @@ export default function VehicleSearchWorkspace() {
|
||||
event.preventDefault();
|
||||
applyMobileFilters();
|
||||
};
|
||||
const selectDirectoryView = (nextView: VehicleDirectoryView) => {
|
||||
setDirectoryView(nextView);
|
||||
setPage(1);
|
||||
setCandidatesOpen(false);
|
||||
};
|
||||
const directoryViewLabel = vehicleDirectoryViews.find((item) => item.key === directoryView)?.label ?? '全部车辆';
|
||||
const vehicleScope = candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权` : '授权范围';
|
||||
const mobileFilterSummary = deferredKeyword
|
||||
? `${deferredKeyword} · ${candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆匹配` : '正在查找'}`
|
||||
: candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权车辆` : '正在读取授权范围';
|
||||
? `${deferredKeyword}${directoryView === 'all' ? '' : ` · ${directoryViewLabel}`} · ${candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆匹配` : '正在查找'}`
|
||||
: candidates.data
|
||||
? directoryView === 'all' ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权车辆` : `${directoryViewLabel} · ${candidates.data.total.toLocaleString('zh-CN')} 辆`
|
||||
: '正在读取授权范围';
|
||||
const directoryMetrics: WorkspaceQueueMetricRailItem[] = [
|
||||
{
|
||||
label: deferredKeyword ? '匹配车辆' : '授权车辆',
|
||||
label: deferredKeyword ? '匹配车辆' : directoryView === 'all' ? '授权车辆' : directoryViewLabel,
|
||||
value: <>{candidates.data ? formatZhNumber(total) : '—'}<i className="v2-vehicle-directory-unit">辆</i></>,
|
||||
note: deferredKeyword ? '当前条件' : '账号范围',
|
||||
note: deferredKeyword || directoryView !== 'all' ? '当前视图' : '账号范围',
|
||||
tone: 'primary',
|
||||
emphasis: 'primary'
|
||||
},
|
||||
@@ -167,29 +193,45 @@ export default function VehicleSearchWorkspace() {
|
||||
emphasis: 'primary'
|
||||
},
|
||||
{
|
||||
label: '多源车辆',
|
||||
label: '本页多源',
|
||||
value: <>{candidates.data ? formatZhNumber(multiSourceOnPage) : '—'}<i className="v2-vehicle-directory-unit">辆</i></>,
|
||||
note: '2+ 协议来源',
|
||||
tone: 'primary',
|
||||
emphasis: 'secondary'
|
||||
}
|
||||
];
|
||||
const directoryTitle = deferredKeyword ? '匹配车辆' : mobileLayout ? '授权车辆' : '授权车辆目录';
|
||||
const directoryTitle = deferredKeyword
|
||||
? '匹配车辆'
|
||||
: directoryView === 'all'
|
||||
? mobileLayout ? '授权车辆' : '授权车辆目录'
|
||||
: directoryViewLabel;
|
||||
const directoryDescription = deferredKeyword
|
||||
? '按当前搜索条件展示,可直接进入车辆数字档案'
|
||||
: '按照最新上报时间排列,分页浏览全部授权车辆';
|
||||
: directoryView === 'all'
|
||||
? '按照最新上报时间排列,分页浏览全部授权车辆'
|
||||
: `仅展示${directoryViewLabel},按最新上报时间排列`;
|
||||
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
|
||||
className={`v2-vehicle-command-bar${mobileLayout ? ' is-mobile-admin' : ''}`}
|
||||
ariaLabel={mobileLayout ? '车辆主档操作' : '查车操作'}
|
||||
eyebrow={mobileLayout ? '车辆治理' : '车辆目录'}
|
||||
eyebrow={mobileLayout ? '车辆治理' : undefined}
|
||||
icon={mobileLayout ? <IconBox /> : <IconSearch />}
|
||||
title={mobileLayout ? '车辆主档' : '车辆快速定位'}
|
||||
title={mobileLayout ? '车辆主档' : '查找车辆'}
|
||||
description={mobileLayout ? '批量维护品牌、车型与运营属性' : '车牌、VIN 或手机号快速查车'}
|
||||
status={mobileLayout ? undefined : candidates.isPending ? '正在读取授权范围' : vehicleScope}
|
||||
statusColor={candidates.isError ? 'red' : 'blue'}
|
||||
actions={administrator ? <Button theme="light" type="tertiary" icon={mobileLayout ? <IconBox /> : undefined} onClick={() => setSyncOpen((value) => !value)}>{syncOpen ? '收起同步' : mobileLayout ? '主档同步' : '批量同步主档'}</Button> : null}
|
||||
actions={<div className="v2-vehicle-command-actions">
|
||||
{!mobileLayout ? <SegmentedTabs
|
||||
className="v2-vehicle-directory-view-tabs"
|
||||
ariaLabel="车辆目录视图"
|
||||
value={directoryView}
|
||||
items={vehicleDirectoryViews}
|
||||
onChange={selectDirectoryView}
|
||||
variant="filled"
|
||||
/> : null}
|
||||
{administrator ? <Button theme="light" type="tertiary" icon={mobileLayout ? <IconBox /> : undefined} onClick={() => setSyncOpen((value) => !value)}>{syncOpen ? '收起同步' : mobileLayout ? '主档同步' : '批量同步主档'}</Button> : null}
|
||||
</div>}
|
||||
/> : null}
|
||||
{mobileLayout ? <div className="v2-vehicle-mobile-discovery">
|
||||
<MobileFilterToggle
|
||||
@@ -313,6 +355,14 @@ export default function VehicleSearchWorkspace() {
|
||||
/> : null}
|
||||
</form>
|
||||
</WorkspaceFilterPanel>}
|
||||
{mobileLayout ? <SegmentedTabs
|
||||
className="v2-vehicle-directory-view-tabs"
|
||||
ariaLabel="车辆目录视图"
|
||||
value={directoryView}
|
||||
items={vehicleDirectoryViews}
|
||||
onChange={selectDirectoryView}
|
||||
variant="filled"
|
||||
/> : null}
|
||||
</div>
|
||||
<Card className="v2-vehicle-recent-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspaceMetricRail
|
||||
|
||||
@@ -18358,6 +18358,56 @@
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.v2-vehicle-command-actions {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-view-tabs.v2-segmented-tabs {
|
||||
display: grid;
|
||||
min-width: min(386px, 40vw);
|
||||
grid-template-columns: repeat(4, minmax(78px, 1fr));
|
||||
gap: 3px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e1e8f1;
|
||||
border-radius: 9px;
|
||||
background: #f3f6fa;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-view-tabs.v2-segmented-tabs > .semi-button {
|
||||
min-width: 0;
|
||||
min-height: 30px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 7px;
|
||||
background: transparent;
|
||||
color: #64758b;
|
||||
font-size: 10px;
|
||||
font-weight: 680;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-view-tabs.v2-segmented-tabs > .semi-button:hover {
|
||||
border-color: #dce5ef;
|
||||
background: #fff;
|
||||
color: #315f98;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-view-tabs.v2-segmented-tabs > .semi-button.is-active,
|
||||
.v2-vehicle-directory-view-tabs.v2-segmented-tabs > .semi-button.is-active:hover {
|
||||
border-color: #c5d8f4;
|
||||
background: #fff !important;
|
||||
color: #1268df;
|
||||
box-shadow: 0 2px 8px rgba(18, 104, 223, .08), inset 0 -2px #6fa4ed;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-view-tabs .v2-segmented-tab-label {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 > .v2-vehicle-recent-card.semi-card {
|
||||
width: min(1280px, 100%);
|
||||
min-height: 0;
|
||||
@@ -18802,6 +18852,22 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 > .v2-vehicle-discovery-shell > .v2-vehicle-directory-view-tabs {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
border-width: 1px 0 0;
|
||||
border-radius: 0 0 13px 13px;
|
||||
background: #f7f9fc;
|
||||
padding: 4px 6px 6px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-view-tabs.v2-segmented-tabs > .semi-button {
|
||||
min-height: 34px;
|
||||
padding-inline: 2px;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-command-bar.is-mobile-admin {
|
||||
height: 44px;
|
||||
min-height: 44px;
|
||||
|
||||
Reference in New Issue
Block a user