From f4f8bf9f4b0d46a2fb50c2c28d8c98cc682e67f2 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 19 Jul 2026 02:15:10 +0800 Subject: [PATCH] feat: densify mobile monitor workspace --- .../web/src/v2/pages/MonitorPage.test.tsx | 25 ++ .../apps/web/src/v2/pages/MonitorPage.tsx | 13 +- .../apps/web/src/v2/styles/workspace.css | 217 ++++++++++++++++++ 3 files changed, 252 insertions(+), 3 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.test.tsx index a7da6faf..ba8eec88 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.test.tsx @@ -112,6 +112,7 @@ test('starts without a selection and supports expand, collapse, reselection, and const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); const view = render(); const workspace = view.container.querySelector('.v2-monitor-workspace')!; + const monitorPage = view.container.querySelector('.v2-monitor-page')!; const firstVehicle = screen.getByRole('button', { name: /粤A12345 LTEST000000000001/ }); const secondVehicle = screen.getByRole('button', { name: /粤B67890 LTEST000000000002/ }); @@ -136,6 +137,7 @@ test('starts without a selection and supports expand, collapse, reselection, and expect(liveStatus).toHaveTextContent('最近同步等待数据'); expect(workspace).not.toHaveClass('is-detail-open'); expect(workspace).not.toHaveClass('is-detail-collapsed'); + expect(monitorPage).toHaveClass('is-map-mode'); expect(firstVehicle).not.toHaveClass('is-selected'); expect(screen.queryByRole('button', { name: '取消选择车辆' })).not.toBeInTheDocument(); expect(screen.getByTestId('fleet-map')).toHaveAttribute('data-selected-vin', ''); @@ -149,6 +151,7 @@ test('starts without a selection and supports expand, collapse, reselection, and fireEvent.click(firstVehicle); expect(await screen.findByRole('region', { name: '粤A12345车辆详情' })).toBeInTheDocument(); expect(workspace).toHaveClass('is-detail-open'); + expect(monitorPage).toHaveClass('is-map-mode'); expect(firstVehicle).toHaveClass('is-selected'); expect(screen.getByRole('button', { name: '取消选择车辆' })).toBeInTheDocument(); expect(view.container.querySelector('.v2-vehicle-detail')).toHaveClass('semi-card'); @@ -203,6 +206,28 @@ test('starts without a selection and supports expand, collapse, reselection, and expect(secondVehicle).toHaveClass('is-selected'); }); +test('returns the monitor scroll owner to the command bar when changing views', async () => { + vi.spyOn(api, 'vehicleRealtime').mockResolvedValue({ items: vehicles, total: 2, limit: 50, offset: 0 }); + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + const view = render( +
+ +
+ ); + const scrollOwner = screen.getByTestId('monitor-scroll-owner'); + const monitorPage = view.container.querySelector('.v2-monitor-page')!; + + scrollOwner.scrollTop = 180; + fireEvent.click(screen.getByRole('tab', { name: /列表/ })); + expect(scrollOwner.scrollTop).toBe(0); + expect(monitorPage).toHaveClass('is-list-mode'); + + scrollOwner.scrollTop = 120; + fireEvent.click(screen.getByRole('tab', { name: /地图/ })); + expect(scrollOwner.scrollTop).toBe(0); + expect(monitorPage).toHaveClass('is-map-mode'); +}); + test('restores URL-backed monitor context and carries it into every vehicle workflow', async () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); const entry = '/monitor?keyword=%E7%B2%A4A12345&protocol=JT808&status=online&selectedVin=LTEST000000000001&detail=collapsed&zoom=13&bounds=113.100000%2C23.000000%2C113.400000%2C23.300000'; diff --git a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx index f8bba80e..28662e24 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx @@ -204,6 +204,7 @@ const MemoFleetMap = memo(FleetMap); export default function MonitorPage() { const [routeParams, setRouteParams] = useSearchParams(); + const pageRef = useRef(null); const initialContextRef = useRef(parseMonitorRouteContext(routeParams)); const initialContext = initialContextRef.current; const mobileLayout = useMobileLayout(760); @@ -280,11 +281,16 @@ export default function MonitorPage() { const selected = selectedVin ? rows.find((vehicle) => vehicle.vin === selectedVin) ?? selectedVehicle.data?.items[0] : undefined; + const resetMonitorScroll = useCallback(() => { + const scrollOwner = pageRef.current?.closest('.v2-content'); + if (scrollOwner) scrollOwner.scrollTop = 0; + }, []); const selectVehicle = useCallback((vin: string) => { + resetMonitorScroll(); setSelectedVin(vin); setDetailOpen(true); setMode('map'); - }, []); + }, [resetMonitorScroll]); const clearSelection = useCallback(() => { setSelectedVin(''); setDetailOpen(false); @@ -293,9 +299,10 @@ export default function MonitorPage() { const collapseDetail = useCallback(() => setDetailOpen(false), []); const expandDetail = useCallback(() => setDetailOpen(true), []); const changeMonitorMode = useCallback((nextMode: 'map' | 'list') => { + resetMonitorScroll(); setMode(nextMode); if (nextMode === 'list') setDetailOpen(false); - }, []); + }, [resetMonitorScroll]); const clearFilters = useCallback(() => { setKeyword(''); setProtocol(''); @@ -314,7 +321,7 @@ export default function MonitorPage() { const lastSyncAt = vehicles.dataUpdatedAt ? new Date(vehicles.dataUpdatedAt) : undefined; return ( -
+
1 ? ' is-batch' : ''}`}> diff --git a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css index 14d2295d..b9c851af 100644 --- a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css +++ b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css @@ -12797,6 +12797,223 @@ .v2-kpi strong { margin-top: 7px; } + + .v2-monitor-mobile-cards { + gap: 6px; + padding: 6px; + } + + .v2-monitor-mobile-cards > .v2-monitor-mobile-card.semi-card { + contain-intrinsic-size: auto 132px; + } + + .v2-monitor-mobile-card .semi-card-body { + padding: 7px 9px; + } + + .v2-monitor-mobile-card .semi-card-body > header { + min-height: 34px; + align-items: center; + } + + .v2-monitor-mobile-card .semi-card-body > header strong { + font-size: 14px; + } + + .v2-monitor-mobile-card .semi-card-body > header span { + font-size: 9px; + } + + .v2-monitor-card-heading-actions > b { + font-size: 15px; + } + + .v2-monitor-card-heading-actions > .v2-monitor-card-locate.semi-button { + height: 28px; + min-height: 28px; + } + + .v2-monitor-mobile-cards dl { + gap: 2px 9px; + margin-top: 5px; + padding-top: 5px; + } + + .v2-monitor-mobile-cards dl > div { + min-height: 24px; + } + + .v2-monitor-mobile-cards dl > div.is-address { + min-height: 28px; + } + + .v2-monitor-mobile-cards dl > div.is-address .semi-button { + min-height: 26px; + height: 26px; + } +} + +@media (max-width: 760px) and (max-height: 500px) and (orientation: landscape) { + .v2-monitor-page { + gap: 6px; + padding: 6px 8px; + } + + .v2-monitor-page > .v2-filterbar > .semi-card-body { + padding: 4px; + } + + .v2-monitor-filter-actions { + grid-template-columns: 64px minmax(0, 1fr) 38px; + gap: 5px; + } + + .v2-monitor-filter-toggle.semi-button { + width: 64px; + height: 36px; + } + + .v2-monitor-mode.v2-segmented-tabs { + height: 36px; + } + + .v2-monitor-mode.v2-segmented-tabs > .semi-button { + height: 30px; + font-size: 11px; + } + + .v2-monitor-filter-actions > .v2-monitor-mobile-entry.semi-button { + width: 38px; + height: 36px; + } + + .v2-monitor-page > .v2-kpis > .semi-card-body { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .v2-monitor-page > .v2-kpis .v2-kpi.is-support { + display: none; + } + + .v2-monitor-page > .v2-kpis .v2-kpi { + min-height: 50px; + padding: 5px 10px; + } + + .v2-monitor-page > .v2-kpis .v2-kpi small { + font-size: 9px; + } + + .v2-monitor-page > .v2-kpis .v2-kpi strong { + margin-top: 3px; + font-size: 18px; + } + + .v2-monitor-page > .v2-kpis .v2-kpi strong em { + font-size: 8px; + } + + .v2-monitor-table-panel > .semi-card-body > .v2-workspace-panel-header { + min-height: 35px; + align-items: center; + padding: 3px 9px; + } + + .v2-monitor-table-panel > .semi-card-body > .v2-workspace-panel-header .v2-workspace-panel-copy { + gap: 0; + } + + .v2-monitor-table-panel > .semi-card-body > .v2-workspace-panel-header .v2-workspace-panel-copy > h5 { + font-size: 12px; + } + + .v2-monitor-table-panel > .semi-card-body > .v2-workspace-panel-header .v2-workspace-panel-copy > .semi-typography:not(h5) { + display: none; + } + + .v2-monitor-table-panel > .semi-card-body > .v2-workspace-panel-header .v2-workspace-panel-meta { + font-size: 9px; + } + + .v2-monitor-mobile-cards { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-content: start; + gap: 5px; + padding: 5px; + } + + .v2-monitor-mobile-cards > .v2-monitor-mobile-card.semi-card { + contain-intrinsic-size: auto 92px; + } + + .v2-monitor-mobile-card .semi-card-body { + padding: 5px 7px; + } + + .v2-monitor-mobile-card .semi-card-body > header { + min-height: 29px; + gap: 5px; + } + + .v2-monitor-mobile-card .semi-card-body > header strong { + font-size: 12px; + } + + .v2-monitor-mobile-card .semi-card-body > header span { + font-size: 8px; + } + + .v2-monitor-card-heading-actions { + gap: 5px !important; + } + + .v2-monitor-card-heading-actions > b { + font-size: 13px; + } + + .v2-monitor-card-heading-actions > .v2-monitor-card-locate.semi-button { + width: 44px; + height: 26px; + min-height: 26px; + padding-inline: 5px; + font-size: 9px; + } + + .v2-monitor-mobile-cards dl { + gap: 1px 7px; + margin-top: 3px; + padding-top: 3px; + } + + .v2-monitor-mobile-cards dl > div { + min-height: 20px; + gap: 4px; + } + + .v2-monitor-mobile-cards dl > div.is-address { + display: none; + } + + .v2-monitor-mobile-cards dt { + font-size: 7px; + } + + .v2-monitor-mobile-cards dd { + font-size: 9px; + } + + .v2-monitor-mobile-cards dd code { + font-size: 7px; + } + + .v2-monitor-table-panel > .semi-card-body > footer { + bottom: calc(58px + env(safe-area-inset-bottom)); + } + + .v2-monitor-page > .v2-event-strip { + display: none; + } } /* Semi UI workspace migration: monitor vehicle inspector and live status rail. */