feat(platform): add customer time-window review package
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user