refine Semi UI mobile history tools
This commit is contained in:
@@ -215,6 +215,9 @@ test('renders only selectable Semi history cards on mobile', async () => {
|
|||||||
expect(screen.queryByLabelText('表格密度')).not.toBeInTheDocument();
|
expect(screen.queryByLabelText('表格密度')).not.toBeInTheDocument();
|
||||||
expect(view.container.querySelector('.v2-history-side')).not.toBeInTheDocument();
|
expect(view.container.querySelector('.v2-history-side')).not.toBeInTheDocument();
|
||||||
expect(mocks.historyExports).not.toHaveBeenCalled();
|
expect(mocks.historyExports).not.toHaveBeenCalled();
|
||||||
|
expect(screen.getByRole('button', { name: '展开趋势' })).toHaveTextContent('趋势');
|
||||||
|
expect(screen.getByRole('button', { name: '打开历史数据工具' })).toHaveTextContent('工具');
|
||||||
|
expect(screen.queryByText('坐标、速度、里程及设备/服务时间通过基础校验')).not.toBeInTheDocument();
|
||||||
fireEvent.click(action);
|
fireEvent.click(action);
|
||||||
expect(action).toHaveAttribute('aria-pressed', 'true');
|
expect(action).toHaveAttribute('aria-pressed', 'true');
|
||||||
expect(action).toHaveAttribute('aria-expanded', 'true');
|
expect(action).toHaveAttribute('aria-expanded', 'true');
|
||||||
@@ -228,25 +231,27 @@ test('renders only selectable Semi history cards on mobile', async () => {
|
|||||||
expect(document.body.style.overflow).not.toBe('hidden');
|
expect(document.body.style.overflow).not.toBe('hidden');
|
||||||
expect(screen.queryByText('MOBILEVIN-evidence')).not.toBeInTheDocument();
|
expect(screen.queryByText('MOBILEVIN-evidence')).not.toBeInTheDocument();
|
||||||
|
|
||||||
const exportJobsButton = screen.getByRole('button', { name: '查看导出任务' });
|
const toolsButton = screen.getByRole('button', { name: '打开历史数据工具' });
|
||||||
fireEvent.click(exportJobsButton);
|
fireEvent.click(toolsButton);
|
||||||
|
await waitFor(() => expect(toolsButton).toHaveAttribute('aria-expanded', 'true'));
|
||||||
|
fireEvent.click(screen.getByRole('menuitem', { name: /导出任务/ }));
|
||||||
expect(await screen.findByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument();
|
expect(await screen.findByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument();
|
||||||
expect(document.querySelector('.v2-history-export-sidesheet')).toHaveClass('semi-sidesheet-bottom');
|
expect(document.querySelector('.v2-history-export-sidesheet')).toHaveClass('semi-sidesheet-bottom');
|
||||||
expect(document.querySelector('.v2-history-export-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(76dvh, 680px)' });
|
expect(document.querySelector('.v2-history-export-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(76dvh, 680px)' });
|
||||||
await waitFor(() => expect(mocks.historyExports).toHaveBeenCalledTimes(1));
|
await waitFor(() => expect(mocks.historyExports).toHaveBeenCalledTimes(1));
|
||||||
expect(screen.getByText('暂无导出任务')).toBeInTheDocument();
|
expect(screen.getByText('暂无导出任务')).toBeInTheDocument();
|
||||||
fireEvent.click(screen.getByRole('button', { name: '关闭历史数据导出任务' }));
|
fireEvent.click(screen.getByRole('button', { name: '关闭历史数据导出任务' }));
|
||||||
expect(exportJobsButton).toHaveAttribute('aria-expanded', 'false');
|
expect(toolsButton).toHaveAttribute('aria-expanded', 'false');
|
||||||
expect(document.body.style.overflow).not.toBe('hidden');
|
expect(document.body.style.overflow).not.toBe('hidden');
|
||||||
expect(screen.queryByText('暂无导出任务')).not.toBeInTheDocument();
|
expect(screen.queryByText('暂无导出任务')).not.toBeInTheDocument();
|
||||||
|
|
||||||
const columnSettingsButton = screen.getByRole('button', { name: '列设置' });
|
fireEvent.click(toolsButton);
|
||||||
fireEvent.click(columnSettingsButton);
|
fireEvent.click(screen.getByRole('menuitem', { name: /字段设置/ }));
|
||||||
expect(await screen.findByRole('dialog', { name: '列显示设置' })).toBeInTheDocument();
|
expect(await screen.findByRole('dialog', { name: '列显示设置' })).toBeInTheDocument();
|
||||||
expect(document.querySelector('.v2-history-column-sidesheet')).toHaveClass('semi-sidesheet-bottom');
|
expect(document.querySelector('.v2-history-column-sidesheet')).toHaveClass('semi-sidesheet-bottom');
|
||||||
expect(document.querySelector('.v2-history-column-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(68dvh, 580px)' });
|
expect(document.querySelector('.v2-history-column-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(68dvh, 580px)' });
|
||||||
fireEvent.click(screen.getByRole('button', { name: '关闭列显示设置' }));
|
fireEvent.click(screen.getByRole('button', { name: '关闭列显示设置' }));
|
||||||
expect(columnSettingsButton).toHaveAttribute('aria-expanded', 'false');
|
expect(toolsButton).toHaveAttribute('aria-expanded', 'false');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('releases export job state immediately after leaving the history page', async () => {
|
test('releases export job state immediately after leaving the history page', async () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { IconChevronRight, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
import { IconChevronRight, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
||||||
import { Button, Card, CardGroup, Checkbox, Descriptions, Empty, Input, Progress, Select, SideSheet, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui';
|
import { Button, Card, CardGroup, Checkbox, Descriptions, Dropdown, Empty, Input, Progress, Select, SideSheet, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui';
|
||||||
import { FormEvent, KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
import { FormEvent, KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { api } from '../../api/client';
|
import { api } from '../../api/client';
|
||||||
@@ -35,8 +35,12 @@ function historyQualityLabel(quality: string) {
|
|||||||
return quality || '未知';
|
return quality || '未知';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isHealthyHistoryQuality(quality: string) {
|
||||||
|
return quality === 'normal' || quality === 'good';
|
||||||
|
}
|
||||||
|
|
||||||
function HistoryQualityTag({ quality }: { quality: string }) {
|
function HistoryQualityTag({ quality }: { quality: string }) {
|
||||||
const color = quality === 'normal' || quality === 'good' ? 'green' : quality === 'error' || quality === 'critical' ? 'red' : 'orange';
|
const color = isHealthyHistoryQuality(quality) ? 'green' : quality === 'error' || quality === 'critical' ? 'red' : 'orange';
|
||||||
return <Tag className="v2-history-quality-tag" color={color} type="light" size="small">{historyQualityLabel(quality)}</Tag>;
|
return <Tag className="v2-history-quality-tag" color={color} type="light" size="small">{historyQualityLabel(quality)}</Tag>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +263,7 @@ export default function HistoryPage() {
|
|||||||
const [trendExpanded, setTrendExpanded] = useState(false);
|
const [trendExpanded, setTrendExpanded] = useState(false);
|
||||||
const [filtersCollapsed, setFiltersCollapsed] = useState(true);
|
const [filtersCollapsed, setFiltersCollapsed] = useState(true);
|
||||||
const [exportJobsOpen, setExportJobsOpen] = useState(false);
|
const [exportJobsOpen, setExportJobsOpen] = useState(false);
|
||||||
|
const [mobileToolsOpen, setMobileToolsOpen] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!mobileLayout) return;
|
if (!mobileLayout) return;
|
||||||
setLimit(MOBILE_HISTORY_PAGE_SIZE);
|
setLimit(MOBILE_HISTORY_PAGE_SIZE);
|
||||||
@@ -424,11 +429,27 @@ export default function HistoryPage() {
|
|||||||
description={keywords.length ? `${(result?.total ?? 0).toLocaleString('zh-CN')} 条记录 · ${resultSummary?.vehicleCount ?? 0} 辆车辆 · ${summarySources}` : '选择车辆和时间范围后查询'}
|
description={keywords.length ? `${(result?.total ?? 0).toLocaleString('zh-CN')} 条记录 · ${resultSummary?.vehicleCount ?? 0} 辆车辆 · ${summarySources}` : '选择车辆和时间范围后查询'}
|
||||||
meta={<span className="v2-history-result-meta"><Tag color="blue" type="light" size="small">{visibleMetrics.length} / {allMetrics.length} 字段</Tag>{resultSummary ? <span>{resultSummary.queryDurationMs} ms</span> : null}</span>}
|
meta={<span className="v2-history-result-meta"><Tag color="blue" type="light" size="small">{visibleMetrics.length} / {allMetrics.length} 字段</Tag>{resultSummary ? <span>{resultSummary.queryDurationMs} ms</span> : null}</span>}
|
||||||
actionsClassName={`v2-history-result-actions${mobileLayout ? ' is-mobile-actions' : ''}`}
|
actionsClassName={`v2-history-result-actions${mobileLayout ? ' is-mobile-actions' : ''}`}
|
||||||
actions={<>
|
actions={mobileLayout ? <>
|
||||||
|
<Button className={`v2-history-table-trend${trendExpanded ? ' is-expanded' : ''}`} theme="light" aria-label={trendExpanded ? '收起趋势' : '展开趋势'} aria-expanded={trendExpanded} icon={<IconChevronRight />} onClick={() => setTrendExpanded((value) => !value)}>{trendExpanded ? '收起' : '趋势'}</Button>
|
||||||
|
<Dropdown
|
||||||
|
trigger="click"
|
||||||
|
position="bottomRight"
|
||||||
|
visible={mobileToolsOpen}
|
||||||
|
onVisibleChange={setMobileToolsOpen}
|
||||||
|
contentClassName="v2-history-mobile-tools-menu"
|
||||||
|
render={<Dropdown.Menu>
|
||||||
|
<Dropdown.Item icon={<IconSetting />} onClick={() => { setMobileToolsOpen(false); setColumnSettingsOpen(true); }}>字段设置</Dropdown.Item>
|
||||||
|
{exportAllowed ? <Dropdown.Item icon={<IconDownload />} onClick={() => { setMobileToolsOpen(false); setExportJobsOpen(true); }}>导出任务</Dropdown.Item> : null}
|
||||||
|
<Dropdown.Item icon={<IconRefresh />} onClick={() => { setMobileToolsOpen(false); void dataQuery.refetch(); }}>刷新数据</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>}
|
||||||
|
>
|
||||||
|
<Button theme="light" aria-label="打开历史数据工具" aria-haspopup="menu" aria-expanded={mobileToolsOpen} icon={<IconSetting />}>工具</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</> : <>
|
||||||
<Button className={`v2-history-table-trend${trendExpanded ? ' is-expanded' : ''}`} theme="borderless" aria-label={trendExpanded ? '收起趋势' : '展开趋势'} aria-expanded={trendExpanded} icon={<IconChevronRight />} onClick={() => setTrendExpanded((value) => !value)}>{trendExpanded ? '收起趋势' : '聚合趋势'}</Button>
|
<Button className={`v2-history-table-trend${trendExpanded ? ' is-expanded' : ''}`} theme="borderless" aria-label={trendExpanded ? '收起趋势' : '展开趋势'} aria-expanded={trendExpanded} icon={<IconChevronRight />} onClick={() => setTrendExpanded((value) => !value)}>{trendExpanded ? '收起趋势' : '聚合趋势'}</Button>
|
||||||
<Button theme="borderless" aria-label="列设置" aria-haspopup="dialog" aria-expanded={columnSettingsOpen} aria-controls="v2-history-column-settings" icon={<IconSetting />} onClick={() => setColumnSettingsOpen((value) => !value)}>列设置</Button>
|
<Button theme="borderless" aria-label="列设置" aria-haspopup="dialog" aria-expanded={columnSettingsOpen} aria-controls="v2-history-column-settings" icon={<IconSetting />} onClick={() => setColumnSettingsOpen((value) => !value)}>列设置</Button>
|
||||||
{exportAllowed ? <Button theme="borderless" aria-label="查看导出任务" aria-haspopup="dialog" aria-expanded={exportJobsOpen} aria-controls="v2-history-export-jobs" icon={<IconDownload />} onClick={() => setExportJobsOpen(true)}>导出任务</Button> : null}
|
{exportAllowed ? <Button theme="borderless" aria-label="查看导出任务" aria-haspopup="dialog" aria-expanded={exportJobsOpen} aria-controls="v2-history-export-jobs" icon={<IconDownload />} onClick={() => setExportJobsOpen(true)}>导出任务</Button> : null}
|
||||||
{!mobileLayout ? <Select aria-label="表格密度" value={density} onChange={(value) => setDensity(String(value) as typeof density)} optionList={[{ value: 'compact', label: '紧凑' }, { value: 'comfortable', label: '舒适' }]} /> : null}
|
<Select aria-label="表格密度" value={density} onChange={(value) => setDensity(String(value) as typeof density)} optionList={[{ value: 'compact', label: '紧凑' }, { value: 'comfortable', label: '舒适' }]} />
|
||||||
<Button theme="borderless" onClick={() => dataQuery.refetch()} aria-label="刷新历史数据" icon={<IconRefresh />} />
|
<Button theme="borderless" onClick={() => dataQuery.refetch()} aria-label="刷新历史数据" icon={<IconRefresh />} />
|
||||||
</>}
|
</>}
|
||||||
/>
|
/>
|
||||||
@@ -440,7 +461,7 @@ export default function HistoryPage() {
|
|||||||
onKeyDown={scrollHistoryTable}
|
onKeyDown={scrollHistoryTable}
|
||||||
>
|
>
|
||||||
{mobileLayout
|
{mobileLayout
|
||||||
? <div className="v2-history-mobile-list">{resultRows.map((row) => <Card key={row.id} className={`v2-history-mobile-card${selectedRow?.id === row.id ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" className="v2-history-mobile-action" aria-pressed={selectedRow?.id === row.id} aria-expanded={selectedRow?.id === row.id} aria-label={`查看 ${row.plate || row.vin} ${row.deviceTime} 数据详情`} onClick={() => selectRow(row)}><span className="v2-history-mobile-card-content"><header><span><strong>{row.plate || '未绑定车牌'}</strong><small>{row.vin}</small></span><span className={`v2-quality is-${row.quality}`}><i />{historyQualityLabel(row.quality)}</span></header><p><PlatformTime className="v2-history-time" value={row.deviceTime} /><b>{row.protocol}</b></p><small className="v2-history-mobile-quality">{row.qualityReason || '平台未返回质量说明'}</small><dl>{visibleMetrics.slice(0, 4).map((metric) => <div key={metric.key}><dt>{metric.label}</dt><dd>{formatHistoryValue(row.values[metric.key], metric)}</dd></div>)}</dl><footer>查看完整详情<IconChevronRight /></footer></span></Button></Card>)}</div>
|
? <div className="v2-history-mobile-list">{resultRows.map((row) => <Card key={row.id} className={`v2-history-mobile-card${selectedRow?.id === row.id ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" className="v2-history-mobile-action" aria-pressed={selectedRow?.id === row.id} aria-expanded={selectedRow?.id === row.id} aria-label={`查看 ${row.plate || row.vin} ${row.deviceTime} 数据详情`} onClick={() => selectRow(row)}><span className="v2-history-mobile-card-content"><header><span><strong>{row.plate || '未绑定车牌'}</strong><small>{row.vin}</small></span><span className={`v2-quality is-${row.quality}`}><i />{historyQualityLabel(row.quality)}</span></header><p><PlatformTime className="v2-history-time" value={row.deviceTime} /><b>{row.protocol}</b></p>{isHealthyHistoryQuality(row.quality) ? null : <small className="v2-history-mobile-quality">{row.qualityReason || '平台未返回质量说明'}</small>}<dl>{visibleMetrics.slice(0, 4).map((metric) => <div key={metric.key}><dt>{metric.label}</dt><dd>{formatHistoryValue(row.values[metric.key], metric)}</dd></div>)}</dl><footer>查看完整详情<IconChevronRight /></footer></span></Button></Card>)}</div>
|
||||||
: <HistoryDataTable rows={resultRows} metrics={visibleMetrics} selectedRowID={selectedRow?.id} onSelect={selectRow} />}
|
: <HistoryDataTable rows={resultRows} metrics={visibleMetrics} selectedRowID={selectedRow?.id} onSelect={selectRow} />}
|
||||||
{dataQuery.isPending && keywords.length
|
{dataQuery.isPending && keywords.length
|
||||||
? <PanelLoading className="v2-history-loading" title="正在加载当前筛选范围的历史数据" description="按车辆、时间和协议整理可追溯证据。" />
|
? <PanelLoading className="v2-history-loading" title="正在加载当前筛选范围的历史数据" description="按车辆、时间和协议整理可追溯证据。" />
|
||||||
|
|||||||
@@ -6803,14 +6803,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-history-table-card .v2-workspace-panel-actions {
|
.v2-history-table-card .v2-workspace-panel-actions {
|
||||||
gap: 2px;
|
max-width: none;
|
||||||
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-history-table-card .v2-workspace-panel-actions > .semi-button {
|
.v2-history-table-card .v2-workspace-panel-actions > .semi-button {
|
||||||
width: 32px;
|
width: auto;
|
||||||
min-width: 32px;
|
min-width: 62px;
|
||||||
min-height: 32px;
|
min-height: 36px;
|
||||||
padding: 0;
|
border: 1px solid #dbe5f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f7f9fc;
|
||||||
|
padding: 0 10px;
|
||||||
|
color: #536980;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-history-table-card .v2-history-result-actions.is-mobile-actions > .semi-button .semi-button-content {
|
.v2-history-table-card .v2-history-result-actions.is-mobile-actions > .semi-button .semi-button-content {
|
||||||
@@ -6820,12 +6825,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-history-table-card .v2-history-result-actions.is-mobile-actions > .semi-button .semi-button-content-right {
|
.v2-history-table-card .v2-history-result-actions.is-mobile-actions > .semi-button .semi-button-content-right {
|
||||||
display: none;
|
display: inline-flex;
|
||||||
|
margin-left: 5px;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-history-table-card .v2-history-result-actions.is-mobile-actions > .semi-button .semi-icon {
|
.v2-history-table-card .v2-history-result-actions.is-mobile-actions > .semi-button .semi-icon {
|
||||||
color: #5f7188;
|
color: #5f7188;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-history-table-card .v2-history-result-actions.is-mobile-actions > .v2-history-table-trend.is-expanded {
|
||||||
|
border-color: #b8d2f6;
|
||||||
|
background: #edf5ff;
|
||||||
|
color: var(--v2-blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-history-table-card .v2-history-result-actions.is-mobile-actions > .v2-history-table-trend.is-expanded .semi-icon {
|
.v2-history-table-card .v2-history-result-actions.is-mobile-actions > .v2-history-table-trend.is-expanded .semi-icon {
|
||||||
@@ -6924,7 +6939,7 @@
|
|||||||
.v2-history-mobile-list > .v2-history-mobile-card.semi-card {
|
.v2-history-mobile-list > .v2-history-mobile-card.semi-card {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
contain-intrinsic-size: auto 124px;
|
contain-intrinsic-size: auto 108px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-history-mobile-card-content {
|
.v2-history-mobile-card-content {
|
||||||
@@ -6993,6 +7008,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-history-mobile-tools-menu {
|
||||||
|
width: 168px;
|
||||||
|
min-width: 168px;
|
||||||
|
border: 1px solid #dbe4ef;
|
||||||
|
border-radius: 11px;
|
||||||
|
padding: 6px;
|
||||||
|
box-shadow: 0 18px 44px rgba(27, 45, 72, .16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-history-mobile-tools-menu .semi-dropdown-item {
|
||||||
|
min-height: 42px;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding-inline: 11px;
|
||||||
|
color: #445a72;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-history-mobile-tools-menu .semi-dropdown-item:hover,
|
||||||
|
.v2-history-mobile-tools-menu .semi-dropdown-item:focus-visible {
|
||||||
|
background: #edf5ff;
|
||||||
|
color: #1d64c5;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 360px) {
|
@media (max-width: 360px) {
|
||||||
.v2-history-mobile-card-content dl {
|
.v2-history-mobile-card-content dl {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
|||||||
Reference in New Issue
Block a user