polish Semi UI query workspaces

This commit is contained in:
lingniu
2026-07-18 01:43:37 +08:00
parent 6d63783630
commit 1affd30366
8 changed files with 103 additions and 16 deletions

View File

@@ -46,6 +46,9 @@ test('renders only the desktop matrix with dates as columns and a period total',
expect(screen.getAllByText('7/13').length).toBeGreaterThan(0);
expect(screen.getAllByText('7/14').length).toBeGreaterThan(0);
expect(screen.getAllByText('粤A12345').length).toBeGreaterThan(0);
expect(screen.getByRole('heading', { name: '里程范围', level: 5 })).toBeInTheDocument();
expect(screen.getByText('车牌、自然日与里程数据源策略')).toBeInTheDocument();
expect(screen.getByText('1 辆 · 2 天').closest('.semi-tag')).toBeInTheDocument();
expect(screen.queryByText('当日起始里程')).not.toBeInTheDocument();
expect(screen.queryByText('数据来源')).not.toBeInTheDocument();
expect(screen.queryByRole('img', { name: '每日行驶里程趋势图' })).not.toBeInTheDocument();

View File

@@ -9,10 +9,10 @@ import { createMileageExportStream, type MileageExportStream } from '../domain/m
import { formatZhNumber } from '../domain/formatters';
import { InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState';
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
import { TablePagination } from '../shared/TablePagination';
import { VehicleCandidateList } from '../shared/VehicleCandidateList';
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
import { QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext';
@@ -486,8 +486,17 @@ export default function StatisticsPage() {
return <div className="v2-mileage-page" ref={pageRef}>
<MonitorReturnBar />
<Card className="v2-mileage-query-panel" bodyStyle={{ padding: 0 }}>
<MobileFilterToggle title="查询条件" summary={criteria.vehicles.length ? `已选 ${criteria.vehicles.length} 辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)}` : `全部车辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)}`} expanded={!filtersCollapsed} collapsedLabel="修改" onToggle={() => setFiltersCollapsed((value) => !value)} />
<WorkspaceFilterPanel
className="v2-mileage-query-panel"
title="里程范围"
description="车牌、自然日与里程数据源策略"
mobileSummary={criteria.vehicles.length ? `已选 ${criteria.vehicles.length} 辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)}` : `全部车辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)}`}
expanded={!filtersCollapsed}
status={`${criteria.vehicles.length ? `${criteria.vehicles.length}` : '全部授权车辆'} · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)}`}
statusColor={criteria.vehicles.length ? 'blue' : 'grey'}
collapsedLabel="修改"
onToggle={() => setFiltersCollapsed((value) => !value)}
>
<form className={`v2-mileage-filter${filtersCollapsed ? ' is-mobile-collapsed' : ''}`} onSubmit={submit}>
<VehicleMultiSelect value={draft.vehicles} onChange={(vehicles) => setDraft((current) => ({ ...current, vehicles }))} />
<label><span></span><Input aria-label="开始日期" type="date" value={draft.dateFrom} max={draft.dateTo} onChange={(value) => setDraft((current) => ({ ...current, dateFrom: value }))} /></label>
@@ -498,13 +507,13 @@ export default function StatisticsPage() {
</form>
{validationError || criteriaError ? <p className="v2-mileage-validation" role="alert">{validationError || criteriaError}</p> : null}
<SummaryRail data={statistics.data} criteria={criteria} fleetTotal={fleetVehicles.data?.total} loading={statistics.isLoading} />
</Card>
</WorkspaceFilterPanel>
{statistics.isError || mileage.isError || fleetVehicles.isError ? <InlineError message={(statistics.error ?? mileage.error ?? fleetVehicles.error) instanceof Error ? (statistics.error ?? mileage.error ?? fleetVehicles.error as Error).message : '里程数据加载失败'} onRetry={() => { statistics.refetch(); mileage.refetch(); if (!hasVehicles) fleetVehicles.refetch(); }} /> : null}
<Card className="v2-mileage-results" bodyStyle={{ padding: 0 }}>
<WorkspacePanelHeader
title="车辆每日里程"
description={`${criteria.dateFrom}${criteria.dateTo}`}
meta={`${hasVehicles ? `${totalVehicles}` : `当前 ${displayVehicles.length} 辆 / 共 ${totalVehicles}`} · ${dates.length} 个自然日`}
meta={<span className="v2-workspace-result-meta"><Tag color="blue" type="light" size="small">{hasVehicles ? `${totalVehicles}` : `${displayVehicles.length} / ${totalVehicles}`}</Tag><span>{dates.length} </span></span>}
actionsClassName="v2-mileage-result-actions"
actions={<><Button className="is-refresh" theme="borderless" aria-label="刷新里程数据" icon={<IconRefresh />} onClick={() => { statistics.refetch(); mileage.refetch(); if (!hasVehicles) fleetVehicles.refetch(); }} disabled={refreshing}>{refreshing ? '更新中' : '刷新'}</Button><Button theme="light" aria-label={isExporting ? '取消导出' : '导出 Excel'} icon={isExporting ? <IconClose /> : <IconDownload />} onClick={isExporting ? cancelExport : exportExcel} disabled={!totalVehicles || Boolean(criteriaError)}>{isExporting ? '取消导出' : '导出 Excel'}</Button></>}
/>

View File

@@ -234,7 +234,7 @@ const TrackRail = memo(function TrackRail({ draft, track, loading, activeStopInd
className="v2-track-result-header"
title={track ? track.plate || track.vin : '轨迹明细'}
description={track ? track.vin : '停留、事件与行程证据'}
meta={track ? `${number(track.summary.distanceKm)} km` : undefined}
meta={<Tag color={track ? 'blue' : 'grey'} type="light" size="small">{track ? `${number(track.summary.distanceKm)} km` : '等待查询'}</Tag>}
/>
<SegmentedTabs className="v2-track-rail-tabs" ariaLabel="轨迹明细分类" value={tab} onChange={onTab} items={[{ key: 'stops', label: '停留点', count: track?.stops.length ?? 0 }, { key: 'events', label: '事件点', count: track?.events.length ?? 0 }, { key: 'overview', label: '概览' }]} />
<div className="v2-track-rail-scroll">

View File

@@ -146,7 +146,7 @@ function VehicleSearch() {
<WorkspacePanelHeader
title={deferredKeyword ? '匹配车辆快捷入口' : '最近上报车辆'}
description={deferredKeyword ? '按当前搜索条件展示,可直接进入车辆数字档案' : '按照最新上报时间排列,无需搜索即可继续查看'}
meta={candidates.data ? `当前显示 ${formatZhNumber(quickVehicles.length)}` : '授权范围'}
meta={<Tag color={deferredKeyword ? 'blue' : 'grey'} type="light" size="small">{candidates.data ? `当前显示 ${formatZhNumber(quickVehicles.length)}` : '授权范围'}</Tag>}
/>
{candidates.isFetching && !candidates.data
? <div className="v2-vehicle-recent-state" role="status"><span className="v2-spinner" /><span></span></div>

View File

@@ -310,7 +310,7 @@ describe('V2 production entry', () => {
expect(corePageSources.TrackPage).not.toContain('<input');
expect(corePageSources.TrackPage).not.toContain('<button');
expect(corePageSources.StatisticsPage).toContain('<Input');
expect(corePageSources.StatisticsPage).toContain('<Card className="v2-mileage-query-panel"');
expect(corePageSources.StatisticsPage).toContain('className="v2-mileage-query-panel"');
expect(corePageSources.StatisticsPage).toContain('<Card className="v2-mileage-summary"');
expect(corePageSources.StatisticsPage).toContain('<CardGroup className="v2-mileage-summary-secondary"');
expect(corePageSources.StatisticsPage).toContain('<Card className="v2-mileage-summary-card');
@@ -440,15 +440,10 @@ describe('V2 production entry', () => {
expect(v2Styles).toContain('.v2-track-current-card.semi-card { top: auto; right: 8px; bottom: 126px; left: 8px; width: auto; height: auto;');
expect(workspaceFilterPanelSource).toContain("from './MobileFilterToggle'");
expect(workspaceFilterPanelSource).toContain('<MobileFilterToggle');
for (const name of ['HistoryPage', 'AlertsPage', 'AccessPage']) {
for (const name of ['HistoryPage', 'AlertsPage', 'AccessPage', 'StatisticsPage']) {
expect(corePageSources[name]).toContain("from '../shared/WorkspaceFilterPanel'");
expect(corePageSources[name]).toContain('<WorkspaceFilterPanel');
}
for (const name of ['StatisticsPage']) {
expect(corePageSources[name]).toContain("from '../shared/MobileFilterToggle'");
expect(corePageSources[name]).toContain('<MobileFilterToggle');
expect(corePageSources[name]).not.toContain('<button type="button" className="v2-mobile-filter-toggle"');
}
expect(tablePaginationSource).toContain('<Pagination');
});

View File

@@ -21,6 +21,7 @@ describe('WorkspaceFilterPanel', () => {
</WorkspaceFilterPanel>
);
expect(screen.getByRole('heading', { name: '事件筛选', level: 5 })).toBeInTheDocument();
expect(screen.getByText('按关键词与状态收窄范围')).toBeInTheDocument();
expect(screen.getByText('2 项已启用')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '修改事件筛选:已启用 2 项' })).toHaveAttribute('aria-expanded', 'false');

View File

@@ -3,7 +3,7 @@ import { Card, Tag, Typography } from '@douyinfe/semi-ui';
import type { ComponentProps, ReactNode } from 'react';
import { MobileFilterToggle } from './MobileFilterToggle';
const { Text } = Typography;
const { Text, Title } = Typography;
type WorkspaceFilterPanelProps = {
title?: string;
@@ -48,7 +48,7 @@ export function WorkspaceFilterPanel({
<header className="v2-workspace-filter-heading">
<span className="v2-workspace-filter-icon" aria-hidden="true"><IconFilter /></span>
<span className="v2-workspace-filter-copy">
<Text strong>{title}</Text>
<Title heading={5}>{title}</Title>
<Text type="tertiary" size="small">{description}</Text>
</span>
<Tag color={statusColor} type="light" size="small">{status}</Tag>

View File

@@ -435,6 +435,30 @@
white-space: nowrap;
}
.v2-workspace-panel-meta > .semi-tag,
.v2-workspace-result-meta > .semi-tag {
min-height: 24px;
margin: 0;
border-radius: 999px;
padding-inline: 8px;
font-size: 10px;
font-variant-numeric: tabular-nums;
font-weight: 650;
}
.v2-workspace-result-meta {
display: inline-flex;
min-width: 0;
align-items: center;
gap: 7px;
}
.v2-workspace-result-meta > span {
color: #748398;
font-size: 10px;
font-variant-numeric: tabular-nums;
}
.v2-workspace-panel-actions > .semi-button {
min-height: 34px;
border-radius: 8px;
@@ -521,6 +545,10 @@
padding: 7px 10px 6px;
}
.v2-workspace-panel-header.is-inverted > .v2-workspace-panel-copy > h5.semi-typography { font-size: 13px; }
.v2-workspace-result-meta > span {
display: none;
}
}
/*
@@ -3721,9 +3749,11 @@
.v2-workspace-filter-copy > .semi-typography:first-child {
flex: 0 0 auto;
margin: 0;
color: #30445d;
font-size: 12px;
font-weight: 700;
line-height: 1.3;
}
.v2-workspace-filter-copy > .semi-typography:last-child {
@@ -3744,6 +3774,51 @@
font-weight: 650;
}
.v2-mileage-query-panel.v2-workspace-filter-panel.semi-card {
border-radius: 12px;
}
.v2-mileage-query-panel .v2-workspace-filter-heading {
min-height: 42px;
}
@media (min-width: 681px) and (max-width: 1050px) {
.v2-mileage-query-panel .v2-mileage-filter {
min-height: 0;
grid-template-columns: minmax(200px, 1.35fr) repeat(2, minmax(112px, .72fr)) auto;
grid-template-areas:
"vehicle start end submit"
"source ranges ranges ranges";
align-items: end;
}
.v2-mileage-query-panel .v2-mileage-vehicle-field {
grid-area: vehicle;
}
.v2-mileage-query-panel .v2-mileage-filter > label:nth-of-type(2) {
grid-area: start;
}
.v2-mileage-query-panel .v2-mileage-filter > label:nth-of-type(3) {
grid-area: end;
}
.v2-mileage-query-panel .v2-mileage-filter > .v2-primary-button {
grid-area: submit;
width: auto;
margin-top: 0;
}
.v2-mileage-query-panel .v2-mileage-source-strategy {
grid-area: source;
}
.v2-mileage-query-panel .v2-mileage-ranges {
grid-area: ranges;
}
}
.v2-table-pagination.semi-page {
min-width: 0;
color: #607086;
@@ -3802,6 +3877,10 @@
border-radius: 11px;
}
.v2-mileage-query-panel.v2-workspace-filter-panel.semi-card {
border-radius: 11px;
}
.v2-table-pagination-controls {
justify-content: space-between;
}