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(view.container.querySelector('.v2-history-side')).not.toBeInTheDocument();
|
||||
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);
|
||||
expect(action).toHaveAttribute('aria-pressed', '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(screen.queryByText('MOBILEVIN-evidence')).not.toBeInTheDocument();
|
||||
|
||||
const exportJobsButton = screen.getByRole('button', { name: '查看导出任务' });
|
||||
fireEvent.click(exportJobsButton);
|
||||
const toolsButton = screen.getByRole('button', { name: '打开历史数据工具' });
|
||||
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(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)' });
|
||||
await waitFor(() => expect(mocks.historyExports).toHaveBeenCalledTimes(1));
|
||||
expect(screen.getByText('暂无导出任务')).toBeInTheDocument();
|
||||
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(screen.queryByText('暂无导出任务')).not.toBeInTheDocument();
|
||||
|
||||
const columnSettingsButton = screen.getByRole('button', { name: '列设置' });
|
||||
fireEvent.click(columnSettingsButton);
|
||||
fireEvent.click(toolsButton);
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: /字段设置/ }));
|
||||
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 .semi-sidesheet-inner')).toHaveStyle({ height: 'min(68dvh, 580px)' });
|
||||
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 () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
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 { useSearchParams } from 'react-router-dom';
|
||||
import { api } from '../../api/client';
|
||||
@@ -35,8 +35,12 @@ function historyQualityLabel(quality: string) {
|
||||
return quality || '未知';
|
||||
}
|
||||
|
||||
function isHealthyHistoryQuality(quality: string) {
|
||||
return quality === 'normal' || quality === 'good';
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
@@ -259,6 +263,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);
|
||||
useEffect(() => {
|
||||
if (!mobileLayout) return;
|
||||
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}` : '选择车辆和时间范围后查询'}
|
||||
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' : ''}`}
|
||||
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 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}
|
||||
{!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 />} />
|
||||
</>}
|
||||
/>
|
||||
@@ -440,7 +461,7 @@ export default function HistoryPage() {
|
||||
onKeyDown={scrollHistoryTable}
|
||||
>
|
||||
{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} />}
|
||||
{dataQuery.isPending && keywords.length
|
||||
? <PanelLoading className="v2-history-loading" title="正在加载当前筛选范围的历史数据" description="按车辆、时间和协议整理可追溯证据。" />
|
||||
|
||||
@@ -6803,14 +6803,19 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
min-width: 62px;
|
||||
min-height: 36px;
|
||||
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 {
|
||||
@@ -6820,12 +6825,22 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
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 {
|
||||
@@ -6924,7 +6939,7 @@
|
||||
.v2-history-mobile-list > .v2-history-mobile-card.semi-card {
|
||||
border-radius: 8px;
|
||||
box-shadow: none;
|
||||
contain-intrinsic-size: auto 124px;
|
||||
contain-intrinsic-size: auto 108px;
|
||||
}
|
||||
|
||||
.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) {
|
||||
.v2-history-mobile-card-content dl {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
Reference in New Issue
Block a user