feat(platform): align shell with vehicle operations center
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 缺失、字段缺失和容量异常进入告警池。' },
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user