unify Semi UI track evidence

This commit is contained in:
lingniu
2026-07-18 22:05:21 +08:00
parent e40eabac92
commit fd5e16c36c
4 changed files with 25 additions and 7 deletions

View File

@@ -185,7 +185,8 @@ test('renders a map-first replay workspace and connects stop, event, and panel i
expect(view.container.querySelector('.v2-track-current-card.semi-card')).toBeInTheDocument();
expect(view.container.querySelector('.v2-track-current-coverage')).toBeInTheDocument();
expect(view.container.querySelectorAll('.v2-track-current-metrics > span')).toHaveLength(4);
expect(view.container.querySelector('.v2-track-current-evidence')).toHaveTextContent('JT808时间窗完整3 → 3 点报警 —');
expect(view.container.querySelector('.v2-track-current-evidence')).toHaveTextContent('JT/T 808时间窗完整3 → 3 点报警 —');
expect(view.container.querySelector('.v2-track-current-evidence .v2-track-protocol-tag')).toHaveClass('semi-tag-cyan-light');
expect(view.container.querySelector('.v2-track-coverage-float')).not.toBeInTheDocument();
expect(screen.getByRole('toolbar', { name: '地图轨迹工具' })).toBeInTheDocument();
expect(view.container.querySelector('.v2-track-playback-dock.semi-card')).toBeInTheDocument();
@@ -216,6 +217,8 @@ test('renders a map-first replay workspace and connects stop, event, and panel i
expect(view.container.querySelector('.v2-track-overview-descriptions.semi-descriptions')).toBeInTheDocument();
expect(view.container.querySelector('.v2-track-source-list.semi-list')).toBeInTheDocument();
expect(view.container.querySelectorAll('.v2-track-source-item.semi-list-item')).toHaveLength(1);
expect(view.container.querySelector('.v2-track-source-item .v2-track-protocol-tag')).toHaveTextContent('JT/T 808');
expect(view.container.querySelector('.v2-track-source-item .v2-track-protocol-tag')).toHaveClass('semi-tag-cyan-light');
expect(screen.getByText('完整点集').closest('.semi-tag')).toBeInTheDocument();
expect(screen.getByText('通过').closest('.semi-tag')).toBeInTheDocument();
@@ -228,6 +231,9 @@ test('renders a map-first replay workspace and connects stop, event, and panel i
fireEvent.click(screen.getByRole('button', { name: '修改条件' }));
expect(screen.getByRole('textbox', { name: '搜索轨迹车辆' })).toHaveValue('LTEST000000000001');
expect(screen.getByRole('option', { name: 'GB/T 32960 · 仪表盘里程' })).toBeInTheDocument();
expect(screen.getByRole('option', { name: 'JT/T 808 · GPS 里程' })).toBeInTheDocument();
expect(screen.getByRole('option', { name: '宇通 MQTT · 仪表盘里程' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '收起条件' })).toHaveAttribute('aria-expanded', 'true');
fireEvent.click(screen.getByRole('button', { name: '收起条件' }));
expect(screen.queryByRole('textbox', { name: '搜索轨迹车辆' })).not.toBeInTheDocument();

View File

@@ -8,10 +8,12 @@ import { FormEvent, memo, useCallback, useEffect, useMemo, useRef, useState } fr
import { useSearchParams } from 'react-router-dom';
import { api } from '../../api/client';
import type { TrackPlaybackEvent, TrackPlaybackResponse, VehicleRow } from '../../api/types';
import { protocolDisplayLabel } from '../domain/protocols';
import { buildTrackRailSelection, downloadTrackCsv, formatDuration, sampledEventIndex, TRACK_ADDRESS_SETTLE_MS, trackAddressCoordinate, trackPlaybackInterval, type TrackAddressCoordinate } from '../domain/track';
import { TrackMap } from '../map/TrackMap';
import { InlineError } from '../shared/AsyncState';
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
import { ProtocolTag } from '../shared/ProtocolTag';
import { SegmentedTabs } from '../shared/SegmentedTabs';
import { VehicleCandidateList } from '../shared/VehicleCandidateList';
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
@@ -28,6 +30,12 @@ type PanelTab = 'stops' | 'events' | 'overview';
type Draft = { keyword: string; dateFrom: string; dateTo: string; protocol: string };
type TrackPreset = 'today' | 'yesterday' | 'three-days';
const EMPTY_INDEXES: readonly number[] = [];
const TRACK_SOURCE_OPTIONS = [
{ value: '', label: '自动选择最佳来源' },
{ value: 'GB32960', label: `${protocolDisplayLabel('GB32960')} · 仪表盘里程` },
{ value: 'JT808', label: `${protocolDisplayLabel('JT808')} · GPS 里程` },
{ value: 'YUTONG_MQTT', label: `${protocolDisplayLabel('YUTONG_MQTT')} · 仪表盘里程` }
];
const numberFormatters = new Map<number, Intl.NumberFormat>();
const timeFormatter = new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
const dateTimeFormatter = new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false, month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' });
@@ -167,7 +175,7 @@ function OverviewPanel({ track }: { track: TrackPlaybackResponse }) {
</section>
<section className="v2-track-overview-section v2-track-source-card">
<WorkspacePanelHeader variant="compact" title="数据来源" meta={<Tag color="blue" type="light" size="small">{track.coverage.totalPoints.toLocaleString('zh-CN')} </Tag>} />
<div className="v2-track-overview-card-body"><List className="v2-track-source-list">{track.sources.map((source) => <List.Item className="v2-track-source-item" key={source.protocol}><Tag color="blue" type="light" size="small">{source.protocol}</Tag><strong>{source.pointCount.toLocaleString('zh-CN')} </strong><small>{time(source.startTime)}{time(source.endTime)}</small></List.Item>)}</List></div>
<div className="v2-track-overview-card-body"><List className="v2-track-source-list">{track.sources.map((source) => <List.Item className="v2-track-source-item" key={source.protocol}><ProtocolTag className="v2-track-protocol-tag" protocol={source.protocol} compact /><strong>{source.pointCount.toLocaleString('zh-CN')} </strong><small>{time(source.startTime)}{time(source.endTime)}</small></List.Item>)}</List></div>
</section>
<section className={`v2-track-overview-section v2-track-quality-card is-${track.quality.status}`}>
<WorkspacePanelHeader variant="compact" title="轨迹质量" meta={<Tag color={track.quality.status === 'good' ? 'green' : 'orange'} type="light" size="small">{track.quality.status === 'good' ? '通过' : '需关注'}</Tag>} />
@@ -194,7 +202,7 @@ const TrackRail = memo(function TrackRail({ draft, track, loading, activeStopInd
const activePreset = trackPreset(draft);
const choosePreset = (daysBack: number) => onDraft({ ...draft, ...trackWindow(daysBack, daysBack > 0) });
const appliedWindow = `${draft.dateFrom.replace('T', ' ').slice(5)} ${draft.dateTo.replace('T', ' ').slice(5)}`;
const appliedSource = draft.protocol || '自动来源';
const appliedSource = draft.protocol ? protocolDisplayLabel(draft.protocol) : '自动来源';
const contextTitle = queryCollapsed ? track?.plate || draft.keyword || '轨迹查询' : '轨迹查询';
return <aside className="v2-track-rail">
<Card className="v2-track-rail-shell" bodyStyle={{ padding: 0 }}>
@@ -228,7 +236,7 @@ const TrackRail = memo(function TrackRail({ draft, track, loading, activeStopInd
<Button theme={activePreset === 'three-days' ? 'solid' : 'light'} aria-pressed={activePreset === 'three-days'} onClick={() => { const end = new Date(); const start = new Date(end); start.setDate(start.getDate() - 2); start.setHours(0, 0, 0, 0); onDraft({ ...draft, dateFrom: localDateTime(start), dateTo: localDateTime(end) }); }}> 3 </Button>
</div>
<div className="v2-track-date-grid"><label><span></span><Input aria-label="开始时间" type="datetime-local" value={draft.dateFrom} onChange={(value) => onDraft({ ...draft, dateFrom: value })} /></label><label><span></span><Input aria-label="结束时间" type="datetime-local" value={draft.dateTo} onChange={(value) => onDraft({ ...draft, dateTo: value })} /></label></div>
<label><span id="track-source-label"></span><Select aria-labelledby="track-source-label" value={draft.protocol} onChange={(value) => onDraft({ ...draft, protocol: String(value) })} optionList={[{ value: '', label: '自动选择最佳来源' }, { value: 'GB32960', label: 'GB32960 · 仪表盘里程' }, { value: 'JT808', label: 'JT808 · GPS 里程' }, { value: 'YUTONG_MQTT', label: 'YUTONG · 仪表盘里程' }]} /></label>
<label><span id="track-source-label"></span><Select aria-labelledby="track-source-label" value={draft.protocol} onChange={(value) => onDraft({ ...draft, protocol: String(value) })} optionList={TRACK_SOURCE_OPTIONS} /></label>
<Button className="v2-track-query-button" theme="solid" htmlType="submit" icon={<IconSearch />} loading={loading} disabled={!draft.keyword.trim()}></Button>
</form> : null}
</section>
@@ -413,7 +421,7 @@ export default function TrackPage() {
<span><small></small><strong>{number(current?.totalMileageKm ?? 0)}<em> km</em></strong></span>
</div>
<div className={`v2-track-current-evidence v2-track-current-coverage${track.coverage.complete ? '' : ' is-warning'}`}>
<Tag color="blue" type="light" size="small">{current?.protocol || '未知来源'}</Tag>
<ProtocolTag className="v2-track-protocol-tag" protocol={current?.protocol} compact />
<span title={track.coverage.evidence}><i /><strong>{track.coverage.complete ? '时间窗完整' : '最新切片'}</strong></span>
<em>{track.coverage.processedPoints.toLocaleString('zh-CN')} {track.coverage.returnedPoints.toLocaleString('zh-CN')} </em>
<small>{alarm(current?.alarmFlag)}</small>

View File

@@ -8906,7 +8906,7 @@ button, a { -webkit-tap-highlight-color: transparent; }
background: linear-gradient(135deg, #f8fafc 0%, #f4f8fd 100%);
padding: 8px 9px;
}
.v2-track-source-item .semi-tag { width: max-content; max-width: 100%; overflow: hidden; border-radius: 6px; font-size: 8px; text-overflow: ellipsis; white-space: nowrap; }
.v2-track-source-item .v2-track-protocol-tag.semi-tag { width: max-content; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-track-source-item > strong { color: #3e526a; font-size: 10px; text-align: right; font-variant-numeric: tabular-nums; }
.v2-track-source-item > small { grid-column: 1 / -1; color: #8795a7; font-size: 8px; font-variant-numeric: tabular-nums; }
@@ -8917,7 +8917,7 @@ button, a { -webkit-tap-highlight-color: transparent; }
.v2-track-evidence-list button small,
.v2-track-evidence-list .v2-track-list-action .semi-button-content > em { font-size: 10px; }
.v2-track-source-list .v2-track-source-item.semi-list-item { min-height: 54px; padding: 9px 10px; }
.v2-track-source-item .semi-tag,
.v2-track-source-item .v2-track-protocol-tag.semi-tag,
.v2-track-source-item > small { font-size: 9px; }
.v2-track-source-item > strong { font-size: 11px; }
}

View File

@@ -814,6 +814,10 @@
font-size: 10px;
}
.v2-track-current-evidence .v2-track-protocol-tag.semi-tag {
border-radius: 999px;
}
.v2-track-current-evidence > span {
display: inline-flex;
min-width: 0;