Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cc872ceb6 | ||
|
|
39d9cb5403 |
@@ -7,6 +7,10 @@ import './v2/styles/workspace.css';
|
||||
import './v2/styles/foundation.css';
|
||||
import './v2/styles/experience.css';
|
||||
import './v2/styles/event-center.css';
|
||||
import './v2/styles/apple-workspace.css';
|
||||
import './v2/styles/apple-data-pages.css';
|
||||
import './v2/styles/apple-governance.css';
|
||||
import './v2/styles/apple-map-pages.css';
|
||||
|
||||
if ('scrollRestoration' in window.history) {
|
||||
window.history.scrollRestoration = 'manual';
|
||||
|
||||
@@ -115,10 +115,8 @@ test('opens contextual help for the active module and closes it without navigati
|
||||
|
||||
expect(screen.getByRole('dialog', { name: '全局监控' })).toHaveTextContent('筛选会自动生效');
|
||||
expect(document.querySelector('.v2-help-sheet')).toHaveClass('v2-workspace-help-sidesheet');
|
||||
const helpSummary = screen.getByRole('list', { name: '全局监控摘要' });
|
||||
expect(helpSummary).toHaveTextContent('页面目标快速完成');
|
||||
expect(helpSummary).toHaveTextContent('建议步骤3 项');
|
||||
expect(helpSummary).toHaveTextContent('快捷关闭Esc');
|
||||
expect(screen.getByRole('dialog', { name: '全局监控' })).toHaveTextContent('查看全车队实时位置、状态和最新上报。');
|
||||
expect(document.querySelectorAll('.v2-help-panel ol > li')).toHaveLength(3);
|
||||
expect(document.querySelector('.v2-help-overview.semi-card')).toBeInTheDocument();
|
||||
expect(screen.getByText('建议操作')).toBeInTheDocument();
|
||||
expect(help).toHaveAttribute('aria-expanded', 'true');
|
||||
|
||||
@@ -86,18 +86,12 @@ function ContextHelp({ section, visible, mobile, onClose }: { section: string; v
|
||||
title={title}
|
||||
description="当前页面帮助与推荐操作"
|
||||
badge={`${content.tips.length} 项建议`}
|
||||
summaryItems={[
|
||||
{ label: '页面目标', value: '快速完成', detail: '保留当前任务状态', tone: 'primary' },
|
||||
{ label: '建议步骤', value: `${content.tips.length} 项`, detail: '按顺序执行' },
|
||||
{ label: '快捷关闭', value: 'Esc', detail: '随时返回页面' }
|
||||
]}
|
||||
footerNote="关闭帮助不会改变当前筛选、地图或任务状态"
|
||||
primaryAction={{ label: '返回当前页面', onClick: onClose }}
|
||||
onCancel={onClose}
|
||||
>
|
||||
<div className="v2-help-panel">
|
||||
<Card className="v2-help-overview" bodyStyle={{ padding: 0 }}>
|
||||
<Tag color="blue">页面目标</Tag>
|
||||
<Text type="secondary">{content.summary}</Text>
|
||||
</Card>
|
||||
<div className="v2-help-section-label"><Text strong>建议操作</Text><Text type="tertiary" size="small">{content.tips.length} 项</Text></div>
|
||||
|
||||
@@ -127,7 +127,6 @@ test('removes old access rows immediately when the vehicle filter scope changes'
|
||||
expect(await screen.findByText('旧接入车牌')).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: '接入健康工作台', level: 5 })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('接入管理操作')).toHaveClass('v2-workspace-command-bar', 'v2-access-command-bar', 'has-identity');
|
||||
expect(screen.getByLabelText('接入管理操作')).toHaveTextContent('来源治理');
|
||||
expect(screen.getByLabelText('接入管理操作').querySelector('.v2-workspace-command-icon.semi-avatar')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-access-discovery-shell')).toBeInTheDocument();
|
||||
for (const className of ['v2-access-filter-card-v3', 'v2-access-table-v3']) {
|
||||
|
||||
@@ -601,7 +601,6 @@ export default function AccessPage() {
|
||||
<WorkspaceCommandBar
|
||||
className="v2-access-command-bar"
|
||||
ariaLabel="接入管理操作"
|
||||
eyebrow="来源治理"
|
||||
icon={<IconConnectionPoint1 />}
|
||||
title="接入健康工作台"
|
||||
description={mobileLayout
|
||||
@@ -679,7 +678,6 @@ export default function AccessPage() {
|
||||
<div className="v2-access-workspace-v3">
|
||||
<Card className="v2-access-table-v3" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
eyebrow="接入差异"
|
||||
tone="primary"
|
||||
title="接入差异清单"
|
||||
description={`当前范围:${scopeLabels[criteria.connectionState] || '全部主车辆'} · 缺席协议仅表示当前未发现`}
|
||||
|
||||
@@ -1395,6 +1395,7 @@ test.each([false, true])('displays native GB32960 alarms without automation (mob
|
||||
expect(screen.getByLabelText('具体告警信息')).toHaveTextContent('绝缘报警');
|
||||
expect(screen.getByLabelText('具体告警信息')).toHaveTextContent('驱动电机温度报警');
|
||||
expect(screen.getByLabelText('具体告警信息')).toHaveTextContent('bit20、bit21');
|
||||
if (!mobile) expect(screen.getByText('车辆原生告警 · GB/T 32960')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('事件执行轨迹')).toHaveTextContent('车辆原生告警入库');
|
||||
expect(screen.getByLabelText('事件执行轨迹')).not.toHaveTextContent('匹配自动化');
|
||||
});
|
||||
|
||||
@@ -342,7 +342,7 @@ const AlertEventTable = memo(function AlertEventTable({ rows, selectedID, compac
|
||||
{ title: '协议', dataIndex: 'protocol', className: 'is-protocol', width: 98, render: (value: string) => <ProtocolTag className="v2-alert-protocol-tag" protocol={value} compact /> },
|
||||
{ title: '发生时间', dataIndex: 'triggeredAt', className: 'is-triggered-at', width: 124, render: (value: string) => <AlertTime value={value} /> },
|
||||
{ title: '事件来源 / 自动化', dataIndex: 'ruleName', className: 'is-rule', width: 154, render: (value: string, event: AlertEvent) => <span className="v2-alert-event-rule" title={value}>{isNativeAlarm(event) ? '车辆原生告警' : value}</span> },
|
||||
{ title: '执行状态', dataIndex: 'status', className: 'is-status', width: 92, render: (_: AlertEvent['status'], event: AlertEvent) => <EventExecutionTag event={event} /> },
|
||||
{ title: '执行状态', dataIndex: 'status', className: 'is-status', width: 92, fixed: 'right' as const, render: (_: AlertEvent['status'], event: AlertEvent) => <EventExecutionTag event={event} /> },
|
||||
];
|
||||
return compact
|
||||
? allColumns.map((column) => ({ ...column, width: COMPACT_ALERT_COLUMN_WIDTHS[column.dataIndex] ?? column.width }))
|
||||
@@ -352,6 +352,7 @@ const AlertEventTable = memo(function AlertEventTable({ rows, selectedID, compac
|
||||
return <Table
|
||||
className={`v2-alert-event-table${compact ? ' is-compact' : ''}`}
|
||||
columns={columns}
|
||||
scroll={{ x: columns.reduce((width, column) => width + column.width, 0) }}
|
||||
dataSource={rows}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
@@ -378,7 +379,7 @@ function EventInspector({ event, loading = false, detailError, note, acting, act
|
||||
</Card>;
|
||||
const normalized = toVehicleEvent(event);
|
||||
return <Card className={`v2-alert-inspector${sheet ? ' is-sheet' : ''}`} bodyStyle={{ padding: 0 }}>
|
||||
{!sheet ? <WorkspacePanelHeader className="v2-alert-inspector-heading" title="事件详情" description={normalized.type} actions={<><span className="v2-alert-inspector-status"><EventExecutionTag event={event} /></span><Button className="v2-alert-inspector-close" theme="borderless" icon={<IconClose />} aria-label="关闭事件详情" onClick={onClose} /></>} /> : null}
|
||||
{!sheet ? <WorkspacePanelHeader className="v2-alert-inspector-heading" title="事件详情" description={isNativeAlarm(event) ? "车辆原生告警 · GB/T 32960" : `${normalized.categoryLabel} · 自动化事件`} actions={<><span className="v2-alert-inspector-status"><EventExecutionTag event={event} /></span><Button className="v2-alert-inspector-close" theme="borderless" icon={<IconClose />} aria-label="关闭事件详情" onClick={onClose} /></>} /> : null}
|
||||
<div className="v2-alert-inspector-content">
|
||||
{detailError ? <InlineError message={`最新事件状态同步失败:${detailError}`} onRetry={onRetry} /> : null}
|
||||
<Card className="v2-alert-detail-card v2-alert-focus-card" bodyStyle={{ padding: 0 }} aria-label="事件摘要">
|
||||
@@ -392,7 +393,7 @@ function EventInspector({ event, loading = false, detailError, note, acting, act
|
||||
<span><small>发生时间</small><strong><AlertTime value={normalized.occurredAt} /></strong></span>
|
||||
<span><small>{isNativeAlarm(event) ? '事件来源' : '匹配自动化'}</small><strong>{isNativeAlarm(event) ? '车辆原生告警' : `v${event.ruleVersion}`}</strong></span>
|
||||
</div>
|
||||
{isNativeAlarm(event) ? <section aria-label="32960 原生告警字段">
|
||||
{isNativeAlarm(event) ? <section className="v2-native-alarm-fields" aria-label="32960 原生告警字段">
|
||||
<div className="v2-native-alarm-meaning" aria-label="具体告警信息">
|
||||
<strong>具体告警信息</strong>
|
||||
{event.nativeAlarmNames?.length ? <ul>{event.nativeAlarmNames.map((name) => <li key={name}>{name}</li>)}</ul> : <p>{event.ruleName}</p>}
|
||||
@@ -1245,7 +1246,7 @@ function RulesWorkspace({ page, rulesReady, rulesLoading, rulesError, onRetryRul
|
||||
<Button className={`v2-alert-rule-item${selectedRule?.id === rule.id ? ' is-selected' : ''}`} theme="borderless" type="tertiary" aria-pressed={selectedRule?.id === rule.id} aria-haspopup={mobileLayout ? 'dialog' : undefined} aria-expanded={mobileLayout ? (mobileDetailOpen || editorOpen) && selectedID === rule.id : selectedRule?.id === rule.id} onClick={() => mobileLayout ? openMobileRuleDetail(rule) : selectRule(rule)}>
|
||||
<span className="v2-alert-rule-copy"><span><strong>{rule.name}</strong><Tag color={rule.archivedAt ? 'amber' : rule.enabled ? 'green' : 'grey'} type="light" size="small">{rule.archivedAt ? '已归档' : rule.enabled ? '运行中' : '已停用'}</Tag></span><small>{automationSource(rule)} · {ruleCondition(rule, catalogLabels, catalogUnits)}</small></span>
|
||||
</Button>
|
||||
{rule.archivedAt ? <Tag className="v2-alert-automation-readonly" color="grey" type="light" size="small">只读</Tag> : <Switch className="v2-alert-automation-switch" checked={rule.enabled} loading={toggle.isPending} aria-label={`${rule.enabled ? '停用' : '启用'}自动化:${rule.name}`} onChange={() => toggle.mutate(rule)} />}
|
||||
{rule.archivedAt ? <Tag className="v2-alert-automation-readonly" color="grey" type="light" size="small">只读</Tag> : <label className="v2-alert-automation-switch-target"><Switch className="v2-alert-automation-switch" checked={rule.enabled} loading={toggle.isPending} aria-label={`${rule.enabled ? '停用' : '启用'}自动化:${rule.name}`} onChange={() => toggle.mutate(rule)} /></label>}
|
||||
<small className="v2-alert-automation-version">v{rule.version}</small>
|
||||
</article>)}
|
||||
{rulesLoading ? <PanelLoading compact title="正在检索自动化" description="从完整规则库读取当前页。" /> : rulesError ? <PanelError compact title="自动化检索失败" description={rulesError} action={<Button theme="solid" type="primary" onClick={onRetryRules}>重新加载</Button>} /> : !filteredRules.length ? <PanelEmpty className="v2-alert-rule-empty" tone="primary" icon={lifecycle === 'archived' ? <IconBox /> : <IconSearch />} title={search || protocolFilter || statusFilter !== 'all' ? '没有匹配的自动化' : lifecycle === 'archived' ? '暂无审计归档' : '暂无自动化'} description={search || protocolFilter || statusFilter !== 'all' ? '换一个名称、协议或状态筛选试试。' : lifecycle === 'archived' ? '停用规则可在详情中带原因归档,并在这里恢复。' : '创建第一条事件自动化。'} /> : null}
|
||||
|
||||
@@ -577,10 +577,7 @@ test('clears stale rows, charts, and evidence as soon as the history scope chang
|
||||
expect(view.container.querySelector(`.${className}.semi-card`)).toBeInTheDocument();
|
||||
}
|
||||
expect(view.container.querySelector('.v2-history-discovery-shell')).toContainElement(screen.getByLabelText('历史数据查询操作'));
|
||||
expect(screen.getByLabelText('历史数据查询操作')).toHaveClass('has-identity');
|
||||
expect(screen.getByLabelText('历史数据查询操作')).toHaveTextContent('历史证据');
|
||||
expect(screen.getByLabelText('历史数据查询操作').querySelector('.v2-workspace-command-icon.semi-avatar')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('历史数据查询操作')).toHaveTextContent('遥测证据检索');
|
||||
expect(screen.getByLabelText('历史数据查询操作')).toHaveTextContent('查询条件');
|
||||
expect(screen.getByLabelText('历史数据查询操作')).toHaveTextContent('07-16 00:00 – 07-16 05:00');
|
||||
expect(screen.getByLabelText('历史数据查询操作')).toHaveTextContent('位置数据');
|
||||
expect(screen.getByLabelText('历史数据查询操作')).toHaveTextContent('全部来源');
|
||||
|
||||
@@ -1894,10 +1894,8 @@ export default function HistoryPage() {
|
||||
<WorkspaceCommandBar
|
||||
className="v2-history-command-bar"
|
||||
ariaLabel="历史数据查询操作"
|
||||
eyebrow="历史证据"
|
||||
icon={<IconHistory />}
|
||||
title="遥测证据检索"
|
||||
description="按车辆、时间和协议核验可追溯的历史数据"
|
||||
title="查询条件"
|
||||
description="选择车辆和时间,查询历史上报"
|
||||
status={keywords.length ? `${keywords.length} 辆 · ${(result?.total ?? 0).toLocaleString('zh-CN')} 条` : '等待查询'}
|
||||
statusColor={keywords.length ? 'blue' : 'grey'}
|
||||
meta={keywords.length ? <span className="v2-history-applied-scope" aria-label="已应用查询范围">
|
||||
@@ -2029,8 +2027,6 @@ export default function HistoryPage() {
|
||||
<HistoryTrend response={seriesQuery.data} category={criteria.category} loading={seriesQuery.isFetching} error={seriesQuery.isError ? (seriesQuery.error instanceof Error ? seriesQuery.error.message : '未知错误') : undefined} hasMetrics={Boolean(seriesMetricKey)} expanded={trendExpanded} onToggle={() => setTrendExpanded((value) => !value)} onRetry={() => { void seriesQuery.refetch(); }} />
|
||||
<Card className={`v2-history-table-card is-${density}`} bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
eyebrow="证据结果"
|
||||
tone="primary"
|
||||
title="历史明细"
|
||||
description={keywords.length ? `${(result?.total ?? 0).toLocaleString('zh-CN')} 条记录 · ${resultSummary?.vehicleCount ?? 0} 辆车辆 · ${summarySources}` : '选择车辆和时间范围后查询'}
|
||||
meta={<span className="v2-history-result-meta"><Tag color={activeRawSegment ? 'cyan' : activeFieldViewName.includes('未保存') ? 'orange' : 'blue'} type="light" size="small">{activeRawSegment ? `${rawSegments.find((segment) => segment.key === activeRawSegment)?.label} · ${displayMetrics.length - 2} 字段` : `${activeFieldViewName} · ${visibleMetrics.length} / ${allMetrics.length} 字段`}</Tag>{resultSummary ? <span className="v2-history-query-duration">查询 {resultSummary.queryDurationMs} ms</span> : null}</span>}
|
||||
@@ -2096,15 +2092,9 @@ export default function HistoryPage() {
|
||||
/>
|
||||
: <WorkspaceEmptyGuide
|
||||
className="v2-history-empty"
|
||||
eyebrow={mobileLayout ? undefined : '从一个明确的车辆范围开始'}
|
||||
icon={<IconSearch />}
|
||||
title="选择车辆,开始核验历史证据"
|
||||
description="一次最多查询 5 台车辆,结果会保留协议来源、质量说明与 RAW 证据。"
|
||||
steps={mobileLayout ? undefined : [
|
||||
{ icon: <IconSearch />, title: '选择车辆', description: '支持车牌、VIN 与批量粘贴' },
|
||||
{ icon: <IconCalendar />, title: '确认时间', description: '按上海时区精确到分钟' },
|
||||
{ icon: <IconMapPin />, title: '核验数据', description: '位置、RAW 与日里程统一检索' }
|
||||
]}
|
||||
action={<Button className="v2-history-empty-action" theme="solid" icon={<IconSearch />} aria-label="选择车辆并查询" onClick={() => setFiltersCollapsed(false)}>选择车辆并查询</Button>}
|
||||
/>
|
||||
: null}
|
||||
|
||||
@@ -620,7 +620,7 @@ export default function MonitorPage() {
|
||||
{mode === 'map' ? <section className={`v2-monitor-workspace${selected && detailOpen ? ' is-detail-open' : ''}${selected && !detailOpen ? ' is-detail-collapsed' : ''}`}>
|
||||
<div className="v2-vehicle-rail">
|
||||
<header><strong>车辆列表</strong><span>{batchSearchPending ? '查询中' : `${formatNumber(vehicles.data?.total ?? visibleRows.length)} 辆`}</span></header>
|
||||
<div className={`v2-rail-search${batchMatch.missing.length && !batchSearchPending ? ' has-missing' : ''}`} title={searchTerms.length > 1 ? batchStatusTitle : undefined}><IconSearch /><span>{searchTerms.length > 1 ? batchStatus : deferredKeyword ? `正在筛选“${deferredKeyword}”` : '按最新上报排序'}</span></div>
|
||||
<div role="status" className={`v2-rail-search${batchMatch.missing.length && !batchSearchPending ? ' has-missing' : ''}`} title={searchTerms.length > 1 ? batchStatusTitle : undefined}><span>{searchTerms.length > 1 ? batchStatus : deferredKeyword ? `正在筛选“${deferredKeyword}”` : '最新上报优先'}</span></div>
|
||||
<div className="v2-vehicle-scroll">
|
||||
{vehicles.isLoading || batchSearchPending ? <div className="v2-list-loading"><span className="v2-spinner" />{batchSearchPending ? `正在查找 ${searchTerms.length} 辆车` : '加载车辆'}</div> : null}
|
||||
{!vehicles.isLoading && !vehicles.isError && !batchSearchPending && visibleRows.length === 0 ? <EmptyState /> : null}
|
||||
|
||||
@@ -79,7 +79,6 @@ test('renders reconciliation queue, loads evidence on demand and records review
|
||||
expect(await screen.findByText('质量问题队列')).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: '运行与数据质量', level: 5 })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('运维质量操作')).toHaveClass('v2-workspace-command-bar', 'v2-ops-command-bar', 'has-identity');
|
||||
expect(screen.getByLabelText('运维质量操作')).toHaveTextContent('运行治理');
|
||||
expect(screen.getByLabelText('运维质量操作').querySelector('.v2-workspace-command-icon.semi-avatar')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '刷新全局证据' })).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted');
|
||||
const navigation = screen.getByRole('navigation', { name: '运维质量视图' });
|
||||
@@ -294,7 +293,11 @@ test('renders only the compact mobile reconciliation surface and defers secondar
|
||||
expect(toolbar).not.toHaveClass('is-mobile-collapsed');
|
||||
|
||||
expect(screen.queryByText('近 30 天趋势')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '30 天趋势' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '质量问题更多操作' }));
|
||||
expect(await screen.findByRole('menuitem', { name: '批量处置' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('menuitem', { name: /导出当前筛选/ })).toBeInTheDocument();
|
||||
expect(screen.getByRole('menuitem', { name: /刷新结果/ })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: /30 天趋势/ }));
|
||||
expect(await screen.findByRole('dialog', { name: '30 天差异趋势' })).toBeInTheDocument();
|
||||
expect(document.querySelector('.v2-reconcile-trend-sidesheet')).toHaveClass('semi-sidesheet-bottom', 'v2-workspace-detail-sidesheet');
|
||||
expect(document.querySelector('.v2-reconcile-trend-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(72dvh, 620px)' });
|
||||
|
||||
@@ -598,7 +598,6 @@ export default function OperationsPage() {
|
||||
<WorkspaceCommandBar
|
||||
className="v2-ops-command-bar"
|
||||
ariaLabel="运维质量操作"
|
||||
eyebrow="运行治理"
|
||||
icon={<IconPulse />}
|
||||
tone="health"
|
||||
title="运行与数据质量"
|
||||
@@ -634,7 +633,6 @@ export default function OperationsPage() {
|
||||
{readiness.isError ? <InlineError message={readiness.error instanceof Error ? readiness.error.message : '协议来源就绪度读取失败'} onRetry={() => readiness.refetch()} /> : null}
|
||||
<Card className="v2-ops-overview" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
eyebrow="运行快照"
|
||||
tone="health"
|
||||
title="运行概览"
|
||||
description="关键链路、基础设施与车辆覆盖的同一健康快照"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconAlertTriangle, IconBox, IconChevronDown, IconChevronRight, IconChevronUp, IconClose, IconDownload, IconHistory, IconRefresh, IconSearch, IconTickCircle } from '@douyinfe/semi-icons';
|
||||
import { Button, Card, Checkbox, Input, RadioGroup, Select, Table, Tag, TextArea, Toast } from '@douyinfe/semi-ui';
|
||||
import { IconAlertTriangle, IconBox, IconChevronDown, IconChevronRight, IconChevronUp, IconClose, IconDownload, IconHistory, IconMore, IconRefresh, IconSearch, IconTickCircle } from '@douyinfe/semi-icons';
|
||||
import { Button, Card, Checkbox, Dropdown, Input, RadioGroup, Select, Table, Tag, TextArea, Toast } from '@douyinfe/semi-ui';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useDeferredValue, useEffect, useId, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
@@ -615,6 +615,7 @@ function ReconciliationBatchReview({ issues, visible, mobileLayout, onCancel, on
|
||||
|
||||
export default function ReconciliationCenter() {
|
||||
const mobileLayout = useMobileLayout();
|
||||
const [mobileActionsOpen, setMobileActionsOpen] = useState(false);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const initialScope = searchParams.get('reconcileScope') === 'archived' ? 'archived' : 'current';
|
||||
const defaultPageSize = mobileLayout ? MOBILE_PAGE_SIZE : DESKTOP_PAGE_SIZE;
|
||||
@@ -843,12 +844,23 @@ export default function ReconciliationCenter() {
|
||||
return <><Card className="v2-reconcile-center" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
className="v2-reconcile-heading"
|
||||
eyebrow="自动对账"
|
||||
tone="health"
|
||||
title="质量问题队列"
|
||||
description="自动发现并合并重复问题;复核人员基于原始证据形成结论,不直接改写数据。"
|
||||
meta="每日自动对账"
|
||||
actions={<>
|
||||
actions={mobileLayout ? <Dropdown
|
||||
trigger="click"
|
||||
position="bottomRight"
|
||||
visible={mobileActionsOpen}
|
||||
onVisibleChange={setMobileActionsOpen}
|
||||
render={<Dropdown.Menu>
|
||||
{!batchMode && scope === 'current' ? <Dropdown.Item onClick={() => { setMobileActionsOpen(false); enterBatchMode('operate'); }}>批量处置</Dropdown.Item> : null}
|
||||
{!batchMode && administrator ? <Dropdown.Item icon={scope === 'archived' ? <IconRefresh /> : <IconBox />} onClick={() => { setMobileActionsOpen(false); enterBatchMode('lifecycle'); }}>{scope === 'archived' ? '批量恢复' : '批量归档'}</Dropdown.Item> : null}
|
||||
{!batchMode ? <Dropdown.Item icon={<IconDownload />} disabled={!page?.total || exportIssues.isPending} onClick={() => { setMobileActionsOpen(false); exportIssues.mutate(); }}>{exportIssues.isPending ? '正在导出…' : '导出当前筛选'}</Dropdown.Item> : null}
|
||||
{scope === 'current' ? <Dropdown.Item icon={<IconHistory />} onClick={() => { setMobileActionsOpen(false); toggleTrend(); }}>30 天趋势</Dropdown.Item> : null}
|
||||
<Dropdown.Item icon={<IconRefresh />} disabled={summary.isFetching || issues.isFetching} onClick={() => { setMobileActionsOpen(false); refresh(); }}>刷新结果</Dropdown.Item>
|
||||
</Dropdown.Menu>}
|
||||
><Button className="v2-reconcile-mobile-more" theme="borderless" icon={<IconMore />} aria-haspopup="menu" aria-expanded={mobileActionsOpen} aria-label="质量问题更多操作">更多操作</Button></Dropdown> : <>
|
||||
{!batchMode && scope === 'current' ? <Button className="v2-reconcile-batch-entry v2-workspace-mobile-tool-button is-muted" theme="light" aria-label="进入批量处置" onClick={() => enterBatchMode('operate')}>{mobileLayout ? '批量' : '批量处置'}</Button> : null}
|
||||
{!batchMode && administrator ? <Button className="v2-workspace-mobile-tool-button is-muted" theme="light" type="tertiary" icon={scope === 'archived' ? <IconRefresh /> : <IconBox />} aria-label={scope === 'archived' ? '进入批量恢复' : '进入批量归档'} onClick={() => enterBatchMode('lifecycle')}>{mobileLayout ? scope === 'archived' ? '恢复' : '归档' : scope === 'archived' ? '批量恢复' : '批量归档'}</Button> : null}
|
||||
{!batchMode ? <Button className="v2-reconcile-export v2-workspace-mobile-tool-button is-muted" theme="light" icon={<IconDownload />} aria-label="导出当前筛选" loading={exportIssues.isPending} disabled={!page?.total} onClick={() => exportIssues.mutate()}>{mobileLayout ? '导出' : '导出当前筛选'}</Button> : null}
|
||||
|
||||
@@ -1349,8 +1349,6 @@ export default function StatisticsPage() {
|
||||
</div>
|
||||
<Card className="v2-mileage-results" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
eyebrow={metricView === 'mileage' ? '每日里程' : '每日氢耗'}
|
||||
tone={metricView === 'mileage' ? 'primary' : 'health'}
|
||||
title={metricView === 'mileage' ? '车辆每日里程' : '车辆每日氢耗'}
|
||||
description={`${criteria.dateFrom} 至 ${criteria.dateTo}`}
|
||||
meta={<span className="v2-workspace-result-meta"><Tag color="blue" type="light" size="small">{hasVehicles ? `${totalVehicles} 辆` : `${displayVehicles.length} / ${totalVehicles} 辆`}</Tag><span>{dates.length} 个自然日</span></span>}
|
||||
@@ -1370,6 +1368,6 @@ export default function StatisticsPage() {
|
||||
<footer>{totalVehicles > PAGE_SIZE ? <TablePagination page={page} totalPages={totalPages} info={`${hasVehicles ? '已选择' : '共'} ${totalVehicles.toLocaleString('zh-CN')} 辆 · 每页 ${PAGE_SIZE} 辆${exportFeedback ? ` · ${exportFeedback}` : ''}`} disabled={mileage.isFetching || (!hasVehicles && fleetVehicles.isFetching)} onPageChange={setPage} /> : <span className="v2-table-pagination-info">{hasVehicles ? '已选择' : '共'} {totalVehicles.toLocaleString('zh-CN')} 辆车辆{exportFeedback ? ` · ${exportFeedback}` : ''}</span>}</footer>
|
||||
</Card>
|
||||
<HydrogenEvidenceSheet target={hydrogenEvidenceTarget} onClose={() => setHydrogenEvidenceTarget(undefined)} />
|
||||
<footer className="v2-mileage-evidence"><span>数据更新时间:{statistics.data?.asOf || '—'}</span><span>来源优先级:{criteria.sources.filter((source) => source.enabled).map((source) => source.protocol).join(' > ')}</span><span>当前筛选复用 1 分钟 · 离开后释放明细</span></footer>
|
||||
<footer className="v2-mileage-evidence"><span>数据更新时间:{statistics.data?.asOf || '—'}</span><span>来源优先级:{criteria.sources.filter((source) => source.enabled).map((source) => source.protocol).join(' > ')}</span><span>按上海时区统计自然日</span></footer>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -160,6 +160,9 @@ test('explains an empty selected-vehicle time window without asking to select th
|
||||
expect(screen.getByText('车辆已选定,可调整时间范围或数据来源后重新查询。')).toBeInTheDocument();
|
||||
expect(screen.queryByText('先选择车辆,再开始轨迹回放')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '修改查询' })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '修改查询' }));
|
||||
expect(screen.getByRole('textbox', { name: '搜索轨迹车辆' })).toHaveValue('LTEST000000000001');
|
||||
expect(screen.getByRole('button', { name: /查询轨迹/ })).toBeEnabled();
|
||||
});
|
||||
|
||||
test('distinguishes a failed vehicle lookup from an empty result and retries in place', async () => {
|
||||
@@ -456,8 +459,8 @@ test('keeps one clear mobile query entry before a vehicle is selected', async ()
|
||||
expect(screen.queryByRole('button', { name: '打开轨迹详情' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('toolbar', { name: '地图轨迹工具' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('slider', { name: '轨迹播放进度' })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('slider', { name: '轨迹播放进度,等待查询结果' })).toBeDisabled();
|
||||
expect(view.container.querySelector('.v2-track-playback-dock.is-empty')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('slider', { name: '轨迹播放进度,等待查询结果' })).not.toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-track-playback-dock')).not.toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-workspace-empty-guide-eyebrow')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: /选择车辆/ }));
|
||||
expect(await screen.findByRole('dialog', { name: '轨迹查询与明细' })).toBeInTheDocument();
|
||||
|
||||
@@ -135,7 +135,7 @@ function trackSummaryItems(track?: TrackPlaybackResponse): WorkspaceQueueMetricR
|
||||
return [
|
||||
{
|
||||
label: distanceLabel,
|
||||
value: track ? `${number(track.summary.distanceKm)} km` : '—',
|
||||
value: track ? <>{number(track.summary.distanceKm)}<small className="v2-track-distance-unit"> km</small></> : '—',
|
||||
note: track ? formatDuration(track.summary.durationSeconds) : '等待查询',
|
||||
tone: 'primary',
|
||||
emphasis: 'primary'
|
||||
@@ -291,18 +291,18 @@ const TrackRail = memo(function TrackRail({ draft, applied, track, summaryItems,
|
||||
], []).map((preset) => ({ ...preset, active: activePreset === preset.key }));
|
||||
const compactAppliedWindow = compactTrackWindowLabel(applied.dateFrom, applied.dateTo);
|
||||
const appliedSource = applied.protocol ? protocolDisplayLabel(applied.protocol) : '自动来源';
|
||||
const contextTitle = queryCollapsed ? track?.plate || applied.keyword || '轨迹查询' : '轨迹查询';
|
||||
const contextTitle = queryCollapsed ? track?.plate || applied.keyword || '查询条件' : '查询条件';
|
||||
return <aside className="v2-track-rail">
|
||||
<Card className="v2-track-rail-shell" bodyStyle={{ padding: 0 }}>
|
||||
<section className={`v2-track-query-panel${queryCollapsed ? ' is-collapsed' : ''}${showCommandBar ? '' : ' is-command-hidden'}`}>
|
||||
{showCommandBar ? <WorkspaceCommandBar
|
||||
className="v2-track-query-header"
|
||||
ariaLabel="轨迹查询任务栏"
|
||||
eyebrow="轨迹回放"
|
||||
icon={<IconRoute />}
|
||||
eyebrow={track ? '轨迹回放' : undefined}
|
||||
icon={track ? <IconRoute /> : undefined}
|
||||
title={contextTitle}
|
||||
description={queryCollapsed ? `${compactAppliedWindow} · ${appliedSource}` : '最长支持连续 7 天'}
|
||||
status={loading ? '读取中' : track ? undefined : '等待查询'}
|
||||
description={queryCollapsed ? `${compactAppliedWindow} · ${appliedSource}` : '最长 7 天'}
|
||||
status={loading ? '读取中' : undefined}
|
||||
statusColor={loading ? 'blue' : track ? 'green' : 'grey'}
|
||||
actions={<>
|
||||
<Button
|
||||
@@ -335,7 +335,7 @@ const TrackRail = memo(function TrackRail({ draft, applied, track, summaryItems,
|
||||
</form> : null}
|
||||
</section>
|
||||
|
||||
<section className="v2-track-rail-result">
|
||||
{track ? <section className="v2-track-rail-result">
|
||||
<WorkspaceMetricRail
|
||||
variant="queue"
|
||||
ariaLabel="轨迹行程摘要"
|
||||
@@ -345,12 +345,11 @@ const TrackRail = memo(function TrackRail({ draft, applied, track, summaryItems,
|
||||
{track ? <TrackCoverageNotice track={track} onModify={onToggleQuery} onOverview={() => onTab('overview')} /> : null}
|
||||
<SegmentedTabs className="v2-track-rail-tabs" ariaLabel="轨迹明细分类" value={tab} onChange={onTab} items={[{ key: 'stops', label: '停留点', count: track?.stops.length ?? 0 }, { key: 'events', label: '事件点', count: track?.events.length ?? 0 }, { key: 'overview', label: '概览' }]} />
|
||||
<div className="v2-track-rail-scroll">
|
||||
{!track ? <PanelEmpty className="v2-track-rail-empty" compact tone="primary" icon={<IconMapPin />} title="选择车辆后查询轨迹" description="停留点、轨迹事件与行程证据会在这里统一呈现。" /> : null}
|
||||
{track && tab === 'stops' ? <List className="v2-track-evidence-list v2-track-stop-list">{track.stops.map((stop, index) => <List.Item className="v2-track-evidence-item" key={`${stop.startTime}-${index}`}><Button theme="borderless" type="tertiary" className={`v2-track-list-action${activeStopIndexes.includes(index) ? ' is-active' : ''}`} onClick={() => onSelectIndex(stop.sampledIndex)}><i>{index + 1}</i><span><strong>{dateTime(stop.startTime)}</strong><small>停留 {formatDuration(stop.durationSeconds)} · {stop.pointCount} 个点</small></span><em>{time(stop.endTime)}</em></Button></List.Item>)}{!track.stops.length ? <PanelEmpty className="v2-track-list-empty" compact icon={<IconMapPin />} title="当前没有停留点" description="时间窗内没有超过 3 分钟的连续停留。" /> : null}</List> : null}
|
||||
{track && tab === 'events' ? <List className="v2-track-evidence-list v2-track-event-list">{track.events.map((event, index) => { const sampled = sampledEventIndex(event, track.points.length, track.summary.pointCount); return <List.Item className="v2-track-evidence-item" key={`${event.type}-${event.time}-${index}`}><Button theme="borderless" type="tertiary" className={`v2-track-list-action${activeEventIndexes.includes(index) ? ' is-active' : ''}`} onClick={() => onSelectIndex(sampled)}><i className={`is-${eventTone(event.type)}`}>{index + 1}</i><span><strong>{event.title}</strong><small>{dateTime(event.time)}</small></span><em>{number(event.speedKmh, 0)} km/h</em></Button></List.Item>; })}{!track.events.length ? <PanelEmpty className="v2-track-list-empty" compact icon={<IconMapPin />} title="当前没有事件点" description="时间窗内没有识别到启停、急加速或异常间隔。" /> : null}</List> : null}
|
||||
{track && tab === 'overview' ? <OverviewPanel track={track} /> : null}
|
||||
</div>
|
||||
</section>
|
||||
</section> : <p className="v2-track-query-hint">选择车辆和时间范围后,行程明细会在这里呈现。</p>}
|
||||
</Card>
|
||||
</aside>;
|
||||
});
|
||||
@@ -479,6 +478,11 @@ export default function TrackPage() {
|
||||
}, [mobileLayout, selectIndex]);
|
||||
const collapseRail = useCallback(() => setRailCollapsed(true), []);
|
||||
const toggleQuery = useCallback(() => setQueryCollapsed((value) => !value), []);
|
||||
const openEmptyQuery = useCallback(() => {
|
||||
setQueryCollapsed(false);
|
||||
setRailCollapsed(false);
|
||||
window.requestAnimationFrame(() => document.querySelector<HTMLInputElement>('.v2-track-query input')?.focus());
|
||||
}, []);
|
||||
const togglePlayback = () => {
|
||||
if (points.length < 2) return;
|
||||
if (!playing && boundedIndex >= points.length - 1) setActiveIndex(0);
|
||||
@@ -601,12 +605,7 @@ export default function TrackPage() {
|
||||
icon={<IconMapPin />}
|
||||
title={emptyTrackTitle}
|
||||
description={emptyTrackDescription}
|
||||
steps={emptyTrackHasVehicle ? undefined : [
|
||||
{ icon: <IconSearch />, title: '选择车辆', description: '车牌或 VIN 精确定位' },
|
||||
{ icon: <IconMapPin />, title: '确认来源', description: '自动选择最佳定位来源' },
|
||||
{ icon: <IconPlay />, title: '播放核验', description: '逐点查看速度、里程与事件' }
|
||||
]}
|
||||
action={<Button theme="solid" icon={<IconSearch />} aria-label={emptyTrackHasVehicle ? '修改查询' : '选择车辆'} onClick={() => setRailCollapsed(false)}>{emptyTrackHasVehicle ? '修改查询' : '选择车辆'}</Button>}
|
||||
action={<Button theme="solid" icon={<IconSearch />} aria-label={emptyTrackHasVehicle ? '修改查询' : '选择车辆'} onClick={openEmptyQuery}>{emptyTrackHasVehicle ? '修改查询' : '选择车辆'}</Button>}
|
||||
/></Card>}
|
||||
|
||||
{query.isFetching ? <PanelLoading className="v2-track-loading" compact title="正在生成轨迹" description="正在校验来源覆盖并整理停留与事件证据。" /> : null}
|
||||
@@ -628,17 +627,6 @@ export default function TrackPage() {
|
||||
<label><span id="track-playback-speed-label">倍速</span><Select aria-labelledby="track-playback-speed-label" value={playbackSpeed} onChange={(value) => setPlaybackSpeed(Number(value) as PlaybackSpeed)} optionList={speedOptions.map((speed) => ({ value: speed, label: `${speed}×` }))} /></label>
|
||||
<Button theme="borderless" aria-label="回到起点" icon={<IconRefresh />} onClick={() => selectIndex(0)} disabled={!boundedIndex} />
|
||||
</div>
|
||||
</Card> : mobileLayout ? <Card className="v2-track-playback-dock is-empty" bodyStyle={{ padding: 0 }} aria-label="轨迹播放控制,等待查询结果">
|
||||
<div className="v2-track-dock-progress">
|
||||
<Slider className="v2-track-progress-slider" aria-label="轨迹播放进度,等待查询结果" min={0} max={1} value={0} disabled showBoundary={false} />
|
||||
<div className="v2-track-progress-meta"><time>00:00:00</time><span className="v2-track-progress-status">等待轨迹数据</span><time>00:00:00</time></div>
|
||||
</div>
|
||||
<div className="v2-track-dock-controls">
|
||||
<Button theme="borderless" aria-label="上一个轨迹点" icon={<IconChevronLeft />} disabled />
|
||||
<Button theme="solid" className="is-primary" aria-label="开始轨迹播放" icon={<IconPlay />} disabled><span className="v2-track-playback-primary-label">播放</span></Button>
|
||||
<Button theme="borderless" aria-label="下一个轨迹点" icon={<IconChevronRight />} disabled />
|
||||
<label><span id="track-empty-playback-speed-label">倍速</span><Select aria-labelledby="track-empty-playback-speed-label" value={1} disabled optionList={[{ value: 1, label: '1×' }]} /></label>
|
||||
</div>
|
||||
</Card> : null}
|
||||
</section>
|
||||
</div>;
|
||||
|
||||
@@ -158,7 +158,6 @@ test('deduplicates vehicle candidates by VIN and renders granted vehicles plate
|
||||
|
||||
const customer = await screen.findByRole('button', { name: /选择客户 华东客户/ });
|
||||
expect(screen.getByLabelText('账号管理操作')).toHaveClass('has-identity');
|
||||
expect(screen.getByLabelText('账号管理操作')).toHaveTextContent('访问治理');
|
||||
expect(screen.getByLabelText('账号管理操作').querySelector('.v2-workspace-command-icon.semi-avatar')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('账号管理操作')).toHaveTextContent('客户访问治理');
|
||||
expect(screen.getByLabelText('账号管理操作')).toHaveTextContent('变更最多 30 秒生效');
|
||||
@@ -170,7 +169,6 @@ test('deduplicates vehicle candidates by VIN and renders granted vehicles plate
|
||||
expect(view.container.querySelector('.v2-user-list.semi-card')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-user-filter-panel.v2-workspace-filter-panel.semi-card')).toBeInTheDocument();
|
||||
expect(view.container.querySelector('.v2-customer-list.semi-list')).toBeInTheDocument();
|
||||
expect(screen.getByRole('row', { name: '客户账号目录列' })).toHaveTextContent('客户账号菜单权限车辆权限最近登录访问状态');
|
||||
const directoryMetrics = screen.getByRole('list', { name: '客户权限目录统计' });
|
||||
expect(directoryMetrics.closest('.v2-user-directory-metric-rail')).toHaveClass('v2-workspace-metric-rail', 'is-queue');
|
||||
expect(directoryMetrics.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(4);
|
||||
|
||||
@@ -754,7 +754,6 @@ export default function UsersPage() {
|
||||
<WorkspaceCommandBar
|
||||
className="v2-user-command-bar"
|
||||
ariaLabel="账号管理操作"
|
||||
eyebrow="访问治理"
|
||||
icon={<IconUserGroup />}
|
||||
tone="authority"
|
||||
title="客户访问治理"
|
||||
@@ -901,7 +900,7 @@ export default function UsersPage() {
|
||||
description="创建客户账号后,可在这里配置菜单和车辆范围。"
|
||||
action={<Button theme="solid" icon={<IconPlus />} aria-label="创建第一个客户账号" onClick={() => startCreate()}>创建第一个客户账号</Button>}
|
||||
/> : <>
|
||||
{visibleCustomers.length ? <div className="v2-user-directory-columns" role="row" aria-label="客户账号目录列">
|
||||
{visibleCustomers.length ? <div className="v2-user-directory-columns" aria-hidden="true">
|
||||
<span>客户账号</span>
|
||||
<span>菜单权限</span>
|
||||
<span>车辆权限</span>
|
||||
@@ -945,7 +944,7 @@ export default function UsersPage() {
|
||||
>
|
||||
<Avatar className="v2-user-avatar" color={user.status === 'enabled' ? 'light-blue' : 'grey'} shape="square" size="small">{user.displayName.slice(0, 1)}</Avatar>
|
||||
<span className="v2-user-list-identity">
|
||||
<span className="v2-user-list-name"><b>{user.displayName}</b><span className="v2-user-provider-hint" title={authProvider.title}><Tag className="v2-user-provider-tag" color={authProvider.color} type="light" size="small">{authProvider.label}</Tag></span></span>
|
||||
<span className="v2-user-list-name"><b title={user.displayName}>{user.displayName}</b><span className="v2-user-provider-hint" title={authProvider.title}><Tag className="v2-user-provider-tag" color={authProvider.color} type="light" size="small">{authProvider.label}</Tag></span></span>
|
||||
<span className="v2-user-list-secondary">
|
||||
<small>@{user.username}{user.customerRef ? ` · ${user.customerRef}` : ''}</small>
|
||||
<span className="v2-user-list-access-summary" aria-hidden="true"><b>{user.menuKeys.length}</b> 菜单 · <b>{user.vehicles.length}</b> 辆车</span>
|
||||
|
||||
@@ -357,15 +357,10 @@ function VehicleRecordNavigation({ onOpenSources }: { onOpenSources: () => void
|
||||
['archive', '车辆主档', <IconBox />]
|
||||
] as const;
|
||||
return <Card className="v2-record-card v2-vehicle-record-nav" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
variant="compact"
|
||||
title="车辆视图"
|
||||
description="位置、事件、遥测"
|
||||
actions={<nav className="v2-vehicle-section-nav" aria-label="单车详情导航">
|
||||
<nav className="v2-vehicle-section-nav" aria-label="单车详情导航">
|
||||
<VehicleSourceEvidenceButton onClick={onOpenSources} />
|
||||
{navigationItems.map(([key, label, icon]) => <Button key={key} size="small" theme="borderless" type="tertiary" icon={icon} aria-label={`跳转到${label}`} onClick={() => jumpToSection(key)}>{label}</Button>)}
|
||||
</nav>}
|
||||
/>
|
||||
</nav>
|
||||
</Card>;
|
||||
}
|
||||
|
||||
@@ -466,8 +461,6 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
|
||||
<WorkspaceCommandBar
|
||||
className="v2-vehicle-command-header v2-vehicle-record-command"
|
||||
ariaLabel="单车任务栏"
|
||||
eyebrow="车辆数字档案"
|
||||
icon={<IconBox />}
|
||||
title={<span className="v2-identity-primary">
|
||||
<span className="v2-plate">{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>
|
||||
@@ -508,7 +501,7 @@ function VehicleRecord({ detail, liveRealtime, telemetry, telemetryPending, tele
|
||||
<Card className="v2-single-map-card" bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader
|
||||
title="实时位置"
|
||||
description="平滑移动"
|
||||
description="车辆最新有效位置"
|
||||
meta={realtime?.online ? '实时跟随' : '保留最后位置'}
|
||||
/>
|
||||
<FleetMap vehicles={mapVehicles} selectedVin={hasLocation ? detail.vin : undefined} onSelect={() => undefined} />
|
||||
|
||||
@@ -316,9 +316,8 @@ export default function VehicleSearchWorkspace() {
|
||||
{!mobileLayout ? <WorkspaceCommandBar
|
||||
className="v2-vehicle-command-bar"
|
||||
ariaLabel="查车操作"
|
||||
icon={<IconSearch />}
|
||||
title="查找车辆"
|
||||
description="车牌、VIN 或手机号快速查车"
|
||||
description="按车牌、VIN 或终端手机号搜索"
|
||||
status={candidates.isPending ? '正在读取授权范围' : vehicleScope}
|
||||
statusColor={candidates.isError ? 'red' : 'blue'}
|
||||
actions={<div className="v2-vehicle-command-actions">
|
||||
|
||||
@@ -90,19 +90,19 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources[name]).toContain("from '../shared/WorkspaceCommandBar'");
|
||||
expect(corePageSources[name]).toContain('<WorkspaceCommandBar');
|
||||
}
|
||||
for (const [name, eyebrow, tone] of [
|
||||
['AccessPage', '接入差异', 'primary'],
|
||||
['HistoryPage', '证据结果', 'primary'],
|
||||
['OperationsPage', '运行快照', 'health']
|
||||
// Preserve the shared section hierarchy without requiring decorative eyebrows.
|
||||
for (const [name, title] of [
|
||||
['AccessPage', '接入差异清单'],
|
||||
['HistoryPage', '历史明细'],
|
||||
['OperationsPage', '运行概览']
|
||||
]) {
|
||||
expect(corePageSources[name]).toContain(`eyebrow="${eyebrow}"`);
|
||||
expect(corePageSources[name]).toContain(`tone="${tone}"`);
|
||||
expect(corePageSources[name]).toContain('<WorkspacePanelHeader');
|
||||
expect(corePageSources[name]).toContain(`title="${title}"`);
|
||||
}
|
||||
expect(corePageSources.StatisticsPage).toContain("eyebrow={metricView === 'mileage' ? '每日里程' : '每日氢耗'}");
|
||||
expect(corePageSources.StatisticsPage).toContain("tone={metricView === 'mileage' ? 'primary' : 'health'}");
|
||||
expect(corePageSources.StatisticsPage).toContain("title={metricView === 'mileage' ? '车辆每日里程' : '车辆每日氢耗'}");
|
||||
expect(corePageSources.AlertsPage).toContain('aria-label="事件执行状态"');
|
||||
expect(corePageSources.AlertsPage).toContain('aria-label="事件中心工作区"');
|
||||
expect(reconciliationSource).toContain('eyebrow="自动对账"');
|
||||
expect(reconciliationSource).toContain('<WorkspacePanelHeader');
|
||||
expect(reconciliationSource).toContain('tone="health"');
|
||||
expect(workspaceStyles).toContain('.v2-workspace-panel-header.has-eyebrow');
|
||||
expect(workspaceStyles).toContain('.v2-workspace-panel-eyebrow.semi-typography');
|
||||
@@ -482,7 +482,7 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.TrackPage).toContain('<Card className="v2-track-playback-dock"');
|
||||
expect(corePageSources.TrackPage).toContain('<Card className="v2-track-overview-section v2-track-overview-card"');
|
||||
expect(corePageSources.TrackPage).toContain('<Descriptions className="v2-track-overview-descriptions"');
|
||||
expect(corePageSources.TrackPage).toContain('<PanelEmpty className="v2-track-rail-empty"');
|
||||
expect(corePageSources.TrackPage).toContain('title={emptyTrackTitle}');
|
||||
expect(corePageSources.TrackPage).toContain('<List className="v2-track-evidence-list v2-track-stop-list"');
|
||||
expect(corePageSources.TrackPage).toContain('<List className="v2-track-evidence-list v2-track-event-list"');
|
||||
expect(corePageSources.TrackPage).toContain('<List.Item className="v2-track-evidence-item"');
|
||||
|
||||
@@ -39,8 +39,8 @@ export function WorkspacePanelHeader({
|
||||
<header className={headerClassName}>
|
||||
<div className="v2-workspace-panel-copy">
|
||||
{eyebrow != null ? <Text className="v2-workspace-panel-eyebrow">{eyebrow}</Text> : null}
|
||||
<Title heading={5}>{title}</Title>
|
||||
{description ? <Text type="tertiary">{description}</Text> : null}
|
||||
<Title className="v2-workspace-panel-title" heading={5}>{title}</Title>
|
||||
{description ? <Text className="v2-workspace-panel-description" type="tertiary">{description}</Text> : null}
|
||||
</div>
|
||||
{meta || actions ? <div className={trailingClassName}>
|
||||
{meta ? <span className="v2-workspace-panel-meta">{meta}</span> : null}
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
/* Data workspaces: quiet grouping, readable records, explicit primary actions.
|
||||
* Loaded after legacy page styles; rules stay within the three data workspaces.
|
||||
*/
|
||||
.v2-vehicle-directory-v3.v2-vehicle-search-page,
|
||||
.v2-history-page,
|
||||
.v2-mileage-page,
|
||||
.v2-vehicle-record-page.v2-vehicle-record-v3 {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 > .v2-vehicle-discovery-shell,
|
||||
.v2-history-page > .v2-history-discovery-shell,
|
||||
.v2-mileage-page .v2-mileage-discovery-shell {
|
||||
border: 1px solid var(--v2-color-border);
|
||||
border-radius: 16px;
|
||||
background: var(--v2-color-surface);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-command-bar.v2-workspace-command-bar,
|
||||
.v2-history-page .v2-history-command-bar.v2-workspace-command-bar {
|
||||
min-height: 66px;
|
||||
margin: 0;
|
||||
padding: 16px 20px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--v2-color-border);
|
||||
border-radius: 16px 16px 0 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-workspace-command-copy,
|
||||
.v2-history-page .v2-workspace-command-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-workspace-command-title.semi-typography,
|
||||
.v2-history-page .v2-workspace-command-title.semi-typography {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -.02em;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-workspace-command-description.semi-typography,
|
||||
.v2-history-page .v2-workspace-command-description.semi-typography {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-search-form,
|
||||
.v2-history-page .v2-history-toolbar,
|
||||
.v2-mileage-page .v2-mileage-filter {
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-business-filter-grid {
|
||||
gap: 16px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-business-filter-grid label > span,
|
||||
.v2-history-page .v2-history-toolbar label > span,
|
||||
.v2-mileage-page .v2-mileage-filter label > span {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 > .v2-vehicle-recent-card.semi-card,
|
||||
.v2-history-page .v2-history-table-card.semi-card,
|
||||
.v2-mileage-page .v2-mileage-results.semi-card {
|
||||
border: 1px solid var(--v2-color-border);
|
||||
border-radius: 16px;
|
||||
background: var(--v2-color-surface);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-context h5 {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-context small,
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-status small {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-table .semi-table-thead > tr > th,
|
||||
.v2-history-page .v2-history-data-table .semi-table-thead > tr > th,
|
||||
.v2-mileage-page .v2-mileage-table .semi-table-thead > tr > th {
|
||||
padding: 12px 16px;
|
||||
border-color: var(--v2-color-border);
|
||||
background: var(--v2-color-surface-subtle);
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-table .semi-table-tbody > tr > td {
|
||||
height: 68px;
|
||||
padding: 13px 16px;
|
||||
border-bottom-color: var(--v2-color-border);
|
||||
box-shadow: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-table .semi-table-row::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-identity strong,
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-profile strong {
|
||||
color: var(--v2-color-text);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-identity small,
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-profile small {
|
||||
margin-top: 5px;
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 12px;
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-open {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.v2-history-page .v2-history-table-card > .semi-card-body > .v2-workspace-panel-header,
|
||||
.v2-mileage-page .v2-mileage-results > .semi-card-body > .v2-workspace-panel-header {
|
||||
min-height: 72px;
|
||||
padding: 16px 20px;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-history-page .v2-history-empty.v2-workspace-empty-guide {
|
||||
min-height: 320px;
|
||||
padding: 56px 24px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-history-page .v2-history-empty .semi-empty-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v2-history-page .v2-history-empty .semi-empty-description {
|
||||
margin-top: 12px;
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.v2-history-page .v2-history-empty .v2-workspace-empty-guide-icon {
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
background: var(--v2-color-surface-subtle);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-history-page .v2-history-data-table .semi-table-tbody > tr > td,
|
||||
.v2-mileage-page .v2-mileage-table .semi-table-tbody > tr > td {
|
||||
min-height: 52px;
|
||||
padding: 15px 16px;
|
||||
font-size: 13px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.v2-mileage-page .v2-mileage-table .semi-table-tbody > tr > td strong,
|
||||
.v2-mileage-page .v2-mileage-table .semi-table-tbody > tr > td span {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.v2-mileage-page .v2-mileage-table .semi-table-tbody > tr > td.is-total strong,
|
||||
.v2-mileage-page .v2-mileage-table .semi-table-tbody > tr > td.is-plate strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v2-mileage-page .v2-mileage-evidence {
|
||||
gap: 16px;
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-identity-primary .v2-plate {
|
||||
color: var(--v2-color-text);
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -.03em;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-identity-vin b {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) and (min-width: 681px) {
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-command-bar.v2-workspace-command-bar,
|
||||
.v2-history-page .v2-history-command-bar.v2-workspace-command-bar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-workspace-command-actions,
|
||||
.v2-history-page .v2-workspace-command-actions {
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.v2-vehicle-directory-v3.v2-vehicle-search-page,
|
||||
.v2-history-page,
|
||||
.v2-mileage-page,
|
||||
.v2-vehicle-record-page.v2-vehicle-record-v3 {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.v2-history-page .v2-history-command-bar.v2-workspace-command-bar,
|
||||
.v2-history-page .v2-history-table-card > .semi-card-body > .v2-workspace-panel-header,
|
||||
.v2-mileage-page .v2-mileage-results > .semi-card-body > .v2-workspace-panel-header {
|
||||
min-height: 60px;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.v2-history-page .v2-history-empty.v2-workspace-empty-guide {
|
||||
min-height: 280px;
|
||||
padding: 36px 18px;
|
||||
}
|
||||
|
||||
.v2-history-page .v2-history-empty .semi-empty-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.v2-mileage-page .v2-mileage-evidence {
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep search suggestions outside the rounded surface without clipping them. */
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-query-panel.v2-workspace-filter-panel.semi-card,
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-query-panel > .semi-card-body,
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-search-form {
|
||||
border: 0;
|
||||
border-radius: 0 0 16px 16px;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-table .semi-table-tbody > .semi-table-row.is-online > .semi-table-row-cell:first-child,
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-table .semi-table-tbody > .semi-table-row.is-offline > .semi-table-row-cell:first-child {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-open {
|
||||
flex-wrap: nowrap;
|
||||
flex-shrink: 0;
|
||||
gap: 5px;
|
||||
padding-inline: 4px;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-open > svg {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 681px) {
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-metric-rail.is-queue.has-context > .semi-card-body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(300px, 1.15fr) minmax(0, 1.5fr);
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-metric-rail .v2-workspace-metric-context {
|
||||
order: -1;
|
||||
padding: 14px 16px;
|
||||
border: 0;
|
||||
border-right: 1px solid var(--v2-color-border);
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-context {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 14px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-context > span:first-child {
|
||||
flex: 1 1 180px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-directory-metric-rail .v2-workspace-metric-list {
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-record-nav > .semi-card-body > .v2-vehicle-section-nav {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
overflow-x: auto;
|
||||
padding: 10px 14px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-section-nav .semi-button {
|
||||
min-height: 38px;
|
||||
flex: 0 0 auto;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-event-list .v2-event-row > div > strong {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-event-list .v2-event-row > div > p,
|
||||
.v2-vehicle-record-v3 .v2-event-list .v2-event-row > time,
|
||||
.v2-vehicle-record-v3 .v2-live-report-meta,
|
||||
.v2-vehicle-record-v3 .v2-map-update-time {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-meta small,
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-vin small,
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-meta strong,
|
||||
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-meta small,
|
||||
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-meta strong {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-actions > .semi-button,
|
||||
.v2-vehicle-record-v3.is-mobile-layout .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-actions > .semi-button {
|
||||
min-height: 38px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.v2-content.is-section-vehicles .v2-monitor-return > a,
|
||||
.v2-content.is-section-vehicles .v2-monitor-return > span,
|
||||
.v2-content.is-section-vehicles .v2-monitor-return > em {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (min-width: 681px) {
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command {
|
||||
min-height: 100px;
|
||||
padding: 0;
|
||||
grid-template-columns: minmax(235px, .7fr) minmax(0, 2fr);
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-workspace-command-copy {
|
||||
display: flex;
|
||||
min-height: 100px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
padding: 16px 20px;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-workspace-command-actions {
|
||||
grid-template-columns: minmax(220px, .85fr) minmax(270px, 1.15fr);
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-primary {
|
||||
justify-content: flex-start;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-plate {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-content: center;
|
||||
gap: 6px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.v2-vehicle-record-v3 .v2-vehicle-command-header.v2-vehicle-record-command .v2-identity-actions > .semi-button {
|
||||
width: auto;
|
||||
flex: 0 0 auto;
|
||||
padding-inline: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-mobile-scope-row {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-mobile-scope-row > .v2-mobile-filter-toggle.semi-button {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-mobile-scope-row .v2-mobile-filter-toggle-content {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
grid-template-columns: 38px minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.v2-vehicle-directory-v3 .v2-vehicle-mobile-scope-row > .v2-vehicle-mobile-sync.semi-button {
|
||||
width: auto;
|
||||
min-width: 118px;
|
||||
min-height: 44px;
|
||||
justify-self: end;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
/* Governance workspaces: readable evidence, quiet grouping, one row per entity.
|
||||
* Keep this after the legacy workspace layers. Prefixing the user selectors also
|
||||
* protects the directory from its asynchronously loaded users.css stylesheet.
|
||||
*/
|
||||
.v2-user-admin,
|
||||
.v2-access-page-v3,
|
||||
.v2-ops-page,
|
||||
.v2-event-center {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-user-discovery-shell,
|
||||
.v2-access-page-v3 .v2-access-discovery-shell,
|
||||
.v2-ops-page .v2-ops-command-bar {
|
||||
background: var(--v2-color-surface);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-user-command-bar,
|
||||
.v2-access-page-v3 .v2-access-command-bar,
|
||||
.v2-ops-page .v2-ops-command-bar {
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* Account identity and every fact share one column definition with the header. */
|
||||
.v2-user-admin .v2-user-list {
|
||||
--v2-user-row-columns: 36px minmax(190px, 2fr) minmax(80px, .55fr) minmax(90px, .6fr) minmax(164px, 1fr) minmax(96px, .6fr);
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-customer-list .semi-list-items {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-user-directory-columns,
|
||||
.v2-user-admin .v2-customer-list .v2-user-list-item .semi-button-content {
|
||||
display: grid;
|
||||
grid-template-columns: var(--v2-user-row-columns);
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-user-directory-columns {
|
||||
min-height: 42px;
|
||||
padding: 0 20px;
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 12px;
|
||||
background: var(--v2-color-surface-subtle);
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-user-directory-columns > span:first-child { grid-column: 1 / 3; }
|
||||
.v2-user-admin .v2-user-directory-columns > span:last-child { text-align: right; }
|
||||
|
||||
.v2-user-admin .v2-customer-list .v2-user-list-row.semi-list-item {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 72px;
|
||||
background: var(--v2-color-surface);
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid var(--v2-color-border);
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-customer-list .v2-user-list-row.semi-list-item:hover {
|
||||
background: var(--v2-color-surface-subtle);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-customer-list .v2-user-list-row.is-active.semi-list-item {
|
||||
background: var(--v2-color-primary-soft, #edf4ff);
|
||||
box-shadow: inset 3px 0 var(--v2-color-primary);
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-customer-list .v2-user-list-item.semi-button {
|
||||
width: 100%;
|
||||
min-height: 72px;
|
||||
padding: 12px 20px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.v2-user-admin .v2-user-list-name > b { font-size: 14px; font-weight: 600; }
|
||||
.v2-user-admin .v2-user-list-secondary > small { font-size: 12px; color: var(--v2-color-text-secondary); }
|
||||
.v2-user-admin .v2-user-list-facts b,
|
||||
.v2-user-admin .v2-user-list-facts.is-login b { font-size: 13px; font-weight: 500; color: var(--v2-color-text); }
|
||||
.v2-user-admin .v2-user-list-facts.is-menu,
|
||||
.v2-user-admin .v2-user-list-facts.is-vehicle { padding: 0; border: 0; background: transparent; }
|
||||
.v2-user-admin .v2-user-provider-tag.semi-tag,
|
||||
.v2-user-admin .v2-user-status-tag.semi-tag { font-size: 12px; height: auto; min-height: 24px; line-height: 20px; }
|
||||
.v2-user-admin .v2-user-avatar.semi-avatar { border: 0; background: #f0f1f3; color: var(--v2-color-text-secondary); }
|
||||
.v2-user-admin .v2-customer-list-scroll { scrollbar-gutter: stable; }
|
||||
|
||||
/* Protocol evidence reads as aligned text; colour remains on the actual state. */
|
||||
.v2-access-page-v3 .v2-access-protocol-cell {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
padding: 4px 0;
|
||||
min-height: 62px;
|
||||
}
|
||||
|
||||
.v2-access-page-v3 .v2-access-primary-cell > strong,
|
||||
.v2-access-page-v3 .v2-access-protocol-cell strong,
|
||||
.v2-access-page-v3 .v2-access-protocol-cell time { font-size: 14px; font-weight: 500; }
|
||||
.v2-access-page-v3 .v2-access-primary-cell > span,
|
||||
.v2-access-page-v3 .v2-access-protocol-cell small,
|
||||
.v2-access-page-v3 .v2-access-connection small { font-size: 12px; color: var(--v2-color-text-secondary); line-height: 1.5; }
|
||||
.v2-access-page-v3 .v2-access-connection p { font-size: 13px; }
|
||||
.v2-access-page-v3 .v2-access-connection-tag.semi-tag { font-size: 12px; min-height: 24px; }
|
||||
.v2-access-page-v3 .v2-access-semi-table .semi-table-row > .semi-table-row-cell:first-child { box-shadow: none; }
|
||||
.v2-access-page-v3 .v2-access-semi-table .semi-table-row > .semi-table-row-cell:first-child::before { display: none; }
|
||||
.v2-access-page-v3 .v2-access-semi-table .semi-table-row { box-shadow: none; }
|
||||
.v2-access-page-v3 .v2-access-semi-table .semi-table-tbody > .semi-table-row:is(.is-state-healthy, .is-state-degraded, .is-state-incomplete, .is-state-offline, .is-state-not_connected, .is-selected) > .semi-table-row-cell:first-child { box-shadow: none; }
|
||||
.v2-access-page-v3 .v2-access-status-tabs { height: auto; overflow: visible; }
|
||||
.v2-access-page-v3 .v2-access-metric-rail.is-queue,
|
||||
.v2-access-page-v3 .v2-access-metric-rail.is-queue > .semi-card-body,
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-list { height: auto; min-height: 80px; }
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-list > span,
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-action.semi-button {
|
||||
height: auto;
|
||||
min-height: 80px;
|
||||
overflow: visible;
|
||||
}
|
||||
/* Action metrics contain a button, unlike plain metrics' three text nodes.
|
||||
* Do not place that button into the legacy 14px/24px text grid: its 80px
|
||||
* content otherwise starts in the first short track and spills below the rail.
|
||||
*/
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-list > span.is-action {
|
||||
display: block;
|
||||
grid-template-columns: none;
|
||||
grid-template-rows: none;
|
||||
padding: 0;
|
||||
}
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-action.semi-button {
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-list > span:not(.is-action),
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-action .semi-button-content {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
grid-template-rows: auto auto;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
min-height: 80px;
|
||||
gap: 4px 12px;
|
||||
padding: 12px 16px;
|
||||
overflow: visible;
|
||||
}
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-list small { grid-column: 1 / -1; grid-row: 1; font-size: 12px; line-height: 1.5; }
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-list strong { grid-column: 1; grid-row: 2; font-size: 24px; line-height: 1.2; }
|
||||
.v2-access-page-v3 .v2-access-metric-rail .v2-workspace-metric-list em { grid-column: 2; grid-row: 2; font-size: 12px; line-height: 1.5; white-space: normal; }
|
||||
|
||||
/* The event itself is primary. The existing native alarm title stays intact. */
|
||||
.v2-event-center .v2-event-identity > span {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-basis: 28px;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
background: var(--v2-color-surface-subtle);
|
||||
color: var(--v2-color-text-secondary);
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-event-center .v2-event-identity strong { font-size: 14px; line-height: 1.5; font-weight: 600; }
|
||||
.v2-event-center .v2-event-identity small,
|
||||
.v2-event-center .v2-alert-event-vehicle small { font-size: 12px; line-height: 1.5; color: var(--v2-color-text-secondary); }
|
||||
.v2-event-center .v2-alert-event-vehicle strong,
|
||||
.v2-event-center .v2-alert-event-rule,
|
||||
.v2-event-center .v2-alert-time { font-size: 13px; line-height: 1.5; font-weight: 500; }
|
||||
.v2-event-center .v2-event-execution.semi-tag { min-height: 24px; height: auto; font-size: 12px; line-height: 20px; }
|
||||
.v2-event-center .v2-alert-event-table .semi-table-row > .semi-table-row-cell { border-right: 0; }
|
||||
.v2-event-center .v2-alert-event-table .semi-table-row > .semi-table-row-cell:first-child { box-shadow: none; }
|
||||
.v2-event-center .v2-alert-event-table .semi-table-row > .semi-table-row-cell:first-child::before { display: none; }
|
||||
/* Let Semi's table body own horizontal scrolling. The legacy 970px minimum on
|
||||
* its outer wrapper put the sticky right edge beyond the visible viewport. */
|
||||
.v2-event-center .v2-alert-workspace .v2-alert-table-scroll .v2-alert-event-table.semi-table-wrapper {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.v2-event-center .v2-alert-event-table .is-status { position: sticky; right: 0; z-index: 2; background: var(--v2-color-surface); box-shadow: -1px 0 var(--v2-color-border); }
|
||||
.v2-event-center .v2-alert-event-table .semi-table-thead .is-status { z-index: 3; background: var(--v2-color-surface-subtle); }
|
||||
.v2-event-center .v2-alert-event-table .is-selected .is-status { background: var(--v2-color-primary-soft); }
|
||||
.v2-event-center .v2-event-status-tabs { gap: 4px; padding: 8px 12px; }
|
||||
.v2-event-center .v2-event-status-tabs .semi-button { min-height: 38px; padding-inline: 12px; font-size: 13px; }
|
||||
.v2-event-center .v2-event-status-tabs strong { font-size: 13px; font-weight: 600; }
|
||||
.v2-event-center .v2-alert-detail-card { border-color: var(--v2-color-border); background: var(--v2-color-surface); box-shadow: none; }
|
||||
.v2-event-center .v2-alert-automation-row .v2-alert-rule-item.semi-button,
|
||||
.v2-event-center .v2-alert-automation-row .v2-alert-rule-item.semi-button:hover,
|
||||
.v2-event-center .v2-alert-automation-row .v2-alert-rule-item.is-selected.semi-button {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
.v2-event-center .v2-alert-automation-row.is-selected { background: var(--v2-color-primary-soft); box-shadow: none; }
|
||||
.v2-event-center .v2-alert-automation-row .v2-alert-rule-copy strong { font-size: 14px; line-height: 1.5; }
|
||||
.v2-event-center .v2-alert-automation-row .v2-alert-rule-copy small { font-size: 12px; line-height: 1.5; }
|
||||
.v2-event-center .v2-alert-automation-row .v2-alert-automation-icon { background: var(--v2-color-surface-subtle); color: var(--v2-color-text-secondary); box-shadow: none; }
|
||||
.v2-event-center .v2-alert-automation-switch-target { grid-area: switch; display: grid; place-items: center; min-width: 44px; min-height: 44px; cursor: pointer; }
|
||||
.v2-event-center .v2-alert-automation-switch-target .v2-alert-automation-switch { grid-area: auto; }
|
||||
.v2-event-center .v2-alert-automation-version { font-size: 12px; }
|
||||
.v2-alert-inspector .v2-native-alarm-fields { padding: 16px; }
|
||||
.v2-alert-inspector .v2-native-alarm-meaning > strong { font-size: 14px; font-weight: 600; }
|
||||
.v2-alert-inspector .v2-native-alarm-meaning ul { margin: 10px 0; padding-left: 20px; font-size: 14px; line-height: 1.7; }
|
||||
.v2-alert-inspector .v2-native-alarm-meaning p,
|
||||
.v2-alert-inspector .v2-native-alarm-fields > p { font-size: 12px; color: var(--v2-color-text-secondary); line-height: 1.6; }
|
||||
.v2-alert-inspector .v2-alert-descriptions .semi-descriptions-item-key { font-size: 12px; line-height: 1.6; }
|
||||
.v2-alert-inspector .v2-alert-descriptions .semi-descriptions-item-value { font-size: 13px; line-height: 1.6; }
|
||||
:is(.v2-event-center, .v2-alert-detail-sidesheet) .v2-alert-inspector .v2-alert-focus-card .v2-alert-evidence {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 0;
|
||||
min-height: 0;
|
||||
height: auto;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
:is(.v2-event-center, .v2-alert-detail-sidesheet) .v2-alert-inspector .v2-alert-focus-card .v2-alert-evidence > .v2-alert-evidence-value.semi-card {
|
||||
min-height: 0;
|
||||
height: auto;
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--v2-color-border);
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
:is(.v2-event-center, .v2-alert-detail-sidesheet) .v2-alert-inspector .v2-alert-focus-card .v2-alert-evidence > .v2-alert-evidence-value > .semi-card-body {
|
||||
display: grid;
|
||||
grid-template-columns: 76px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: auto;
|
||||
min-height: 44px;
|
||||
padding: 10px 0;
|
||||
text-align: left;
|
||||
}
|
||||
:is(.v2-event-center, .v2-alert-detail-sidesheet) .v2-alert-inspector .v2-alert-focus-card .v2-alert-evidence small { font-size: 12px; line-height: 1.5; }
|
||||
:is(.v2-event-center, .v2-alert-detail-sidesheet) .v2-alert-inspector .v2-alert-focus-card .v2-alert-evidence strong { font-size: 14px; line-height: 1.5; white-space: normal; overflow-wrap: anywhere; }
|
||||
.v2-event-center .v2-alert-inspector .v2-alert-inspector-close.semi-button { width: 36px; height: 36px; min-width: 36px; min-height: 36px; padding: 8px; }
|
||||
.v2-event-center .v2-alert-inspector .v2-alert-inspector-close :is(.semi-icon, svg) { width: 16px; height: 16px; font-size: 16px; }
|
||||
|
||||
/* Quality queues and diagnostic evidence share the same reading size. */
|
||||
.v2-ops-page .v2-reconcile-cell > strong,
|
||||
.v2-ops-page .v2-reconcile-cell > b,
|
||||
.v2-ops-page .v2-source-cell > strong { font-size: 14px; line-height: 1.5; font-weight: 600; }
|
||||
.v2-ops-page .v2-reconcile-cell small,
|
||||
.v2-ops-page .v2-source-cell small { font-size: 12px; line-height: 1.5; color: var(--v2-color-text-secondary); }
|
||||
.v2-ops-page .v2-reconcile-table .semi-table-row-cell,
|
||||
.v2-ops-page .v2-source-table .semi-table-row-cell { padding-block: 14px; }
|
||||
.v2-ops-page .v2-ops-link-card,
|
||||
.v2-ops-page .v2-ops-source-card,
|
||||
.v2-ops-page .v2-source-recommendation { background: var(--v2-color-surface); box-shadow: none; border-color: var(--v2-color-border); }
|
||||
.v2-ops-page .v2-ops-source-evidence p,
|
||||
.v2-ops-page .v2-ops-source-action p,
|
||||
.v2-ops-page .v2-ops-link-card p { font-size: 13px; line-height: 1.6; color: var(--v2-color-text-secondary); }
|
||||
.v2-ops-page .v2-ops-source-evidence > strong,
|
||||
.v2-ops-page .v2-ops-source-action > strong,
|
||||
.v2-ops-page .v2-ops-link-status strong { font-size: 14px; font-weight: 600; }
|
||||
|
||||
/* Portal-mounted inspectors need their own scope. */
|
||||
:is(.v2-user-editor-sidesheet, .v2-user-grant-sidesheet, .v2-user-import-sidesheet, .v2-access-detail-sidesheet, .v2-access-governance-sidesheet, .v2-reconcile-detail-sidesheet, .v2-source-policy-sidesheet) .v2-workspace-config-summary > span::before {
|
||||
display: none;
|
||||
}
|
||||
:is(.v2-user-editor-sidesheet, .v2-user-grant-sidesheet, .v2-user-import-sidesheet, .v2-access-detail-sidesheet, .v2-access-governance-sidesheet, .v2-reconcile-detail-sidesheet, .v2-source-policy-sidesheet) .v2-workspace-config-summary > span {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border-left: 0;
|
||||
}
|
||||
:is(.v2-user-editor-sidesheet, .v2-user-grant-sidesheet, .v2-user-import-sidesheet, .v2-access-detail-sidesheet, .v2-access-governance-sidesheet, .v2-reconcile-detail-sidesheet, .v2-source-policy-sidesheet) .v2-workspace-config-summary > span:is(.is-primary, .is-success, .is-warning, .is-danger) {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border-left: 0;
|
||||
}
|
||||
:is(.v2-user-editor-sidesheet, .v2-user-grant-sidesheet, .v2-user-import-sidesheet, .v2-access-detail-sidesheet, .v2-access-governance-sidesheet, .v2-reconcile-detail-sidesheet, .v2-source-policy-sidesheet) .v2-workspace-config-summary > span :is(small, em) {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--v2-color-text-secondary);
|
||||
}
|
||||
:is(.v2-user-editor-sidesheet, .v2-user-grant-sidesheet, .v2-user-import-sidesheet, .v2-access-detail-sidesheet, .v2-access-governance-sidesheet, .v2-reconcile-detail-sidesheet, .v2-source-policy-sidesheet) .v2-workspace-config-summary > span > strong {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--v2-color-text);
|
||||
}
|
||||
.v2-user-editor-sidesheet .v2-workspace-config-content .v2-user-editor-form :is(small, em),
|
||||
.v2-user-editor-sidesheet .v2-user-editor-switcher small {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--v2-color-text-secondary);
|
||||
}
|
||||
.v2-user-editor-sidesheet .v2-workspace-config-content .v2-user-editor-form :is(strong, b) {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
}
|
||||
.v2-user-editor-sidesheet .v2-user-editor-tabs > .semi-tabs-content {
|
||||
background: var(--v2-color-surface);
|
||||
padding: 0;
|
||||
}
|
||||
.v2-user-editor-sidesheet .v2-user-editor-form .v2-user-editor-section.semi-card {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-user-editor-sidesheet .v2-user-editor-form :is(.v2-user-vehicle-composer, .v2-user-identity-overview, .v2-user-menu-overview, .v2-user-identity-source, .v2-assigned-vins) {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-user-editor-sidesheet .v2-user-vehicle-composer-heading {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0 0 12px;
|
||||
}
|
||||
.v2-user-editor-sidesheet .v2-user-vehicle-composer .v2-vehicle-permission-tools { padding: 0 0 16px; }
|
||||
.v2-user-editor-sidesheet .v2-assigned-toolbar { border-top: 1px solid var(--v2-color-border); padding-top: 16px; }
|
||||
.v2-user-editor-sidesheet .v2-assigned-vins > .v2-assigned-vehicle-card.semi-card { padding: 16px 0; }
|
||||
.v2-user-editor-sidesheet .v2-assigned-vehicle-card .v2-assigned-vehicle-actions .semi-button { font-size: 12px; min-height: 36px; }
|
||||
.v2-user-editor-sidesheet .v2-assigned-vehicle-card .v2-assigned-vehicle-interval :is(small, strong) { font-size: 12px; line-height: 1.5; }
|
||||
.v2-user-editor-sidesheet .v2-user-editor-form .v2-user-field-group { border: 0; border-radius: 0; box-shadow: none; }
|
||||
.v2-user-editor-sidesheet .v2-user-editor-form .v2-user-field-group + .v2-user-field-group { border-top: 1px solid var(--v2-color-border); padding-top: 20px; }
|
||||
|
||||
:is(.v2-access-detail-sidesheet, .v2-access-governance-sidesheet, .v2-reconcile-detail-sidesheet, .v2-source-policy-sidesheet) .v2-workspace-config-content :is(small, em) {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
:is(.v2-access-detail-sidesheet, .v2-access-governance-sidesheet, .v2-reconcile-detail-sidesheet, .v2-source-policy-sidesheet) .v2-workspace-config-content :is(strong, b) {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.v2-access-detail-sidesheet .v2-access-inspector-v3.semi-card,
|
||||
.v2-reconcile-detail-sidesheet .v2-reconcile-detail.semi-card {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-access-detail-sidesheet .v2-access-inspector-v3 .v2-access-protocol-detail.semi-card,
|
||||
.v2-reconcile-detail-sidesheet .v2-reconcile-detail .v2-reconcile-evidence.semi-card {
|
||||
border: 0;
|
||||
border-top: 1px solid var(--v2-color-border);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
:is(.v2-access-detail-sidesheet, .v2-access-governance-sidesheet, .v2-alert-detail-sidesheet, .v2-reconcile-detail-sidesheet, .v2-source-policy-sidesheet) .semi-descriptions-item-key {
|
||||
font-size: 12px;
|
||||
color: var(--v2-color-text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
:is(.v2-access-detail-sidesheet, .v2-access-governance-sidesheet, .v2-alert-detail-sidesheet, .v2-reconcile-detail-sidesheet, .v2-source-policy-sidesheet) .semi-descriptions-item-value {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (min-width: 681px) and (max-width: 1180px) {
|
||||
.v2-access-page-v3 .v2-access-filter-actions { display: flex; justify-content: flex-end; grid-column: 1 / -1; }
|
||||
.v2-access-page-v3 .v2-access-filter-actions > .semi-button { width: auto; min-width: 80px; flex: 0 0 auto; }
|
||||
.v2-ops-page .v2-reconcile-heading.v2-workspace-panel-header { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px; }
|
||||
.v2-ops-page .v2-reconcile-heading .v2-workspace-panel-copy { width: 100%; flex: 1 0 100%; min-width: 0; }
|
||||
.v2-ops-page .v2-reconcile-heading .v2-workspace-panel-description { max-width: 100%; white-space: normal; }
|
||||
.v2-ops-page .v2-reconcile-heading .v2-workspace-panel-actions { width: 100%; flex: 1 0 100%; justify-content: flex-end; flex-wrap: wrap; }
|
||||
.v2-user-admin .v2-user-list { --v2-user-row-columns: 32px minmax(150px, 1fr) 76px 84px 100px; }
|
||||
.v2-user-admin .v2-user-directory-columns > span:nth-child(4),
|
||||
.v2-user-admin .v2-user-list-facts.is-login { display: none; }
|
||||
.v2-user-admin .v2-customer-list .v2-user-list-item.semi-button,
|
||||
.v2-user-admin .v2-user-directory-columns { padding-inline: 14px; }
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.v2-ops-page .v2-reconcile-heading.v2-workspace-panel-header { display: flex; flex-direction: row; align-items: center; padding: 12px 16px; min-height: 64px; }
|
||||
.v2-ops-page .v2-reconcile-heading .v2-workspace-panel-actions { width: auto; flex: 0 0 auto; }
|
||||
.v2-ops-page .v2-reconcile-mobile-more.semi-button { min-height: 44px; font-size: 12px; }
|
||||
.v2-user-admin .v2-user-list { --v2-user-row-columns: 34px minmax(0, 1fr) auto; }
|
||||
.v2-user-admin .v2-user-directory-columns { display: none; }
|
||||
.v2-user-admin .v2-customer-list .v2-user-list-item .semi-button-content { gap: 10px; }
|
||||
.v2-user-admin .v2-customer-list .v2-user-list-item.semi-button { padding: 12px; min-height: 76px; }
|
||||
.v2-user-admin .v2-user-list-facts { display: none; }
|
||||
.v2-user-admin .v2-user-list-name > b { white-space: normal; overflow-wrap: anywhere; font-size: 14px; }
|
||||
.v2-user-admin .v2-user-list-secondary { display: grid; gap: 3px; }
|
||||
.v2-user-admin .v2-user-list-access-summary { font-size: 12px; }
|
||||
.v2-user-admin .v2-user-list-trailing { min-width: 0; }
|
||||
.v2-user-admin .v2-user-status-tag.semi-tag { font-size: 11px; }
|
||||
.v2-event-center .v2-alert-mobile-card-content > header > strong { font-size: 14px; white-space: normal; }
|
||||
.v2-event-center .v2-alert-mobile-facts,
|
||||
.v2-access-page-v3 .v2-access-mobile-card-content > p { font-size: 12px; line-height: 1.5; }
|
||||
.v2-event-center .v2-event-status-tabs { padding: 6px 8px; gap: 2px; }
|
||||
.v2-event-center .v2-event-status-tabs .semi-button { padding-inline: 10px; }
|
||||
.v2-access-page-v3 .v2-access-protocol-cell { min-height: 58px; }
|
||||
.v2-access-page-v3 .v2-access-mobile-card { box-shadow: none; border-left: 0; }
|
||||
.v2-access-page-v3 .v2-access-mobile-card::before,
|
||||
.v2-access-page-v3 .v2-access-mobile-card .v2-access-mobile-action::before { display: none; }
|
||||
.v2-access-page-v3 .v2-access-mobile-list .v2-access-protocol-cell {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: auto auto auto;
|
||||
align-content: start;
|
||||
gap: 6px;
|
||||
padding: 10px 7px;
|
||||
min-height: 94px;
|
||||
}
|
||||
.v2-access-page-v3 .v2-access-mobile-list .v2-access-protocol-cell > :is(b, span, strong) {
|
||||
grid-column: 1;
|
||||
justify-self: start;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
}
|
||||
.v2-access-page-v3 .v2-access-mobile-list .v2-access-protocol-cell time { font-size: 12px; line-height: 1.4; }
|
||||
.v2-access-page-v3 .v2-access-mobile-list .v2-access-protocol-cell > span i { width: 5px; height: 5px; }
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/* Quiet map chrome; playback and data controls keep their existing behavior. */
|
||||
.v2-monitor-page .v2-monitor-workspace,
|
||||
.v2-track-page .v2-track-stage,
|
||||
.v2-track-page .v2-track-rail-shell.semi-card {
|
||||
border-color: rgba(60, 60, 67, .13);
|
||||
border-radius: 16px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-monitor-page .v2-vehicle-rail { background: #fff; border-right-color: rgba(60, 60, 67, .12); }
|
||||
.v2-monitor-page .v2-vehicle-rail > header { padding: 17px 18px 9px; }
|
||||
.v2-monitor-page .v2-vehicle-rail > header strong { color: #1d1d1f; font-size: 15px; font-weight: 600; }
|
||||
.v2-monitor-page .v2-rail-search {
|
||||
min-height: 28px;
|
||||
height: auto;
|
||||
margin: 0 18px 8px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
color: #6e6e73;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.v2-monitor-page .v2-vehicle-row.semi-button {
|
||||
width: calc(100% - 16px);
|
||||
margin: 2px 8px;
|
||||
padding: 14px 10px;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-monitor-page .v2-vehicle-row.semi-button:hover { background: #f5f5f7; }
|
||||
.v2-monitor-page .v2-vehicle-row.semi-button.is-selected { background: #eaf3ff; box-shadow: none; }
|
||||
.v2-monitor-page .v2-vehicle-identity strong { color: #1d1d1f; font-size: 14px; font-weight: 600; }
|
||||
.v2-monitor-page .v2-vehicle-identity small,
|
||||
.v2-monitor-page .v2-vehicle-motion > small { color: #6e6e73; font-size: 11px; }
|
||||
.v2-monitor-page .v2-map-control-button.semi-button {
|
||||
min-height: 48px;
|
||||
border: 1px solid rgba(60, 60, 67, .12);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, .96);
|
||||
box-shadow: 0 3px 16px rgba(0, 0, 0, .08);
|
||||
}
|
||||
.v2-monitor-page .v2-map-control-copy strong { color: #1d1d1f; font-size: 12px; font-weight: 600; }
|
||||
.v2-monitor-page .v2-map-control-copy small { color: #6e6e73; font-size: 10px; }
|
||||
.v2-monitor-page .v2-map-legend {
|
||||
min-height: 36px;
|
||||
border-color: rgba(60, 60, 67, .12);
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, .96);
|
||||
box-shadow: 0 3px 16px rgba(0, 0, 0, .06);
|
||||
}
|
||||
.v2-monitor-page .v2-event-strip.semi-card { border-color: rgba(60, 60, 67, .1); border-radius: 12px; box-shadow: none; }
|
||||
.v2-track-page { background: #f5f5f7; }
|
||||
.v2-track-page .v2-track-stage,
|
||||
.v2-track-page .v2-track-map { background: #eef0f2; }
|
||||
.v2-track-query-hint { margin: 0; padding: 18px; color: #6e6e73; font-size: 12px; line-height: 1.7; }
|
||||
.v2-track-query-panel .v2-track-query-header.v2-workspace-command-bar { background: #fff; box-shadow: none; }
|
||||
.v2-track-query { gap: 18px; padding: 18px; }
|
||||
.v2-track-query > label > span { color: #6e6e73; font-size: 12px; font-weight: 500; }
|
||||
.v2-track-page .v2-track-empty-state.semi-card {
|
||||
width: min(520px, calc(100% - 48px));
|
||||
border: 0;
|
||||
border-radius: 20px;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-track-page .v2-track-empty-state .v2-workspace-empty-guide,
|
||||
.v2-track-page .v2-track-mobile-empty .v2-workspace-empty-guide { border: 0; background: transparent; box-shadow: none; }
|
||||
.v2-track-page .v2-track-empty-state .v2-workspace-empty-guide::before,
|
||||
.v2-track-page .v2-track-mobile-empty .v2-workspace-empty-guide::before { display: none; }
|
||||
.v2-track-page .v2-track-empty-state .semi-empty-title { color: #1d1d1f; font-size: 22px; font-weight: 600; letter-spacing: -.025em; line-height: 1.4; }
|
||||
.v2-track-page .v2-track-empty-state .semi-empty-description { color: #6e6e73; font-size: 13px; line-height: 1.7; }
|
||||
.v2-track-page .v2-track-current-card.semi-card {
|
||||
border-color: rgba(60, 60, 67, .12);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, .97);
|
||||
box-shadow: 0 6px 24px rgba(0, 0, 0, .09);
|
||||
}
|
||||
.v2-track-page .v2-track-stage-tools {
|
||||
padding: 4px;
|
||||
border: 1px solid rgba(60, 60, 67, .12);
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, .97);
|
||||
box-shadow: 0 3px 16px rgba(0, 0, 0, .08);
|
||||
}
|
||||
.v2-track-page .v2-track-stage-tools .semi-button { min-height: 36px; border-radius: 8px; font-size: 12px; font-weight: 500; }
|
||||
.v2-track-page .v2-track-stage-tools .semi-button.is-active { color: #007aff; background: #eaf3ff; }
|
||||
.v2-track-page .v2-track-playback-dock.semi-card { border-color: rgba(60, 60, 67, .13); background: rgba(255, 255, 255, .98); box-shadow: 0 -3px 20px rgba(0, 0, 0, .06); }
|
||||
.v2-track-page .v2-track-progress-meta { color: #6e6e73; font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.v2-track-page .v2-track-progress-meta time { color: #1d1d1f; font-weight: 500; }
|
||||
.v2-track-page .v2-track-dock-controls .semi-button { min-width: 40px; height: 40px; border-radius: 10px; }
|
||||
.v2-track-page .v2-track-dock-controls .semi-button.is-primary { min-width: 64px; background: #007aff; box-shadow: none; }
|
||||
@media (max-width: 960px) {
|
||||
.v2-monitor-page .v2-map-control-button.semi-button,
|
||||
.v2-monitor-page .v2-monitor-mobile-card .semi-button,
|
||||
.v2-track-page .v2-track-stage-tools .semi-button,
|
||||
.v2-track-page .v2-track-dock-controls .semi-button,
|
||||
.v2-track-page .v2-track-dock-controls .semi-select,
|
||||
.v2-track-detail-sidesheet .v2-track-query .semi-button,
|
||||
.v2-track-detail-sidesheet .v2-track-query .semi-input-wrapper,
|
||||
.v2-track-detail-sidesheet .v2-track-query .semi-select,
|
||||
.v2-track-detail-sidesheet .v2-track-list-action.semi-button { min-height: 44px; }
|
||||
.v2-track-page .v2-track-dock-controls .semi-button { min-width: 44px; }
|
||||
.v2-track-page .v2-track-progress-slider.semi-slider-wrapper { min-height: 44px; display: flex; align-items: center; }
|
||||
.v2-track-page .v2-track-mobile-query-launcher.semi-button { min-height: 60px; border-radius: 14px; background: #fff; box-shadow: 0 3px 18px rgba(0, 0, 0, .08); }
|
||||
.v2-track-page .v2-track-mobile-empty { bottom: 24px; }
|
||||
.v2-track-page .v2-track-mobile-empty .semi-empty-title { color: #1d1d1f; font-size: 20px; font-weight: 600; }
|
||||
}
|
||||
|
||||
/* A narrow evidence rail must show complete dates and complete metric values. */
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 28px;
|
||||
gap: 0;
|
||||
height: auto;
|
||||
min-height: 88px;
|
||||
padding: 0 6px 0 10px;
|
||||
border: 1px solid rgba(60, 60, 67, .15);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 44px;
|
||||
grid-column: 1;
|
||||
padding: 0;
|
||||
}
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-wrapper-start { grid-row: 1; }
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-wrapper-end { grid-row: 2; border-top: 1px solid rgba(60, 60, 67, .09); }
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-wrapper::before { flex: 0 0 36px; color: #6e6e73; font-size: 12px; }
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-wrapper-start::before { content: '开始'; }
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-wrapper-end::before { content: '结束'; }
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-wrapper .semi-input-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-wrapper .semi-input {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
font-size: 14px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-separator { display: none; }
|
||||
.v2-track-date-time-picker .semi-datepicker-range-input-suffix { grid-column: 2; grid-row: 1 / 3; align-self: center; width: 28px; margin: 0; }
|
||||
.v2-track-result-metric-rail .v2-workspace-metric-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.v2-track-result-metric-rail .v2-workspace-metric-list > span { padding: 12px 16px; border-bottom: 1px solid rgba(60, 60, 67, .09); }
|
||||
.v2-track-result-metric-rail .v2-workspace-metric-list > span > strong,
|
||||
.v2-track-result-metric-rail .v2-workspace-metric-list > span.is-secondary > strong {
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
text-overflow: clip;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.v2-track-distance-unit { font-size: 12px; font-weight: 500; color: #6e6e73; }
|
||||
.v2-track-page .v2-track-current-card .v2-track-current-metrics small,
|
||||
.v2-track-page .v2-track-current-card .v2-track-current-metrics em,
|
||||
.v2-track-page .v2-track-current-card .v2-track-current-evidence em,
|
||||
.v2-track-page .v2-track-current-card .v2-track-current-evidence small,
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > p { color: #6e6e73; font-size: 12px; }
|
||||
.v2-track-page .v2-track-current-card .v2-track-current-metrics strong {
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
text-overflow: clip;
|
||||
}
|
||||
.v2-track-page .v2-track-current-card .v2-track-current-evidence { flex-wrap: wrap; gap: 6px 10px; }
|
||||
.v2-track-result-metric-rail .v2-workspace-metric-list > span > small,
|
||||
.v2-track-result-metric-rail .v2-workspace-metric-list > span > em { color: #6e6e73; font-size: 12px; line-height: 1.5; white-space: normal; }
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > p.is-address-error,
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > p.is-address-error > span { color: #6e6e73; font-size: 12px; }
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > .v2-track-current-metrics small > b { color: #6e6e73; font-size: 11px; }
|
||||
@media (max-width: 960px) {
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > .v2-track-current-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > .v2-track-current-metrics > span { padding: 8px 12px; }
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > .v2-track-current-metrics > span:nth-child(3) { border-left: 0; }
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > .v2-track-current-metrics > span:nth-child(n+3) { border-top: 1px solid rgba(60, 60, 67, .09); }
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > .v2-track-current-metrics strong,
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > .v2-track-current-metrics > span:last-child strong { font-size: 16px; overflow: visible; text-overflow: clip; white-space: normal; }
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > .v2-track-current-metrics small,
|
||||
.v2-track-page .v2-track-current-card > .semi-card-body > .v2-track-current-metrics em { font-size: 12px; }
|
||||
}
|
||||
|
||||
/* Let the map consume the space left by controls inside the mobile shell. */
|
||||
@media (max-width: 760px) {
|
||||
.v2-monitor-page.is-map-mode {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.v2-monitor-page.is-map-mode > .v2-filterbar,
|
||||
.v2-monitor-page.is-map-mode > .v2-monitor-summary-rail,
|
||||
.v2-monitor-page.is-map-mode > .v2-event-strip { flex: 0 0 auto; }
|
||||
.v2-monitor-page.is-map-mode > .v2-monitor-workspace,
|
||||
.v2-monitor-page.is-map-mode > .v2-monitor-workspace.is-detail-open,
|
||||
.v2-monitor-page.is-map-mode > .v2-monitor-workspace.is-detail-collapsed {
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
}
|
||||
.v2-monitor-page.is-map-mode .v2-fleet-map { min-height: 0; height: 100%; }
|
||||
}
|
||||
|
||||
.v2-track-query-panel .v2-track-query-header.v2-workspace-command-bar,
|
||||
.v2-track-query-panel .v2-track-query-header.has-identity.v2-workspace-command-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 10px;
|
||||
}
|
||||
.v2-track-query-header .v2-workspace-command-copy,
|
||||
.v2-track-query-header.has-identity .v2-workspace-command-copy {
|
||||
flex: 1 1 130px;
|
||||
min-width: 90px;
|
||||
}
|
||||
.v2-track-query-header .v2-workspace-command-actions,
|
||||
.v2-track-query-header.has-identity .v2-workspace-command-actions {
|
||||
flex: 0 0 auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
.v2-track-query-header .v2-workspace-command-stack { min-width: 90px; }
|
||||
.v2-track-query-header .v2-workspace-command-title,
|
||||
.v2-track-query-header.has-identity .v2-workspace-command-title,
|
||||
.v2-track-query-header .v2-workspace-command-description,
|
||||
.v2-track-query-header.has-identity .v2-workspace-command-description {
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
/* Shared desktop workspace: restrained chrome, readable content, native controls.
|
||||
* Keep page-specific layouts in apple-*-pages.css. Overlays also live outside
|
||||
* the shell, so their selectors deliberately use the workspace component class.
|
||||
*/
|
||||
body {
|
||||
--semi-color-primary: var(--v2-color-primary);
|
||||
--semi-color-primary-hover: var(--v2-color-primary-hover);
|
||||
--semi-color-primary-active: #005bb5;
|
||||
--semi-color-primary-light-default: var(--v2-color-primary-soft);
|
||||
--semi-color-primary-light-hover: #deebfa;
|
||||
--semi-color-primary-light-active: #d3e4f8;
|
||||
--semi-color-border: var(--v2-color-border);
|
||||
--semi-color-bg-0: var(--v2-color-canvas);
|
||||
--semi-color-text-0: var(--v2-color-text);
|
||||
--semi-color-text-1: #3a3a3c;
|
||||
--semi-color-text-2: var(--v2-color-text-secondary);
|
||||
--semi-color-text-3: var(--v2-color-text-tertiary);
|
||||
--v2-control-border: var(--v2-color-border-strong);
|
||||
--v2-control-bg-muted: #f5f5f7;
|
||||
--v2-control-text-muted: var(--v2-color-text-secondary);
|
||||
--v2-control-height-mobile: 44px;
|
||||
--v2-control-height-touch: 44px;
|
||||
}
|
||||
|
||||
.v2-shell .v2-sidebar {
|
||||
width: var(--v2-sidebar-width);
|
||||
background: #f0f0f2;
|
||||
border-right: 1px solid rgba(0,0,0,.08);
|
||||
}
|
||||
.v2-shell .v2-sidebar.is-collapsed { width: var(--v2-sidebar-width-compact); }
|
||||
.v2-shell .v2-main { margin-left: var(--v2-sidebar-width); }
|
||||
.v2-shell .v2-sidebar.is-collapsed + .v2-main { margin-left: var(--v2-sidebar-width-compact); }
|
||||
.v2-shell .v2-brand { height: 72px; padding-inline: 24px; border-bottom: 0; }
|
||||
.v2-shell .v2-brand-logo { width: 146px; }
|
||||
.v2-shell .v2-navigation.semi-navigation { background: transparent; padding: 12px; }
|
||||
.v2-shell .v2-navigation.semi-navigation .semi-navigation-item {
|
||||
min-height: 42px;
|
||||
border-radius: 8px;
|
||||
color: #49494d;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
.v2-shell .v2-navigation.semi-navigation .semi-navigation-item-text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.v2-shell .v2-navigation.semi-navigation .semi-navigation-item-icon {
|
||||
background: transparent;
|
||||
color: #636366;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
.v2-shell .v2-navigation.semi-navigation .semi-navigation-item:hover { background: #e7e7ea; }
|
||||
.v2-shell .v2-navigation.semi-navigation .semi-navigation-item-selected {
|
||||
background: #dfe5ed;
|
||||
color: #1d1d1f;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-shell .v2-navigation.semi-navigation .semi-navigation-item-selected .semi-navigation-item-icon {
|
||||
background: transparent;
|
||||
color: var(--v2-color-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-shell .v2-navigation.semi-navigation .semi-navigation-item.v2-nav-group-start::before {
|
||||
color: #76767b;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.v2-shell .v2-collapse.semi-button { border-top-color: rgba(0,0,0,.06); color: #636366; }
|
||||
.v2-shell .v2-topbar {
|
||||
height: 72px;
|
||||
background: rgba(250,250,252,.94);
|
||||
border-bottom: 1px solid rgba(0,0,0,.08);
|
||||
padding-inline: 24px;
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
.v2-shell .v2-topbar-title h4.semi-typography {
|
||||
color: var(--v2-color-text);
|
||||
font-size: 22px;
|
||||
font-weight: 650;
|
||||
letter-spacing: -.025em;
|
||||
}
|
||||
.v2-shell .v2-topbar-title .v2-topbar-description.semi-typography { color: #76767b; font-size: 13px; }
|
||||
.v2-shell .v2-topbar-actions { gap: 8px; }
|
||||
.v2-shell .v2-topbar-actions > .v2-help-trigger.semi-button {
|
||||
min-width: 40px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #636366;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-shell .v2-current-user .semi-tag { display: none; }
|
||||
.v2-shell .v2-current-user b { font-weight: 500; }
|
||||
.v2-shell .v2-current-user .semi-avatar { background: #e3e3e8; color: #48484a; }
|
||||
.v2-shell .v2-topbar-actions .semi-button:hover { background: #ededf0; }
|
||||
|
||||
/* Common toolbars contain a single title and an optional supporting sentence. */
|
||||
.v2-shell .v2-content .v2-workspace-command-bar {
|
||||
min-height: 66px;
|
||||
margin-inline: 0;
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--v2-color-border);
|
||||
border-radius: 0;
|
||||
background: var(--v2-color-surface);
|
||||
padding: 14px 20px;
|
||||
box-shadow: none;
|
||||
gap: 16px;
|
||||
}
|
||||
.v2-shell .v2-content .v2-workspace-command-bar::before,
|
||||
.v2-shell .v2-content .v2-workspace-panel-header::before { display: none; }
|
||||
.v2-shell .v2-content .v2-workspace-command-copy { gap: 12px; }
|
||||
.v2-shell .v2-content .v2-workspace-command-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex-basis: 30px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #636366;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-shell .v2-content .v2-workspace-command-eyebrow { display: none; }
|
||||
.v2-shell .v2-content .v2-workspace-command-title.semi-typography {
|
||||
color: var(--v2-color-text);
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
letter-spacing: -.015em;
|
||||
}
|
||||
.v2-shell .v2-content .v2-workspace-command-description.semi-typography {
|
||||
margin-top: 3px;
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.v2-shell .v2-content .v2-workspace-command-meta { color: #76767b; font-size: 12px; }
|
||||
.v2-shell .v2-content .v2-workspace-command-actions { gap: 10px; }
|
||||
.v2-shell .v2-content .v2-workspace-panel-header {
|
||||
min-height: 60px;
|
||||
padding: 14px 20px;
|
||||
border-left: 0;
|
||||
border-bottom: 1px solid var(--v2-color-border);
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
gap: 16px;
|
||||
}
|
||||
.v2-shell .v2-content .v2-workspace-panel-header .v2-workspace-panel-title.semi-typography {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--v2-color-text);
|
||||
}
|
||||
.v2-shell .v2-content .v2-workspace-panel-header .v2-workspace-panel-eyebrow { display: none; }
|
||||
.v2-shell .v2-content .v2-workspace-panel-header .v2-workspace-panel-description.semi-typography {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Controls share geometry without changing their native disabled/pressed state. */
|
||||
.v2-shell .semi-button { font-size: 13px; font-weight: 500; border-radius: 8px; }
|
||||
.v2-shell .semi-input-wrapper,
|
||||
.v2-shell .semi-select { border-radius: 8px; background: #fff; }
|
||||
.v2-shell .semi-input,
|
||||
.v2-shell .semi-select-selection { font-size: 14px; color: var(--v2-color-text); }
|
||||
.v2-shell .semi-input::placeholder { color: #76767b; }
|
||||
.v2-shell .semi-tag { font-size: 12px; font-weight: 500; letter-spacing: 0; }
|
||||
.v2-shell .v2-content .semi-table-thead > tr > th {
|
||||
background: #f8f8fa;
|
||||
color: #636366;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-bottom-color: var(--v2-color-border);
|
||||
}
|
||||
.v2-shell .v2-content .semi-table-tbody > tr > td {
|
||||
font-size: 14px;
|
||||
border-bottom-color: #efeff1;
|
||||
}
|
||||
.v2-shell .v2-content .semi-table-tbody > tr:hover > td { background: #f5f7fa; }
|
||||
.v2-shell .v2-content .v2-segmented-tabs.is-filled {
|
||||
gap: 2px;
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
background: #eeeef0;
|
||||
padding: 3px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-shell .v2-content .v2-segmented-tabs > .semi-button {
|
||||
min-height: 34px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #636366;
|
||||
}
|
||||
.v2-shell .v2-content .v2-segmented-tabs.is-filled > .semi-button.is-active {
|
||||
background: #fff;
|
||||
color: #1d1d1f;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.13);
|
||||
border-radius: 7px;
|
||||
}
|
||||
.v2-shell .v2-content .v2-segmented-tab-count {
|
||||
background: rgba(0,0,0,.05);
|
||||
color: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.v2-shell .v2-content .v2-workspace-metric-list > span {
|
||||
border-color: var(--v2-color-border);
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
}
|
||||
.v2-shell .v2-content .v2-workspace-metric-list > span > small,
|
||||
.v2-shell .v2-content .v2-workspace-metric-action > small { font-size: 12px; color: #636366; font-weight: 500; }
|
||||
.v2-shell .v2-content .v2-workspace-metric-list > span > strong,
|
||||
.v2-shell .v2-content .v2-workspace-metric-action > strong { font-size: 24px; font-weight: 600; letter-spacing: -.025em; font-variant-numeric: tabular-nums; }
|
||||
.v2-shell .v2-content .v2-workspace-metric-list > span > em,
|
||||
.v2-shell .v2-content .v2-workspace-metric-action > em { color: #76767b; font-size: 12px; line-height: 1.5; }
|
||||
.v2-shell .v2-content .v2-workspace-metric-list > span.is-active { background: #f0f5fc; }
|
||||
|
||||
/* Sheets and dialogs are the only elevated content surfaces. */
|
||||
body [data-workspace-sheet-id] .semi-sidesheet-inner {
|
||||
background: #fff;
|
||||
box-shadow: -12px 0 48px rgba(0,0,0,.12);
|
||||
}
|
||||
body [data-workspace-sheet-id] .semi-sidesheet-header {
|
||||
border-bottom: 1px solid var(--v2-color-border);
|
||||
background: #fafafa;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-title { gap: 12px; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-title-icon {
|
||||
border: 0;
|
||||
background: #eeeef0;
|
||||
color: #636366;
|
||||
box-shadow: none;
|
||||
}
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-title strong { color: #1d1d1f; font-size: 20px; font-weight: 600; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-title small { color: #636366; font-size: 13px; line-height: 1.5; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-body { background: #fff; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-content { font-size: 14px; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-footer { background: #fafafa; gap: 16px; padding: 16px 24px; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-footer > span { color: #636366; font-size: 12px; }
|
||||
body [data-workspace-sheet-id] .semi-button { min-height: 36px; font-size: 13px; border-radius: 8px; }
|
||||
body [data-workspace-sheet-id] .semi-sidesheet-close { min-width: 36px; min-height: 36px; border-radius: 50%; }
|
||||
body .v2-help-panel li { background: #f5f5f7; color: #3a3a3c; font-size: 14px; padding: 16px; }
|
||||
body .v2-help-panel > footer { font-size: 12px; }
|
||||
body .v2-help-overview.semi-card { border: 0; background: transparent; box-shadow: none; }
|
||||
body .v2-help-overview .semi-typography { color: #636366; font-size: 14px; line-height: 1.6; }
|
||||
body .v2-account-dropdown { border-color: #e5e5e7; border-radius: 12px; box-shadow: 0 12px 40px rgba(0,0,0,.16); }
|
||||
body .v2-account-dropdown-profile { background: #f5f5f7; }
|
||||
body .v2-account-dropdown-profile strong { font-size: 14px; }
|
||||
body .v2-account-dropdown-profile small,
|
||||
body .v2-account-dropdown-profile em { font-size: 12px; color: #636366; }
|
||||
|
||||
:where(.v2-shell, [data-workspace-sheet-id]) :is(button, a, input, [role="button"], [role="tab"]):focus-visible {
|
||||
outline: 2px solid var(--v2-color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
@media (max-width: 1100px) and (min-width: 681px) {
|
||||
.v2-shell .v2-main { margin-left: var(--v2-sidebar-width-compact); }
|
||||
.v2-shell .v2-topbar-description { display: none; }
|
||||
}
|
||||
@media (max-width: 680px) {
|
||||
.v2-shell .v2-main { margin-left: 0; }
|
||||
.v2-shell .v2-topbar { height: 60px; padding-inline: 16px; }
|
||||
.v2-shell .v2-topbar-title h4.semi-typography { font-size: 20px; }
|
||||
.v2-shell .v2-topbar-description { display: none; }
|
||||
.v2-shell .v2-topbar-actions { gap: 0; }
|
||||
.v2-shell .v2-help-trigger-label { display: none; }
|
||||
.v2-shell .v2-topbar-actions > .semi-button { min-height: 44px; }
|
||||
.v2-shell .v2-content .v2-workspace-command-bar,
|
||||
.v2-shell .v2-content .v2-workspace-panel-header { padding: 12px 16px; gap: 10px; }
|
||||
.v2-shell .v2-content .v2-workspace-command-title.semi-typography { font-size: 16px; }
|
||||
.v2-shell .v2-content .v2-workspace-command-actions { flex-wrap: wrap; }
|
||||
.v2-shell .v2-content .v2-workspace-command-description.semi-typography { font-size: 12px; }
|
||||
.v2-shell .v2-content .v2-workspace-metric-list > span > strong,
|
||||
.v2-shell .v2-content .v2-workspace-metric-action > strong { font-size: 21px; }
|
||||
.v2-shell .v2-content .semi-button:not(.semi-button-size-small),
|
||||
.v2-shell .v2-content .semi-input-wrapper,
|
||||
.v2-shell .v2-content .semi-select,
|
||||
body [data-workspace-sheet-id] .semi-button { min-height: 44px; }
|
||||
.v2-shell .v2-content .v2-segmented-tabs > .semi-button { min-height: 40px; }
|
||||
.v2-shell .v2-mobile-navigation { background: rgba(250,250,252,.96); backdrop-filter: blur(20px); border-color: #e5e5e7; }
|
||||
body [data-workspace-sheet-id] .semi-sidesheet-header { padding: 18px 16px; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-footer { padding: 12px 16px; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.v2-shell *, [data-workspace-sheet-id] * { transition-duration: 0s !important; animation-duration: 0s !important; }
|
||||
}
|
||||
|
||||
.v2-shell .v2-navigation.semi-navigation .semi-navigation-item-selected::after { display: none; }
|
||||
.v2-shell .v2-topbar-title > .v2-topbar-heading > .v2-topbar-page-title.semi-typography { font-size: 22px; font-weight: 650; }
|
||||
body .v2-workspace-help-sidesheet .v2-help-overview.semi-card { min-height: 0; }
|
||||
body .v2-workspace-help-sidesheet .v2-help-overview > .semi-card-body { padding: 8px 2px 20px; }
|
||||
|
||||
/* Long titles must leave room for the close control at narrow widths. */
|
||||
body [data-workspace-sheet-id] .semi-sidesheet-header { display: flex; align-items: flex-start; gap: 12px; }
|
||||
body [data-workspace-sheet-id] .semi-sidesheet-title { flex: 1; min-width: 0; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-title { min-width: 0; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-title > span { min-width: 0; flex: 1; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-title small { white-space: normal; overflow-wrap: anywhere; }
|
||||
body [data-workspace-sheet-id] .semi-sidesheet-close { flex: 0 0 36px; margin: 0; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-summary > span { background: #fafafa; box-shadow: none; border-color: #e5e5e7; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-summary small,
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-summary em { font-size: 12px; color: #636366; line-height: 1.5; }
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-summary strong { font-size: 16px; font-weight: 600; }
|
||||
@media (max-width: 680px) {
|
||||
body [data-workspace-sheet-id] .v2-workspace-config-title-icon { display: none; }
|
||||
body [data-workspace-sheet-id] .semi-sidesheet-close { flex-basis: 44px; min-width: 44px; }
|
||||
.v2-shell .v2-topbar-title > .v2-topbar-heading > .v2-topbar-page-title.semi-typography { font-size: 20px; }
|
||||
}
|
||||
|
||||
body .v2-mobile-more-section > header strong { font-size: 14px; }
|
||||
body .v2-mobile-more-section > header span,
|
||||
body .v2-mobile-more-section nav a > span { font-size: 12px; color: #636366; }
|
||||
body .v2-mobile-more-section nav a strong { font-size: 14px; }
|
||||
body .v2-mobile-more-section nav a :is(small, em) { font-size: 12px; color: #636366; }
|
||||
.v2-shell .semi-input:focus-visible,
|
||||
body [data-workspace-sheet-id] .semi-input:focus-visible { outline: none; box-shadow: none; }
|
||||
+17
-17
@@ -10,24 +10,24 @@
|
||||
color-scheme: light;
|
||||
|
||||
/* Color */
|
||||
--v2-color-canvas: #f6f8fb;
|
||||
--v2-color-canvas: #f5f5f7;
|
||||
--v2-color-surface: #ffffff;
|
||||
--v2-color-surface-subtle: #fafbfd;
|
||||
--v2-color-text: #172033;
|
||||
--v2-color-text-secondary: #66758a;
|
||||
--v2-color-text-tertiary: #8a96a7;
|
||||
--v2-color-border: #dfe5ed;
|
||||
--v2-color-border-strong: #cfd7e2;
|
||||
--v2-color-primary: #1467e8;
|
||||
--v2-color-primary-hover: #0f5dcc;
|
||||
--v2-color-primary-soft: #edf4ff;
|
||||
--v2-color-success: #15965f;
|
||||
--v2-color-danger: #dc3f3f;
|
||||
--v2-color-warning: #d98900;
|
||||
--v2-color-surface-subtle: #fafafa;
|
||||
--v2-color-text: #1d1d1f;
|
||||
--v2-color-text-secondary: #636366;
|
||||
--v2-color-text-tertiary: #76767b;
|
||||
--v2-color-border: #e5e5e7;
|
||||
--v2-color-border-strong: #c7c7cc;
|
||||
--v2-color-primary: #0071e3;
|
||||
--v2-color-primary-hover: #0064cc;
|
||||
--v2-color-primary-soft: #eaf3ff;
|
||||
--v2-color-success: #248a3d;
|
||||
--v2-color-danger: #d70015;
|
||||
--v2-color-warning: #a15c00;
|
||||
|
||||
/* Typography */
|
||||
--v2-font-sans: Inter, "SF Pro Text", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
--v2-type-caption: 11px;
|
||||
--v2-font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
--v2-type-caption: 12px;
|
||||
--v2-type-meta: 12px;
|
||||
--v2-type-control: 14px;
|
||||
--v2-type-body: 14px;
|
||||
@@ -46,14 +46,14 @@
|
||||
--v2-space-7: 28px;
|
||||
--v2-space-8: 32px;
|
||||
--v2-radius-control: 8px;
|
||||
--v2-radius-surface: 10px;
|
||||
--v2-radius-surface: 12px;
|
||||
--v2-control-height: 40px;
|
||||
--v2-control-height-large: 44px;
|
||||
--v2-touch-target: 44px;
|
||||
--v2-sidebar-width: 228px;
|
||||
--v2-sidebar-width-compact: 72px;
|
||||
--v2-page-gutter: clamp(18px, 1.7vw, 28px);
|
||||
--v2-page-gap: 12px;
|
||||
--v2-page-gap: 16px;
|
||||
|
||||
/* Motion */
|
||||
--v2-motion-fast: 140ms;
|
||||
|
||||
@@ -5,29 +5,10 @@
|
||||
* keeps one visual hierarchy: identity, menu access, then vehicle scope.
|
||||
*/
|
||||
|
||||
.v2-user-admin,
|
||||
.v2-user-editor-sidesheet {
|
||||
--v2-color-canvas: #f6f8fb;
|
||||
--v2-color-surface-subtle: #fafbfd;
|
||||
--v2-color-text: #172033;
|
||||
--v2-color-text-secondary: #66758a;
|
||||
--v2-color-text-tertiary: #8a96a7;
|
||||
--v2-color-border: #dfe5ed;
|
||||
--v2-color-primary: #1467e8;
|
||||
--v2-color-warning: #d98900;
|
||||
--v2-radius-surface: 10px;
|
||||
--v2-motion-fast: 140ms;
|
||||
}
|
||||
|
||||
/* Batch account import keeps upload, server preflight, repair and retry in one wide task surface. */
|
||||
.v2-user-import-sidesheet {
|
||||
--v2-color-text: #172033;
|
||||
--v2-color-text-secondary: #66758a;
|
||||
--v2-color-border: #dfe5ed;
|
||||
}
|
||||
/* Colours, typography and motion inherit the shared foundation. */
|
||||
|
||||
.v2-user-import-sidesheet .v2-workspace-config-content {
|
||||
background: #f6f8fb;
|
||||
background: var(--v2-color-canvas);
|
||||
}
|
||||
|
||||
.v2-user-import-workspace {
|
||||
@@ -70,7 +51,7 @@
|
||||
.v2-user-import-row-result small,
|
||||
.v2-user-import-upload small {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-import-upload {
|
||||
@@ -106,14 +87,14 @@
|
||||
|
||||
.v2-user-import-format span {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.v2-user-import-format code {
|
||||
overflow-x: auto;
|
||||
color: #36506f;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -123,7 +104,7 @@
|
||||
background: #fff4f3;
|
||||
padding: 9px 14px;
|
||||
color: #b42318;
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-import-list {
|
||||
@@ -161,7 +142,7 @@
|
||||
border-radius: 6px;
|
||||
background: #eef3f9;
|
||||
color: #5b6b80;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@@ -211,7 +192,7 @@
|
||||
|
||||
.v2-user-import-repair-fields label > span {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
@@ -370,7 +351,7 @@
|
||||
|
||||
.v2-user-directory-metric-rail .v2-workspace-metric-list > span > small {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-directory-metric-rail .v2-workspace-metric-list > span > strong {
|
||||
@@ -381,7 +362,7 @@
|
||||
|
||||
.v2-user-directory-metric-rail .v2-workspace-metric-list > span > em {
|
||||
color: var(--v2-color-text-tertiary);
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-directory-body {
|
||||
@@ -418,7 +399,7 @@
|
||||
|
||||
.v2-user-route-notice small {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-directory-columns {
|
||||
@@ -429,7 +410,7 @@
|
||||
background: var(--v2-color-surface-subtle);
|
||||
padding: 0 16px;
|
||||
color: var(--v2-color-text-tertiary);
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -520,7 +501,7 @@
|
||||
.v2-user-provider-tag.semi-tag {
|
||||
min-height: 20px;
|
||||
border-radius: 5px;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-list-secondary {
|
||||
@@ -533,7 +514,7 @@
|
||||
.v2-user-list-secondary > small {
|
||||
overflow: hidden;
|
||||
color: var(--v2-color-text-tertiary);
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -555,7 +536,7 @@
|
||||
|
||||
.v2-user-list-facts > b > i {
|
||||
color: var(--v2-color-text-tertiary);
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -580,7 +561,7 @@
|
||||
min-width: 64px;
|
||||
min-height: 24px;
|
||||
border-radius: 999px;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
@@ -652,7 +633,7 @@
|
||||
.v2-user-editor-switcher small {
|
||||
overflow: hidden;
|
||||
color: var(--v2-color-text-tertiary);
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -687,7 +668,7 @@
|
||||
|
||||
.v2-user-change-preview small {
|
||||
color: #9b7249;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-change-preview > div {
|
||||
@@ -730,7 +711,7 @@
|
||||
border: 1px solid #d9e2ee;
|
||||
border-radius: 50%;
|
||||
color: var(--v2-color-text-tertiary);
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -815,7 +796,7 @@
|
||||
.v2-user-identity-source > p,
|
||||
.v2-user-identity-source > div small {
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
@@ -843,7 +824,7 @@
|
||||
.v2-user-identity-source > div strong {
|
||||
overflow: hidden;
|
||||
color: #30445d;
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
font-weight: 680;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -929,7 +910,7 @@
|
||||
|
||||
.v2-user-vehicle-composer-heading small {
|
||||
color: var(--v2-color-text-tertiary);
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-vehicle-composer .v2-vehicle-permission-tools {
|
||||
@@ -1057,7 +1038,7 @@
|
||||
}
|
||||
|
||||
.v2-user-directory-glance {
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-directory-glance b {
|
||||
@@ -1086,7 +1067,7 @@
|
||||
}
|
||||
|
||||
.v2-user-directory-metric-rail .v2-workspace-metric-list > span > em {
|
||||
font-size: 9px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-directory-body,
|
||||
@@ -1170,7 +1151,7 @@
|
||||
overflow: hidden;
|
||||
max-width: 47%;
|
||||
flex: 0 1 auto;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -1186,7 +1167,7 @@
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
color: var(--v2-color-text-secondary);
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 520;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -1204,7 +1185,7 @@
|
||||
.v2-user-status-tag.semi-tag {
|
||||
min-width: 40px;
|
||||
min-height: 22px;
|
||||
font-size: 9px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-config-label {
|
||||
@@ -1246,11 +1227,11 @@
|
||||
|
||||
.v2-user-editor-sidesheet .v2-workspace-config-summary > span > small,
|
||||
.v2-user-editor-sidesheet .v2-workspace-config-summary > span > em {
|
||||
font-size: 9px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-editor-sidesheet .v2-workspace-config-summary > span > strong {
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-change-preview {
|
||||
@@ -1269,7 +1250,7 @@
|
||||
}
|
||||
|
||||
.v2-user-change-preview .semi-tag {
|
||||
font-size: 9px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-editor-switcher {
|
||||
@@ -1294,7 +1275,7 @@
|
||||
.v2-user-editor-sidesheet .v2-user-editor-tabs > .semi-tabs-bar .semi-tabs-tab {
|
||||
min-height: 49px;
|
||||
padding-inline: 8px;
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-user-tab-label {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Apple Design 发布验收
|
||||
|
||||
发布版本:`apple-design-202609171849`,时间:2026-09-17 18:49–18:54(Asia/Shanghai)。
|
||||
|
||||
## 范围
|
||||
|
||||
现有九个业务入口、共享导航和帮助、桌面/手机查询与详情界面。采用系统字体、中性表面、清晰层级、克制蓝色动作与状态色;精简重复说明,优化密集表格和手机操作区。三个子代理分别负责数据页、治理页、地图/轨迹页,主代理统一设计和逐页验收。
|
||||
|
||||
UI源码提交:`39d9cb54`,分支 `codex/apple-design-20260917`,已推送origin。最终发布Tag:`apple-design-20260917-1849`。
|
||||
|
||||
## 前置回退保障
|
||||
|
||||
改造前完整代码快照提交 `f7e7176f88b4fd49338dda8e72d701a24617fb2f` 已推送;回退Tag `pre-apple-design-20260917-1805` 已核对远端存在。线上应用与配置归档位于 `/opt/lingniu-vehicle-platform/backups/pre-apple-design-202609171805/`,发布前 SHA256SUMS 两项再次校验成功。详情见 [回退基线](rollback-baseline.md)。
|
||||
|
||||
## 自动化与构建
|
||||
|
||||
- 完整Web测试:79个文件542项通过。
|
||||
- 后续改动:AppShell、Alerts、Track、Operations共88项通过;最后事件表格滚动修复后Alerts49项通过。
|
||||
- 最终 `npm run build`(TypeScript、Vite、prepare-dist、verify-build)通过,84个发布资源验证通过。
|
||||
- `git diff --check`通过。
|
||||
- Web归档SHA-256:`af53ef1f1364a756c2300d1bafa3ac726aea8cd43ff1f7c1f915704532be91c9`。
|
||||
|
||||
## 生产发布与核验
|
||||
|
||||
- 使用现有原子发布脚本,仅传Web归档;当前目录为 `/opt/lingniu-vehicle-platform/releases/apple-design-202609171849`。脚本具备失败自动切回旧版本机制。
|
||||
- 安装门禁通过:84个当前资源、132个兼容资源;公开HTTPS入口复验通过;保留3代兼容资源,未裁剪历史release。
|
||||
- API、流式告警、规则告警三个服务均active。API、alert-stream-evaluator、alert-evaluator文件哈希与前版相同。前版没有独立alert-notification-dispatcher文件,因此发布后附加校验曾在检查此可选文件时报FileNotFoundError;安装已经成功,此错误不代表安装回滚或服务失败。
|
||||
- 正式HTTPS健康接口返回新版本;逐项检查当时51条原生告警的列表标题和详情一致,均有具体名称(48条绝缘,1条SOC跳变,1条SOC低,1条仅等级且明确标注未上报具体项)。
|
||||
- 线上浏览器逐页加载九个入口并检查实际画面:事件具体告警详情、车辆目录、账号目录、接入来源、历史查询、里程矩阵、轨迹查询、运维队列、监控地图。证据为 `outputs/apple-design-20260917/after/44-production-native-detail.png` 至 `52-production-monitor.png`。
|
||||
- 生产车辆分页从1/103切换至2/103;历史导出任务中心打开并显示已有66个任务;里程矩阵可区分无数据与数值;监控显示实际14个省级区域880辆有定位;生产浏览器本次采集的error日志为空。
|
||||
- 未改动业务数据、告警定义、来源优先级或客户权限。未触发通知、归档或生产批量处置。地图逐帧播放、查询、抽屉和手机菜单交互在本地mock另行验证,详细边界见 [UI审查](ui-review.md)。
|
||||
|
||||
## 回滚
|
||||
|
||||
前版 `native-alarm-details-202609171739` 及独立备份保留。将current原子指回该release并恢复对应PLATFORM_RELEASE后重启平台API即可恢复此前前端;本轮没有数据库迁移。需要源代码时从 `pre-apple-design-20260917-1805` 创建修复分支。
|
||||
|
||||
构建、测试、部署、原生告警核验日志存于本地 `outputs/apple-design-20260917/`。包含车辆信息的截图与配置备份不推送到Git。
|
||||
@@ -0,0 +1,70 @@
|
||||
# 车辆数据中台 Apple Design UI/UX 审查
|
||||
|
||||
日期:2026-09-17。范围:现有 9 个业务入口、共享导航、详情/查询抽屉及手机布局。
|
||||
|
||||
## 审查方法与证据边界
|
||||
|
||||
本轮从线上 before 截图和当前代码确认问题,再在本地改造后使用 UI mock 场景检查渲染及交互。方向是系统字体、清晰文字层级、中性连续内容表面、少量蓝色主操作、轻分隔、足够触控面积;不是给所有区域添加玻璃效果。
|
||||
|
||||
下表引用的 before/after 截图均已由本报告作者实际打开检查。其他截图由主代理进行操作及视觉检查,另列为补充证据,不将仅存在的文件视为本报告作者已验证。截图是拍摄时刻的中间状态,不能自动证明之后的修复正确。
|
||||
|
||||
本地 mock 仅验证 UI 渲染和操作路径,不能证明生产车辆数量、在线状态、原生告警等级/故障码、统计结果或服务健康准确。生产数据语义与业务接口应保留;此次设计调整不重新定义“离线”“无来源”“无数据”和数值 0,不删查询/导出、权限、事件证据与数据源策略功能。原生 GB/T 32960 告警应继续显示具体名称,并保留来源与原始字段详情。
|
||||
|
||||
## 九页审查矩阵
|
||||
|
||||
证据链接相对于仓库根目录中的 `outputs/apple-design-20260917/`。
|
||||
|
||||
| 页面 | 实际观察到的问题 | 本轮改变 | 已检查证据与限制 |
|
||||
|---|---|---|---|
|
||||
| 全局监控 `/monitor` | 桌面指标区、列表、地图叠加多种蓝底及细小文字;地图覆盖率和真实位置状态需要保持可辨认。 | 系统感地图工作区与中性控件;保留地图/列表切换、筛选、跟随、聚合标记和覆盖说明。 | [before](../../../../outputs/apple-design-20260917/before/08-monitor.png)、[手机 after](../../../../outputs/apple-design-20260917/after/22-monitor-390.png)。手机地图已可见;底部图例紧邻固定导航,仍需最终安全区复核。 |
|
||||
| 车辆查询 `/vehicles` | 搜索卡及列表指标存在重复装饰;旧表格文字偏小。首轮 after 中“查看档案”断行、在线行绿条、搜索卡底角仍方;手机“修改”与批量同步相撞。 | 14px 车辆行、12px VIN;搜索与目录中性分组;目录标题置左、指标置右;单行查看操作;去行侧色条;范围独占手机一行、批量同步单独下一行且至少 44px 高。单车页减少首栏高度,导航改纯工具栏,操作 13px、辅助至少 12px、事件标题 14px。 | [before](../../../../outputs/apple-design-20260917/before/02-vehicles.png)、[目录 after](../../../../outputs/apple-design-20260917/after/01-vehicles-1440.png)、[单车 after](../../../../outputs/apple-design-20260917/after/02-vehicle-detail-1440.png)、[手机 after](../../../../outputs/apple-design-20260917/after/10-vehicles-390.png)。上述 after 揭示的问题已修代码,需最新重截图确认。 |
|
||||
| 轨迹回放 `/tracks` | 未查询时左右各一套空状态,并提前显示无数据指标/标签;地图中央步骤卡重复说明查询表单。 | 单一主要空状态与查询按钮;查询后才展开结果指标;浅中性地图画布与明确播放操作,保留时间、来源及回放。 | [before](../../../../outputs/apple-design-20260917/before/09-tracks.png)、[桌面 after](../../../../outputs/apple-design-20260917/after/06-tracks-1440.png)。首轮 after 日期输入和描述有截断,需检查后续修复及播放结果。 |
|
||||
| 历史数据 `/history` | “历史证据/遥测证据检索/证据结果/历史明细”层级冗余,空状态又放三步卡;表格和工具文字密集。 | 收敛为查询条件→历史明细;空状态保留描述与 CTA,去重复步骤;13px 表格和更清晰表头,保留多车、协议、原始报文、字段设置、趋势、导出任务。 | [before](../../../../outputs/apple-design-20260917/before/04-history.png)、[有结果 after](../../../../outputs/apple-design-20260917/after/23-history-result-1440.png)。结果表 VIN、时间、协议和数值可读;查询回执与工具辅助文字仍需最终密度复核。 |
|
||||
| 里程查询 `/statistics` | 重复“每日里程”眉题;表格小字且核心数据与辅助说明层级弱;底部暴露缓存实现文案。 | 保留动态日期列/热度与区间合计;13px 数值、tabular-nums、清晰主标题;底部改为上海时区统计说明;不改变缺失与 0 的差别、来源优先级和导出。 | [before](../../../../outputs/apple-design-20260917/before/03-statistics.png)、[桌面 after](../../../../outputs/apple-design-20260917/after/24-statistics-1440.png)。当前 3 日矩阵清晰;长日期及氢耗证据要继续核验。 |
|
||||
| 事件中心 `/alerts` | 表格图标底色与侧条重复强调;多标签和筛选并列占据注意力。 | 中性列表与克制状态色;保留事件流/自动化/通知导航、具体事件标题和处理状态。 | [before](../../../../outputs/apple-design-20260917/before/01-events.png)、[1024 after](../../../../outputs/apple-design-20260917/after/27-alerts-1024.png)。右侧执行状态超出当前截图宽度,需验证可横向访问且不丢列。mock 事件名称不构成原生告警线上验证。 |
|
||||
| 接入管理 `/access` | “来源治理/接入健康工作台/接入差异”重复眉题;每个协议状态单独卡片造成边框密集。 | 精简眉题和协议容器装饰,保留在线/离线/无来源文字;适配窄桌面筛选换行及抽屉。 | [before](../../../../outputs/apple-design-20260917/before/05-access.png)、[1024 after](../../../../outputs/apple-design-20260917/after/25-access-1024.png)。协议状态可区分;需复核更多车辆、治理与认领操作。 |
|
||||
| 账号管理 `/users` | 旧桌面列表同时并排两账号,顶部列名与两组值难对应;授权信息阅读路径不连续。 | 桌面统一一行一账号,账户/菜单/车辆/最近登录/状态对齐;中性头像,创建账号作为主操作;保留配置和权限确认。 | [before](../../../../outputs/apple-design-20260917/before/06-users.png)、[桌面 after](../../../../outputs/apple-design-20260917/after/04-users-1440.png)。可辨认正常、未关联、停用三类;数据为 mock,不能据此验证真实授权。 |
|
||||
| 运维质量 `/operations` | 眉题、彩色头栏、对账标签重复;队列操作多,窄桌面容易挤压说明和表格。 | 精简运行快照/自动对账眉题;中性分组,保留差异处置/单车诊断/全局健康;批量、导出、趋势及筛选继续可用。 | [before](../../../../outputs/apple-design-20260917/before/07-operations.png)、[1024 after](../../../../outputs/apple-design-20260917/after/26-operations-1024.png)。截图中队列说明与按钮仍拥挤、右列需要横向查看,后续修改需重验。 |
|
||||
|
||||
## 共享实现与复杂层叠
|
||||
|
||||
- `foundation.css` 收敛颜色、系统字体和基础几何 token;共享顶栏、导航、输入和按钮由 `apple-workspace.css` 管理。
|
||||
- 页面细节分别置于 `apple-data-pages.css`、`apple-governance.css`、`apple-map-pages.css`。数据页规则有局部作用域,不覆盖共享 topbar token。
|
||||
- 帮助抽屉移除“页面目标/建议步骤/Esc”装饰指标,保留当前模块说明和操作建议。
|
||||
- 既有 CSS 约 55,150 行,混有高 specificity、inline Card padding、媒体查询和懒加载用户样式;仅调整 token 不能证明视觉生效。首轮截图中的车辆左色条就是高 specificity 旧规则覆盖的实例,已用局部精确选择器处理。
|
||||
- 搜索卡圆角通过内部透明背景处理,避免用 overflow:hidden 裁掉候选搜索浮层。
|
||||
|
||||
主代理补充检查证据:帮助 `after/03-help-1440.png`、账号详情 `05-user-detail-1440.png`、轨迹结果 `07-track-result-1440.png`、手机查询/地图 `08-tracks-390.png` 与 `09-track-map-390.png`、车辆搜索抽屉 `11-vehicle-search-390.png`、更多导航 `13-more-390.png`、事件/自动化手机 `15-alerts-390.png` 与 `16-automation-390.png`、接入详情 `18-access-detail-390.png`、健康页 `21-health-390.png`。本报告作者未独立逐一打开这些补充图,不将其归为个人完成验证。
|
||||
|
||||
## 已完成验证
|
||||
|
||||
- 数据页业务测试:VehiclePage 15、HistoryPage 52、StatisticsPage 29,共 96 项通过;单车导航改变后 VehiclePage 15 项重跑通过。
|
||||
- `productionEntry.test.ts` 10 项独立通过。仅将删除的眉题/装饰色/重复空状态断言改为共享组件、主标题及统一空态标题,保留启动、构建、ARIA、路由与业务契约断言。
|
||||
- TypeScript 编译检查在本报告提交前重新执行并通过;`git diff --check` 通过。当前 tsconfig 未启用 `noUnusedLocals`,因此编译通过不等于所有历史未使用 import 已清理;本次未擅自修改其他代理文件。
|
||||
- 当前 Node 测试环境使用 `NODE_OPTIONS=--no-experimental-webstorage`,避免运行时 experimental Web Storage 覆盖 jsdom 存储实现;业务存储逻辑未因此更改。
|
||||
- 上述局部测试结果不是最终全套测试或生产验收的替代。
|
||||
|
||||
## 首轮审查提出的验证清单(结果见下)
|
||||
|
||||
1. 最新 CSS/JSX 改动后重拍车辆目录、单车首屏、390px 范围栏;核对 nowrap、圆角、100px 首栏及44px手机动作实际生效。
|
||||
2. 重验轨迹日期完整显示、1024px 运维头部和右侧表列、手机地图图例安全区;长文本和权限不同的账户需检查。
|
||||
3. 九页最终桌面/手机冒烟:键盘焦点、返回上下文、分页、筛选提交/重置、抽屉开关、导出任务入口、地图播放与跟随。不能仅凭静态图宣称交互通过。
|
||||
4. 最终完整测试、TypeScript、生产 build 与产物检查;只以实际完成日志作为最终证据。
|
||||
5. 生产更新后验证资源加载和九页关键路径,并抽查真实原生 32960 告警名称/详情及既有数据来源语义。发布前后数值不得拿 mock 样例比对。
|
||||
6. 备份、提交、push、回退 Tag 和部署版本由主代理维护;本报告未独立检查远端备份、Git remote 或部署状态,不将其列为已完成证据。
|
||||
|
||||
|
||||
## 主代理最终本地验收(18:48)
|
||||
|
||||
本节由主代理按实际浏览器结果补充,取代首轮中间截图的待修状态:
|
||||
|
||||
- 车辆目录与单车详情:`after/34-vehicles-final-1440.png`、`35-vehicle-detail-final-1440.png`、`41-vehicles-final-390.png` 已检查;单行操作、圆角、手机范围与批量同步不重叠。
|
||||
- 轨迹:`40-tracks-final-1024.png` 查询标题、开始/结束日期完整;`31-track-final-390.png` 里程值完整;查询和键盘逐帧播放已实测。
|
||||
- 全局监控:`32-monitor-final-390.png` 内容高度716px,底部图例不被导航遮挡;`33-monitor-1440.png` 桌面地图与列表可用。
|
||||
- 接入:`38-access-fixed-1024.png` 指标恢复正常两行显示,修复旧网格轨道导致的裁切;手机协议保留在线/离线/无来源文案。
|
||||
- 事件:`43-alerts-fixed-1024.png` 执行状态完整显示。修复外层970px最小宽度造成的滚动容器错位,由Semi表格承载横向滚动。
|
||||
- 运维:`39-operations-final-1024.png` 头部说明和操作分行;`42-operations-menu-390.png` 手机更多操作包括批量处置、归档、导出、趋势、刷新,点击趋势成功打开详情。
|
||||
- 历史查询、里程日期范围查询、车辆档案返回、帮助关闭、账号权限标签、事件/自动化切换均已实测。没有执行生产权限修改或发送通知。
|
||||
- 完整Web测试79文件542项通过;后续修改针对AppShell/Alerts/Track/Operations重跑88项通过,最终表格修复后Alerts49项通过。最终 `npm run build`(包含TypeScript、Vite与产物门禁)通过,84个资源检查通过;`git diff --check`通过。
|
||||
- 开发控制台存在Semi组件findDOMNode弃用告警,以及高德UI脚本初始化提示;地图实际可见。未将其报告为无错误控制台;生产构建继续单独检查。
|
||||
- 本地截图用于UI验收;实际生产版本、原生告警与发布结果记录在 `release-verification.md`。
|
||||
Reference in New Issue
Block a user