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 b59412f3..0496ea6e 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 @@ -216,10 +216,13 @@ test('clears stale rows, charts, and evidence as soon as the history scope chang fireEvent.change(screen.getByPlaceholderText('车牌 / VIN,多台用逗号分隔'), { target: { value: 'NEWVIN' } }); fireEvent.click(screen.getByRole('button', { name: '查询' })); - expect(await screen.findByRole('status')).toHaveTextContent('正在加载当前筛选范围的历史数据'); + await waitFor(() => { + expect(document.querySelector('.v2-history-loading[role="status"]')).toHaveTextContent('正在加载当前筛选范围的历史数据'); + }); expect(screen.queryByText('旧车牌')).not.toBeInTheDocument(); expect(screen.queryByText('OLDVIN-evidence')).not.toBeInTheDocument(); - expect(screen.getByText('正在聚合时间序列…')).toBeInTheDocument(); + expect(screen.getByText('正在聚合时间序列')).toBeInTheDocument(); + expect(document.querySelector('.v2-history-chart-loading.v2-state-surface')).toBeInTheDocument(); await act(async () => { resolveNewData(historyData('NEWVIN', '新车牌', 'new-as-of')); @@ -334,6 +337,7 @@ test('renders only selectable Semi history cards on mobile', async () => { expect(screen.getByRole('button', { name: '刷新导出任务' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '完成' })).toBeInTheDocument(); expect(screen.getByText('暂无导出任务')).toBeInTheDocument(); + expect(document.querySelector('.v2-history-side-empty.v2-state-surface.tone-primary')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: '关闭历史数据导出任务' })); expect(toolsButton).toHaveAttribute('aria-expanded', 'false'); expect(document.body.style.overflow).not.toBe('hidden'); 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 3a4d50b4..ee55f14a 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, IconFilter, IconHistory, 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 { IconAlertTriangle, IconCalendar, IconChevronRight, IconClose, IconDownload, IconFilter, IconHistory, IconMapPin, IconMore, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons'; +import { Button, Card, CardGroup, Checkbox, Descriptions, Dropdown, Input, Progress, Select, 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'; @@ -53,7 +53,7 @@ function HistoryQualityTag({ quality }: { quality: string }) { return {historyQualityLabel(quality)}; } -function HistoryTrend({ response, category, loading, error, hasMetrics, expanded, onToggle }: { +function HistoryTrend({ response, category, loading, error, hasMetrics, expanded, onToggle, onRetry }: { response?: HistorySeriesResponse; category: string; loading: boolean; @@ -61,6 +61,7 @@ function HistoryTrend({ response, category, loading, error, hasMetrics, expanded hasMetrics: boolean; expanded: boolean; onToggle: () => void; + onRetry: () => void; }) { const panels = useMemo(() => buildHistorySeriesPanels(response), [response]); const summary = response?.summary; @@ -89,14 +90,43 @@ function HistoryTrend({ response, category, loading, error, hasMetrics, expanded >收起趋势} />; if (!expanded) return null; - if (category !== 'location') return {header}; - if (!hasMetrics) return {header}; + if (category !== 'location') return {header} : } + title={category === 'raw' ? '原始报文不生成趋势' : '日里程按自然日展示'} + description={category === 'raw' ? '离散报文请通过明细与导出核验证据。' : '请通过明细表核对每日起止里程。'} + />; + if (!hasMetrics) return {header}} + title="尚未选择趋势指标" + description="通过字段设置选择“速度”或“总里程”后展示聚合趋势。" + />; return {header} - {error ? : loading && !response ?
: panels.length ?
{panels.map((panel) =>
{panel.label}{panel.unit || '数值'} · {panel.lines.reduce((sum, line) => sum + line.points, 0)} 个桶点
+ {error ? } + title="趋势加载失败" + description={error} + action={} aria-label="重新聚合趋势" onClick={onRetry}>重新聚合} + /> : loading && !response ? : panels.length ?
{panels.map((panel) =>
{panel.label}{panel.unit || '数值'} · {panel.lines.reduce((sum, line) => sum + line.points, 0)} 个桶点
{panel.maximum.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}{panel.minimum.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}{formatAxisTime(panel.start)}{formatAxisTime(panel.end)} {panel.lines.flatMap((line) => line.paths.map((path, index) => {line.label}))} -
{panel.lines.map((line) => {line.label})}
)}
: } +
{panel.lines.map((line) => {line.label})}
)}
: } + title="当前时间窗没有趋势点" + description="没有可聚合的数值点,空窗不会被人工补值。" + />} {summary ?