refine Semi UI vehicle detail workspace
This commit is contained in:
@@ -77,7 +77,10 @@ test('polls lightweight single-vehicle realtime data and passes its report inter
|
|||||||
await waitFor(() => expect(fleetMapVehicles).toHaveBeenLastCalledWith([
|
await waitFor(() => expect(fleetMapVehicles).toHaveBeenLastCalledWith([
|
||||||
expect.objectContaining({ longitude: 113.28, latitude: 23.15, reportIntervalMs: 30_000 })
|
expect.objectContaining({ longitude: 113.28, latitude: 23.15, reportIntervalMs: 30_000 })
|
||||||
]));
|
]));
|
||||||
expect(view.container.querySelector('.v2-identity-band')).toHaveClass('semi-card');
|
const commandCard = view.container.querySelector('.v2-identity-band');
|
||||||
|
expect(commandCard).toHaveClass('semi-card', 'v2-vehicle-command-card', 'v2-live-overview');
|
||||||
|
expect(screen.getByRole('list', { name: '车辆实时指标' })).toHaveClass('v2-live-grid');
|
||||||
|
expect(commandCard?.querySelectorAll('.v2-live-grid > [role="listitem"]')).toHaveLength(6);
|
||||||
expect(screen.getByRole('heading', { name: '最新上报', level: 5 })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: '最新上报', level: 5 })).toBeInTheDocument();
|
||||||
expect(screen.getByRole('heading', { name: '实时位置', level: 5 })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: '实时位置', level: 5 })).toBeInTheDocument();
|
||||||
expect(screen.getByRole('heading', { name: '最近事件', level: 5 })).toBeInTheDocument();
|
expect(screen.getByRole('heading', { name: '最近事件', level: 5 })).toBeInTheDocument();
|
||||||
@@ -99,6 +102,7 @@ test('polls lightweight single-vehicle realtime data and passes its report inter
|
|||||||
const totalMileageSource = screen.getByRole('button', { name: '查看总里程全部来源' });
|
const totalMileageSource = screen.getByRole('button', { name: '查看总里程全部来源' });
|
||||||
expect(totalMileageSource).toHaveClass('semi-button', 'v2-live-source-link');
|
expect(totalMileageSource).toHaveClass('semi-button', 'v2-live-source-link');
|
||||||
expect(totalMileageSource.closest('.semi-card')).toHaveClass('v2-live-overview');
|
expect(totalMileageSource.closest('.semi-card')).toHaveClass('v2-live-overview');
|
||||||
|
expect(totalMileageSource.closest('.semi-card')).toBe(commandCard);
|
||||||
fireEvent.click(locationSource);
|
fireEvent.click(locationSource);
|
||||||
await waitFor(() => expect(sourceEvidence).toHaveBeenCalledTimes(1));
|
await waitFor(() => expect(sourceEvidence).toHaveBeenCalledTimes(1));
|
||||||
});
|
});
|
||||||
@@ -347,4 +351,5 @@ test('uses compact Semi telemetry evidence cards on mobile', async () => {
|
|||||||
expect(document.querySelector('.v2-telemetry-table')).not.toBeInTheDocument();
|
expect(document.querySelector('.v2-telemetry-table')).not.toBeInTheDocument();
|
||||||
expect(document.querySelector('.v2-telemetry-mobile-list.semi-list')).toBeInTheDocument();
|
expect(document.querySelector('.v2-telemetry-mobile-list.semi-list')).toBeInTheDocument();
|
||||||
expect(document.querySelector('.v2-telemetry-mobile-item.semi-list-item')).toHaveTextContent('36km/h正常');
|
expect(document.querySelector('.v2-telemetry-mobile-item.semi-list-item')).toHaveTextContent('36km/h正常');
|
||||||
|
expect(document.querySelector('.v2-vehicle-command-card .v2-live-grid')).toHaveAttribute('aria-label', '车辆实时指标');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ function vehicleLastSeenLabel(value?: string) {
|
|||||||
return normalized.length >= 16 ? `${normalized.slice(5, 16)} 更新` : normalized;
|
return normalized.length >= 16 ? `${normalized.slice(5, 16)} 更新` : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function eventTimeLabel(value?: string) {
|
||||||
|
if (!value) return '—';
|
||||||
|
const normalized = value.replace('T', ' ');
|
||||||
|
return normalized.length >= 16 ? normalized.slice(5, 16) : normalized;
|
||||||
|
}
|
||||||
|
|
||||||
function VehicleSearch() {
|
function VehicleSearch() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { session } = usePlatformSession();
|
const { session } = usePlatformSession();
|
||||||
@@ -255,7 +261,7 @@ function Events({ detail }: { detail: VehicleDetail }) {
|
|||||||
emptyContent={<Empty className="v2-event-empty" title="暂无可用事件证据" description="车辆产生质量提醒或来源状态变化后会显示在这里。" />}
|
emptyContent={<Empty className="v2-event-empty" title="暂无可用事件证据" description="车辆产生质量提醒或来源状态变化后会显示在这里。" />}
|
||||||
renderItem={(event, index) => <List.Item className={`v2-event-row is-${event.tone}`} key={`${event.title}-${event.time}-${index}`}>
|
renderItem={(event, index) => <List.Item className={`v2-event-row is-${event.tone}`} key={`${event.title}-${event.time}-${index}`}>
|
||||||
<span className="v2-event-icon">{event.tone === 'success' ? <IconTickCircle /> : <IconAlarm />}</span>
|
<span className="v2-event-icon">{event.tone === 'success' ? <IconTickCircle /> : <IconAlarm />}</span>
|
||||||
<div><strong>{event.title}</strong><p>{event.detail}</p></div><time>{fmt(event.time)}</time>
|
<div><strong>{event.title}</strong><p>{event.detail}</p></div><time title={fmt(event.time)}>{eventTimeLabel(event.time)}</time>
|
||||||
</List.Item>}
|
</List.Item>}
|
||||||
/>
|
/>
|
||||||
</Card>;
|
</Card>;
|
||||||
@@ -409,16 +415,19 @@ function VehicleRecordNavigation() {
|
|||||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
target.focus({ preventScroll: true });
|
target.focus({ preventScroll: true });
|
||||||
};
|
};
|
||||||
|
const navigationItems = [
|
||||||
|
['location', '实时位置', <IconMapPin />],
|
||||||
|
['events', '最近事件', <IconAlarm />],
|
||||||
|
['telemetry', '实时遥测', <IconClock />],
|
||||||
|
['archive', '车辆主档', <IconBox />]
|
||||||
|
] as const;
|
||||||
return <Card className="v2-record-card v2-vehicle-record-nav" bodyStyle={{ padding: 0 }}>
|
return <Card className="v2-record-card v2-vehicle-record-nav" bodyStyle={{ padding: 0 }}>
|
||||||
<WorkspacePanelHeader
|
<WorkspacePanelHeader
|
||||||
variant="compact"
|
variant="compact"
|
||||||
title="详情导航"
|
title="车辆视图"
|
||||||
description="快速定位当前车辆的数据区域"
|
description="位置、事件、遥测与主档"
|
||||||
actions={<nav className="v2-vehicle-section-nav" aria-label="单车详情导航">
|
actions={<nav className="v2-vehicle-section-nav" aria-label="单车详情导航">
|
||||||
<Button size="small" theme="borderless" type="tertiary" icon={<IconMapPin />} aria-label="跳转到实时位置" onClick={() => jumpToSection('location')}>实时位置</Button>
|
{navigationItems.map(([key, label, icon]) => <Button key={key} size="small" theme="borderless" type="tertiary" icon={icon} aria-label={`跳转到${label}`} onClick={() => jumpToSection(key)}>{label}</Button>)}
|
||||||
<Button size="small" theme="borderless" type="tertiary" icon={<IconAlarm />} aria-label="跳转到最近事件" onClick={() => jumpToSection('events')}>最近事件</Button>
|
|
||||||
<Button size="small" theme="borderless" type="tertiary" icon={<IconClock />} aria-label="跳转到实时遥测" onClick={() => jumpToSection('telemetry')}>实时遥测</Button>
|
|
||||||
<Button size="small" theme="borderless" type="tertiary" icon={<IconBox />} aria-label="跳转到车辆主档" onClick={() => jumpToSection('archive')}>车辆主档</Button>
|
|
||||||
</nav>}
|
</nav>}
|
||||||
/>
|
/>
|
||||||
</Card>;
|
</Card>;
|
||||||
@@ -441,28 +450,37 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
|
|||||||
];
|
];
|
||||||
return <div className="v2-vehicle-record-page">
|
return <div className="v2-vehicle-record-page">
|
||||||
<MonitorReturnBar />
|
<MonitorReturnBar />
|
||||||
<Card className="v2-identity-band" bodyStyle={{ padding: 0 }}>
|
<Card className="v2-identity-band v2-record-card v2-live-card v2-live-overview v2-vehicle-command-card" bodyStyle={{ padding: 0 }}>
|
||||||
<div className="v2-identity-primary"><span className="v2-plate"><IconBox />{fmt(identity?.plate || realtime?.plate)}</span><Tag className={`v2-online-label ${realtime?.online ? 'is-online' : ''}`} color={realtime?.online ? 'green' : 'grey'} size="small">{realtime?.online ? '在线' : '离线'}</Tag><small>VIN</small><b>{detail.vin}</b><Button theme="borderless" aria-label="复制 VIN" title="复制 VIN" icon={<IconCopy />} onClick={() => navigator.clipboard?.writeText(detail.vin)} /></div>
|
<div className="v2-vehicle-command-header">
|
||||||
<div className="v2-identity-meta"><div><small>车辆品牌 / 车型</small><strong>{[detail.profile?.brandName, detail.profile?.modelName].filter(Boolean).join(' / ') || fmt(identity?.oem || realtime?.oem)}</strong></div><div><small>可用数据来源</small><p>{detail.sources.length ? detail.sources.map((source) => <span key={source}>{source}</span>) : <span>暂无来源</span>}</p></div></div>
|
<div className="v2-identity-primary">
|
||||||
|
<span className="v2-plate"><IconBox />{fmt(identity?.plate || realtime?.plate)}</span>
|
||||||
|
<Tag className={`v2-online-label ${realtime?.online ? 'is-online' : ''}`} color={realtime?.online ? 'green' : 'grey'} size="small">{realtime?.online ? '在线' : '离线'}</Tag>
|
||||||
|
<span className="v2-identity-vin"><small>VIN</small><b>{detail.vin}</b><Button theme="borderless" aria-label="复制 VIN" title="复制 VIN" icon={<IconCopy />} onClick={() => navigator.clipboard?.writeText(detail.vin)} /></span>
|
||||||
|
</div>
|
||||||
|
<div className="v2-identity-meta">
|
||||||
|
<div><small>车辆品牌 / 车型</small><strong>{[detail.profile?.brandName, detail.profile?.modelName].filter(Boolean).join(' / ') || fmt(identity?.oem || realtime?.oem)}</strong></div>
|
||||||
|
<div><small>可用数据来源</small><div className="v2-identity-sources">{detail.sources.length ? detail.sources.map((source) => <Tag key={source} color="blue" type="light" size="small">{source}</Tag>) : <Tag color="grey" type="light" size="small">暂无来源</Tag>}</div></div>
|
||||||
|
</div>
|
||||||
<div className="v2-identity-actions" role="group" aria-label="车辆快捷操作">
|
<div className="v2-identity-actions" role="group" aria-label="车辆快捷操作">
|
||||||
{actions.map((action) => <Button key={action.key} theme="light" type={action.type} icon={action.icon} aria-label={action.label} onClick={() => navigate(action.to)}>{action.label}</Button>)}
|
{actions.map((action) => <Button key={action.key} theme="light" type={action.type} icon={action.icon} aria-label={action.label} onClick={() => navigate(action.to)}>{action.label}</Button>)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
<section className="v2-vehicle-live-section" aria-labelledby="vehicle-live-heading">
|
||||||
<Card className="v2-record-card v2-live-card v2-live-overview" bodyStyle={{ padding: 0 }}>
|
|
||||||
<WorkspacePanelHeader
|
<WorkspacePanelHeader
|
||||||
title="最新上报"
|
title={<span id="vehicle-live-heading">最新上报</span>}
|
||||||
|
description="关键运行指标与推荐数据来源"
|
||||||
meta={<span className="v2-live-report-meta"><i className={realtime?.online ? 'is-online' : ''} />{realtime?.online ? '实时在线' : '当前离线'} · <IconClock />{fmt(realtime?.lastSeen || identity?.lastSeen)}</span>}
|
meta={<span className="v2-live-report-meta"><i className={realtime?.online ? 'is-online' : ''} />{realtime?.online ? '实时在线' : '当前离线'} · <IconClock />{fmt(realtime?.lastSeen || identity?.lastSeen)}</span>}
|
||||||
/>
|
/>
|
||||||
<div className="v2-live-grid">
|
<div className="v2-live-grid" role="list" aria-label="车辆实时指标">
|
||||||
<div><small>速度</small><strong>{availableMetric(realtime?.speedKmh, realtime?.speedAvailable)}<em>km/h</em></strong></div>
|
<div role="listitem"><small>速度</small><strong>{availableMetric(realtime?.speedKmh, realtime?.speedAvailable)}<em>km/h</em></strong><span>实时车速</span></div>
|
||||||
<div><small>SOC</small><strong>{availableMetric(realtime?.socPercent, realtime?.socAvailable)}<em>%</em></strong></div>
|
<div role="listitem"><small>SOC</small><strong>{availableMetric(realtime?.socPercent, realtime?.socAvailable)}<em>%</em></strong><span>剩余电量</span></div>
|
||||||
<div><small>总里程</small><Button theme="borderless" type="tertiary" className="v2-live-source-link" title="展开全部里程来源" aria-label="查看总里程全部来源" onClick={() => setSourceEvidenceOpen(true)}><span>{availableMetric(realtime?.totalMileageKm, realtime?.mileageAvailable)}<em>km</em></span><IconChevronRight /></Button></div>
|
<div role="listitem"><small>总里程</small><Button theme="borderless" type="tertiary" className="v2-live-source-link" title="展开全部里程来源" aria-label="查看总里程全部来源" onClick={() => setSourceEvidenceOpen(true)}><span>{availableMetric(realtime?.totalMileageKm, realtime?.mileageAvailable)}<em>km</em></span><IconChevronRight /></Button><span>推荐口径</span></div>
|
||||||
<div><small>当日里程</small><Button theme="borderless" type="tertiary" className="v2-live-source-link" title="展开全部里程来源" aria-label="查看当日里程全部来源" onClick={() => setSourceEvidenceOpen(true)}><span>{availableMetric(realtime?.todayMileageKm, realtime?.todayMileageAvailable)}<em>km</em></span><IconChevronRight /></Button></div>
|
<div role="listitem"><small>当日里程</small><Button theme="borderless" type="tertiary" className="v2-live-source-link" title="展开全部里程来源" aria-label="查看当日里程全部来源" onClick={() => setSourceEvidenceOpen(true)}><span>{availableMetric(realtime?.todayMileageKm, realtime?.todayMileageAvailable)}<em>km</em></span><IconChevronRight /></Button><span>今日累计</span></div>
|
||||||
<div><small>推荐来源</small><strong className="is-text">{fmt(realtime?.primaryProtocol)}<em>{realtime?.locationSource ? ` · ${realtime.locationSource}` : ''}</em></strong></div>
|
<div role="listitem"><small>推荐来源</small><strong className="is-text">{fmt(realtime?.primaryProtocol)}</strong><span title={realtime?.locationSource}>{realtime?.locationSource || '当前最优来源'}</span></div>
|
||||||
<div><small>来源状态</small><strong>{realtime?.onlineSourceCount ?? 0}<em> 在线 / {detail.sourceStatus.length} 个</em></strong></div>
|
<div role="listitem"><small>来源状态</small><strong>{realtime?.onlineSourceCount ?? 0}<em> / {detail.sourceStatus.length}</em></strong><span>在线来源 / 全部</span></div>
|
||||||
</div>
|
</div>
|
||||||
<CurrentVehicleAddress vehicle={realtime} fallback={identity?.locationText} />
|
<CurrentVehicleAddress vehicle={realtime} fallback={identity?.locationText} />
|
||||||
|
</section>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<VehicleSourceEvidencePanel vin={detail.vin} open={sourceEvidenceOpen} onOpenChange={setSourceEvidenceOpen} />
|
<VehicleSourceEvidencePanel vin={detail.vin} open={sourceEvidenceOpen} onOpenChange={setSourceEvidenceOpen} />
|
||||||
|
|||||||
@@ -62,10 +62,11 @@ describe('V2 production entry', () => {
|
|||||||
expect(corePageSources.StatisticsPage).not.toContain("from '../shared/PageHeader'");
|
expect(corePageSources.StatisticsPage).not.toContain("from '../shared/PageHeader'");
|
||||||
expect(corePageSources.StatisticsPage).not.toContain('<PageHeader');
|
expect(corePageSources.StatisticsPage).not.toContain('<PageHeader');
|
||||||
expect(corePageSources.VehiclePage).toContain('<Select');
|
expect(corePageSources.VehiclePage).toContain('<Select');
|
||||||
expect(corePageSources.VehiclePage).toContain('<Card className="v2-record-card v2-live-card v2-live-overview"');
|
expect(corePageSources.VehiclePage).toContain('<Card className="v2-identity-band v2-record-card v2-live-card v2-live-overview v2-vehicle-command-card"');
|
||||||
|
expect(corePageSources.VehiclePage).toContain('className="v2-live-grid" role="list" aria-label="车辆实时指标"');
|
||||||
expect(corePageSources.VehiclePage).toContain('<Card className="v2-single-map-card"');
|
expect(corePageSources.VehiclePage).toContain('<Card className="v2-single-map-card"');
|
||||||
expect(corePageSources.VehiclePage).toContain('className="v2-record-section-anchor v2-record-location-anchor"');
|
expect(corePageSources.VehiclePage).toContain('className="v2-record-section-anchor v2-record-location-anchor"');
|
||||||
expect(corePageSources.VehiclePage).toContain('<Card className="v2-identity-band"');
|
expect(corePageSources.VehiclePage).toContain('v2-identity-band');
|
||||||
expect(corePageSources.VehiclePage).not.toContain('<section className="v2-record-card');
|
expect(corePageSources.VehiclePage).not.toContain('<section className="v2-record-card');
|
||||||
expect(corePageSources.VehiclePage).not.toContain('<section className="v2-single-map-card');
|
expect(corePageSources.VehiclePage).not.toContain('<section className="v2-single-map-card');
|
||||||
expect(corePageSources.VehiclePage).not.toContain('<section className="v2-identity-band"');
|
expect(corePageSources.VehiclePage).not.toContain('<section className="v2-identity-band"');
|
||||||
|
|||||||
@@ -4260,3 +4260,574 @@
|
|||||||
min-width: 62px;
|
min-width: 62px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Semi UI single-vehicle command workspace. */
|
||||||
|
.v2-vehicle-command-card.semi-card {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #d7e2ee;
|
||||||
|
border-radius: 13px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 10px 30px rgba(31, 53, 80, .065);
|
||||||
|
content-visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card > .semi-card-body {
|
||||||
|
display: block;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(310px, .9fr) minmax(360px, 1.15fr) minmax(360px, auto);
|
||||||
|
align-items: stretch;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-primary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
gap: 8px 10px;
|
||||||
|
padding: 18px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-plate {
|
||||||
|
min-height: 42px;
|
||||||
|
border-color: #b9d2fb;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: #edf4ff;
|
||||||
|
padding-inline: 14px;
|
||||||
|
color: var(--v2-blue);
|
||||||
|
font-size: 18px;
|
||||||
|
letter-spacing: .01em;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-online-label.semi-tag {
|
||||||
|
min-height: 25px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding-inline: 9px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-identity-vin {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-vin small,
|
||||||
|
.v2-vehicle-command-header .v2-identity-vin b,
|
||||||
|
.v2-vehicle-command-header .v2-identity-vin .semi-button {
|
||||||
|
grid-row: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-vin small {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: #8290a2;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-vin b {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #41536a;
|
||||||
|
font-size: 11px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
letter-spacing: .015em;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-vin .semi-button {
|
||||||
|
width: 28px;
|
||||||
|
min-width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border-radius: 7px;
|
||||||
|
color: #73849a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
grid-template-columns: minmax(180px, 1.15fr) minmax(160px, .85fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
border: 0;
|
||||||
|
border-left: 1px solid #e4eaf2;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta > div {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta > div + div {
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
border-left: 1px solid #e7edf4;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta small {
|
||||||
|
display: block;
|
||||||
|
color: #7b899b;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta strong {
|
||||||
|
display: block;
|
||||||
|
margin-top: 7px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #2c4057;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.45;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-sources {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
margin: 7px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta .semi-tag {
|
||||||
|
min-height: 22px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding-inline: 7px;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-actions {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 7px;
|
||||||
|
border-left: 1px solid #e4eaf2;
|
||||||
|
padding: 15px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-actions > .semi-button {
|
||||||
|
min-width: 100px;
|
||||||
|
min-height: 38px;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding-inline: 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-actions > .semi-button-primary {
|
||||||
|
border-color: #bcd3f8;
|
||||||
|
background: #edf4ff;
|
||||||
|
color: var(--v2-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-live-section {
|
||||||
|
border-top: 1px solid #dfe7f0;
|
||||||
|
background: #fbfcfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-live-section > .v2-workspace-panel-header {
|
||||||
|
min-height: 58px;
|
||||||
|
border-bottom: 1px solid #e6ecf3;
|
||||||
|
background: #f8fafd;
|
||||||
|
padding: 10px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-live-section .v2-live-report-meta {
|
||||||
|
max-width: min(520px, 42vw);
|
||||||
|
color: #65778d;
|
||||||
|
font-size: 10px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div {
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 104px;
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 1px solid #e7edf4;
|
||||||
|
padding: 15px 16px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div:first-child {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div:nth-child(4) {
|
||||||
|
border-left: 1px solid #e7edf4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div:nth-child(n + 4) {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div::after {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
bottom: 0;
|
||||||
|
left: 16px;
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 999px 999px 0 0;
|
||||||
|
background: #dce5ef;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div:nth-child(-n + 4)::after {
|
||||||
|
background: #8eb6f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid small {
|
||||||
|
color: #6c7d92;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid strong,
|
||||||
|
.v2-vehicle-command-card .v2-live-source-link {
|
||||||
|
margin-top: 9px;
|
||||||
|
color: #213950;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 750;
|
||||||
|
letter-spacing: -.025em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid strong.is-text {
|
||||||
|
font-size: 15px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-source-link .semi-button-content {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid em {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: #8290a3;
|
||||||
|
font-size: 9px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div > span:last-child {
|
||||||
|
display: block;
|
||||||
|
margin-top: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #8996a7;
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 1.35;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-location {
|
||||||
|
min-height: 58px;
|
||||||
|
grid-template-columns: minmax(310px, .8fr) minmax(420px, 1.2fr);
|
||||||
|
border-top: 1px solid #e4eaf2;
|
||||||
|
background: #f8fafd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-location > span {
|
||||||
|
padding: 9px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-location small {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-location strong {
|
||||||
|
color: #405269;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-address-action.semi-button {
|
||||||
|
min-height: 30px;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-page .v2-record-grid {
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-page .v2-events-card .v2-event-list .v2-event-row {
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-page .v2-events-card .v2-event-row strong {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-page .v2-events-card .v2-event-row p,
|
||||||
|
.v2-vehicle-record-page .v2-events-card .v2-event-row time {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1400px) {
|
||||||
|
.v2-vehicle-command-header {
|
||||||
|
grid-template-columns: minmax(300px, .85fr) minmax(0, 1.15fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-actions {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-top: 1px solid #e4eaf2;
|
||||||
|
border-left: 0;
|
||||||
|
padding: 9px 14px;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-actions > .semi-button {
|
||||||
|
min-width: 118px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.v2-vehicle-record-page {
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header {
|
||||||
|
grid-template-columns: minmax(250px, .8fr) minmax(0, 1.2fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-primary {
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta {
|
||||||
|
grid-template-columns: minmax(0, 1.1fr) minmax(130px, .9fr);
|
||||||
|
border-top: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-left: 1px solid #e4eaf2;
|
||||||
|
padding: 13px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta > div {
|
||||||
|
padding-inline: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta > div + div {
|
||||||
|
margin: 0;
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 1px solid #e7edf4;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-actions {
|
||||||
|
display: flex;
|
||||||
|
grid-template-columns: none;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
|
scroll-snap-type: x proximity;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div {
|
||||||
|
min-width: 148px;
|
||||||
|
min-height: 100px;
|
||||||
|
flex: 0 0 148px;
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 1px solid #e7edf4;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div:first-child {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-location {
|
||||||
|
grid-template-columns: minmax(210px, .78fr) minmax(0, 1.22fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-location > span + span {
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 1px solid #e4eaf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-nav .v2-workspace-panel-header {
|
||||||
|
min-height: 50px;
|
||||||
|
padding: 7px 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-nav .v2-workspace-panel-copy > .semi-typography {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.v2-vehicle-record-page {
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card.semi-card {
|
||||||
|
border-radius: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-primary {
|
||||||
|
padding: 13px 13px 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-plate {
|
||||||
|
min-height: 38px;
|
||||||
|
padding-inline: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta {
|
||||||
|
grid-template-columns: minmax(0, 1.15fr) minmax(120px, .85fr);
|
||||||
|
border-top: 1px solid #e7edf4;
|
||||||
|
border-left: 0;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta > div {
|
||||||
|
padding-inline: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta > div + div {
|
||||||
|
padding-inline: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta strong {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-sources {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-top: 5px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-meta .semi-tag {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-actions {
|
||||||
|
display: flex;
|
||||||
|
grid-template-columns: none;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-actions::-webkit-scrollbar,
|
||||||
|
.v2-vehicle-command-header .v2-identity-sources::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-header .v2-identity-actions > .semi-button {
|
||||||
|
width: auto;
|
||||||
|
min-width: 112px;
|
||||||
|
min-height: 38px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-live-section > .v2-workspace-panel-header {
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 8px 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-live-section .v2-workspace-panel-copy > .semi-typography {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-live-section .v2-live-report-meta {
|
||||||
|
max-width: 48vw;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid > div {
|
||||||
|
min-width: 138px;
|
||||||
|
min-height: 94px;
|
||||||
|
flex-basis: 138px;
|
||||||
|
padding: 13px 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-live-grid strong,
|
||||||
|
.v2-vehicle-command-card .v2-live-source-link {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-location {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-location > span {
|
||||||
|
min-height: 46px;
|
||||||
|
padding: 8px 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-command-card .v2-current-location > span + span {
|
||||||
|
border-top: 1px solid #e4eaf2;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-nav .v2-workspace-panel-header {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-nav .v2-workspace-panel-copy > h5.semi-typography {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-section-nav > .semi-button {
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-page .v2-record-grid {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-vehicle-record-page .v2-single-map-card {
|
||||||
|
min-height: 330px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user