From fb105a67c844d4fbc628bc9a3892edd37b2bf7fd Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 19 Jul 2026 15:11:15 +0800 Subject: [PATCH] refactor: unify semi operational time ranges --- .../apps/web/src/v2/pages/AlertsPage.test.tsx | 12 +- .../apps/web/src/v2/pages/AlertsPage.tsx | 20 ++- .../apps/web/src/v2/pages/HistoryPage.tsx | 72 ++------ .../apps/web/src/v2/pages/TrackPage.test.tsx | 3 + .../apps/web/src/v2/pages/TrackPage.tsx | 23 ++- .../v2/shared/WorkspaceDateTimeRange.test.tsx | 43 +++++ .../src/v2/shared/WorkspaceDateTimeRange.tsx | 139 +++++++++++++++ .../apps/web/src/v2/styles/workspace.css | 161 ++++++++++++++++++ 8 files changed, 407 insertions(+), 66 deletions(-) create mode 100644 vehicle-data-platform/apps/web/src/v2/shared/WorkspaceDateTimeRange.test.tsx create mode 100644 vehicle-data-platform/apps/web/src/v2/shared/WorkspaceDateTimeRange.tsx diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.test.tsx index 878d59c1..9609f781 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.test.tsx @@ -295,9 +295,13 @@ test('keeps the primary alert query compact and applies advanced filters from a await screen.findByText('当前筛选条件没有告警事件'); expect(view.container.querySelector('.v2-alert-filter-primary')).toBeInTheDocument(); expect(screen.queryByLabelText('协议')).not.toBeInTheDocument(); - fireEvent.click(screen.getByRole('button', { name: /更多筛选/ })); + fireEvent.click(screen.getByRole('button', { name: /更多筛选/ })); const dialog = await screen.findByRole('dialog', { name: '告警高级筛选' }); + expect(within(dialog).getByRole('group', { name: '告警快捷时间范围' })).toHaveClass('semi-button-group'); + expect(within(dialog).getByPlaceholderText('开始时间')).toBeInTheDocument(); + expect(within(dialog).getByPlaceholderText('结束时间')).toBeInTheDocument(); + expect(within(dialog).queryByRole('textbox', { name: '告警起始时间' })).not.toBeInTheDocument(); fireEvent.click(within(dialog).getByRole('combobox', { name: '协议' })); fireEvent.click(await screen.findByText('JT808')); fireEvent.click(within(dialog).getByRole('button', { name: '应用筛选' })); @@ -325,8 +329,10 @@ test('uses one focused Semi bottom SideSheet for the complete mobile event filte expect(document.querySelector('.v2-alert-mobile-filter-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(82dvh, 690px)' }); expect(within(dialog).getByRole('textbox', { name: /关键词/ })).toBeInTheDocument(); expect(within(dialog).getByRole('combobox', { name: '协议' })).toBeInTheDocument(); - expect(within(dialog).getByLabelText('告警起始时间')).toBeInTheDocument(); - expect(within(dialog).getByLabelText('告警结束时间')).toBeInTheDocument(); + expect(within(dialog).getByPlaceholderText('开始时间')).toBeInTheDocument(); + expect(within(dialog).getByPlaceholderText('结束时间')).toBeInTheDocument(); + expect(within(dialog).getByRole('group', { name: '告警快捷时间范围' })).toHaveClass('semi-button-group'); + expect(within(dialog).queryByRole('textbox', { name: '告警起始时间' })).not.toBeInTheDocument(); fireEvent.click(within(dialog).getByRole('button', { name: '重置条件' })); expect(screen.getByRole('dialog', { name: '告警事件筛选' })).toBeInTheDocument(); fireEvent.click(within(dialog).getByRole('combobox', { name: '协议' })); diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.tsx index 08df83dc..38c1b791 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/AlertsPage.tsx @@ -13,6 +13,7 @@ import { ProtocolTag } from '../shared/ProtocolTag'; import { SegmentedTabs } from '../shared/SegmentedTabs'; import { TablePagination } from '../shared/TablePagination'; import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; +import { WorkspaceDateTimeRange, workspaceDateTimePresetWindow } from '../shared/WorkspaceDateTimeRange'; import { WorkspaceDetailSideSheet } from '../shared/WorkspaceDetailSideSheet'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel'; @@ -195,6 +196,11 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e const selectEvent = useCallback((id: string) => setSelection({ scope: eventScope, id }), [eventScope]); const selectedEvent = detail.data ?? rows.find((row) => row.id === selectedID); const mobileFiltersOpen = mobileLayout && !filtersCollapsed; + const alertTimePresets = useMemo(() => [ + { key: 'today', label: '今天', range: workspaceDateTimePresetWindow('today') }, + { key: 'yesterday', label: '昨天', range: workspaceDateTimePresetWindow('yesterday') }, + { key: 'last24Hours', label: '近 24 小时', range: workspaceDateTimePresetWindow('last24Hours') } + ], []); const applyDraft = () => { setFilters(draft); setOffset(0); @@ -226,8 +232,18 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e const advancedFilterFields = <> - - + setDraft({ ...draft, ...range })} + presets={alertTimePresets} + showClear + /> ; const metricItem = ( label: string, diff --git a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx index 680c8772..3fc89997 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx @@ -1,6 +1,6 @@ import { useMutation, useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query'; import { IconCalendar, IconChevronRight, IconClose, IconDownload, IconMapPin, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons'; -import { Button, ButtonGroup, Card, CardGroup, Checkbox, DatePicker, Descriptions, Dropdown, Empty, Input, Progress, Select, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui'; +import { Button, Card, CardGroup, Checkbox, Descriptions, Dropdown, Empty, Input, Progress, Select, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui'; import { FormEvent, KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { api } from '../../api/client'; @@ -16,6 +16,11 @@ import { PlatformTime } from '../shared/PlatformTime'; import { ProtocolTag } from '../shared/ProtocolTag'; import { TablePagination } from '../shared/TablePagination'; import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; +import { + normalizeWorkspaceDateTimeRange, + WorkspaceDateTimeRange, + workspaceDateTimePresetWindow +} from '../shared/WorkspaceDateTimeRange'; import { WorkspaceDetailSideSheet } from '../shared/WorkspaceDetailSideSheet'; import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel'; import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail'; @@ -117,40 +122,15 @@ function currentHistoryWindow() { type HistoryRangePreset = 'today' | 'yesterday' | 'last6Hours' | 'last24Hours'; -function localHistoryMinute(value: Date) { - const pad = (part: number) => String(part).padStart(2, '0'); - return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}T${pad(value.getHours())}:${pad(value.getMinutes())}`; -} - export function historyPresetWindow(preset: HistoryRangePreset, now = new Date()) { - if (preset === 'yesterday') { - const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 0, 0); - const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 23, 59); - return { dateFrom: localHistoryMinute(start), dateTo: localHistoryMinute(end) }; - } - if (preset === 'last6Hours' || preset === 'last24Hours') { - const hours = preset === 'last6Hours' ? 6 : 24; - return { dateFrom: localHistoryMinute(new Date(now.getTime() - hours * 60 * 60_000)), dateTo: localHistoryMinute(now) }; - } - const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0); - return { dateFrom: localHistoryMinute(start), dateTo: localHistoryMinute(now) }; + return workspaceDateTimePresetWindow(preset, now); } export function historyDateTimePickerRange( dateValue?: Date | Date[] | string | string[], formattedValue?: string | string[] | Date | Date[] ) { - const values = Array.isArray(formattedValue) && formattedValue.length === 2 - ? formattedValue - : Array.isArray(dateValue) && dateValue.length === 2 ? dateValue : []; - if (values.length !== 2) return; - const normalized = values.map((value) => { - if (value instanceof Date) return localHistoryMinute(value); - if (typeof value === 'number') return localHistoryMinute(new Date(value)); - return String(value).trim().replace(' ', 'T').slice(0, 16); - }); - if (normalized.some((value) => !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value))) return; - return { dateFrom: normalized[0], dateTo: normalized[1] }; + return normalizeWorkspaceDateTimeRange(dateValue, formattedValue); } function CreateExportButton({ request, disabled }: { request: HistoryExportRequest; disabled: boolean }) { @@ -518,32 +498,16 @@ export default function HistoryPage() { ]; const historyFilterFields = <> -
- 时间范围 - { - const range = historyDateTimePickerRange(value, formattedValue); - if (range) setDraft((current) => ({ ...current, ...range })); - }} - /> - - {historyRangePresets.map((preset) => )} - -
+ setDraft((current) => ({ ...current, ...range }))} + presets={historyRangePresets} + /> ; diff --git a/vehicle-data-platform/apps/web/src/v2/pages/TrackPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/TrackPage.test.tsx index 4e9b9918..06b52bb7 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/TrackPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/TrackPage.test.tsx @@ -154,6 +154,9 @@ test('merges duplicate vehicle sources and keeps the active date shortcut visibl const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); render(); + expect(document.querySelector('.v2-track-date-time-picker.semi-datepicker')).toBeInTheDocument(); + expect(screen.getByRole('group', { name: '轨迹快捷时间范围' })).toHaveClass('semi-button-group'); + expect(document.querySelector('input[type="datetime-local"]')).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: '今天' })).toHaveAttribute('aria-pressed', 'true'); fireEvent.click(screen.getByRole('button', { name: '昨天' })); expect(screen.getByRole('button', { name: '昨天' })).toHaveAttribute('aria-pressed', 'true'); diff --git a/vehicle-data-platform/apps/web/src/v2/pages/TrackPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/TrackPage.tsx index 0ce7d5f2..c6a6daa6 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/TrackPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/TrackPage.tsx @@ -18,6 +18,7 @@ import { SegmentedTabs } from '../shared/SegmentedTabs'; import { VehicleCandidateList } from '../shared/VehicleCandidateList'; import { mergeVehicleCandidates } from '../shared/vehicleCandidates'; import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; +import { WorkspaceDateTimeRange, workspaceDateTimePresetWindow } from '../shared/WorkspaceDateTimeRange'; import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet'; @@ -249,7 +250,11 @@ const TrackRail = memo(function TrackRail({ draft, track, summaryItems, loading, onCollapse: () => void; }) { const activePreset = trackPreset(draft); - const choosePreset = (daysBack: number) => onDraft({ ...draft, ...trackWindow(daysBack, daysBack > 0) }); + const trackRangePresets = useMemo(() => [ + { key: 'today', label: '今天', range: workspaceDateTimePresetWindow('today') }, + { key: 'yesterday', label: '昨天', range: workspaceDateTimePresetWindow('yesterday') }, + { key: 'three-days', label: '近 3 天', range: workspaceDateTimePresetWindow('last3Days') } + ], []).map((preset) => ({ ...preset, active: activePreset === preset.key })); const appliedWindow = `${draft.dateFrom.replace('T', ' ').slice(5)} – ${draft.dateTo.replace('T', ' ').slice(5)}`; const appliedSource = draft.protocol ? protocolDisplayLabel(draft.protocol) : '自动来源'; const contextTitle = queryCollapsed ? track?.plate || draft.keyword || '轨迹查询' : '轨迹查询'; @@ -279,12 +284,16 @@ const TrackRail = memo(function TrackRail({ draft, track, summaryItems, loading, /> : null} {!queryCollapsed ?
-
- - - -
-
+ onDraft({ ...draft, ...range })} + presets={trackRangePresets} + />