feat(platform): add customer time-window review package

This commit is contained in:
lingniu
2026-07-05 03:00:03 +08:00
parent 6cd95f7ab3
commit 0df1d61eb1
3 changed files with 150 additions and 1 deletions

View File

@@ -774,6 +774,60 @@ export function Dashboard({
const range = scope.dateFrom || scope.dateTo ? `${scope.dateFrom || '-'}${scope.dateTo || '-'}` : '全部时间';
return `${vehicle} / ${source} / ${range}`;
})();
const timeMonitorReviewItems = [
{
label: '车辆范围',
value: timeMonitorScope().keyword || '全部车辆',
detail: timeMonitorScope().protocol || '全部数据通道',
color: timeMonitorScope().keyword ? 'green' as const : 'blue' as const
},
{
label: '复盘时间',
value: timeMonitorScope().dateFrom || timeMonitorScope().dateTo ? `${timeMonitorScope().dateFrom || '-'}${timeMonitorScope().dateTo || '-'}` : '全部时间',
detail: '同步带入轨迹、里程、历史证据和告警',
color: 'blue' as const
},
{
label: '轨迹证据',
value: `${formatCount(summary?.activeToday)} 活跃`,
detail: '位置、速度、里程断点同窗复核',
color: 'blue' as const
},
{
label: '统计证据',
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆`,
detail: '区间里程、日统计和异常点闭合',
color: 'green' as const
},
{
label: '风险事件',
value: `${formatCount(summary?.issueVehicles)} 告警`,
detail: highPriorityIssue ? qualityIssueLabel(highPriorityIssue.issueType) : '暂无高优先级',
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const
}
];
const copyTimeMonitorReviewPackage = () => {
const scope = timeMonitorScope();
const rawFilters = { ...scope, tab: 'raw', includeFields: 'true' };
const lines = [
'【客户时间窗复盘包】',
`范围:${timeMonitorSummary}`,
'平台能力:实时地图 / 轨迹回放 / 里程统计 / 历史数据查询 / 告警通知',
`车辆范围:${scope.keyword || '全部车辆'}`,
`数据通道:${scope.protocol || '全部数据通道'}`,
`时间范围:${scope.dateFrom || '-'}${scope.dateTo || '-'}`,
`轨迹证据:今日活跃 ${formatCount(summary?.activeToday)},用于回放位置、速度、里程断点`,
`统计证据:车辆口径 ${formatCount(serviceSummary?.totalVehicles)},用于区间里程和日统计闭合`,
`历史证据:今日数据 ${formatCount(summary?.frameToday)},用于 RAW 与解析字段复核`,
`告警事件:${formatCount(summary?.issueVehicles)} 辆车存在告警`,
`轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
`里程统计:${appURL(buildAppHash({ page: 'mileage', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
`历史数据查询:${appURL(buildAppHash({ page: 'history-query', keyword: scope.keyword, protocol: scope.protocol, filters: rawFilters }))}`,
`告警通知:${appURL(buildAppHash({ page: 'alert-events', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`,
`实时地图:${appURL(buildAppHash({ page: 'map', keyword: scope.keyword, protocol: scope.protocol, filters: scope }))}`
];
copyText(lines.join('\n'), '时间窗复盘包');
};
const timeMonitorWorkbench = [
{
label: '轨迹回放',
@@ -1308,6 +1362,34 @@ export function Dashboard({
<Typography.Text type="secondary">
</Typography.Text>
<div className="vp-time-review-package">
<div className="vp-time-review-summary">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={timeMonitorScope().keyword ? 'green' : 'grey'}>{timeMonitorScope().keyword ? '单车复盘' : '全域复盘'}</Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>{timeMonitorSummary}</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
<Space wrap>
<Button size="small" theme="solid" type="primary" onClick={copyTimeMonitorReviewPackage}></Button>
<Button size="small" theme="light" type="primary" onClick={openTimeMonitorHistory}></Button>
<Button size="small" theme="light" type="primary" onClick={openTimeMonitorMileage}></Button>
<Button size="small" theme="light" type="primary" onClick={openTimeMonitorRaw}></Button>
<Button size="small" theme="light" type="warning" onClick={openTimeMonitorAlerts}></Button>
</Space>
</div>
<div className="vp-time-review-grid">
{timeMonitorReviewItems.map((item) => (
<div key={item.label} className="vp-time-review-item">
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</div>
))}
</div>
</div>
<div className="vp-time-monitor-workbench">
<div className="vp-time-monitor-scope">
<Space wrap>

View File

@@ -527,6 +527,59 @@ body {
outline: none;
}
.vp-time-review-package {
margin-top: 14px;
display: grid;
grid-template-columns: minmax(300px, 0.86fr) minmax(0, 1.14fr);
gap: 12px;
}
.vp-time-review-summary {
min-height: 180px;
padding: 14px;
border: 1px solid rgba(0, 164, 184, 0.28);
border-radius: 8px;
background: #f3fbfc;
display: grid;
gap: 12px;
align-content: start;
}
.vp-time-review-summary .semi-typography {
line-height: 21px;
}
.vp-time-review-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 12px;
}
.vp-time-review-item {
min-height: 180px;
padding: 14px;
border: 1px solid var(--vp-border);
border-radius: 8px;
background: #fbfcff;
display: grid;
gap: 10px;
align-content: start;
}
.vp-time-review-item strong {
color: var(--vp-text);
font-size: 20px;
font-weight: 700;
line-height: 26px;
word-break: break-word;
}
.vp-time-review-item span {
color: var(--vp-text-muted);
font-size: 13px;
line-height: 20px;
}
.vp-time-monitor-workbench {
margin-top: 14px;
display: grid;
@@ -3809,6 +3862,8 @@ button.vp-realtime-command-item:focus-visible {
.vp-vehicle-service-desk,
.vp-vehicle-service-grid,
.vp-vehicle-decision-grid,
.vp-time-review-package,
.vp-time-review-grid,
.vp-realtime-command-board,
.vp-realtime-command-grid,
.vp-realtime-impact-board,

View File

@@ -813,6 +813,12 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
expect(screen.getByText('应用时间窗')).toBeInTheDocument();
expect(screen.getByText('重置最近一天')).toBeInTheDocument();
expect(screen.getByText('当前时间窗')).toBeInTheDocument();
expect(screen.getByText('客户时间窗复盘包')).toBeInTheDocument();
expect(screen.getByText('客户问某辆车、某段时间发生了什么时,直接按这个时间窗串起轨迹、里程、历史数据、告警通知和地图定位。')).toBeInTheDocument();
expect(screen.getByText('车辆范围')).toBeInTheDocument();
expect(screen.getByText('复盘时间')).toBeInTheDocument();
expect(screen.getByText('统计证据')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '复制复盘包' })).toBeInTheDocument();
expect(screen.getByText('这个范围会同步带入轨迹、里程、历史证据和告警复盘适合客户问询、BI 核对和异常解释。')).toBeInTheDocument();
expect(screen.getByText('查看轨迹')).toBeInTheDocument();
expect(screen.getByText('统计里程')).toBeInTheDocument();
@@ -826,7 +832,7 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
expect(screen.getByText('今天车辆轨迹')).toBeInTheDocument();
expect(screen.getByText('历史时间窗')).toBeInTheDocument();
expect(screen.getByText('区间里程')).toBeInTheDocument();
expect(screen.getByText('告警复盘')).toBeInTheDocument();
expect(screen.getAllByText('告警复盘').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('车辆运营工作台')).toBeInTheDocument();
expect(screen.getByText('地图运营能力')).toBeInTheDocument();
expect(screen.getByText('车联网场景导航')).toBeInTheDocument();
@@ -918,6 +924,12 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
expect(new URLSearchParams(window.location.hash.split('?')[1] ?? '').get('dateTo')).toBeTruthy();
cleanup();
await renderDashboard();
fireEvent.click(screen.getByRole('button', { name: '复制复盘包' }));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户时间窗复盘包】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('平台能力'));
cleanup();
await renderDashboard();
fireEvent.click(screen.getByRole('button', { name: '导出原始记录' }));
expect(window.location.hash.startsWith('#/history-query')).toBe(true);