refine Semi UI vehicle workspace
This commit is contained in:
@@ -4,7 +4,7 @@ import {
|
||||
IconChevronRight, IconMapPin, IconSearch, IconTickCircle
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { Button, Card, Descriptions, Empty, Input, List, Select, SideSheet, Table, Tag } from '@douyinfe/semi-ui';
|
||||
import { FormEvent, lazy, Suspense, useEffect, useMemo, useState } from 'react';
|
||||
import { FormEvent, lazy, Suspense, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { api } from '../../api/client';
|
||||
import type { LatestTelemetryResponse, LatestTelemetryValue, QualityIssueRow, VehicleDetail, VehicleRealtimeRow } from '../../api/types';
|
||||
@@ -35,11 +35,28 @@ const SINGLE_VEHICLE_REFRESH_MS = 10_000;
|
||||
const VehicleSearch = lazy(() => import('./VehicleSearchWorkspace'));
|
||||
|
||||
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 revealVehicleIdentity(root: HTMLElement | null) {
|
||||
resetWorkspaceScroll();
|
||||
if (!root) return;
|
||||
root.scrollTop = 0;
|
||||
root.scrollLeft = 0;
|
||||
root.scrollIntoView?.({ behavior: 'auto', block: 'start', inline: 'nearest' });
|
||||
}
|
||||
|
||||
function eventTimeLabel(value?: string) {
|
||||
@@ -394,6 +411,7 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
|
||||
const { session } = usePlatformSession();
|
||||
const navigate = useNavigate();
|
||||
const mobileLayout = useMobileLayout();
|
||||
const recordRootRef = useRef<HTMLDivElement>(null);
|
||||
const [sourceEvidenceOpen, setSourceEvidenceOpen] = useState(false);
|
||||
const realtime = liveRealtime ?? detail.realtimeSummary;
|
||||
const identity = detail.identity;
|
||||
@@ -406,7 +424,33 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
|
||||
...(hasMenu(session, 'statistics') ? [{ key: 'statistics', label: '里程查询', icon: <IconClock />, to: withMonitorReturn(`/statistics?vins=${encodeURIComponent(detail.vin)}`, monitorReturn), type: 'tertiary' as const }] : []),
|
||||
...(hasMenu(session, 'alerts') ? [{ key: 'alerts', label: '告警事件', icon: <IconAlarm />, to: `/alerts?vin=${encodeURIComponent(detail.vin)}`, type: 'tertiary' as const }] : [])
|
||||
];
|
||||
return <div className={`v2-vehicle-record-page${mobileLayout ? ' is-mobile-layout' : ''}`}>
|
||||
useLayoutEffect(() => {
|
||||
const root = recordRootRef.current;
|
||||
if (!root) return;
|
||||
let cancelled = false;
|
||||
const timers: number[] = [];
|
||||
const revealIdentity = () => {
|
||||
if (!cancelled) revealVehicleIdentity(root);
|
||||
};
|
||||
const cancelEntryReset = () => {
|
||||
cancelled = true;
|
||||
for (const timer of timers) window.clearTimeout(timer);
|
||||
};
|
||||
const interactionEvents = ['wheel', 'touchstart', 'pointerdown', 'keydown'] as const;
|
||||
for (const eventName of interactionEvents) window.addEventListener(eventName, cancelEntryReset, { capture: true, passive: true });
|
||||
revealIdentity();
|
||||
const frame = window.requestAnimationFrame(revealIdentity);
|
||||
// The map and live evidence arrive independently. Keep the identity band
|
||||
// anchored only during initial settlement, then immediately yield to input.
|
||||
for (const delay of [80, 180, 360, 720, 1200, 1800, 2400]) timers.push(window.setTimeout(revealIdentity, delay));
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.cancelAnimationFrame(frame);
|
||||
for (const timer of timers) window.clearTimeout(timer);
|
||||
for (const eventName of interactionEvents) window.removeEventListener(eventName, cancelEntryReset, true);
|
||||
};
|
||||
}, [detail.vin]);
|
||||
return <div ref={recordRootRef} className={`v2-vehicle-record-page${mobileLayout ? ' is-mobile-layout' : ''}`}>
|
||||
<MonitorReturnBar />
|
||||
<Card className="v2-identity-band v2-record-card v2-live-card v2-live-overview v2-vehicle-command-card" bodyStyle={{ padding: 0 }}>
|
||||
<div className="v2-vehicle-command-header">
|
||||
|
||||
Reference in New Issue
Block a user