feat(platform): organize console around vehicle service
This commit is contained in:
@@ -18,18 +18,33 @@ import { isAMapConfigured } from '../config/appConfig';
|
||||
|
||||
export type PageKey = 'dashboard' | 'vehicles' | 'map' | 'realtime' | 'detail' | 'history' | 'history-query' | 'mileage' | 'alert-events' | 'quality' | 'notification-rules' | 'ops-quality';
|
||||
|
||||
const navItems = [
|
||||
{ itemKey: 'dashboard', text: '运营驾驶舱', icon: <IconHome /> },
|
||||
{ itemKey: 'map', text: '实时地图', icon: <IconMapPin /> },
|
||||
{ itemKey: 'vehicles', text: '车辆中心', icon: <IconServer /> },
|
||||
{ itemKey: 'realtime', text: '实时数据', icon: <IconActivity /> },
|
||||
{ itemKey: 'detail', text: '车辆档案', icon: <IconSetting /> },
|
||||
{ itemKey: 'history', text: '轨迹回放', icon: <IconMapPin /> },
|
||||
{ itemKey: 'history-query', text: '历史查询', icon: <IconHistogram /> },
|
||||
{ itemKey: 'mileage', text: '统计查询', icon: <IconBarChartHStroked /> },
|
||||
{ itemKey: 'alert-events', text: '告警事件', icon: <IconHistogram /> },
|
||||
{ itemKey: 'notification-rules', text: '通知规则', icon: <IconBell /> },
|
||||
{ itemKey: 'ops-quality', text: '运维质量', icon: <IconPulse /> }
|
||||
const navGroups = [
|
||||
{
|
||||
title: '车辆工作台',
|
||||
items: [
|
||||
{ itemKey: 'dashboard', text: '运营驾驶舱', icon: <IconHome /> },
|
||||
{ itemKey: 'map', text: '实时地图', icon: <IconMapPin /> },
|
||||
{ itemKey: 'vehicles', text: '车辆中心', icon: <IconServer /> },
|
||||
{ itemKey: 'realtime', text: '实时监控', icon: <IconActivity /> },
|
||||
{ itemKey: 'detail', text: '车辆档案', icon: <IconSetting /> }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '查询分析',
|
||||
items: [
|
||||
{ itemKey: 'history', text: '轨迹回放', icon: <IconMapPin /> },
|
||||
{ itemKey: 'history-query', text: '历史查询', icon: <IconHistogram /> },
|
||||
{ itemKey: 'mileage', text: '统计查询', icon: <IconBarChartHStroked /> }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '告警运维',
|
||||
items: [
|
||||
{ itemKey: 'alert-events', text: '告警事件', icon: <IconHistogram /> },
|
||||
{ itemKey: 'notification-rules', text: '通知规则', icon: <IconBell /> },
|
||||
{ itemKey: 'ops-quality', text: '运维质量', icon: <IconPulse /> }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function linkHealthClassName(count: number | null) {
|
||||
@@ -113,15 +128,22 @@ export function AppShell({
|
||||
<span className="vp-brand-mark" />
|
||||
<span className="vp-brand-copy">
|
||||
<span>车辆服务中台</span>
|
||||
<span>实时监控 / 轨迹 / 告警 / 统计</span>
|
||||
<span>一车一服务 / 多源归并</span>
|
||||
</span>
|
||||
</div>
|
||||
<Nav
|
||||
selectedKeys={[selectedPage]}
|
||||
items={navItems}
|
||||
onSelect={({ itemKey }) => onChange(itemKey as PageKey)}
|
||||
style={{ maxWidth: '100%' }}
|
||||
/>
|
||||
<div className="vp-nav-groups">
|
||||
{navGroups.map((group) => (
|
||||
<div key={group.title} className="vp-nav-group">
|
||||
<div className="vp-nav-section-title">{group.title}</div>
|
||||
<Nav
|
||||
selectedKeys={[selectedPage]}
|
||||
items={group.items}
|
||||
onSelect={({ itemKey }) => onChange(itemKey as PageKey)}
|
||||
style={{ maxWidth: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
<main className="vp-main">
|
||||
<header className="vp-topbar">
|
||||
@@ -150,7 +172,7 @@ export function AppShell({
|
||||
) : null}
|
||||
<Tag color="blue">生产环境</Tag>
|
||||
{platformRelease ? <Tag color="blue">版本 {platformRelease}</Tag> : null}
|
||||
<Tag color="grey">32960 / 808 / MQTT 归一</Tag>
|
||||
<Tag color="grey">多源数据归并</Tag>
|
||||
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图就绪' : '地图待配置'}</Tag>
|
||||
<Button size="small" aria-label="顶部地图态势" onClick={() => onChange('map')}>实时地图</Button>
|
||||
<Button size="small" aria-label="顶部实时监控" onClick={() => onChange('realtime')}>实时监控</Button>
|
||||
|
||||
@@ -360,6 +360,28 @@ export function Dashboard({
|
||||
onClick: () => onOpenMileage()
|
||||
}
|
||||
];
|
||||
const vehicleServicePrinciples = [
|
||||
{
|
||||
title: '一车一档',
|
||||
value: formatCount(serviceSummary?.totalVehicles),
|
||||
detail: '以 VIN 为主对象沉淀车牌、手机号、OEM、绑定状态和来源覆盖。'
|
||||
},
|
||||
{
|
||||
title: '一车一实时',
|
||||
value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles),
|
||||
detail: '实时状态合并位置、在线、速度、SOC、里程和来源新鲜度。'
|
||||
},
|
||||
{
|
||||
title: '一车一轨迹',
|
||||
value: formatCount(commandLocatedCount),
|
||||
detail: '轨迹回放围绕车辆展开,协议来源只作为证据过滤条件。'
|
||||
},
|
||||
{
|
||||
title: '一车一统计',
|
||||
value: formatCount(serviceSummary?.totalVehicles),
|
||||
detail: '里程等运营指标按车辆口径闭合,再回溯到来源证据。'
|
||||
}
|
||||
];
|
||||
const workflowSteps = [
|
||||
{
|
||||
title: '接入巡检',
|
||||
@@ -438,6 +460,17 @@ export function Dashboard({
|
||||
<Tag color={(summary?.kafkaLag ?? 0) > 0 ? 'orange' : 'green'}>Kafka Lag {formatLag(summary?.kafkaLag)}</Tag>
|
||||
</Space>
|
||||
</Card>
|
||||
<Card bordered title="一车一服务模型" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-service-model-grid">
|
||||
{vehicleServicePrinciples.map((item) => (
|
||||
<div key={item.title} className="vp-service-model-item">
|
||||
<Tag color="blue">{item.title}</Tag>
|
||||
<div className="vp-service-model-value">{item.value}</div>
|
||||
<Typography.Text type="secondary">{item.detail}</Typography.Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered title="全链路值班闭环" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-operation-flow">
|
||||
{workflowSteps.map((item, index) => (
|
||||
|
||||
@@ -69,6 +69,23 @@ body {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vp-nav-groups {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vp-nav-group {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vp-nav-section-title {
|
||||
padding: 0 12px 6px;
|
||||
color: var(--vp-text-subtle);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.vp-main {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -878,6 +895,30 @@ body {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vp-service-model-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vp-service-model-item {
|
||||
min-height: 132px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--vp-border);
|
||||
border-radius: var(--vp-radius);
|
||||
background: #fbfcff;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.vp-service-model-value {
|
||||
color: var(--vp-text);
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.vp-operation-step {
|
||||
min-height: 176px;
|
||||
padding: 14px;
|
||||
@@ -1031,6 +1072,7 @@ body {
|
||||
.vp-evidence-grid,
|
||||
.vp-action-grid,
|
||||
.vp-operation-flow,
|
||||
.vp-service-model-grid,
|
||||
.vp-capability-grid,
|
||||
.vp-conclusion-grid,
|
||||
.vp-monitor-layout,
|
||||
|
||||
@@ -12,6 +12,10 @@ afterEach(() => {
|
||||
test('renders vehicle platform shell', () => {
|
||||
render(<App />);
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user