feat(platform): align shell with vehicle operations center

This commit is contained in:
lingniu
2026-07-04 16:13:46 +08:00
parent 0c7910d6b9
commit 775073a95c
4 changed files with 28 additions and 28 deletions

View File

@@ -17,13 +17,13 @@ import { isAMapConfigured } from '../config/appConfig';
export type PageKey = 'dashboard' | 'vehicles' | 'realtime' | 'detail' | 'history' | 'mileage' | 'quality';
const navItems = [
{ itemKey: 'dashboard', text: '运营总览', icon: <IconHome /> },
{ itemKey: 'dashboard', text: '运营驾驶舱', icon: <IconHome /> },
{ itemKey: 'vehicles', text: '车辆中心', icon: <IconServer /> },
{ itemKey: 'realtime', text: '实时监控', icon: <IconActivity /> },
{ itemKey: 'detail', text: '车辆档案', icon: <IconSetting /> },
{ itemKey: 'history', text: '轨迹回放', icon: <IconMapPin /> },
{ itemKey: 'mileage', text: '统计分析', icon: <IconBarChartHStroked /> },
{ itemKey: 'quality', text: '告警通知', icon: <IconHistogram /> }
{ itemKey: 'quality', text: '告警事件', icon: <IconHistogram /> }
];
function linkHealthClassName(count: number | null) {
@@ -118,11 +118,11 @@ export function AppShell({
<Button size="small" aria-label="顶部统计查询" onClick={() => onChange('mileage')}></Button>
<Button
size="small"
aria-label={linkIssueCount == null ? '顶部告警通知' : linkIssueCount > 0 ? `顶部告警通知 ${linkIssueCount}项关注` : '顶部告警通知正常'}
aria-label={linkIssueCount == null ? '顶部告警事件' : linkIssueCount > 0 ? `顶部告警事件 ${linkIssueCount}项关注` : '顶部告警事件正常'}
className={linkHealthClassName(linkIssueCount)}
onClick={() => onChange('quality')}
>
{linkIssueCount == null ? '告警通知' : linkIssueCount > 0 ? `告警通知 ${linkIssueCount}项关注` : '告警通知正常'}
{linkIssueCount == null ? '告警事件' : linkIssueCount > 0 ? `告警事件 ${linkIssueCount}项关注` : '告警事件正常'}
</Button>
</Space>
</header>

View File

@@ -384,11 +384,11 @@ export function Dashboard({
onClick: () => onOpenHistory({ tab: 'raw' })
},
{
title: '告警通知',
title: '告警事件',
value: `${formatCount(summary?.issueVehicles)} 车辆`,
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
detail: '把断链、缺 VIN、字段缺失等问题进入通知和处置队列。',
action: '告警通知',
action: '告警事件',
onClick: () => onOpenQuality()
},
{
@@ -403,7 +403,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) => (
@@ -422,9 +422,9 @@ export function Dashboard({
<Tag color="green">{formatCount(serviceSummary?.multiSourceVehicles)} </Tag>
<Button size="small" theme="light" type="primary" onClick={() => onOpenVehicles({ coverage: 'multi' })}></Button>
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>
{formatCount(summary?.issueVehicles)}
{formatCount(summary?.issueVehicles)}
</Tag>
<Button size="small" theme="light" type={(summary?.issueVehicles ?? 0) > 0 ? 'warning' : 'tertiary'} onClick={() => onOpenQuality()}></Button>
<Button size="small" theme="light" type={(summary?.issueVehicles ?? 0) > 0 ? 'warning' : 'tertiary'} onClick={() => onOpenQuality()}></Button>
<Tag color={(summary?.kafkaLag ?? 0) > 0 ? 'orange' : 'green'}>Kafka Lag {formatLag(summary?.kafkaLag)}</Tag>
</Space>
</Card>
@@ -512,7 +512,7 @@ export function Dashboard({
</div>
</Card>
) : null}
<Card bordered title="车辆业务能力矩阵" style={{ marginBottom: 16 }}>
<Card bordered title="车联网能力矩阵" style={{ marginBottom: 16 }}>
<div className="vp-capability-grid">
{capabilities.map((item) => (
<div key={item.title} className="vp-capability-item">
@@ -530,7 +530,7 @@ export function Dashboard({
</Card>
<Card
bordered
title="车辆态势指挥台"
title="实时态势地图"
style={{ marginBottom: 16 }}
>
<div className="vp-monitor-layout">

View File

@@ -657,7 +657,7 @@ export function Quality({
return (
<div className="vp-page">
<PageHeader title="告警通知" description="围绕车辆服务沉淀断链、VIN 缺失、字段缺失和链路健康告警,并形成通知闭环" />
<PageHeader title="告警事件" description="围绕车辆服务沉淀断链、VIN 缺失、字段缺失和链路健康事件,并形成通知闭环" />
<div className="vp-kpi-grid">
{[
{ label: '问题车辆', value: summary.issueVehicleCount.toLocaleString() },
@@ -702,7 +702,7 @@ export function Quality({
</Card>
</Col>
</Row>
<Card bordered title="告警通知闭环" style={{ marginTop: 16 }}>
<Card bordered title="告警事件闭环" style={{ marginTop: 16 }}>
<div className="vp-alert-flow">
{[
{ label: '事件触发', value: `${activeRuleCount} 类活跃`, detail: '断链、无来源、VIN 缺失、字段缺失和容量异常进入告警池。' },

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 () => {
@@ -88,7 +88,7 @@ test('exposes AMap operations shortcuts when map key is configured', async () =>
expect(window.location.hash.startsWith('#/history')).toBe(true);
fireEvent.click(screen.getByRole('button', { name: '顶部统计查询' }));
expect(window.location.hash.startsWith('#/mileage')).toBe(true);
fireEvent.click(await screen.findByRole('button', { name: '顶部告警通知正常' }));
fireEvent.click(await screen.findByRole('button', { name: '顶部告警事件正常' }));
expect(window.location.hash).toBe('#/quality');
});
@@ -286,7 +286,7 @@ test('dashboard presents one vehicle service operating posture', async () => {
expect(await screen.findByText('统一车辆服务')).toBeInTheDocument();
expect(screen.getByText('208 / 1,033 在线')).toBeInTheDocument();
expect(screen.getByText('181 多源覆盖')).toBeInTheDocument();
expect(screen.getByText('7 质量关注')).toBeInTheDocument();
expect(screen.getByText('7 告警事件')).toBeInTheDocument();
});
test('dashboard exposes vehicle data center capability matrix', async () => {
@@ -386,7 +386,7 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
const renderDashboard = async () => {
window.history.replaceState(null, '', '/#/dashboard');
render(<App />);
expect(await screen.findByText('车辆业务能力矩阵')).toBeInTheDocument();
expect(await screen.findByText('车联网能力矩阵')).toBeInTheDocument();
};
await renderDashboard();
@@ -575,7 +575,7 @@ test('dashboard exposes vehicle command center map actions', async () => {
render(<App />);
expect(await screen.findByText('车辆态势指挥台')).toBeInTheDocument();
expect(await screen.findByText('实时态势地图')).toBeInTheDocument();
expect(screen.getByText('在线 1 / 2')).toBeInTheDocument();
expect(screen.getAllByText('有效定位 1').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('降级/离线 2')).toBeInTheDocument();
@@ -586,7 +586,7 @@ test('dashboard exposes vehicle command center map actions', async () => {
window.history.replaceState(null, '', '/#/dashboard');
render(<App />);
expect(await screen.findByText('车辆态势指挥台')).toBeInTheDocument();
expect(await screen.findByText('实时态势地图')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '查看实时态势' }));
expect(window.location.hash).toBe('#/realtime?online=online');
});
@@ -675,7 +675,7 @@ test('dashboard shows vehicle service action queue', async () => {
test.each([
{ buttonName: '查看在线车辆', expectedHash: '#/vehicles?online=online' },
{ buttonName: '查看多源车辆', expectedHash: '#/vehicles?coverage=multi' },
{ buttonName: '查看质量关注', expectedHash: '#/quality' }
{ buttonName: '查看告警事件', expectedHash: '#/quality' }
])('dashboard posture opens %s', async ({ buttonName, expectedHash }) => {
window.history.replaceState(null, '', '/#/dashboard');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
@@ -842,7 +842,7 @@ test('dashboard exposes end-to-end operations workflow entries', async () => {
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();
fireEvent.click(screen.getByRole('button', { name: '闭环入口 轨迹复盘' }));
@@ -8009,7 +8009,7 @@ test('opens quality issues from realtime vehicle row with source evidence', asyn
await waitFor(() => {
expect(window.location.hash).toBe('#/quality?keyword=VIN-RT-QUALITY&protocol=JT808');
});
expect(await screen.findByRole('heading', { name: '告警通知' })).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '告警事件' })).toBeInTheDocument();
});
test('loads realtime vehicles from shareable source filter hash', async () => {
@@ -8711,7 +8711,7 @@ test('opens quality issues from source-filtered vehicle list with source evidenc
await waitFor(() => {
expect(window.location.hash).toBe('#/quality?keyword=VIN-LIST-QUALITY&protocol=JT808');
});
expect(await screen.findByRole('heading', { name: '告警通知' })).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '告警事件' })).toBeInTheDocument();
});
test('filters vehicle list by service status', async () => {
@@ -8999,8 +8999,8 @@ test('opens quality governance from vehicle detail overview', async () => {
await waitFor(() => {
expect(window.location.hash).toBe('#/quality?keyword=VIN001');
});
expect(await screen.findByRole('heading', { name: '告警通知' })).toBeInTheDocument();
expect(screen.getByText('告警通知闭环')).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '告警事件' })).toBeInTheDocument();
expect(screen.getByText('告警事件闭环')).toBeInTheDocument();
expect(screen.getByText('事件触发')).toBeInTheDocument();
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/quality/issues?keyword=VIN001&limit=20&offset=0'), undefined);
});
@@ -9026,7 +9026,7 @@ test('quality health storage card stays pending before ops health loads', async
render(<App />);
expect(await screen.findByRole('heading', { name: '告警通知' })).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '告警事件' })).toBeInTheDocument();
expect(screen.getByText('存储读取')).toBeInTheDocument();
expect(screen.getByText('检测中')).toBeInTheDocument();
expect(screen.queryByText('异常')).not.toBeInTheDocument();
@@ -9052,7 +9052,7 @@ test('quality health shows active connection capacity metric', async () => {
render(<App />);
expect(await screen.findByRole('heading', { name: '告警通知' })).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '告警事件' })).toBeInTheDocument();
expect(await screen.findByText('活跃连接')).toBeInTheDocument();
expect(screen.getByText('120,000')).toBeInTheDocument();
expect(screen.getByText('运行版本')).toBeInTheDocument();
@@ -9079,7 +9079,7 @@ test('quality health shows structured capacity findings', async () => {
render(<App />);
expect(await screen.findByRole('heading', { name: '告警通知' })).toBeInTheDocument();
expect(await screen.findByRole('heading', { name: '告警事件' })).toBeInTheDocument();
expect(await screen.findByText('容量检查发现')).toBeInTheDocument();
expect(screen.getByText('kafka lag 42')).toBeInTheDocument();
});