feat(platform): streamline vehicle operations shell
This commit is contained in:
@@ -20,21 +20,21 @@ export type PageKey = 'dashboard' | 'vehicles' | 'map' | 'realtime' | 'detail' |
|
|||||||
|
|
||||||
const navGroups = [
|
const navGroups = [
|
||||||
{
|
{
|
||||||
title: '车辆工作台',
|
title: '车辆运营',
|
||||||
items: [
|
items: [
|
||||||
{ itemKey: 'dashboard', text: '运营驾驶舱', icon: <IconHome /> },
|
{ itemKey: 'dashboard', text: '运营驾驶舱', icon: <IconHome /> },
|
||||||
{ itemKey: 'map', text: '实时地图', icon: <IconMapPin /> },
|
{ itemKey: 'map', text: '实时地图', icon: <IconMapPin /> },
|
||||||
{ itemKey: 'vehicles', text: '车辆中心', icon: <IconServer /> },
|
|
||||||
{ itemKey: 'realtime', text: '实时监控', icon: <IconActivity /> },
|
{ itemKey: 'realtime', text: '实时监控', icon: <IconActivity /> },
|
||||||
|
{ itemKey: 'vehicles', text: '车辆中心', icon: <IconServer /> },
|
||||||
{ itemKey: 'detail', text: '车辆档案', icon: <IconSetting /> }
|
{ itemKey: 'detail', text: '车辆档案', icon: <IconSetting /> }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '轨迹与统计',
|
title: '轨迹报表',
|
||||||
items: [
|
items: [
|
||||||
{ itemKey: 'history', text: '轨迹回放', icon: <IconMapPin /> },
|
{ itemKey: 'history', text: '轨迹回放', icon: <IconMapPin /> },
|
||||||
{ itemKey: 'history-query', text: '数据导出', icon: <IconHistogram /> },
|
{ itemKey: 'mileage', text: '里程统计', icon: <IconBarChartHStroked /> },
|
||||||
{ itemKey: 'mileage', text: '里程统计', icon: <IconBarChartHStroked /> }
|
{ itemKey: 'history-query', text: '数据导出', icon: <IconHistogram /> }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -45,7 +45,7 @@ const navGroups = [
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '系统运维',
|
title: '平台运维',
|
||||||
items: [
|
items: [
|
||||||
{ itemKey: 'ops-quality', text: '运维质量', icon: <IconPulse /> }
|
{ itemKey: 'ops-quality', text: '运维质量', icon: <IconPulse /> }
|
||||||
]
|
]
|
||||||
@@ -125,6 +125,14 @@ export function AppShell({
|
|||||||
Promise.resolve(onVehicleSearch(value)).finally(() => setSearching(false));
|
Promise.resolve(onVehicleSearch(value)).finally(() => setSearching(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const quickActions = [
|
||||||
|
{ label: '地图', ariaLabel: '顶部地图态势', page: 'map' as const },
|
||||||
|
{ label: '监控', ariaLabel: '顶部实时监控', page: 'realtime' as const },
|
||||||
|
{ label: '轨迹', ariaLabel: '顶部轨迹回放', page: 'history' as const },
|
||||||
|
{ label: '里程', ariaLabel: '顶部里程统计', page: 'mileage' as const },
|
||||||
|
{ label: '导出', ariaLabel: '顶部数据导出', page: 'history-query' as const }
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vp-app">
|
<div className="vp-app">
|
||||||
<div className="vp-shell">
|
<div className="vp-shell">
|
||||||
@@ -152,7 +160,7 @@ export function AppShell({
|
|||||||
</aside>
|
</aside>
|
||||||
<main className="vp-main">
|
<main className="vp-main">
|
||||||
<header className="vp-topbar">
|
<header className="vp-topbar">
|
||||||
<Space spacing={12}>
|
<Space spacing={12} className="vp-topbar-search">
|
||||||
<Input
|
<Input
|
||||||
prefix={<IconSearch />}
|
prefix={<IconSearch />}
|
||||||
placeholder="搜索 VIN / 车牌 / 手机号"
|
placeholder="搜索 VIN / 车牌 / 手机号"
|
||||||
@@ -163,7 +171,7 @@ export function AppShell({
|
|||||||
/>
|
/>
|
||||||
<Button theme="solid" type="primary" loading={searching} onClick={search}>查询车辆</Button>
|
<Button theme="solid" type="primary" loading={searching} onClick={search}>查询车辆</Button>
|
||||||
</Space>
|
</Space>
|
||||||
<Space spacing={12}>
|
<div className="vp-topbar-context">
|
||||||
{currentVehicleLabel ? <Tag color="grey">{currentVehicleLabel}</Tag> : null}
|
{currentVehicleLabel ? <Tag color="grey">{currentVehicleLabel}</Tag> : null}
|
||||||
{currentVehicleStatus ? (
|
{currentVehicleStatus ? (
|
||||||
<Tag color={currentVehicleStatus.severity === 'ok' ? 'green' : currentVehicleStatus.severity === 'error' ? 'red' : 'orange'}>
|
<Tag color={currentVehicleStatus.severity === 'ok' ? 'green' : currentVehicleStatus.severity === 'error' ? 'red' : 'orange'}>
|
||||||
@@ -175,13 +183,14 @@ export function AppShell({
|
|||||||
一致性:{currentVehicleConsistency.title || `${currentVehicleConsistency.onlineSourceCount}/${currentVehicleConsistency.sourceCount} 来源`}
|
一致性:{currentVehicleConsistency.title || `${currentVehicleConsistency.onlineSourceCount}/${currentVehicleConsistency.sourceCount} 来源`}
|
||||||
</Tag>
|
</Tag>
|
||||||
) : null}
|
) : null}
|
||||||
<Tag color="blue">车辆运营</Tag>
|
|
||||||
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图就绪' : '地图待配置'}</Tag>
|
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图就绪' : '地图待配置'}</Tag>
|
||||||
<Button size="small" aria-label="顶部地图态势" onClick={() => onChange('map')}>实时地图</Button>
|
<div className="vp-topbar-quick-actions" aria-label="车辆服务快捷入口">
|
||||||
<Button size="small" aria-label="顶部实时监控" onClick={() => onChange('realtime')}>实时监控</Button>
|
{quickActions.map((item) => (
|
||||||
<Button size="small" aria-label="顶部轨迹回放" onClick={() => onChange('history')}>轨迹回放</Button>
|
<Button key={item.page} size="small" aria-label={item.ariaLabel} onClick={() => onChange(item.page)}>
|
||||||
<Button size="small" aria-label="顶部数据导出" onClick={() => onChange('history-query')}>数据导出</Button>
|
{item.label}
|
||||||
<Button size="small" aria-label="顶部里程统计" onClick={() => onChange('mileage')}>里程统计</Button>
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
aria-label={`顶部${alertLabel}`}
|
aria-label={`顶部${alertLabel}`}
|
||||||
@@ -190,8 +199,8 @@ export function AppShell({
|
|||||||
>
|
>
|
||||||
{alertLabel}
|
{alertLabel}
|
||||||
</Button>
|
</Button>
|
||||||
{platformRelease ? <Tag color="grey">版本 {platformRelease}</Tag> : null}
|
{platformRelease ? <span className="vp-topbar-release">版本 {platformRelease}</span> : null}
|
||||||
</Space>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -92,9 +92,10 @@ body {
|
|||||||
|
|
||||||
.vp-topbar {
|
.vp-topbar {
|
||||||
height: var(--vp-shell-header);
|
height: var(--vp-shell-header);
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: minmax(380px, 0.8fr) minmax(0, 1.2fr);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 16px;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
border-bottom: 1px solid var(--vp-border);
|
border-bottom: 1px solid var(--vp-border);
|
||||||
@@ -104,6 +105,50 @@ body {
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vp-topbar-search {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-topbar-context {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-topbar-quick-actions {
|
||||||
|
height: 32px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px;
|
||||||
|
border: 1px solid var(--vp-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-topbar-quick-actions .semi-button {
|
||||||
|
min-width: 44px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--vp-text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-topbar-quick-actions .semi-button:hover,
|
||||||
|
.vp-topbar-quick-actions .semi-button:focus-visible {
|
||||||
|
background: #ffffff;
|
||||||
|
color: var(--vp-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-topbar-release {
|
||||||
|
color: var(--vp-text-subtle);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.vp-link-health-button-warning {
|
.vp-link-health-button-warning {
|
||||||
color: var(--vp-warning);
|
color: var(--vp-warning);
|
||||||
border-color: rgba(247, 144, 9, 0.45);
|
border-color: rgba(247, 144, 9, 0.45);
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ test('renders vehicle platform shell', () => {
|
|||||||
render(<App />);
|
render(<App />);
|
||||||
expect(screen.getByText('车辆服务中台')).toBeInTheDocument();
|
expect(screen.getByText('车辆服务中台')).toBeInTheDocument();
|
||||||
expect(screen.getByText('地图监控 / 轨迹回放 / 里程统计')).toBeInTheDocument();
|
expect(screen.getByText('地图监控 / 轨迹回放 / 里程统计')).toBeInTheDocument();
|
||||||
expect(screen.getByText('车辆工作台')).toBeInTheDocument();
|
expect(screen.getByText('车辆运营')).toBeInTheDocument();
|
||||||
expect(screen.getByText('轨迹与统计')).toBeInTheDocument();
|
expect(screen.getByText('轨迹报表')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('告警通知').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('告警通知').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText('系统运维')).toBeInTheDocument();
|
expect(screen.getByText('平台运维')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('运营驾驶舱').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('运营驾驶舱').length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user