refine Semi UI vehicle workspace
This commit is contained in:
@@ -20,11 +20,20 @@ import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||
const VehicleProfileSyncPanel = lazy(() => import('./VehicleProfileSyncPanel'));
|
||||
|
||||
function resetWorkspaceScroll() {
|
||||
const content = document.querySelector<HTMLElement>('.v2-content');
|
||||
if (!content) return;
|
||||
content.scrollTop = 0;
|
||||
content.scrollLeft = 0;
|
||||
content.scrollTo?.({ top: 0, left: 0, behavior: 'auto' });
|
||||
const targets = [
|
||||
document.scrollingElement,
|
||||
document.documentElement,
|
||||
document.body,
|
||||
document.querySelector<HTMLElement>('.v2-shell'),
|
||||
document.querySelector<HTMLElement>('.v2-main'),
|
||||
document.querySelector<HTMLElement>('.v2-content')
|
||||
];
|
||||
for (const target of targets) {
|
||||
if (!(target instanceof HTMLElement)) continue;
|
||||
target.scrollTop = 0;
|
||||
target.scrollLeft = 0;
|
||||
target.scrollTo?.({ top: 0, left: 0, behavior: 'auto' });
|
||||
}
|
||||
}
|
||||
|
||||
function vehicleLastSeenLabel(value?: string) {
|
||||
@@ -61,6 +70,8 @@ export default function VehicleSearchWorkspace() {
|
||||
const pageVehicles = useMemo(() => options.slice(0, pageSize), [options, pageSize]);
|
||||
const total = candidates.data?.total ?? 0;
|
||||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||||
const onlineOnPage = useMemo(() => pageVehicles.filter((vehicle) => vehicle.online).length, [pageVehicles]);
|
||||
const multiSourceOnPage = useMemo(() => pageVehicles.filter((vehicle) => vehicle.protocols.length > 1).length, [pageVehicles]);
|
||||
const administrator = canAdminister(session);
|
||||
useEffect(() => () => window.clearTimeout(closeTimerRef.current), []);
|
||||
useEffect(() => {
|
||||
@@ -79,6 +90,7 @@ export default function VehicleSearchWorkspace() {
|
||||
const normalized = value.trim();
|
||||
if (!normalized) return;
|
||||
setCandidatesOpen(false);
|
||||
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
|
||||
resetWorkspaceScroll();
|
||||
navigate(`/vehicles/${encodeURIComponent(normalized)}`);
|
||||
window.queueMicrotask(resetWorkspaceScroll);
|
||||
@@ -91,64 +103,70 @@ export default function VehicleSearchWorkspace() {
|
||||
};
|
||||
const vehicleScope = candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权` : '授权范围';
|
||||
return <section className={`v2-vehicle-search-page${syncOpen ? ' has-sync-panel' : ''}${candidatesOpen ? ' has-candidates' : ''}`}>
|
||||
{!mobileLayout || administrator ? <WorkspaceCommandBar
|
||||
className={`v2-vehicle-command-bar${mobileLayout ? ' is-mobile-admin' : ''}`}
|
||||
ariaLabel={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}
|
||||
/> : null}
|
||||
<WorkspaceFilterPanel
|
||||
className="v2-vehicle-query-panel"
|
||||
title={mobileLayout ? '车辆筛选' : '车辆定位'}
|
||||
description="车牌优先,VIN 辅助"
|
||||
mobileSummary={deferredKeyword ? `正在查找 ${deferredKeyword}` : candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权车辆` : '正在读取授权范围'}
|
||||
expanded={!filtersCollapsed}
|
||||
status={deferredKeyword ? candidates.isFetching ? '正在查找' : `${options.length} 辆匹配` : vehicleScope}
|
||||
statusColor={deferredKeyword ? 'blue' : 'grey'}
|
||||
collapsedLabel="修改"
|
||||
onToggle={() => setFiltersCollapsed((value) => !value)}
|
||||
>
|
||||
<form className={`v2-vehicle-search-form${filtersCollapsed ? ' is-mobile-collapsed' : ''}`} onSubmit={submit}>
|
||||
<div className={`v2-vehicle-search-picker${candidatesOpen ? ' is-open' : ''}`}>
|
||||
<Input
|
||||
aria-label="搜索车辆"
|
||||
aria-controls="v2-vehicle-search-options"
|
||||
aria-expanded={candidatesOpen}
|
||||
prefix={<IconSearch />}
|
||||
value={keyword}
|
||||
onChange={(value) => { setKeyword(value); setPage(1); setCandidatesOpen(true); }}
|
||||
onFocus={openCandidates}
|
||||
onBlur={closeCandidates}
|
||||
placeholder="输入车牌 / VIN / 终端手机号"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<Button theme="solid" htmlType="submit" icon={<IconArrowRight />} iconPosition="right">查询车辆</Button>
|
||||
{candidatesOpen ? <VehicleCandidateList
|
||||
id="v2-vehicle-search-options"
|
||||
className="v2-vehicle-search-options"
|
||||
items={options}
|
||||
loading={candidates.isFetching}
|
||||
loadingText="正在搜索授权车辆"
|
||||
error={candidates.isError ? (candidates.error instanceof Error ? candidates.error.message : '车辆候选加载失败') : undefined}
|
||||
onRetry={() => candidates.refetch()}
|
||||
emptyText="没有匹配的授权车辆"
|
||||
header="车辆候选"
|
||||
meta="车牌优先 · VIN 辅助"
|
||||
showProtocols
|
||||
layout={mobileLayout ? 'list' : 'grid'}
|
||||
onSelect={(vehicle) => openVehicle(vehicle.vin)}
|
||||
/> : null}
|
||||
</form>
|
||||
</WorkspaceFilterPanel>
|
||||
<div className="v2-vehicle-discovery-shell">
|
||||
{!mobileLayout || administrator ? <WorkspaceCommandBar
|
||||
className={`v2-vehicle-command-bar${mobileLayout ? ' is-mobile-admin' : ''}`}
|
||||
ariaLabel={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}
|
||||
/> : null}
|
||||
<WorkspaceFilterPanel
|
||||
className="v2-vehicle-query-panel"
|
||||
title={mobileLayout ? '车辆筛选' : '车辆定位'}
|
||||
description="车牌优先,VIN 辅助"
|
||||
mobileSummary={deferredKeyword ? `正在查找 ${deferredKeyword}` : candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆授权车辆` : '正在读取授权范围'}
|
||||
expanded={!filtersCollapsed}
|
||||
status={deferredKeyword ? candidates.isFetching ? '正在查找' : `${options.length} 辆匹配` : vehicleScope}
|
||||
statusColor={deferredKeyword ? 'blue' : 'grey'}
|
||||
collapsedLabel="修改"
|
||||
onToggle={() => setFiltersCollapsed((value) => !value)}
|
||||
>
|
||||
<form className={`v2-vehicle-search-form${filtersCollapsed ? ' is-mobile-collapsed' : ''}`} onSubmit={submit}>
|
||||
<div className={`v2-vehicle-search-picker${candidatesOpen ? ' is-open' : ''}`}>
|
||||
<Input
|
||||
aria-label="搜索车辆"
|
||||
aria-controls="v2-vehicle-search-options"
|
||||
aria-expanded={candidatesOpen}
|
||||
prefix={<IconSearch />}
|
||||
value={keyword}
|
||||
onChange={(value) => { setKeyword(value); setPage(1); setCandidatesOpen(true); }}
|
||||
onFocus={openCandidates}
|
||||
onBlur={closeCandidates}
|
||||
placeholder="输入车牌 / VIN / 终端手机号"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<Button theme="solid" htmlType="submit" icon={<IconArrowRight />} iconPosition="right">查询车辆</Button>
|
||||
{candidatesOpen ? <VehicleCandidateList
|
||||
id="v2-vehicle-search-options"
|
||||
className="v2-vehicle-search-options"
|
||||
items={options}
|
||||
loading={candidates.isFetching}
|
||||
loadingText="正在搜索授权车辆"
|
||||
error={candidates.isError ? (candidates.error instanceof Error ? candidates.error.message : '车辆候选加载失败') : undefined}
|
||||
onRetry={() => candidates.refetch()}
|
||||
emptyText="没有匹配的授权车辆"
|
||||
header="车辆候选"
|
||||
meta="车牌优先 · VIN 辅助"
|
||||
showProtocols
|
||||
layout={mobileLayout ? 'list' : 'grid'}
|
||||
onSelect={(vehicle) => openVehicle(vehicle.vin)}
|
||||
/> : null}
|
||||
</form>
|
||||
</WorkspaceFilterPanel>
|
||||
</div>
|
||||
<Card className="v2-vehicle-recent-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
title={deferredKeyword ? '匹配车辆' : mobileLayout ? '授权车辆' : '授权车辆目录'}
|
||||
description={deferredKeyword ? '按当前搜索条件展示,可直接进入车辆数字档案' : '按照最新上报时间排列,分页浏览全部授权车辆'}
|
||||
meta={<Tag color={deferredKeyword ? 'blue' : 'grey'} type="light" size="small">{candidates.data ? `本页 ${formatZhNumber(pageVehicles.length)} 辆` : '授权范围'}</Tag>}
|
||||
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>}
|
||||
/>
|
||||
{candidates.isFetching && !candidates.data
|
||||
@@ -186,10 +204,16 @@ export default function VehicleSearchWorkspace() {
|
||||
width: 220,
|
||||
render: (_value: string, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-identity"><strong>{vehicle.plate || '未绑定车牌'}</strong><small>{vehicle.vin}</small></span>
|
||||
},
|
||||
{
|
||||
title: '车辆信息',
|
||||
dataIndex: 'oem',
|
||||
width: 260,
|
||||
render: (_value: string, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-profile"><strong>{vehicle.oem || '品牌待维护'}</strong><small>{vehicle.phone || '暂无终端手机号'}</small></span>
|
||||
},
|
||||
{
|
||||
title: '实时状态',
|
||||
dataIndex: 'online',
|
||||
width: 150,
|
||||
width: 180,
|
||||
render: (_value: boolean, vehicle: VehicleCoverageRow) => <span className="v2-vehicle-directory-status"><Tag color={vehicle.online ? 'green' : 'grey'} type="light" size="small">{vehicle.online ? '在线' : '离线'}</Tag><small>{vehicleLastSeenLabel(vehicle.lastSeen)}</small></span>
|
||||
},
|
||||
{
|
||||
@@ -200,13 +224,22 @@ export default function VehicleSearchWorkspace() {
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'vin',
|
||||
width: 104,
|
||||
width: 116,
|
||||
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={() => openVehicle(vehicleVin)}>查看档案</Button>
|
||||
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>
|
||||
}
|
||||
]}
|
||||
dataSource={pageVehicles}
|
||||
rowKey="vin"
|
||||
onRow={(vehicle) => vehicle ? ({
|
||||
tabIndex: 0,
|
||||
onClick: () => openVehicle(vehicle.vin),
|
||||
onKeyDown: (event) => {
|
||||
if (event.key !== 'Enter' && event.key !== ' ') return;
|
||||
event.preventDefault();
|
||||
openVehicle(vehicle.vin);
|
||||
}
|
||||
}) : ({})}
|
||||
pagination={false}
|
||||
loading={candidates.isFetching}
|
||||
empty={null}
|
||||
|
||||
Reference in New Issue
Block a user