From fa2dcf14d6be4a0906e38e929d33dfa210eb7bb5 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 19 Jul 2026 17:13:55 +0800 Subject: [PATCH] refactor(ui): converge semi history workspace --- .../web/src/v2/pages/HistoryPage.test.tsx | 35 +++++++++-- .../apps/web/src/v2/pages/HistoryPage.tsx | 46 +++++++------- .../apps/web/src/v2/styles/workspace.css | 60 +++++++++++++++++++ 3 files changed, 116 insertions(+), 25 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.test.tsx index 8b9317ae..36acc507 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.test.tsx @@ -54,6 +54,12 @@ function renderPage(initialEntry = '/history?keywords=OLDVIN&dateFrom=2026-07-16 return { ...view, client }; } +async function openDesktopTool(name: RegExp | string) { + fireEvent.click(screen.getByRole('button', { name: '打开历史数据更多操作' })); + const item = await screen.findByRole('menuitem', { name }); + fireEvent.click(item); +} + test('renders protocol timestamps in Asia/Shanghai while preserving local values', () => { expect(formatHistoryDateTime('2026-07-18T02:33:00Z')).toBe('2026-07-18 10:33:00'); expect(formatHistoryDateTime('2026-07-18T02:33:00+08:00')).toBe('2026-07-18 02:33:00'); @@ -141,12 +147,12 @@ test('clears stale rows, charts, and evidence as soon as the history scope chang expect(view.container.querySelector('input[type="datetime-local"]')).not.toBeInTheDocument(); expect(view.container.querySelector('.v2-history-summary-rail')).toHaveClass('v2-workspace-metric-rail', 'is-queue'); expect(view.container.querySelector('.v2-history-summary-rail .v2-workspace-metric-list')).toHaveAttribute('aria-label', '历史数据查询统计信息'); - expect(view.container.querySelectorAll('.v2-history-summary-rail .v2-workspace-metric-list > span')).toHaveLength(4); + expect(view.container.querySelectorAll('.v2-history-summary-rail .v2-workspace-metric-list > span')).toHaveLength(3); expect(view.container.querySelector('.v2-history-summary-rail .v2-workspace-metric-list > span.is-primary')).toHaveTextContent('结果行数1当前范围'); expect(view.container.querySelector('.v2-history-summary-rail .v2-workspace-metric-list')).toHaveTextContent('车辆数1辆1 个车辆标识'); expect(view.container.querySelector('.v2-history-summary-rail .v2-workspace-metric-list')).toHaveTextContent('数据来源1路JT808'); - expect(view.container.querySelector('.v2-history-summary-rail .v2-workspace-metric-list')).toHaveTextContent('查询耗时12ms1 / 1 个业务字段'); expect(screen.getByText('1 / 1 字段').closest('.semi-tag')).toBeInTheDocument(); + expect(screen.getByText('查询 12 ms')).toHaveClass('v2-history-query-duration'); expect(view.container.querySelector('.v2-history-result-actions.v2-workspace-panel-actions')).toBeInTheDocument(); expect(view.container.querySelector('.v2-history-evidence')).not.toBeInTheDocument(); expect(view.container.querySelector('.v2-history-workspace')).not.toHaveClass('is-inspector-open'); @@ -331,7 +337,8 @@ test('releases export job state immediately after leaving the history page', asy mocks.historyExports.mockResolvedValue([{ id: 'export-running', name: '运行中导出', status: 'running', progress: 30, format: 'csv', category: 'location', keywords: ['OLDVIN'], rowCount: 0, totalRows: 100, processedRows: 30, fileSizeBytes: 0, createdAt: '2026-07-16T04:00:00Z', updatedAt: '2026-07-16T04:00:01Z', evidence: 'test export' }]); const { client, unmount } = renderPage(); - fireEvent.click(await screen.findByRole('button', { name: '查看导出任务' })); + await screen.findByText('旧车牌'); + await openDesktopTool(/导出任务/); expect(await screen.findByText('运行中导出')).toBeInTheDocument(); expect(client.getQueryCache().find({ queryKey: ['history-exports'] })).toBeDefined(); @@ -339,6 +346,25 @@ test('releases export job state immediately after leaving the history page', asy await waitFor(() => expect(client.getQueryCache().find({ queryKey: ['history-exports'] })).toBeUndefined()); }); +test('keeps low-frequency desktop table controls in one working tools menu', async () => { + mocks.historyMetricCatalog.mockResolvedValue({ categories: [{ key: 'location', label: '位置数据' }], metrics: [metric] }); + mocks.historyData.mockResolvedValue(historyData('OLDVIN', '旧车牌', 'old-as-of')); + mocks.historySeries.mockResolvedValue(historySeries('OLDVIN', '旧车牌', 'old-as-of')); + mocks.historyExports.mockResolvedValue([]); + const view = renderPage(); + + await screen.findByText('旧车牌'); + expect(view.container.querySelector('.v2-history-table-card')).toHaveClass('is-compact'); + await openDesktopTool(/切换为舒适密度/); + expect(view.container.querySelector('.v2-history-table-card')).toHaveClass('is-comfortable'); + await openDesktopTool(/切换为紧凑密度/); + expect(view.container.querySelector('.v2-history-table-card')).toHaveClass('is-compact'); + + const historyRequests = mocks.historyData.mock.calls.length; + await openDesktopTool(/刷新数据/); + await waitFor(() => expect(mocks.historyData.mock.calls.length).toBeGreaterThan(historyRequests)); +}); + test('opens a working column visibility panel and preserves non-hideable identity columns', async () => { const data = historyData('OLDVIN', '旧车牌', 'old-as-of'); data.columns = [metric, optionalMetric]; @@ -409,7 +435,8 @@ test('shows only the authenticated customer export workspace with owner and scop }]); renderPage(); - fireEvent.click(await screen.findByRole('button', { name: '查看导出任务' })); + await screen.findByText('旧车牌'); + await openDesktopTool(/导出任务/); expect(await screen.findByText(/customer-a · 1 辆/)).toBeInTheDocument(); expect(screen.getByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument(); expect(screen.getByRole('list', { name: '历史数据导出任务摘要' })).toHaveTextContent('生成中0排队与执行可下载1已完成任务需处理0失败任务'); 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 3fc89997..1c12eddd 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query'; -import { IconCalendar, IconChevronRight, IconClose, IconDownload, IconMapPin, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons'; +import { IconCalendar, IconChevronRight, IconClose, IconDownload, IconMapPin, IconMore, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons'; 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'; @@ -370,7 +370,7 @@ export default function HistoryPage() { const [trendExpanded, setTrendExpanded] = useState(false); const [filtersCollapsed, setFiltersCollapsed] = useState(true); const [exportJobsOpen, setExportJobsOpen] = useState(false); - const [mobileToolsOpen, setMobileToolsOpen] = useState(false); + const [toolsOpen, setToolsOpen] = useState(false); useEffect(() => { if (!mobileLayout) return; setLimit(MOBILE_HISTORY_PAGE_SIZE); @@ -487,13 +487,6 @@ export default function HistoryPage() { note: summarySources, tone: 'primary', emphasis: 'secondary' - }, - { - label: '查询耗时', - value: summaryLoading ? '—' : resultSummary ? <>{resultSummary.queryDurationMs}ms : '—', - note: `${visibleMetrics.length} / ${allMetrics.length} 个业务字段`, - tone: 'neutral', - emphasis: 'secondary' } ]; const historyFilterFields = <> @@ -585,31 +578,42 @@ export default function HistoryPage() { {visibleMetrics.length} / {allMetrics.length} 字段{resultSummary ? {resultSummary.queryDurationMs} ms : null}} + meta={{visibleMetrics.length} / {allMetrics.length} 字段{resultSummary ? 查询 {resultSummary.queryDurationMs} ms : null}} actionsClassName={`v2-history-result-actions${mobileLayout ? ' is-mobile-actions' : ''}`} actions={mobileLayout ? keywords.length ? <> - } onClick={() => { setMobileToolsOpen(false); setColumnSettingsOpen(true); }}>字段设置 - {exportAllowed ? } onClick={() => { setMobileToolsOpen(false); setExportJobsOpen(true); }}>导出任务 : null} - } onClick={() => { setMobileToolsOpen(false); void dataQuery.refetch(); }}>刷新数据 + } onClick={() => { setToolsOpen(false); setColumnSettingsOpen(true); }}>字段设置 + {exportAllowed ? } onClick={() => { setToolsOpen(false); setExportJobsOpen(true); }}>导出任务 : null} + } onClick={() => { setToolsOpen(false); void dataQuery.refetch(); }}>刷新数据 } > - + : null : <> - - + + {exportAllowed ? : null} - {exportAllowed ? : null} -