feat: unify mobile filter sheets
This commit is contained in:
@@ -29,7 +29,7 @@ import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||
|
||||
function fmt(value?: string) { return value?.trim() || '—'; }
|
||||
function metric(value: number | undefined, fallback = '—') { return typeof value === 'number' && Number.isFinite(value) ? formatZhNumber(value, 1) : fallback; }
|
||||
function metric(value: number | undefined) { return Number.isFinite(value as number) ? formatZhNumber(value!, 1) : '—'; }
|
||||
function availableMetric(value: number | undefined, available?: boolean) { return available === false ? '—' : metric(value); }
|
||||
function issueTone(issue: QualityIssueRow) { return issue.severity === 'error' ? 'error' : 'warning'; }
|
||||
function durationHours(seconds?: number | null) { return seconds == null ? '—' : `${formatZhNumber(seconds / 3600, 1)} 小时`; }
|
||||
@@ -246,7 +246,7 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
|
||||
const [copiedValue, setCopiedValue] = useState(false);
|
||||
const inspectedPresentation = useMemo(() => inspectedValue ? telemetryValuePresentation(inspectedValue) : undefined, [inspectedValue]);
|
||||
const inspectedDetail = useMemo(() => inspectedValue ? telemetryValueDetail(inspectedValue) : '', [inspectedValue]);
|
||||
useSideSheetA11y(Boolean(inspectedValue), '.v2-telemetry-value-sidesheet', 'v2-telemetry-value-detail', '遥测字段详情', '关闭遥测字段详情');
|
||||
useSideSheetA11y(!!inspectedValue, '.v2-telemetry-value-sidesheet', 'v2-telemetry-value-detail', '遥测字段详情', '关闭遥测字段详情');
|
||||
const indexed = useMemo(() => {
|
||||
const valuesByProtocolCategory = new Map<string, LatestTelemetryResponse['values']>();
|
||||
const sources = new Map<string, { protocol: string; endpoint?: string }>();
|
||||
@@ -314,7 +314,7 @@ function TelemetryPanel({ data, pending, error }: { data?: LatestTelemetryRespon
|
||||
<footer><b>来源</b>{indexed.sources.map((source) => <span key={`${source.protocol}-${source.endpoint ?? ''}`} title={source.endpoint}>{protocolDisplayLabel(source.protocol)}</span>)}<small>扫描 {data?.scannedFrames ?? 0} 帧 · 上海时间 {formatTelemetryTime(data?.asOf)}</small></footer>
|
||||
<SideSheet
|
||||
className="v2-telemetry-value-sidesheet"
|
||||
visible={Boolean(inspectedValue)}
|
||||
visible={!!inspectedValue}
|
||||
placement={mobileLayout ? 'bottom' : 'right'}
|
||||
width={mobileLayout ? undefined : 520}
|
||||
height={mobileLayout ? 'min(88dvh, 780px)' : undefined}
|
||||
@@ -373,7 +373,7 @@ function CurrentVehicleAddress({ vehicle, fallback }: { vehicle?: VehicleRealtim
|
||||
refetchOnWindowFocus: false,
|
||||
retry: 1
|
||||
});
|
||||
const moved = Boolean(coordinateKey && requestedKey && coordinateKey !== requestedKey);
|
||||
const moved = !!(coordinateKey && requestedKey && coordinateKey !== requestedKey);
|
||||
|
||||
return <div className="v2-current-location">
|
||||
<span><IconMapPin /><small>当前位置</small><strong>{coordinate ? `${vehicle!.longitude.toFixed(6)}, ${vehicle!.latitude.toFixed(6)}` : '—'}</strong></span>
|
||||
@@ -433,7 +433,7 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
|
||||
const [sourceEvidenceOpen, setSourceEvidenceOpen] = useState(false);
|
||||
const realtime = liveRealtime ?? detail.realtimeSummary;
|
||||
const identity = detail.identity;
|
||||
const hasLocation = Boolean(realtime && realtime.locationAvailable !== false && isValidAMapCoordinate(realtime.longitude, realtime.latitude));
|
||||
const hasLocation = !!(realtime && realtime.locationAvailable !== false && isValidAMapCoordinate(realtime.longitude, realtime.latitude));
|
||||
const mapVehicles = hasLocation && realtime ? [realtime] : [];
|
||||
const actions = [
|
||||
{ key: 'switch', label: '切换车辆', icon: <IconSearch />, to: '/vehicles', type: 'primary' as const },
|
||||
@@ -543,18 +543,18 @@ export default function VehiclePage() {
|
||||
const { vin } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const monitorReturn = monitorReturnFromParams(searchParams);
|
||||
const query = useQuery({ queryKey: ['vehicle-detail', vin], enabled: Boolean(vin), queryFn: ({ signal }) => api.vehicleDetail(new URLSearchParams({ keyword: vin!, limit: '20' }), signal), gcTime: QUERY_MEMORY.summaryGcTime });
|
||||
const query = useQuery({ queryKey: ['vehicle-detail', vin], enabled: !!vin, queryFn: ({ signal }) => api.vehicleDetail(new URLSearchParams({ keyword: vin!, limit: '20' }), signal), gcTime: QUERY_MEMORY.summaryGcTime });
|
||||
const resolvedVin = query.data?.lookupResolved ? query.data.vin : '';
|
||||
const realtime = useQuery({
|
||||
queryKey: ['vehicle-detail-realtime', resolvedVin],
|
||||
queryFn: ({ signal }) => api.vehicleRealtime(new URLSearchParams({ keywords: resolvedVin, limit: '1', offset: '0' }), signal),
|
||||
enabled: Boolean(resolvedVin),
|
||||
enabled: !!resolvedVin,
|
||||
staleTime: 5_000,
|
||||
gcTime: QUERY_MEMORY.highVolumeGcTime,
|
||||
refetchInterval: resolvedVin ? SINGLE_VEHICLE_REFRESH_MS : false,
|
||||
...LIVE_QUERY_POLICY
|
||||
});
|
||||
const telemetry = useQuery({ queryKey: ['vehicle-latest-telemetry', vin], enabled: Boolean(vin), queryFn: ({ signal }) => api.latestTelemetry(vin!, signal), staleTime: 10_000, gcTime: QUERY_MEMORY.summaryGcTime, refetchInterval: 20_000, ...LIVE_QUERY_POLICY });
|
||||
const telemetry = useQuery({ queryKey: ['vehicle-latest-telemetry', vin], enabled: !!vin, queryFn: ({ signal }) => api.latestTelemetry(vin!, signal), staleTime: 10_000, gcTime: QUERY_MEMORY.summaryGcTime, refetchInterval: 20_000, ...LIVE_QUERY_POLICY });
|
||||
useEffect(() => {
|
||||
resetWorkspaceScroll();
|
||||
let trailingFrame = 0;
|
||||
|
||||
Reference in New Issue
Block a user