feat(web): unify Semi UI workspace states
This commit is contained in:
@@ -43,6 +43,12 @@ function resetWorkspaceScroll() {
|
||||
content.scrollTo?.({ top: 0, left: 0, behavior: 'auto' });
|
||||
}
|
||||
|
||||
function vehicleLastSeenLabel(value?: string) {
|
||||
if (!value) return '暂无上报时间';
|
||||
const normalized = value.replace('T', ' ');
|
||||
return normalized.length >= 16 ? `${normalized.slice(5, 16)} 更新` : normalized;
|
||||
}
|
||||
|
||||
function VehicleSearch() {
|
||||
const navigate = useNavigate();
|
||||
const { session } = usePlatformSession();
|
||||
@@ -60,11 +66,11 @@ function VehicleSearch() {
|
||||
const candidates = useQuery({
|
||||
queryKey: ['vehicle-search-options', candidateParams.toString()],
|
||||
queryFn: ({ signal }) => api.vehicles(candidateParams, signal),
|
||||
enabled: candidatesOpen,
|
||||
staleTime: 30_000,
|
||||
gcTime: QUERY_MEMORY.optionGcTime
|
||||
});
|
||||
const options = useMemo(() => mergeVehicleCandidates(candidates.data?.items ?? []), [candidates.data?.items]);
|
||||
const quickVehicles = useMemo(() => options.slice(0, mobileLayout ? 5 : 6), [mobileLayout, options]);
|
||||
useEffect(() => () => window.clearTimeout(closeTimerRef.current), []);
|
||||
const openCandidates = () => {
|
||||
window.clearTimeout(closeTimerRef.current);
|
||||
@@ -136,6 +142,41 @@ function VehicleSearch() {
|
||||
</div>
|
||||
{canAdminister(session) ? <Button className="v2-profile-sync-open" theme="borderless" onClick={() => setSyncOpen((value) => !value)}>{syncOpen ? '收起批量同步' : '批量同步主档'}</Button> : null}
|
||||
</Card>
|
||||
<Card className="v2-vehicle-recent-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
title={deferredKeyword ? '匹配车辆快捷入口' : '最近上报车辆'}
|
||||
description={deferredKeyword ? '按当前搜索条件展示,可直接进入车辆数字档案' : '按照最新上报时间排列,无需搜索即可继续查看'}
|
||||
meta={candidates.data ? `当前显示 ${formatZhNumber(quickVehicles.length)} 辆` : '授权范围'}
|
||||
/>
|
||||
{candidates.isFetching && !candidates.data
|
||||
? <div className="v2-vehicle-recent-state" role="status"><span className="v2-spinner" /><span>正在读取授权车辆…</span></div>
|
||||
: candidates.isError
|
||||
? <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>
|
||||
: quickVehicles.length
|
||||
? <div className="v2-vehicle-recent-grid" role="list" aria-label={deferredKeyword ? '匹配车辆' : '最近上报车辆'}>
|
||||
{quickVehicles.map((vehicle) => <Button
|
||||
key={vehicle.vin}
|
||||
className="v2-vehicle-recent-item"
|
||||
theme="borderless"
|
||||
type="tertiary"
|
||||
role="listitem"
|
||||
aria-label={`打开 ${vehicle.plate || '未绑定车牌'} 车辆档案`}
|
||||
onClick={() => openVehicle(vehicle.vin)}
|
||||
>
|
||||
<span className="v2-vehicle-recent-identity">
|
||||
<strong>{vehicle.plate || '未绑定车牌'}</strong>
|
||||
<small>{vehicle.vin}</small>
|
||||
</span>
|
||||
<span className="v2-vehicle-recent-status">
|
||||
<Tag color={vehicle.online ? 'green' : 'grey'} type="light" size="small">{vehicle.online ? '在线' : '离线'}</Tag>
|
||||
<small>{vehicleLastSeenLabel(vehicle.lastSeen)}</small>
|
||||
</span>
|
||||
<span className="v2-vehicle-recent-protocols">{vehicle.protocols.slice(0, 2).map((protocol) => <Tag key={protocol} color="blue" type="light" size="small">{protocol}</Tag>)}</span>
|
||||
<IconChevronRight className="v2-vehicle-recent-arrow" />
|
||||
</Button>)}
|
||||
</div>
|
||||
: <Empty className="v2-vehicle-recent-empty" title={deferredKeyword ? '没有匹配车辆' : '暂无授权车辆'} description={deferredKeyword ? '调整车牌、VIN 或手机号后重试。' : '管理员分配车辆权限后会显示在这里。'} />}
|
||||
</Card>
|
||||
{syncOpen ? <Suspense fallback={<Card className="v2-profile-sync-panel v2-profile-sync-loading" bodyStyle={{ padding: 0 }}><span role="status"><span className="v2-spinner" />正在加载主档同步工具…</span></Card>}><VehicleProfileSyncPanel onClose={() => setSyncOpen(false)} /></Suspense> : null}
|
||||
</section>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user