refactor: unify semi operational time ranges

This commit is contained in:
lingniu
2026-07-19 15:11:15 +08:00
parent 476069adc6
commit fb105a67c8
8 changed files with 407 additions and 66 deletions

View File

@@ -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 = <>
<label className="v2-history-vehicles"><span> 5 </span><Input prefix={<IconSearch />} value={draft.keywords} onChange={(value) => setDraft((current) => ({ ...current, keywords: value }))} placeholder="车牌 / VIN多台用逗号分隔" /></label>
<fieldset className="v2-history-range">
<legend id="v2-history-date-time-label"></legend>
<DatePicker
className="v2-history-date-time-range"
type="dateTimeRange"
density="compact"
format="yyyy-MM-dd HH:mm"
value={[new Date(draft.dateFrom), new Date(draft.dateTo)]}
placeholder={['开始时间', '结束时间']}
showClear={false}
onChangeWithDateFirst
aria-labelledby="v2-history-date-time-label"
onChange={(value, formattedValue) => {
const range = historyDateTimePickerRange(value, formattedValue);
if (range) setDraft((current) => ({ ...current, ...range }));
}}
/>
<ButtonGroup className="v2-history-range-presets" aria-label="快捷时间范围">
{historyRangePresets.map((preset) => <Button
key={preset.key}
htmlType="button"
theme={draft.dateFrom === preset.range.dateFrom && draft.dateTo === preset.range.dateTo ? 'solid' : 'light'}
onClick={() => setDraft((current) => ({ ...current, ...preset.range }))}
>{preset.label}</Button>)}
</ButtonGroup>
</fieldset>
<WorkspaceDateTimeRange
id="v2-history-date-time"
className="v2-history-range"
pickerClassName="v2-history-date-time-range"
presetsClassName="v2-history-range-presets"
presetsAriaLabel="快捷时间范围"
value={draft}
onChange={(range) => setDraft((current) => ({ ...current, ...range }))}
presets={historyRangePresets}
/>
<label><span id="history-category-label"></span><Select aria-labelledby="history-category-label" value={draft.category} onChange={(value) => setDraft((current) => ({ ...current, category: String(value) }))} optionList={(catalogQuery.data?.categories ?? [{ key: 'location', label: '位置数据' }, { key: 'raw', label: '原始报文' }, { key: 'mileage', label: '日里程' }]).map((item) => ({ value: item.key, label: item.label }))} /></label>
<label><span id="history-protocol-label"></span><Select aria-labelledby="history-protocol-label" value={draft.protocol} onChange={(value) => setDraft((current) => ({ ...current, protocol: String(value) }))} optionList={[{ value: '', label: '全部来源' }, { value: 'GB32960', label: 'GB32960' }, { value: 'JT808', label: 'JT808' }, { value: 'YUTONG_MQTT', label: 'YUTONG_MQTT' }]} /></label>
</>;