feat: unify mobile track workspace
This commit is contained in:
@@ -39,8 +39,8 @@ const TRACK_SOURCE_OPTIONS = [
|
||||
const numberFormatters = new Map<number, Intl.NumberFormat>();
|
||||
const timeFormatter = new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
const dateTimeFormatter = new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false, month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
const TRACK_SHEET_MAX_HEIGHT_PX = 720;
|
||||
const TRACK_SHEET_CHROME_HEIGHT_PX = 264;
|
||||
const TRACK_SHEET_MAX_HEIGHT_PX = 780;
|
||||
const TRACK_SHEET_CHROME_HEIGHT_PX = 332;
|
||||
const TRACK_SHEET_ROW_HEIGHT_PX = 62;
|
||||
|
||||
function number(value: number, digits = 1) {
|
||||
@@ -120,11 +120,11 @@ function eventTone(type: string) {
|
||||
}
|
||||
|
||||
function mobileTrackSheetHeight(track: TrackPlaybackResponse | undefined, queryCollapsed: boolean, tab: PanelTab) {
|
||||
if (!track || !queryCollapsed || tab === 'overview') return `min(82dvh, ${TRACK_SHEET_MAX_HEIGHT_PX}px)`;
|
||||
if (!track || !queryCollapsed || tab === 'overview') return `min(88dvh, ${TRACK_SHEET_MAX_HEIGHT_PX}px)`;
|
||||
const itemCount = tab === 'stops' ? track.stops.length : track.events.length;
|
||||
const visibleRows = itemCount ? Math.min(itemCount, 7) : 2;
|
||||
const contentHeight = Math.min(TRACK_SHEET_MAX_HEIGHT_PX, TRACK_SHEET_CHROME_HEIGHT_PX + visibleRows * TRACK_SHEET_ROW_HEIGHT_PX);
|
||||
return `min(82dvh, ${contentHeight}px)`;
|
||||
return `min(88dvh, ${contentHeight}px)`;
|
||||
}
|
||||
|
||||
function VehiclePicker({ value, onChange, onSelect }: { value: string; onChange: (value: string) => void; onSelect: (vehicle: VehicleRow) => void }) {
|
||||
@@ -195,7 +195,7 @@ function OverviewPanel({ track }: { track: TrackPlaybackResponse }) {
|
||||
</div>;
|
||||
}
|
||||
|
||||
const TrackRail = memo(function TrackRail({ draft, track, loading, activeStopIndexes, activeEventIndexes, tab, queryCollapsed, onDraft, onSubmit, onTab, onSelectIndex, onToggleQuery, onCollapse }: {
|
||||
const TrackRail = memo(function TrackRail({ draft, track, loading, activeStopIndexes, activeEventIndexes, tab, queryCollapsed, showCommandBar, onDraft, onSubmit, onTab, onSelectIndex, onToggleQuery, onCollapse }: {
|
||||
draft: Draft;
|
||||
track?: TrackPlaybackResponse;
|
||||
loading: boolean;
|
||||
@@ -203,6 +203,7 @@ const TrackRail = memo(function TrackRail({ draft, track, loading, activeStopInd
|
||||
activeEventIndexes: readonly number[];
|
||||
tab: PanelTab;
|
||||
queryCollapsed: boolean;
|
||||
showCommandBar: boolean;
|
||||
onDraft: (draft: Draft) => void;
|
||||
onSubmit: (event: FormEvent) => void;
|
||||
onTab: (tab: PanelTab) => void;
|
||||
@@ -217,8 +218,8 @@ const TrackRail = memo(function TrackRail({ draft, track, loading, activeStopInd
|
||||
const contextTitle = queryCollapsed ? track?.plate || draft.keyword || '轨迹查询' : '轨迹查询';
|
||||
return <aside className="v2-track-rail">
|
||||
<Card className="v2-track-rail-shell" bodyStyle={{ padding: 0 }}>
|
||||
<section className={`v2-track-query-panel${queryCollapsed ? ' is-collapsed' : ''}`}>
|
||||
<WorkspaceCommandBar
|
||||
<section className={`v2-track-query-panel${queryCollapsed ? ' is-collapsed' : ''}${showCommandBar ? '' : ' is-command-hidden'}`}>
|
||||
{showCommandBar ? <WorkspaceCommandBar
|
||||
className="v2-track-query-header"
|
||||
ariaLabel="轨迹查询任务栏"
|
||||
title={contextTitle}
|
||||
@@ -238,7 +239,7 @@ const TrackRail = memo(function TrackRail({ draft, track, loading, activeStopInd
|
||||
>{queryCollapsed ? '修改条件' : '收起条件'}</Button>
|
||||
<Button className="v2-track-rail-hide" theme="borderless" aria-label="隐藏查询与明细" icon={<IconChevronLeft />} onClick={onCollapse}><span className="v2-track-rail-hide-label">地图</span></Button>
|
||||
</>}
|
||||
/>
|
||||
/> : null}
|
||||
{!queryCollapsed ? <form className="v2-track-query" onSubmit={onSubmit}>
|
||||
<label><span>车辆</span><VehiclePicker value={draft.keyword} onChange={(keyword) => onDraft({ ...draft, keyword })} onSelect={(vehicle) => onDraft({ ...draft, keyword: vehicle.plate || vehicle.vin })} /></label>
|
||||
<div className="v2-track-presets">
|
||||
@@ -392,16 +393,41 @@ export default function TrackPage() {
|
||||
setPlaying((value) => !value);
|
||||
};
|
||||
const progress = points.length > 1 ? boundedIndex / (points.length - 1) * 100 : 0;
|
||||
const trackRail = <TrackRail draft={draft} track={track} loading={query.isFetching} activeStopIndexes={activeStopIndexes} activeEventIndexes={activeEventIndexes} tab={panelTab} queryCollapsed={queryCollapsed} onDraft={setDraft} onSubmit={submit} onTab={setPanelTab} onSelectIndex={selectRailIndex} onToggleQuery={() => setQueryCollapsed((value) => !value)} onCollapse={collapseRail} />;
|
||||
const trackRail = <TrackRail draft={draft} track={track} loading={query.isFetching} activeStopIndexes={activeStopIndexes} activeEventIndexes={activeEventIndexes} tab={panelTab} queryCollapsed={queryCollapsed} showCommandBar={!mobileLayout} onDraft={setDraft} onSubmit={submit} onTab={setPanelTab} onSelectIndex={selectRailIndex} onToggleQuery={() => setQueryCollapsed((value) => !value)} onCollapse={collapseRail} />;
|
||||
const appliedWindow = `${draft.dateFrom.replace('T', ' ').slice(5)} – ${draft.dateTo.replace('T', ' ').slice(5)}`;
|
||||
const appliedSource = draft.protocol ? protocolDisplayLabel(draft.protocol) : '自动来源';
|
||||
const mobileSheetTitle = track?.plate || draft.keyword.trim() || '轨迹查询与明细';
|
||||
const mobileSheetDescription = track
|
||||
? `${number(track.summary.distanceKm)} km · ${track.stops.length} 个停留 · ${track.events.length} 个事件`
|
||||
: '选择车辆和时间范围';
|
||||
? `${appliedWindow} · ${appliedSource}`
|
||||
: '选择车辆、时间范围与数据来源';
|
||||
const mobileSheetBadge = query.isFetching
|
||||
? '读取中'
|
||||
: track
|
||||
? `${track.points.length.toLocaleString('zh-CN')} 点`
|
||||
? track.coverage.complete ? '时间窗完整' : '最新切片'
|
||||
: '待查询';
|
||||
const mobileSheetSummary = track ? [
|
||||
{
|
||||
label: '行程里程',
|
||||
value: `${number(track.summary.distanceKm)} km`,
|
||||
detail: formatDuration(track.summary.durationSeconds),
|
||||
tone: 'primary' as const
|
||||
},
|
||||
{
|
||||
label: '停留 / 事件',
|
||||
value: `${track.stops.length} / ${track.events.length}`,
|
||||
detail: '地图证据点'
|
||||
},
|
||||
{
|
||||
label: '数据点',
|
||||
value: track.points.length.toLocaleString('zh-CN'),
|
||||
detail: track.coverage.complete ? '时间窗完整' : '已返回切片',
|
||||
tone: track.coverage.complete ? 'success' as const : 'warning' as const
|
||||
}
|
||||
] : [
|
||||
{ label: '查询状态', value: '待查询', detail: '请先选择车辆', tone: 'primary' as const },
|
||||
{ label: '时间范围', value: appliedWindow, detail: '最长连续 7 天' },
|
||||
{ label: '数据来源', value: appliedSource, detail: '支持自动优选' }
|
||||
];
|
||||
|
||||
return <div className={`v2-track-page${mobileLayout ? ' is-mobile-layout' : ''}${railCollapsed ? ' is-rail-collapsed' : ''}${monitorReturn ? ' has-monitor-return' : ''}`}>
|
||||
<MonitorReturnBar />
|
||||
@@ -410,12 +436,24 @@ export default function TrackPage() {
|
||||
variant="editor"
|
||||
visible={!railCollapsed}
|
||||
ariaLabel="轨迹查询与明细"
|
||||
closeLabel="关闭轨迹查询与明细"
|
||||
dialogId="v2-track-mobile-workbench"
|
||||
placement="bottom"
|
||||
height={mobileTrackSheetHeight(track, queryCollapsed, panelTab)}
|
||||
title={mobileSheetTitle}
|
||||
description={mobileSheetDescription}
|
||||
icon={<IconMapPin />}
|
||||
badge={mobileSheetBadge}
|
||||
badgeColor={query.isFetching ? 'blue' : track ? 'green' : 'grey'}
|
||||
summaryItems={mobileSheetSummary}
|
||||
footerNote={`${appliedWindow} · ${appliedSource}`}
|
||||
secondaryActions={[{
|
||||
label: queryCollapsed ? '修改查询' : '收起查询',
|
||||
ariaLabel: queryCollapsed ? '修改轨迹查询' : '收起轨迹查询',
|
||||
icon: <IconSearch />,
|
||||
onClick: () => setQueryCollapsed((value) => !value)
|
||||
}]}
|
||||
primaryAction={{ label: '查看地图', icon: <IconMapPin />, onClick: collapseRail }}
|
||||
onCancel={collapseRail}
|
||||
>{trackRail}</WorkspaceSideSheet> : trackRail}
|
||||
<section className="v2-track-stage">
|
||||
|
||||
Reference in New Issue
Block a user