feat(platform): align console with vehicle operations center

This commit is contained in:
lingniu
2026-07-04 18:53:52 +08:00
parent 48642726aa
commit 504a49a13c
8 changed files with 70 additions and 26 deletions

View File

@@ -455,7 +455,7 @@ export default function App() {
const pages: Record<PageKey, JSX.Element> = {
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={openQuality} onOpenMap={openMap} onOpenRealtime={openRealtime} onOpenVehicles={openVehicles} onOpenHistory={openHistoryWithFilters} onOpenMileage={openMileageWithFilters} />,
vehicles: <Vehicles onOpenVehicle={openVehicle} onOpenQuality={openQuality} onOpenRealtime={openRealtime} onOpenHistory={openHistoryWithFilters} onFiltersChange={updateVehicleFilters} initialFilters={vehicleFilters} />,
map: <Realtime title="地图态势" description="以车辆服务为中心查看实时位置、在线状态、来源一致性和高德地图接入状态" onOpenVehicle={openVehicle} onOpenHistory={openHistoryForVehicle} onOpenQuality={openQuality} onFiltersChange={updateMapFilters} initialFilters={realtimeFilters} />,
map: <Realtime title="实时地图" description="以车辆服务为中心查看实时位置、在线状态、来源一致性和高德地图接入状态" onOpenVehicle={openVehicle} onOpenHistory={openHistoryForVehicle} onOpenQuality={openQuality} onFiltersChange={updateMapFilters} initialFilters={realtimeFilters} />,
realtime: <Realtime onOpenVehicle={openVehicle} onOpenHistory={openHistoryForVehicle} onOpenQuality={openQuality} onFiltersChange={updateRealtimeFilters} initialFilters={realtimeFilters} />,
detail: <VehicleDetail vin={activeVin} protocol={activeProtocol} platformRelease={platformRelease} onOpenRealtime={openRealtimeForVehicle} onOpenHistory={openHistoryForVehicle} onOpenRaw={openRawForVehicle} onOpenMileage={openMileageForVehicle} onOpenVehicles={openVehicles} onOpenQuality={openQuality} onOpenHistoryEvidence={openHistoryWithFilters} onOpenRawEvidence={openRawWithFilters} onQueryChange={updateVehicleDetailQuery} />,
history: <History mode="trajectory" initialVin={analysisVin} initialProtocol={activeProtocol} initialTab={historyTab} initialFilters={historyFilters} onFiltersChange={updateHistoryFilters} onOpenVehicle={openVehicle} onOpenMileage={openMileageWithFilters} onOpenRaw={openRawWithFilters} />,

View File

@@ -19,14 +19,14 @@ 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: 'dashboard', text: '运营驾驶舱', icon: <IconHome /> },
{ itemKey: 'map', text: '实时地图', icon: <IconMapPin /> },
{ itemKey: 'vehicles', text: '车辆中心', icon: <IconServer /> },
{ itemKey: 'map', text: '地图态势', icon: <IconMapPin /> },
{ itemKey: 'realtime', text: '实时监控', icon: <IconActivity /> },
{ 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: 'mileage', text: '统计查询', icon: <IconBarChartHStroked /> },
{ itemKey: 'alert-events', text: '告警事件', icon: <IconHistogram /> },
{ itemKey: 'notification-rules', text: '通知规则', icon: <IconBell /> },
{ itemKey: 'ops-quality', text: '运维质量', icon: <IconPulse /> }
@@ -111,7 +111,10 @@ export function AppShell({
<aside className="vp-sidebar">
<div className="vp-brand">
<span className="vp-brand-mark" />
<span></span>
<span className="vp-brand-copy">
<span></span>
<span> / / / </span>
</span>
</div>
<Nav
selectedKeys={[selectedPage]}
@@ -147,9 +150,9 @@ export function AppShell({
) : null}
<Tag color="blue"></Tag>
{platformRelease ? <Tag color="blue"> {platformRelease}</Tag> : null}
<Tag color="grey"> / </Tag>
<Tag color="grey">32960 / 808 / MQTT </Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图就绪' : '地图待配置'}</Tag>
<Button size="small" aria-label="顶部地图态势" onClick={() => onChange('map')}></Button>
<Button size="small" aria-label="顶部地图态势" onClick={() => onChange('map')}></Button>
<Button size="small" aria-label="顶部实时监控" onClick={() => onChange('realtime')}></Button>
<Button size="small" aria-label="顶部轨迹回放" onClick={() => onChange('history')}></Button>
<Button size="small" aria-label="顶部历史查询" onClick={() => onChange('history-query')}></Button>

View File

@@ -312,10 +312,10 @@ export function Dashboard({
}));
const capabilities = [
{
title: '地图态势',
title: '实时地图',
status: `在线态势 ${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)}`,
color: 'green' as const,
description: '以地图为主视角查看车辆在线分布、定位有效性、来源一致性和高德接入状态。',
description: '以高德地图为主视角查看车辆在线分布、定位有效性、来源一致性和地图接入状态。',
action: '打开地图',
onClick: () => onOpenMap({ online: 'online' })
},
@@ -323,7 +323,7 @@ export function Dashboard({
title: '实时监控',
status: `在线 ${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)}`,
color: 'green' as const,
description: '按车辆聚合最新位置、在线状态和多来源实时字段,支持地图态势查看。',
description: '按车辆聚合最新位置、在线状态和多来源实时字段,支持实时地图查看。',
action: '查看在线车辆',
onClick: () => onOpenRealtime({ online: 'online' })
},
@@ -413,7 +413,7 @@ export function Dashboard({
return (
<div className="vp-page">
<PageHeader title="车辆服务总览" description="三个数据源最终汇总为一个车辆服务,统一承载实时监控、轨迹回放、历史查询、告警通知、统计查询和链路质量" />
<PageHeader title="运营驾驶舱" description="三个数据源最终汇总为一个车辆服务,统一承载实时监控、轨迹回放、历史查询、告警通知、统计查询和链路质量" />
<Spin spinning={loading}>
<div className="vp-kpi-grid">
{kpis.map((item) => (

View File

@@ -34,7 +34,7 @@ body {
}
.vp-brand {
height: 44px;
min-height: 54px;
display: flex;
align-items: center;
gap: 10px;
@@ -50,6 +50,25 @@ body {
background: linear-gradient(135deg, var(--vp-primary), var(--vp-cyan));
}
.vp-brand-copy {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
line-height: 18px;
}
.vp-brand-copy span:first-child {
font-size: 15px;
}
.vp-brand-copy span:last-child {
color: var(--vp-text-muted);
font-size: 11px;
font-weight: 500;
white-space: nowrap;
}
.vp-main {
min-width: 0;
}

View File

@@ -12,7 +12,7 @@ afterEach(() => {
test('renders vehicle platform shell', () => {
render(<App />);
expect(screen.getByText('车辆服务中台')).toBeInTheDocument();
expect(screen.getAllByText('车辆服务总览').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('运营驾驶舱').length).toBeGreaterThanOrEqual(1);
});
test('exposes AMap operations shortcuts when map key is configured', async () => {
@@ -82,7 +82,7 @@ test('exposes AMap operations shortcuts when map key is configured', async () =>
expect(screen.getByText('版本 platform-20260704153357')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '顶部地图态势' }));
expect(window.location.hash).toBe('#/map');
expect(await screen.findByRole('heading', { name: '地图态势' })).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '实时地图' })).toBeInTheDocument();
expect(screen.getByText('地图车辆服务队列')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '顶部实时监控' }));
expect(window.location.hash).toBe('#/realtime');
@@ -452,7 +452,7 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
};
await renderDashboard();
expect(screen.getAllByText('地图态势').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('实时地图').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('实时监控').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('轨迹回放').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('历史数据查询').length).toBeGreaterThanOrEqual(1);
@@ -470,7 +470,7 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
cleanup();
await renderDashboard();
fireEvent.click(screen.getByRole('button', { name: '能力入口 地图态势' }));
fireEvent.click(screen.getByRole('button', { name: '能力入口 实时地图' }));
expect(window.location.hash).toBe('#/map?online=online');
cleanup();
@@ -7923,7 +7923,7 @@ test('shows realtime freshness status for recently updated and stale vehicles',
render(<App />);
expect(await screen.findByRole('heading', { name: '地图态势' })).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '实时地图' })).toBeInTheDocument();
expect(screen.getAllByText('数据新鲜').length).toBeGreaterThan(0);
expect(screen.getAllByText('更新超时').length).toBeGreaterThan(0);
expect(screen.getByText('超时车辆')).toBeInTheDocument();

View File

@@ -40,11 +40,11 @@ AMAP_WEB_JS_KEY=***
AMAP_SECURITY_JS_CODE=***
AMAP_SECURITY_SERVICE_HOST=/_AMapService
# Optional reserved server-side key for AMap REST/track services.
AMAP_API_SERVICE_KEY=***
AMAP_API_KEY=***
PLATFORM_RELEASE=platform-YYYYMMDDHHMMSS
```
`AMAP_WEB_JS_KEY` is served to the browser through `/app-config.js` so the same static build can be reused across environments. For production, set both `AMAP_SECURITY_JS_CODE` and `AMAP_SECURITY_SERVICE_HOST=/_AMapService`; the API service keeps the security code on the server and proxies AMap requests with `jscode` appended. Only omit `AMAP_SECURITY_SERVICE_HOST` for controlled debugging where exposing `AMAP_SECURITY_JS_CODE` to the browser is acceptable. `AMAP_API_SERVICE_KEY` is reserved for backend-only AMap service APIs such as geocoding, route planning, geofence, or trajectory service integration.
`AMAP_WEB_JS_KEY` is served to the browser through `/app-config.js` so the same static build can be reused across environments. For production, set both `AMAP_SECURITY_JS_CODE` and `AMAP_SECURITY_SERVICE_HOST=/_AMapService`; the API service keeps the security code on the server and proxies AMap requests with `jscode` appended. Only omit `AMAP_SECURITY_SERVICE_HOST` for controlled debugging where exposing `AMAP_SECURITY_JS_CODE` to the browser is acceptable. `AMAP_API_KEY` is reserved for backend-only AMap service APIs such as geocoding, route planning, geofence, or trajectory service integration.
`PLATFORM_RELEASE` is surfaced by `/api/ops/health.runtime.platformRelease` so operators can confirm which ECS release is currently active after a deployment.

View File

@@ -2,7 +2,7 @@
## Visual Direction
The UI is a professional vehicle data operations console. It uses Semi UI as the component base, a restrained gray-white control-room palette, dense but readable tables, right-side drawers for detail, and blue-cyan accents for primary actions. It should feel closer to a cloud data console than a marketing dashboard.
The UI is a professional vehicle data operations console. It uses Semi UI as the component base, a restrained gray-white control-room palette, dense but readable tables, right-side drawers for detail, and blue-cyan accents for primary actions. It should feel closer to a cloud data console and mature fleet telematics platform than a marketing dashboard.
## Product North Star
@@ -12,19 +12,21 @@ Every primary workflow should answer a vehicle-level question first, then allow
The current product goal is to evolve toward a production vehicle data management center, comparable to mature fleet IoT and telematics platforms: realtime monitoring, trajectory replay, historical evidence query, alert triggering and notification, statistics, and production operation quality all serve the same vehicle object.
The product must not expose GB32960, JT808, and Yutong MQTT as three final products. They are acquisition paths. The final product is a vehicle service that answers online state, current location, historical trajectory, source reliability, alert responsibility, and statistical evidence for one vehicle.
## Primary Screens
The platform navigation should use vehicle-service language rather than protocol-product language:
1. Operations Cockpit: vehicle service KPI summary, service status distribution, live map preview, alert queue, data quality, and link health.
2. Live Monitor: map-first current vehicle monitoring with online/offline state, abnormal highlights, source freshness, and a dense vehicle table.
2. Live Monitor: map-first current vehicle monitoring with online/offline state, abnormal highlights, source freshness, AMap readiness, and a dense vehicle table.
3. Vehicle Center: table-first vehicle registry, binding state, source coverage, service status, and missing-source governance.
4. Vehicle Detail: one vehicle service file with identity, latest realtime state, source evidence, history, raw, mileage, alerts, quality evidence, and copyable diagnostics.
5. Trajectory Replay: historical location playback with AMap polyline, playback marker, auto play/pause, speed controls, and table evidence.
5. Trajectory Replay: historical location playback with AMap polyline, playback marker, auto play/pause, speed controls, gap detection, mileage evidence, and table evidence.
6. History Query: historical locations, raw frames, and flattened parsed fields with pagination and field trimming.
7. Alert Events: alert trigger records, affected vehicles, rule evidence, manual state, notification state, and escalation readiness.
8. Notification Rules: alert trigger rules, notification targets, channels, escalation windows, acceptance criteria, and copyable runbooks.
9. Statistics: one vehicle-service statistics workspace covering daily/range mileage, online rate, offline duration, data completeness, and source consistency.
7. Alert Events: alert trigger records, affected vehicles, rule evidence, manual state, notification state, escalation readiness, and recovery evidence.
8. Notification Rules: alert trigger rules, notification targets, channels, escalation windows, acceptance criteria, affected-user scope, and copyable runbooks.
9. Statistics: one vehicle-service statistics workspace covering daily/range mileage, online rate, offline duration, data completeness, source consistency, and exportable evidence.
10. Ops Quality: GB32960, JT808, Yutong MQTT, Kafka, NATS, Redis, TDengine, MySQL, AMap, gateway, and runtime health.
See `docs/vehicle-platform-blueprint.md` for the detailed page-level blueprint and phased implementation plan.
@@ -77,6 +79,7 @@ AMap should be treated as infrastructure for vehicle operations, not only as a v
- History query: location evidence can open the same coordinate in the map context.
- Alert events: vehicle location at trigger time should be shown when longitude and latitude are available.
- Ops quality: map key readiness, security proxy readiness, and frontend loader failures should be visible.
- Statistics: mileage and online-rate anomalies should link back to a map/time-window context so operators can verify whether the issue is vehicle behavior or source data loss.
AMap credentials supplied by operations must be configured through ECS environment variables:
@@ -85,6 +88,12 @@ AMap credentials supplied by operations must be configured through ECS environme
- `AMAP_SECURITY_JS_CODE`: private security code kept by the API service.
- `AMAP_SECURITY_SERVICE_HOST=/_AMapService`: frontend proxy path used by the AMap loader.
Supplied AMap production credentials must be applied on ECS only:
- API service key: configure as `AMAP_API_KEY`.
- Web JS key: configure as `AMAP_WEB_JS_KEY`.
- Web JS security code: configure as `AMAP_SECURITY_JS_CODE`.
## Alert And Notification Scope
Alert events are first-class product records, but notification delivery can be implemented incrementally.

View File

@@ -13,6 +13,8 @@ Build one vehicle data management platform around a single vehicle service. GB32
The product direction should reference production telematics and fleet IoT platforms, but keep the Lingniu domain model simple: one vehicle service, multiple source evidences, auditable history, and operational alerts. Realtime data source pages are not product destinations; they are evidence and diagnosis layers under the vehicle.
The current product goal is therefore adjusted to: build a complete vehicle data management center with realtime monitoring, trajectory replay, history query, alert triggering and notification, statistics, and operations quality. GB32960, JT808, and Yutong MQTT remain source evidence and should disappear from the primary user mental model unless the user is diagnosing a specific source.
## Market Reference
Common telematics, fleet-management, and vehicle IoT platforms converge on a small set of durable workflows:
@@ -31,6 +33,12 @@ Relevant references:
- Commercial fleet platforms: live GPS tracking, historical route playback, geofence, driver behavior, alerts, reports, and asset management.
- Domestic vehicle-monitoring platforms: monitoring cockpit, vehicle monitor, trip replay, realtime alerts, running statistics, and alert management.
Reference principles used for the Lingniu platform:
- Samsara-style telematics products emphasize realtime GPS visibility, diagnostics, routing/navigation, energy/fuel, alerts, and operational efficiency.
- AMap Web JS should power map rendering, marker interaction, and trajectory display while keeping the security code behind a service proxy.
- TDengine vehicle IoT guidance fits the platform's realtime location monitoring and trajectory replay query pattern, while Redis remains the low-latency realtime projection.
## Information Architecture
The left navigation should be vehicle-service oriented:
@@ -83,6 +91,7 @@ Primary modules:
- Right-side vehicle queue: online, offline, degraded, abnormal, no location.
- Bottom table: latest position, speed, mileage, source freshness, service status.
- Map overlays: online state, protocol source, alert severity.
- Production readiness rail: AMap Web JS state, security proxy state, location point cap, latest refresh time, and fallback mode.
AMap behavior:
@@ -140,6 +149,7 @@ Primary modules:
- Playback controls: play, pause, speed, step, seek, and auto-stop at the last point.
- Evidence table: time, longitude, latitude, speed, mileage, protocol, raw frame link.
- Quality strip: missing points, time gaps, mileage jumps.
- Export and share controls: copy query link, open AMap line, export current page evidence.
Query:
@@ -193,6 +203,7 @@ Required alert domains:
- Raw parse failure or unknown field burst.
- Binding missing or VIN unresolved.
- Cross-source time/location/mileage inconsistency.
- AMap unavailable or security proxy misconfigured.
### Notification Rules
@@ -220,6 +231,7 @@ Primary modules:
- Completeness: location, mileage, SOC, speed, source freshness.
- Consistency: cross-source time delta and mileage delta for multi-source vehicles.
- Statistics overview: show the four domains together so operations users understand that GB32960, JT808, and Yutong MQTT are evidence feeding one vehicle service.
- Notification impact: affected vehicles, affected BI users, last normal data time, and recovery time after alert closure.
Rule:
@@ -250,6 +262,7 @@ Use Semi UI as the component base. The style should feel like a cloud operations
- Cards only for repeated metrics or detail blocks; avoid nested cards.
- Drawer for detail and evidence, full routes for heavy workflows.
- No landing page, no marketing hero, no oversized decorative cards.
- Primary navigation should use user jobs: operations cockpit, realtime monitor, vehicle center, trajectory replay, history query, alerts, statistics, notification rules, and ops quality.
## Data Model Boundaries