feat(platform): position dashboard as vehicle service center

This commit is contained in:
lingniu
2026-07-05 11:54:23 +08:00
parent 856e4f7dfc
commit 358bb642f4
2 changed files with 27 additions and 27 deletions

View File

@@ -525,7 +525,7 @@ export function Dashboard({
{
title: '按时间窗复盘',
value: taskTimeMonitorSummary,
detail: '同一时间窗贯穿轨迹、里程、RAW 和告警,不需要跨页面重复输入。',
detail: '同一时间窗贯穿轨迹、里程、原始记录和告警,不需要跨页面重复输入。',
color: 'blue' as const,
primaryAction: '轨迹回放',
secondaryAction: '里程统计',
@@ -871,7 +871,7 @@ export function Dashboard({
{
label: '数据可导出',
value: `${formatCount(summary?.frameToday)} 今日数据`,
detail: '历史查询默认带 RAW 与解析字段,支持按字段缩小导出体积。',
detail: '历史查询默认带原始记录与解析字段,支持按字段缩小导出体积。',
color: (summary?.frameToday ?? 0) > 0 ? 'blue' as const : 'orange' as const
},
{
@@ -894,7 +894,7 @@ export function Dashboard({
`时间范围:${scope.dateFrom || '-'}${scope.dateTo || '-'}`,
`轨迹证据:今日活跃 ${formatCount(summary?.activeToday)},用于回放位置、速度、里程断点`,
`统计证据:车辆口径 ${formatCount(serviceSummary?.totalVehicles)},用于区间里程和日统计闭合`,
`历史证据:今日数据 ${formatCount(summary?.frameToday)},用于 RAW 与解析字段复核`,
`历史证据:今日数据 ${formatCount(summary?.frameToday)},用于原始记录与解析字段复核`,
`告警事件:${formatCount(summary?.issueVehicles)} 辆车存在告警`,
'交付核对:',
...timeMonitorChecklist.map((item, index) => `${index + 1}. ${item.label}${item.value}${item.detail}`),
@@ -926,7 +926,7 @@ export function Dashboard({
{
label: '历史证据',
value: `${formatCount(summary?.frameToday)}`,
detail: '按车辆、时间和字段裁剪导出 RAW 与解析字段。',
detail: '按车辆、时间和字段裁剪导出原始记录与解析字段。',
action: '导出证据',
color: 'blue' as const,
onClick: openTimeMonitorRaw
@@ -943,10 +943,10 @@ export function Dashboard({
const dataFlowStages = [
{
stage: '01',
title: '协议接入',
owner: '网关接收',
title: '车辆接入',
owner: '接入网关',
evidence: `32960 / 808 / MQTT连接 ${formatCount(opsHealth?.activeConnections)}`,
detail: '只接收真实实时数据帧和必要鉴权注册帧,协议不是最终产品。',
detail: '只接收真实实时数据帧和必要鉴权注册帧,接入来源最终归并到车辆服务。',
action: '运维质量',
onClick: () => onOpenQuality()
},
@@ -981,8 +981,8 @@ export function Dashboard({
stage: '05',
title: '历史证据',
owner: 'TDengine / MySQL',
evidence: `轨迹 ${formatCount(summary?.activeToday)} 活跃,RAW ${formatCount(summary?.frameToday)}`,
detail: '位置、RAW 和解析字段可分页查询,并能回跳轨迹、车辆服务和告警。',
evidence: `轨迹 ${formatCount(summary?.activeToday)} 活跃,原始记录 ${formatCount(summary?.frameToday)}`,
detail: '位置、原始记录和解析字段可分页查询,并能回跳轨迹、车辆服务和告警。',
action: '轨迹回放',
onClick: () => onOpenHistory()
},
@@ -999,7 +999,7 @@ export function Dashboard({
const scenarioNavigation = [
{
title: '实时监控',
objective: '确认车辆是否在线、位置是否可信、三类来源是否齐全。',
objective: '确认车辆是否在线、位置是否可信、必要接入来源是否齐全。',
evidence: `Redis 在线态 / 最新定位 / 有效坐标 ${commandLocatedCount.toLocaleString()}`,
sla: '目标 0-1 秒内进入实时视图',
primaryAction: '实时监控',
@@ -1054,7 +1054,7 @@ export function Dashboard({
const highPriorityLookupKey = highPriorityLookup?.key;
const highPriorityFilters = highPriorityEvidenceFilters;
const lines = [
'【车辆数据中台运营交接摘要】',
'【车辆服务中台运营交接摘要】',
`在线车辆:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} / ${formatCount(serviceSummary?.totalVehicles)}`,
`今日活跃:${formatCount(summary?.activeToday)}`,
`今日帧量:${formatCount(summary?.frameToday)}`,
@@ -1076,8 +1076,8 @@ export function Dashboard({
};
const copyScenarioBlueprint = () => {
const lines = [
'【车辆数据中台功能蓝图】',
'定位32960 / 808 / 宇通 MQTT 都只是车辆服务证据源,最终围绕一辆车提供实时、轨迹、历史、告警、统计能力。',
'【车辆服务中台功能蓝图】',
'定位32960 / 808 / 宇通 MQTT 都只是车辆接入来源,最终围绕一辆车提供实时、轨迹、历史、告警、统计能力。',
`高德地图Web JS ${amapConfigured ? '已配置' : '待配置'};服务端 API ${amapApiConfigured ? '已配置' : '待配置'};安全代理 ${amapSecurityProxyEnabled ? '已启用' : '未启用'};安全码${amapSecurityCodeExposed ? '已暴露' : '未暴露'}`,
`车辆规模:${formatCount(serviceSummary?.totalVehicles)};在线:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)};告警:${formatCount(summary?.issueVehicles)}`,
'',
@@ -1094,8 +1094,8 @@ export function Dashboard({
};
const copyDataFlowBlueprint = () => {
const lines = [
'【车辆数据中台数据流转图】',
'原则32960 / 808 / 宇通 MQTT 是接入证据,最终都归并为同一辆车的车辆服务。',
'【车辆服务中台数据流转图】',
'原则32960 / 808 / 宇通 MQTT 是车辆接入来源,最终都归并为同一辆车的车辆服务。',
`运行态:连接 ${formatCount(opsHealth?.activeConnections)};在线 Key ${formatCount(opsHealth?.redisOnlineKeys)}Kafka Lag ${formatLag(summary?.kafkaLag)};今日帧 ${formatCount(summary?.frameToday)}`,
'',
...dataFlowStages.map((item) => [
@@ -1272,7 +1272,7 @@ export function Dashboard({
{
title: '复盘时间',
value: dayRangeText(timeMonitorScopeValue.dateFrom, timeMonitorScopeValue.dateTo),
detail: '同一个时间窗贯穿轨迹回放、里程统计、RAW 证据和告警复盘。',
detail: '同一个时间窗贯穿轨迹回放、里程统计、原始记录和告警复盘。',
action: '轨迹回放',
color: 'blue' as const,
onClick: openTimeMonitorHistory
@@ -1299,7 +1299,7 @@ export function Dashboard({
const rawFilters = { ...scope, tab: 'raw', includeFields: 'true' };
const lines = [
'【车辆客户服务说明】',
'服务对象:车辆,不是协议数据源。',
'服务对象:车辆,不是接入来源。',
`车辆规模:${formatCount(serviceSummary?.totalVehicles)},在线车辆:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)},有效定位:${commandLocatedCount.toLocaleString()}`,
`当前时间窗:${timeMonitorSummary}${dayRangeText(scope.dateFrom, scope.dateTo)}`,
`告警车辆:${formatCount(summary?.issueVehicles)},最高优先级:${highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无'}`,
@@ -1308,7 +1308,7 @@ export function Dashboard({
'1. 找车:先按 VIN、车牌、手机号或 OEM 锁定车辆。',
'2. 看位置:打开实时地图确认车辆在线、坐标和最新上报。',
'3. 复盘时间:同一时间窗查看轨迹、里程和告警。',
'4. 交付数据:按车辆、时间、字段导出位置历史、RAW 和字段明细。',
'4. 交付数据:按车辆、时间、字段导出位置历史、原始记录和字段明细。',
'5. 闭环异常:告警事件进入通知和处置流程。',
`车辆中心:${appURL(buildAppHash({ page: 'vehicles' }))}`,
`实时地图:${appURL(buildAppHash({ page: 'map', filters: { online: 'online' } }))}`,

View File

@@ -891,12 +891,12 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
expect(screen.getAllByText('今日帧 1,286,320').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('告警 7').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('统计车辆 1,033').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('确认车辆是否在线、位置是否可信、三类来源是否齐全。')).toBeInTheDocument();
expect(screen.getByText('确认车辆是否在线、位置是否可信、必要接入来源是否齐全。')).toBeInTheDocument();
expect(screen.getByText('按车辆复盘位置、速度、里程和断点,定位平台转发或车辆异常。')).toBeInTheDocument();
expect(screen.getByText('优先返回必要字段,避免大 JSON 拖慢查询')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '复制功能蓝图' }));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆数据中台功能蓝图】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('32960 / 808 / 宇通 MQTT 都只是车辆服务证据源'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆服务中台功能蓝图】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('32960 / 808 / 宇通 MQTT 都只是车辆接入来源'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆规模1,033在线208告警7'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('高德地图Web JS 已配置;服务端 API 已配置;安全代理 已启用;安全码未暴露'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. 实时监控'));
@@ -1281,8 +1281,8 @@ test('dashboard shows vehicle service action queue', async () => {
test.each([
{ buttonName: '查看在线车辆', expectedHash: '#/vehicles?online=online' },
{ buttonName: '实时地图', expectedHash: '#/map?online=online' },
{ buttonName: '查看告警事件', expectedHash: '#/alert-events' }
{ buttonName: '打开实时地图', expectedHash: '#/map?online=online' },
{ buttonName: '顶部告警事件', expectedHash: '#/alert-events' }
])('dashboard posture opens %s', async ({ buttonName, expectedHash }) => {
window.history.replaceState(null, '', '/#/dashboard');
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
@@ -1448,7 +1448,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.getByText('告警复盘')).toBeInTheDocument();
expect(screen.getAllByText('告警复盘').length).toBeGreaterThanOrEqual(1);
fireEvent.click(screen.getByRole('button', { name: '时间监控 今天车辆轨迹' }));
@@ -1570,7 +1570,7 @@ test('dashboard surfaces highest priority quality issue with evidence shortcuts'
expect(screen.getByText('实时证据')).toBeInTheDocument();
expect(screen.getAllByText('轨迹证据').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('告警证据')).toBeInTheDocument();
expect(screen.getByText('统计证据')).toBeInTheDocument();
expect(screen.getAllByText('统计证据').length).toBeGreaterThanOrEqual(1);
fireEvent.click(screen.getByRole('button', { name: '复制处置卡' }));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【重点车辆服务处置卡】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆粤A告警1 / 13307795425'));
@@ -1578,7 +1578,7 @@ test('dashboard surfaces highest priority quality issue with evidence shortcuts'
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('原始记录http://localhost:3000/#/history-query?keyword=%E7%B2%A4A%E5%91%8A%E8%AD%A61&protocol=JT808&tab=raw&includeFields=true'));
expect(await screen.findByText('最高优先级告警')).toBeInTheDocument();
expect(screen.getAllByText('粤A告警1 / 13307795425').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('VIN 缺失')).toBeInTheDocument();
expect(screen.getAllByText('VIN 缺失').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('JT808 数据缺少 VIN 映射').length).toBeGreaterThanOrEqual(1);
fireEvent.click(screen.getByRole('button', { name: '首页告警轨迹证据' }));
@@ -1713,7 +1713,7 @@ test('dashboard copies highest priority quality issue notification text', async
fireEvent.click(screen.getByRole('button', { name: '复制运营交接摘要' }));
await waitFor(() => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆数据中台运营交接摘要】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆服务中台运营交接摘要】'));
});
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('在线车辆88 / 1,033'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('今日活跃96'));