feat: unify mileage metrics with semi ui
This commit is contained in:
@@ -60,15 +60,18 @@ test('renders only the desktop matrix with dates as columns and a period total',
|
||||
expect(screen.queryByText('车辆里程排名')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('已绑定主车辆')).toBeInTheDocument();
|
||||
expect(screen.getByText('已选择 1 辆')).toBeInTheDocument();
|
||||
expect(screen.getByText('已绑定主车辆').closest('.semi-card')).toHaveClass('v2-mileage-summary-card');
|
||||
expect(screen.getByText('已绑定主车辆').closest('[role="listitem"]')).toHaveClass('is-primary', 'is-success');
|
||||
expect(screen.getByText('车辆每日里程').closest('.semi-card')).toHaveClass('v2-mileage-results');
|
||||
expect(view.container.querySelector('.v2-mileage-query-panel.semi-card')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-mileage-discovery-shell > .v2-mileage-command-bar')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-mileage-page > .v2-page-heading')).not.toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-mileage-summary.semi-card')).toHaveAttribute('aria-label', '里程查询统计信息');
|
||||
expect(view.container.querySelector('.v2-mileage-summary-secondary.semi-card-group')).toBeInTheDocument();
|
||||
expect(view.container.querySelectorAll('.v2-mileage-summary-card.semi-card')).toHaveLength(4);
|
||||
expect(view.container.querySelector('.v2-mileage-summary-card.is-primary .v2-mileage-summary-value')).toHaveTextContent('193.3 km');
|
||||
expect(view.container.querySelector('.v2-mileage-summary')).toHaveClass('v2-workspace-metric-rail', 'is-queue');
|
||||
expect(view.container.querySelector('.v2-mileage-summary .v2-workspace-metric-list')).toHaveAttribute('aria-label', '里程查询统计信息');
|
||||
expect(view.container.querySelectorAll('.v2-mileage-summary .v2-workspace-metric-list > span')).toHaveLength(4);
|
||||
expect(view.container.querySelector('.v2-mileage-summary .v2-workspace-metric-list > span.is-primary')).toHaveTextContent('区间总里程193.3 km2 条车辆日记录');
|
||||
expect(view.container.querySelector('.v2-mileage-summary .v2-workspace-metric-list')).toHaveTextContent('已绑定主车辆1 辆已选择 1 辆');
|
||||
expect(view.container.querySelector('.v2-mileage-summary .v2-workspace-metric-list')).toHaveTextContent('统计天数2 天2026/07/13 — 07/14');
|
||||
expect(view.container.querySelector('.v2-mileage-summary .v2-workspace-metric-list')).toHaveTextContent('日均里程96.7 km按有效车辆日平均');
|
||||
expect(view.container.querySelector('.v2-mileage-table-wrap')).toBeInTheDocument();
|
||||
expect(screen.getByRole('note', { name: '里程矩阵读表说明' })).toHaveTextContent('0 km已上报、无里程增量');
|
||||
expect(screen.getByRole('note', { name: '里程矩阵读表说明' })).toHaveTextContent('—无可用里程');
|
||||
|
||||
@@ -16,7 +16,7 @@ import { VehicleCandidateList } from '../shared/VehicleCandidateList';
|
||||
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
|
||||
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
||||
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
||||
import { WorkspaceMetricRail } from '../shared/WorkspaceMetricRail';
|
||||
import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
|
||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
|
||||
import { QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||
@@ -253,27 +253,42 @@ function SummaryRail({ data, criteria, fleetTotal, loading }: { data?: MileageSt
|
||||
const summaryRange = criteria.dateFrom.slice(0, 4) === criteria.dateTo.slice(0, 4)
|
||||
? `${criteria.dateFrom.replace(/-/g, '/')} — ${criteria.dateTo.slice(5).replace('-', '/')}`
|
||||
: `${criteria.dateFrom.replace(/-/g, '/')} — ${criteria.dateTo.replace(/-/g, '/')}`;
|
||||
const items = loading ? [
|
||||
['已绑定主车辆', '—', '正在按新筛选范围查询'],
|
||||
['统计天数', `${days} 天`, summaryRange],
|
||||
['区间总里程', '—', '正在计算区间汇总'],
|
||||
['日均里程', '—', '正在计算有效车辆日']
|
||||
] : [
|
||||
['已绑定主车辆', `${vehicleCount} 辆`, criteria.vehicles.length ? `已选择 ${criteria.vehicles.length} 辆` : `档案口径 · ${data?.vehicleCount ?? 0} 辆有里程`],
|
||||
['统计天数', `${days} 天`, summaryRange],
|
||||
['区间总里程', `${formatKm(data?.periodMileageKm)} km`, `${data?.recordCount ?? 0} 条车辆日记录`],
|
||||
['日均里程', `${formatKm(data?.averageDailyMileageKm)} km`, '按有效车辆日平均']
|
||||
const withUnit = (value: string | number, unit: string) => <>{value}<i className="v2-mileage-summary-unit"> {unit}</i></>;
|
||||
const items: WorkspaceQueueMetricRailItem[] = [
|
||||
{
|
||||
label: '区间总里程',
|
||||
value: loading ? '—' : withUnit(formatKm(data?.periodMileageKm), 'km'),
|
||||
note: loading ? '正在计算区间汇总' : `${data?.recordCount ?? 0} 条车辆日记录`,
|
||||
tone: 'primary',
|
||||
emphasis: 'primary'
|
||||
},
|
||||
{
|
||||
label: '已绑定主车辆',
|
||||
value: loading ? '—' : withUnit(vehicleCount, '辆'),
|
||||
note: loading ? '正在按新筛选范围查询' : criteria.vehicles.length ? `已选择 ${criteria.vehicles.length} 辆` : `档案口径 · ${data?.vehicleCount ?? 0} 辆有里程`,
|
||||
tone: 'success',
|
||||
emphasis: 'primary'
|
||||
},
|
||||
{
|
||||
label: '统计天数',
|
||||
value: withUnit(days, '天'),
|
||||
note: summaryRange,
|
||||
tone: 'neutral',
|
||||
emphasis: 'secondary'
|
||||
},
|
||||
{
|
||||
label: '日均里程',
|
||||
value: loading ? '—' : withUnit(formatKm(data?.averageDailyMileageKm), 'km'),
|
||||
note: loading ? '正在计算有效车辆日' : '按有效车辆日平均',
|
||||
tone: 'primary',
|
||||
emphasis: 'secondary'
|
||||
}
|
||||
];
|
||||
const primary = items[2];
|
||||
const secondary = [items[0], items[1], items[3]];
|
||||
return <WorkspaceMetricRail
|
||||
variant="queue"
|
||||
className="v2-mileage-summary"
|
||||
cardClassName="v2-mileage-summary-card"
|
||||
secondaryClassName="v2-mileage-summary-secondary"
|
||||
primaryValueClassName="v2-mileage-summary-value"
|
||||
ariaLabel="里程查询统计信息"
|
||||
primary={{ label: primary[0], value: primary[1], note: primary[2] }}
|
||||
secondary={secondary.map(([label, value, note], index) => ({ label, value, note, className: `is-${['fleet', 'range', 'average'][index]}` }))}
|
||||
items={items}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
||||
@@ -430,9 +430,11 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.StatisticsPage).toContain('<Input');
|
||||
expect(corePageSources.StatisticsPage).toContain('className="v2-mileage-query-panel"');
|
||||
expect(corePageSources.StatisticsPage).toContain('<WorkspaceMetricRail');
|
||||
expect(corePageSources.StatisticsPage).toContain('variant="queue"');
|
||||
expect(corePageSources.StatisticsPage).toContain('className="v2-mileage-summary"');
|
||||
expect(corePageSources.StatisticsPage).toContain('secondaryClassName="v2-mileage-summary-secondary"');
|
||||
expect(corePageSources.StatisticsPage).toContain('cardClassName="v2-mileage-summary-card"');
|
||||
expect(corePageSources.StatisticsPage).toContain('items={items}');
|
||||
expect(corePageSources.StatisticsPage).not.toContain('secondaryClassName="v2-mileage-summary-secondary"');
|
||||
expect(corePageSources.StatisticsPage).not.toContain('cardClassName="v2-mileage-summary-card"');
|
||||
expect(corePageSources.StatisticsPage).toContain('<Card key={source.protocol} className={`v2-mileage-source-card');
|
||||
expect(corePageSources.StatisticsPage).not.toContain('<section className="v2-mileage-summary"');
|
||||
expect(corePageSources.StatisticsPage).not.toContain('<article key={source.protocol}');
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('WorkspaceMetricRail', () => {
|
||||
/>);
|
||||
|
||||
const rail = view.container.querySelector('.v2-workspace-metric-rail');
|
||||
expect(rail).toHaveClass('semi-card', 'is-queue', 'queue-metrics');
|
||||
expect(rail).toHaveClass('semi-card', 'is-queue', 'has-context', 'queue-metrics');
|
||||
const list = screen.getByRole('list', { name: '处置队列概览' });
|
||||
expect(within(list).getAllByRole('listitem')).toHaveLength(4);
|
||||
expect(within(list).getByText('待复核').closest('[role="listitem"]')).toHaveClass('is-primary');
|
||||
@@ -70,6 +70,7 @@ describe('WorkspaceMetricRail', () => {
|
||||
/>);
|
||||
|
||||
const list = screen.getByRole('list', { name: '告警队列概览' });
|
||||
expect(list.closest('.v2-workspace-metric-rail')).not.toHaveClass('has-context');
|
||||
const action = within(list).getByRole('button', { name: '筛选未处理,共 687 条' });
|
||||
expect(action).toHaveClass('semi-button', 'v2-workspace-metric-action');
|
||||
expect(action).toHaveAttribute('aria-pressed', 'true');
|
||||
|
||||
@@ -39,7 +39,8 @@ type WorkspaceQueueMetricRailProps = {
|
||||
|
||||
export function WorkspaceMetricRail(props: WorkspaceSummaryMetricRailProps | WorkspaceQueueMetricRailProps) {
|
||||
if ('items' in props) {
|
||||
return <Card className={`v2-workspace-metric-rail is-queue ${props.className}`} bodyStyle={{ padding: 0 }}>
|
||||
const hasContext = props.context != null;
|
||||
return <Card className={`v2-workspace-metric-rail is-queue${hasContext ? ' has-context' : ''} ${props.className}`} bodyStyle={{ padding: 0 }}>
|
||||
<div className="v2-workspace-metric-list" role="list" aria-label={props.ariaLabel}>
|
||||
{props.items.map((item, index) => {
|
||||
const toneClassName = `is-${item.tone ?? 'neutral'}`;
|
||||
@@ -72,7 +73,7 @@ export function WorkspaceMetricRail(props: WorkspaceSummaryMetricRailProps | Wor
|
||||
</span>;
|
||||
})}
|
||||
</div>
|
||||
{props.context ? <div className="v2-workspace-metric-context">{props.context}</div> : null}
|
||||
{hasContext ? <div className="v2-workspace-metric-context">{props.context}</div> : null}
|
||||
</Card>;
|
||||
}
|
||||
|
||||
|
||||
@@ -15646,6 +15646,10 @@
|
||||
.v2-workspace-metric-rail.is-queue > .semi-card-body {
|
||||
display: grid;
|
||||
min-height: 58px;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.v2-workspace-metric-rail.is-queue.has-context > .semi-card-body {
|
||||
grid-template-columns: minmax(0, 1fr) 218px;
|
||||
}
|
||||
|
||||
@@ -15942,7 +15946,7 @@
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.v2-workspace-metric-rail.is-queue > .semi-card-body {
|
||||
.v2-workspace-metric-rail.is-queue.has-context > .semi-card-body {
|
||||
grid-template-columns: minmax(0, 1fr) 180px;
|
||||
}
|
||||
|
||||
@@ -23218,3 +23222,72 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Mileage analysis now uses the shared Semi UI metric rail. These late
|
||||
* overrides retire the former hero-card dimensions without carrying their
|
||||
* fixed heights into the compact queue layout.
|
||||
*/
|
||||
.v2-mileage-summary.v2-workspace-metric-rail.is-queue.semi-card {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-top: 1px solid #e2e9f2;
|
||||
border-radius: 0 0 11px 11px;
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-mileage-summary.v2-workspace-metric-rail.is-queue > .semi-card-body {
|
||||
display: grid;
|
||||
height: auto;
|
||||
min-height: 58px;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.v2-mileage-summary .v2-workspace-metric-list {
|
||||
grid-template-columns: 1.3fr 1.1fr .9fr .9fr;
|
||||
}
|
||||
|
||||
.v2-mileage-summary .v2-workspace-metric-list > span {
|
||||
display: grid;
|
||||
min-height: 58px;
|
||||
}
|
||||
|
||||
.v2-mileage-summary-unit {
|
||||
margin-left: 2px;
|
||||
color: #8493a5;
|
||||
font-size: 8px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.v2-mileage-summary .v2-workspace-metric-list > span.is-primary .v2-mileage-summary-unit {
|
||||
color: #6f90bb;
|
||||
}
|
||||
|
||||
.v2-mileage-summary .v2-workspace-metric-list > span.is-success .v2-mileage-summary-unit {
|
||||
color: #5b9c7f;
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.v2-mileage-summary.v2-workspace-metric-rail.is-queue.semi-card,
|
||||
.v2-mileage-summary.v2-workspace-metric-rail.is-queue > .semi-card-body {
|
||||
height: auto;
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
.v2-mileage-summary .v2-workspace-metric-list {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.v2-mileage-summary .v2-workspace-metric-list > span {
|
||||
min-height: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user