unify Semi UI query metric rails
This commit is contained in:
@@ -110,11 +110,14 @@ test('clears stale rows, charts, and evidence as soon as the history scope chang
|
||||
const view = renderPage();
|
||||
expect((await screen.findAllByText('旧车牌')).length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText('OLDVIN-evidence')).not.toBeInTheDocument();
|
||||
for (const className of ['v2-history-filter-card', 'v2-history-context-card', 'v2-history-table-card']) {
|
||||
for (const className of ['v2-history-filter-card', 'v2-history-summary-rail', 'v2-history-table-card']) {
|
||||
expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
|
||||
}
|
||||
expect(view.container.querySelector('.v2-history-context .v2-history-metrics')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-history-context .v2-history-summary')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-history-summary-rail')).toHaveAttribute('aria-label', '历史数据查询统计信息');
|
||||
expect(view.container.querySelector('.v2-history-summary-secondary.semi-card-group')).toBeInTheDocument();
|
||||
expect(view.container.querySelectorAll('.v2-history-summary-card.semi-card')).toHaveLength(4);
|
||||
expect(view.container.querySelector('.v2-history-summary-card.is-primary .v2-history-summary-value')).toHaveTextContent('1');
|
||||
expect(screen.getByText('1 / 1 字段').closest('.semi-tag')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-history-result-actions.v2-workspace-panel-actions')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-history-evidence')).not.toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-history-workspace')).not.toHaveClass('is-inspector-open');
|
||||
@@ -180,8 +183,8 @@ test('renders only selectable Semi history cards on mobile', async () => {
|
||||
expect(action).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(view.container.querySelector('.v2-history-table-scroll.is-mobile-scroll')).toHaveAttribute('aria-label', '历史明细列表,可上下滚动');
|
||||
expect(view.container.querySelector('.v2-history-table-scroll.is-mobile-scroll')).toHaveAttribute('tabindex', '0');
|
||||
expect(view.container.querySelector('.v2-history-metric-scroll')).toHaveAttribute('aria-label', '当前显示字段,可左右滑动');
|
||||
expect(view.container.querySelector('.v2-history-metric-scroll')).toHaveAttribute('tabindex', '0');
|
||||
expect(view.container.querySelector('.v2-history-metric-scroll')).not.toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-history-summary-rail')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-history-result-actions')).toHaveClass('is-mobile-actions');
|
||||
expect(screen.queryByText('MOBILEVIN-evidence')).not.toBeInTheDocument();
|
||||
expect((mocks.historyData.mock.calls[0]?.[0] as URLSearchParams).get('limit')).toBe('20');
|
||||
@@ -238,13 +241,8 @@ test('opens a working column visibility panel and preserves non-hideable identit
|
||||
|
||||
expect(await screen.findByRole('columnheader', { name: '速度 (km/h)' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('columnheader', { name: 'SOC (%)' })).not.toBeInTheDocument();
|
||||
const metricScroll = view.container.querySelector('.v2-history-metric-scroll');
|
||||
const manageMetrics = screen.getByRole('button', { name: '管理字段' });
|
||||
expect(metricScroll).toBeInTheDocument();
|
||||
expect(metricScroll).toHaveAttribute('aria-label', '当前显示字段');
|
||||
expect(metricScroll?.contains(screen.getByLabelText(/已选字段 速度/))).toBe(true);
|
||||
expect(metricScroll?.contains(manageMetrics)).toBe(false);
|
||||
expect(manageMetrics.parentElement).toHaveClass('v2-history-metrics');
|
||||
expect(view.container.querySelector('.v2-history-metric-scroll')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('1 / 2 字段').closest('.semi-tag')).toBeInTheDocument();
|
||||
const columnSettingsButton = screen.getByRole('button', { name: '列设置' });
|
||||
fireEvent.click(columnSettingsButton);
|
||||
|
||||
@@ -326,8 +324,7 @@ test('uses selected chartable fields for trends and omits empty RAW evidence UI'
|
||||
expect((mocks.historySeries.mock.calls[mocks.historySeries.mock.calls.length - 1]?.[0] as URLSearchParams).get('metrics')).toBe('speedKmh,totalMileageKm');
|
||||
expect(screen.queryByText('RAW 证据')).not.toBeInTheDocument();
|
||||
|
||||
const mileageToggle = screen.getAllByLabelText(/已选字段 .*点击取消显示/).find((button) => button.textContent?.includes('总里程'));
|
||||
expect(mileageToggle).toBeDefined();
|
||||
fireEvent.click(mileageToggle!);
|
||||
fireEvent.click(screen.getByRole('button', { name: '列设置' }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: '总里程 (km)' }));
|
||||
await waitFor(() => expect((mocks.historySeries.mock.calls[mocks.historySeries.mock.calls.length - 1]?.[0] as URLSearchParams).get('metrics')).toBe('speedKmh'));
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import { InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState';
|
||||
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
|
||||
import { TablePagination } from '../shared/TablePagination';
|
||||
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
||||
import { WorkspaceMetricRail } from '../shared/WorkspaceMetricRail';
|
||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||
import { detailTriggerRow } from '../shared/detailTriggerRow';
|
||||
import { LIVE_QUERY_POLICY, QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||
@@ -327,6 +328,32 @@ export default function HistoryPage() {
|
||||
const totalPages = Math.max(1, Math.ceil((result?.total ?? 0) / limit));
|
||||
const page = Math.floor(offset / limit) + 1;
|
||||
const summarySources = resultSummary?.sources?.join('、') || '—';
|
||||
const summaryLoading = keywords.length > 0 && dataQuery.isFetching && !result;
|
||||
const summaryPrimary = {
|
||||
label: '结果行数',
|
||||
value: summaryLoading ? '—' : (result?.total ?? 0).toLocaleString('zh-CN'),
|
||||
note: summaryLoading ? '正在读取当前筛选范围' : `${resultSummary?.vehicleCount ?? 0} 辆车辆`
|
||||
};
|
||||
const summarySecondary = [
|
||||
{
|
||||
label: '车辆数',
|
||||
value: summaryLoading ? '—' : String(resultSummary?.vehicleCount ?? 0),
|
||||
note: `当前筛选 ${keywords.length} 个车辆标识`,
|
||||
className: 'is-vehicles'
|
||||
},
|
||||
{
|
||||
label: '数据来源',
|
||||
value: summaryLoading ? '—' : `${resultSummary?.sources?.length ?? 0} 个来源`,
|
||||
note: summarySources,
|
||||
className: 'is-sources'
|
||||
},
|
||||
{
|
||||
label: '查询耗时',
|
||||
value: summaryLoading ? '—' : resultSummary ? `${resultSummary.queryDurationMs} ms` : '—',
|
||||
note: `${visibleMetrics.length} / ${allMetrics.length} 个业务字段`,
|
||||
className: 'is-duration'
|
||||
}
|
||||
];
|
||||
|
||||
return <div className="v2-history-page">
|
||||
<MonitorReturnBar />
|
||||
@@ -348,31 +375,15 @@ export default function HistoryPage() {
|
||||
<div className="v2-history-toolbar-actions"><Button className="v2-primary-button" theme="solid" htmlType="submit" disabled={!parseHistoryKeywords(draft.keywords).length}>查询</Button><Button className="v2-secondary-button" theme="light" onClick={reset}>重置</Button>{exportAllowed ? <CreateExportButton disabled={!resultRows.length} request={{ keywords, category: criteria.category, protocol: criteria.protocol || undefined, dateFrom: criteria.dateFrom, dateTo: criteria.dateTo, metrics: visibleKeys, format: 'csv' }} /> : <Tag className="v2-history-permission-tag" color="grey" type="light" size="large">只读</Tag>}</div>
|
||||
</form>
|
||||
</WorkspaceFilterPanel>
|
||||
<Card className="v2-history-context-card" bodyStyle={{ padding: 0 }}>
|
||||
<div className="v2-history-context">
|
||||
<div className="v2-history-metrics">
|
||||
<div className="v2-history-metrics-heading">
|
||||
<strong>显示字段</strong>
|
||||
<small>{visibleMetrics.length} / {allMetrics.length}</small>
|
||||
</div>
|
||||
<div
|
||||
className="v2-history-metric-scroll"
|
||||
tabIndex={mobileLayout ? 0 : undefined}
|
||||
aria-label={mobileLayout ? '当前显示字段,可左右滑动' : '当前显示字段'}
|
||||
>
|
||||
{visibleMetrics.map((metric) => <Tag className="v2-history-metric-tag" color="blue" type="light" size="large" closable tabIndex={0} aria-label={`已选字段 ${metric.label}${metric.unit ? ` ${metric.unit}` : ''},点击取消显示`} onClick={() => toggleMetric(metric.key)} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); toggleMetric(metric.key); } }} onClose={(_, event) => { event.stopPropagation(); toggleMetric(metric.key); }} key={metric.key}>{metric.label}{metric.unit ? ` (${metric.unit})` : ''}</Tag>)}
|
||||
{allMetrics.length > visibleMetrics.length ? <span>另有 {allMetrics.length - visibleMetrics.length} 项可选</span> : !allMetrics.length ? <span>查询后加载可用指标</span> : null}
|
||||
</div>
|
||||
<Button className="v2-history-metrics-manage" theme="borderless" icon={<IconSetting />} aria-label="管理字段" onClick={() => setColumnSettingsOpen(true)} disabled={!allMetrics.length}>管理字段</Button>
|
||||
</div>
|
||||
<div className="v2-history-summary" aria-label="当前查询摘要">
|
||||
<div className="is-primary"><small>结果行数</small><strong>{result?.total?.toLocaleString('zh-CN') ?? '—'}</strong></div>
|
||||
<div><small>车辆数</small><strong>{resultSummary?.vehicleCount ?? '—'}</strong></div>
|
||||
<div><small>数据源</small><strong className="is-source" title={summarySources}>{summarySources}</strong></div>
|
||||
<div><small>查询耗时</small><strong>{resultSummary ? `${resultSummary.queryDurationMs} ms` : '—'}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{keywords.length ? <WorkspaceMetricRail
|
||||
className="v2-history-summary-rail"
|
||||
cardClassName="v2-history-summary-card"
|
||||
secondaryClassName="v2-history-summary-secondary"
|
||||
primaryValueClassName="v2-history-summary-value"
|
||||
ariaLabel="历史数据查询统计信息"
|
||||
primary={summaryPrimary}
|
||||
secondary={summarySecondary}
|
||||
/> : null}
|
||||
{dataQuery.isError ? <InlineError message={dataQuery.error instanceof Error ? dataQuery.error.message : '历史查询失败'} onRetry={() => dataQuery.refetch()} /> : null}
|
||||
<div className={`v2-history-workspace${selectedRow && !mobileLayout ? ' is-inspector-open' : ''}`}>
|
||||
<div className={`v2-history-main${trendExpanded ? ' has-expanded-trend' : ''}`}>
|
||||
@@ -381,7 +392,7 @@ export default function HistoryPage() {
|
||||
<WorkspacePanelHeader
|
||||
title="历史明细"
|
||||
description={keywords.length ? `${(result?.total ?? 0).toLocaleString('zh-CN')} 条记录 · ${resultSummary?.vehicleCount ?? 0} 辆车辆 · ${summarySources}` : '选择车辆和时间范围后查询'}
|
||||
meta={resultSummary ? `${resultSummary.queryDurationMs} ms` : undefined}
|
||||
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={<>
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconArrowDown, IconArrowUp, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
||||
import { Button, Card, CardGroup, Empty, Input, SideSheet, Spin, Switch, Table, Tag } from '@douyinfe/semi-ui';
|
||||
import { Button, Card, Empty, Input, SideSheet, Spin, Switch, Table, Tag } from '@douyinfe/semi-ui';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { type CSSProperties, FormEvent, memo, type RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
@@ -13,6 +13,7 @@ import { TablePagination } from '../shared/TablePagination';
|
||||
import { VehicleCandidateList } from '../shared/VehicleCandidateList';
|
||||
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
|
||||
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
||||
import { WorkspaceMetricRail } from '../shared/WorkspaceMetricRail';
|
||||
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||
import { QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||
import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext';
|
||||
@@ -249,10 +250,15 @@ function SummaryRail({ data, criteria, fleetTotal, loading }: { data?: MileageSt
|
||||
];
|
||||
const primary = items[2];
|
||||
const secondary = [items[0], items[1], items[3]];
|
||||
return <Card className="v2-mileage-summary" aria-label="里程查询统计信息" bodyStyle={{ padding: 0 }}>
|
||||
<Card className="v2-mileage-summary-card is-primary" bodyStyle={{ padding: 0 }} aria-label={`${primary[0]}:${primary[1]};${primary[2]}`}><small>{primary[0]}</small><strong className="v2-mileage-summary-value">{primary[1]}</strong><span title={primary[2]}>{primary[2]}</span></Card>
|
||||
<CardGroup className="v2-mileage-summary-secondary" type="grid" spacing={0}>{secondary.map(([label, value, note], index) => <Card className={`v2-mileage-summary-card is-${['fleet', 'range', 'average'][index]}`} bodyStyle={{ padding: 0 }} aria-label={`${label}:${value};${note}`} key={label}><small>{label}</small><strong>{value}</strong><span title={note}>{note}</span></Card>)}</CardGroup>
|
||||
</Card>;
|
||||
return <WorkspaceMetricRail
|
||||
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]}` }))}
|
||||
/>;
|
||||
}
|
||||
|
||||
type VehicleMileageMatrix = VehicleOption & { days: Map<string, number>; sources: Map<string, string>; totalMileageKm: number };
|
||||
|
||||
@@ -17,6 +17,7 @@ const profileSyncPanelSource = readFileSync(resolve(process.cwd(), 'src/v2/pages
|
||||
const sourceEvidenceSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/VehicleSourceEvidencePanel.tsx'), 'utf8');
|
||||
const mobileFilterToggleSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/MobileFilterToggle.tsx'), 'utf8');
|
||||
const workspaceFilterPanelSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/WorkspaceFilterPanel.tsx'), 'utf8');
|
||||
const workspaceMetricRailSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/WorkspaceMetricRail.tsx'), 'utf8');
|
||||
const segmentedTabsSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/SegmentedTabs.tsx'), 'utf8');
|
||||
const metricActionSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/MetricActionButton.tsx'), 'utf8');
|
||||
const tablePaginationSource = readFileSync(resolve(process.cwd(), 'src/v2/shared/TablePagination.tsx'), 'utf8');
|
||||
@@ -127,10 +128,13 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.HistoryPage).toContain('v2-history-column-sidesheet');
|
||||
expect(corePageSources.HistoryPage).toContain('<Card key={row.id} className={`v2-history-mobile-card');
|
||||
expect(corePageSources.HistoryPage).toContain('v2-history-trend');
|
||||
for (const surface of ['v2-export-jobs', 'v2-history-evidence', 'v2-history-context-card']) {
|
||||
for (const surface of ['v2-export-jobs', 'v2-history-evidence']) {
|
||||
expect(corePageSources.HistoryPage).toContain(`<Card className="${surface}"`);
|
||||
}
|
||||
expect(corePageSources.HistoryPage).toContain('<div className="v2-history-summary" aria-label="当前查询摘要">');
|
||||
expect(corePageSources.HistoryPage).toContain('<WorkspaceMetricRail');
|
||||
expect(corePageSources.HistoryPage).toContain('className="v2-history-summary-rail"');
|
||||
expect(corePageSources.HistoryPage).not.toContain('v2-history-context-card');
|
||||
expect(corePageSources.HistoryPage).not.toContain('v2-history-metric-scroll');
|
||||
expect(corePageSources.HistoryPage).toContain('<WorkspaceFilterPanel');
|
||||
expect(corePageSources.HistoryPage).toContain('className="v2-history-filter-card"');
|
||||
expect(corePageSources.HistoryPage).toContain('<Card className={`v2-history-table-card');
|
||||
@@ -326,9 +330,10 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.TrackPage).not.toContain('<button');
|
||||
expect(corePageSources.StatisticsPage).toContain('<Input');
|
||||
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');
|
||||
expect(corePageSources.StatisticsPage).toContain('<WorkspaceMetricRail');
|
||||
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('<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}');
|
||||
@@ -346,6 +351,9 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.StatisticsPage).toContain('<PanelEmpty className="v2-mileage-empty"');
|
||||
expect(corePageSources.StatisticsPage).not.toContain('<table className="v2-mileage-table"');
|
||||
expect(corePageSources.StatisticsPage).not.toContain('<input');
|
||||
expect(workspaceMetricRailSource).toContain("import { Card, CardGroup } from '@douyinfe/semi-ui'");
|
||||
expect(workspaceMetricRailSource).toContain('<Card className={className}');
|
||||
expect(workspaceMetricRailSource).toContain('<CardGroup className={secondaryClassName}');
|
||||
expect(corePageSources.MonitorPage).toContain('<Input');
|
||||
expect(corePageSources.MonitorPage).toContain('<Select');
|
||||
expect(corePageSources.MonitorPage).toContain('<Modal');
|
||||
@@ -431,7 +439,7 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources[name]).toContain("from '../shared/MetricActionButton'");
|
||||
expect(corePageSources[name]).toContain('<MetricActionButton');
|
||||
}
|
||||
expect(corePageSources.HistoryPage).toContain('<Tag className="v2-history-metric-tag"');
|
||||
expect(corePageSources.HistoryPage).toContain('className="v2-history-result-meta"');
|
||||
expect(corePageSources.AlertsPage).toContain('<Tag className={`v2-alert-severity');
|
||||
expect(corePageSources.AlertsPage).toContain('<Tag className={`v2-alert-status');
|
||||
expect(corePageSources.AlertsPage).toContain('className="v2-alert-inspector-heading"');
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { WorkspaceMetricRail } from './WorkspaceMetricRail';
|
||||
|
||||
describe('WorkspaceMetricRail', () => {
|
||||
it('renders one primary outcome and supporting facts as accessible Semi cards', () => {
|
||||
const view = render(<WorkspaceMetricRail
|
||||
ariaLabel="查询统计"
|
||||
className="test-rail"
|
||||
cardClassName="test-card"
|
||||
secondaryClassName="test-secondary"
|
||||
primaryValueClassName="test-primary-value"
|
||||
primary={{ label: '结果行数', value: '1,024', note: '来自 20 辆车辆' }}
|
||||
secondary={[
|
||||
{ label: '车辆数', value: '20', note: '当前授权范围', className: 'is-vehicles' },
|
||||
{ label: '数据来源', value: 'GB32960', note: '全部协议来源', className: 'is-sources' }
|
||||
]}
|
||||
/>);
|
||||
|
||||
expect(view.container.querySelector('.test-rail.semi-card')).toHaveAttribute('aria-label', '查询统计');
|
||||
expect(view.container.querySelector('.test-card.is-primary .test-primary-value')).toHaveTextContent('1,024');
|
||||
expect(view.container.querySelector('.test-secondary.semi-card-group')).toBeInTheDocument();
|
||||
expect(view.container.querySelectorAll('.test-card.semi-card')).toHaveLength(3);
|
||||
expect(screen.getByLabelText('车辆数:20;当前授权范围')).toHaveClass('is-vehicles');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Card, CardGroup } from '@douyinfe/semi-ui';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export type WorkspaceMetricRailItem = {
|
||||
label: string;
|
||||
value: ReactNode;
|
||||
note: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function WorkspaceMetricRail({
|
||||
ariaLabel,
|
||||
primary,
|
||||
secondary,
|
||||
className,
|
||||
cardClassName,
|
||||
secondaryClassName,
|
||||
primaryValueClassName
|
||||
}: {
|
||||
ariaLabel: string;
|
||||
primary: WorkspaceMetricRailItem;
|
||||
secondary: WorkspaceMetricRailItem[];
|
||||
className: string;
|
||||
cardClassName: string;
|
||||
secondaryClassName: string;
|
||||
primaryValueClassName?: string;
|
||||
}) {
|
||||
const cardClass = (item: WorkspaceMetricRailItem, primaryCard = false) => [
|
||||
cardClassName,
|
||||
primaryCard ? 'is-primary' : '',
|
||||
item.className ?? ''
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return <Card className={className} aria-label={ariaLabel} bodyStyle={{ padding: 0 }}>
|
||||
<Card className={cardClass(primary, true)} bodyStyle={{ padding: 0 }} aria-label={`${primary.label}:${primary.value};${primary.note}`}>
|
||||
<small>{primary.label}</small>
|
||||
<strong className={primaryValueClassName}>{primary.value}</strong>
|
||||
<span title={primary.note}>{primary.note}</span>
|
||||
</Card>
|
||||
<CardGroup className={secondaryClassName} type="grid" spacing={0}>
|
||||
{secondary.map((item) => <Card className={cardClass(item)} bodyStyle={{ padding: 0 }} aria-label={`${item.label}:${item.value};${item.note}`} key={item.label}>
|
||||
<small>{item.label}</small>
|
||||
<strong>{item.value}</strong>
|
||||
<span title={item.note}>{item.note}</span>
|
||||
</Card>)}
|
||||
</CardGroup>
|
||||
</Card>;
|
||||
}
|
||||
@@ -645,31 +645,6 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.v2-history-metrics-card.semi-card {
|
||||
min-height: 50px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.v2-history-metrics {
|
||||
min-height: 50px;
|
||||
gap: 9px;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
.v2-history-metrics > strong {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-history-metric-tag.semi-tag {
|
||||
height: 30px;
|
||||
border-radius: 7px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.v2-history-metric-scroll > span {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.v2-history-workspace {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
@@ -6016,129 +5991,6 @@
|
||||
* Semi UI history workspace refinement.
|
||||
* Keep one query surface and one evidence surface; the table owns the viewport.
|
||||
*/
|
||||
.v2-history-context-card.semi-card {
|
||||
min-width: 0;
|
||||
min-height: 78px;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
border: 1px solid #dce5ef;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
box-shadow: 0 6px 20px rgba(31, 47, 70, .045);
|
||||
}
|
||||
|
||||
.v2-history-context-card > .semi-card-body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v2-history-context {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
min-height: 78px;
|
||||
grid-template-columns: minmax(360px, 1.2fr) minmax(440px, .8fr);
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metrics {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
min-height: 78px;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border-right: 1px solid #e5ebf2;
|
||||
background: linear-gradient(90deg, #f8fbff 0%, #fff 30%);
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.v2-history-metrics-heading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v2-history-metrics-heading > strong {
|
||||
color: #30445d;
|
||||
font-size: 12px;
|
||||
font-weight: 720;
|
||||
}
|
||||
|
||||
.v2-history-metrics-heading > small {
|
||||
color: #8795a8;
|
||||
font-size: 10px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metric-tag.semi-tag {
|
||||
height: 30px;
|
||||
border-color: #c2d8f8;
|
||||
border-radius: 7px;
|
||||
background: #edf5ff;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metrics-manage.semi-button {
|
||||
min-width: 88px;
|
||||
min-height: 34px;
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid #d9e4f1;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: #526983;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
min-height: 78px;
|
||||
grid-template-columns: 1.05fr .75fr 1.45fr .8fr;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary > div {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 10px 13px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary > div + div::before {
|
||||
position: absolute;
|
||||
inset: 17px auto 17px 0;
|
||||
width: 1px;
|
||||
background: #e5ebf2;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary small {
|
||||
color: #7c8a9d;
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary strong {
|
||||
margin-top: 6px;
|
||||
overflow: hidden;
|
||||
color: #27384f;
|
||||
font-size: 17px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 760;
|
||||
line-height: 1.1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary .is-primary strong {
|
||||
color: #1268df;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary strong.is-source {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-history-main {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
@@ -6361,31 +6213,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1260px) and (min-width: 681px) {
|
||||
.v2-history-context {
|
||||
grid-template-columns: minmax(300px, 1fr) minmax(390px, .95fr);
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metrics {
|
||||
gap: 8px;
|
||||
padding-inline: 11px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metrics-manage.semi-button {
|
||||
min-width: 34px;
|
||||
width: 34px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metrics-manage .semi-button-content-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary > div {
|
||||
padding-inline: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 800px) and (min-width: 901px) {
|
||||
.v2-history-page {
|
||||
height: 100%;
|
||||
@@ -6400,25 +6227,6 @@
|
||||
}
|
||||
|
||||
@media (max-width: 900px) and (min-width: 681px) {
|
||||
.v2-history-context {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metrics {
|
||||
min-height: 58px;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid #e5ebf2;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary {
|
||||
min-height: 66px;
|
||||
}
|
||||
|
||||
.v2-history-context-card.semi-card,
|
||||
.v2-history-context {
|
||||
min-height: 124px;
|
||||
}
|
||||
|
||||
.v2-history-workspace.is-inspector-open {
|
||||
grid-template-columns: minmax(0, 1fr) 320px;
|
||||
}
|
||||
@@ -6433,104 +6241,6 @@
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.v2-history-context-card.semi-card {
|
||||
min-height: 126px;
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
.v2-history-context {
|
||||
display: flex;
|
||||
min-height: 126px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metrics {
|
||||
min-height: 68px;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
grid-template-rows: auto auto;
|
||||
gap: 6px 8px;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid #e5ebf2;
|
||||
padding: 7px 9px 8px;
|
||||
}
|
||||
|
||||
.v2-history-metrics-heading {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
align-items: baseline;
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.v2-history-metric-scroll {
|
||||
width: 100%;
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
padding: 1px 12px 1px 0;
|
||||
scroll-padding-inline: 0 12px;
|
||||
scroll-snap-type: inline proximity;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.v2-history-metric-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v2-history-metrics-heading > strong {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.v2-history-metrics-heading > small {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metric-tag.semi-tag {
|
||||
height: 28px;
|
||||
scroll-snap-align: start;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metrics-manage.semi-button {
|
||||
width: auto;
|
||||
min-width: 76px;
|
||||
min-height: 30px;
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-metrics-manage .semi-button-content-right {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary {
|
||||
min-height: 58px;
|
||||
flex: 1;
|
||||
grid-template-columns: 1.1fr .72fr 1.18fr .78fr;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary > div {
|
||||
padding: 8px 7px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary > div + div::before {
|
||||
inset-block: 13px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary small {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary strong,
|
||||
.v2-history-context .v2-history-summary .is-primary strong {
|
||||
margin-top: 4px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.v2-history-context .v2-history-summary strong.is-source {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.v2-history-main {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
@@ -10858,3 +10568,200 @@
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Shared Semi UI metric rail applied to history data.
|
||||
* The rail mirrors the mileage workspace: one primary outcome, then three
|
||||
* supporting facts. Empty history screens omit the rail entirely.
|
||||
*/
|
||||
.v2-history-summary-rail.semi-card {
|
||||
min-width: 0;
|
||||
min-height: 88px;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
border: 1px solid #d8e3ef;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
box-shadow: 0 8px 26px rgba(31, 53, 80, .05);
|
||||
}
|
||||
|
||||
.v2-history-summary-rail > .semi-card-body {
|
||||
display: grid;
|
||||
min-height: 88px;
|
||||
grid-template-columns: minmax(280px, .78fr) minmax(0, 1.72fr);
|
||||
}
|
||||
|
||||
.v2-history-summary-card.semi-card {
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-history-summary-card > .semi-card-body {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 11px 16px !important;
|
||||
}
|
||||
|
||||
.v2-history-summary-card.is-primary > .semi-card-body {
|
||||
overflow: hidden;
|
||||
background: linear-gradient(128deg, #0c61e7 0%, #1c73ee 68%, #2c82ef 100%);
|
||||
}
|
||||
|
||||
.v2-history-summary-card.is-primary > .semi-card-body::after {
|
||||
position: absolute;
|
||||
top: -80px;
|
||||
right: -44px;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
border: 26px solid rgba(255, 255, 255, .08);
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.v2-history-summary-card small,
|
||||
.v2-history-summary-card span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
color: #7c8a9d;
|
||||
font-size: 10px;
|
||||
line-height: 1.35;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v2-history-summary-card strong {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 5px 0 4px;
|
||||
overflow: hidden;
|
||||
color: #22354d;
|
||||
font-size: clamp(18px, 1.45vw, 24px);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 760;
|
||||
line-height: 1.08;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.v2-history-summary-card.is-primary :is(small,strong,span) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.v2-history-summary-card.is-primary small,
|
||||
.v2-history-summary-card.is-primary span {
|
||||
color: rgba(255, 255, 255, .72);
|
||||
}
|
||||
|
||||
.v2-history-summary-card.is-primary .v2-history-summary-value {
|
||||
font-size: clamp(25px, 2.1vw, 34px);
|
||||
letter-spacing: -.035em;
|
||||
}
|
||||
|
||||
.v2-history-summary-secondary.semi-card-group {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.v2-history-summary-secondary > .v2-history-summary-card + .v2-history-summary-card > .semi-card-body::before {
|
||||
position: absolute;
|
||||
inset: 18px auto 18px 0;
|
||||
width: 1px;
|
||||
background: #e3eaf2;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.v2-history-summary-card.is-sources strong {
|
||||
font-size: clamp(13px, 1.05vw, 17px);
|
||||
}
|
||||
|
||||
.v2-history-result-meta {
|
||||
display: inline-flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: #8090a3;
|
||||
font-size: 10px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) and (min-width: 681px) {
|
||||
.v2-history-summary-rail > .semi-card-body {
|
||||
grid-template-columns: minmax(230px, .7fr) minmax(0, 1.6fr);
|
||||
}
|
||||
|
||||
.v2-history-summary-card > .semi-card-body {
|
||||
padding-inline: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.v2-history-summary-rail.semi-card {
|
||||
min-height: 132px;
|
||||
overflow: visible;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-history-summary-rail > .semi-card-body {
|
||||
min-height: 132px;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 70px 58px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.v2-history-summary-card.is-primary.semi-card,
|
||||
.v2-history-summary-card.is-primary > .semi-card-body {
|
||||
min-height: 70px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.v2-history-summary-card.is-primary > .semi-card-body {
|
||||
padding: 7px 13px 8px !important;
|
||||
}
|
||||
|
||||
.v2-history-summary-card.is-primary .v2-history-summary-value {
|
||||
margin-block: 2px 1px;
|
||||
font-size: 24px;
|
||||
line-height: 27px;
|
||||
}
|
||||
|
||||
.v2-history-summary-secondary.semi-card-group {
|
||||
min-height: 58px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #dfe7f0;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.v2-history-summary-secondary .v2-history-summary-card > .semi-card-body {
|
||||
min-height: 58px;
|
||||
padding: 7px 8px !important;
|
||||
}
|
||||
|
||||
.v2-history-summary-secondary > .v2-history-summary-card + .v2-history-summary-card > .semi-card-body::before {
|
||||
inset-block: 8px;
|
||||
}
|
||||
|
||||
.v2-history-summary-secondary .v2-history-summary-card small,
|
||||
.v2-history-summary-secondary .v2-history-summary-card span {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.v2-history-summary-secondary .v2-history-summary-card strong,
|
||||
.v2-history-summary-secondary .v2-history-summary-card.is-sources strong {
|
||||
margin-block: 2px 1px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user