feat(platform): frame history replay as customer delivery
This commit is contained in:
@@ -442,7 +442,7 @@ function scheduledHistoryReportPlanText({
|
||||
`车辆范围:${vehicle || '全部车辆'}`,
|
||||
`数据通道:${protocol || '全部数据通道'}`,
|
||||
`时间范围:${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
|
||||
'推荐频率:日报用于运营复盘,周报用于客户对账,临时导出用于问题解释。',
|
||||
'推荐频率:日报用于客户复盘,周报用于客户对账,临时导出用于问题解释。',
|
||||
'交付内容:轨迹报告 / 位置历史 CSV / 明细证据 CSV / 字段裁剪 CSV / 统计查询链接 / 质量提示。',
|
||||
`当前数据量:位置 ${locationCount.toLocaleString()} 条,有效定位 ${validPointCount.toLocaleString()},明细证据 ${rawCount.toLocaleString()} 帧,字段裁剪 ${fieldCount.toLocaleString()} 个。`,
|
||||
`质量提示:断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
|
||||
@@ -486,7 +486,7 @@ function savedHistoryReportViewText({
|
||||
'【保存报表视图】',
|
||||
`视图范围:${vehicle || '全部车辆'} / ${protocol || '全部数据通道'} / ${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
|
||||
`字段模板:${fieldTemplate}`,
|
||||
'交付节奏:日报用于运营复盘,周报用于客户对账,临时导出用于问题解释。',
|
||||
'交付节奏:日报用于客户复盘,周报用于客户对账,临时导出用于问题解释。',
|
||||
`当前证据:位置 ${locationCount.toLocaleString()} 条,有效定位 ${validPointCount.toLocaleString()},明细 ${rawCount.toLocaleString()} 帧,字段 ${fieldCount.toLocaleString()} 个。`,
|
||||
`质量提示:断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
|
||||
`复用链接:${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: evidenceFilters }))}`
|
||||
@@ -540,7 +540,7 @@ function trajectoryReviewPackageText({
|
||||
? '建议核对明细证据、字段裁剪、当日统计查询,并确认平台是否存在断链或补发。'
|
||||
: '当前页未发现明显断点、里程回退或速度异常,可作为轨迹复盘依据使用。';
|
||||
return [
|
||||
'【轨迹复盘交接包】',
|
||||
'【客户轨迹复盘包】',
|
||||
`车辆:${vehicle || '全部车辆'}`,
|
||||
`数据通道:${protocol || '全部数据通道'}`,
|
||||
`查询范围:${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
|
||||
@@ -601,7 +601,7 @@ function trajectoryImpactPackageText({
|
||||
const anomalyTotal = anomalySummary.gapCount + anomalySummary.mileageRollbackCount + anomalySummary.overspeedCount;
|
||||
const operationState = anomalyTotal > 0 || (coverageRate ?? 100) < 80 ? '需要复核' : '可用于业务回放';
|
||||
return [
|
||||
'【轨迹运营影响】',
|
||||
'【客户轨迹影响】',
|
||||
`车辆:${vehicle || '全部车辆'}`,
|
||||
`数据通道:${protocol || '全部数据通道'}`,
|
||||
`查询范围:${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
|
||||
@@ -1365,9 +1365,9 @@ export function History({
|
||||
];
|
||||
const customerReportCadenceItems = [
|
||||
{
|
||||
title: '每日运营复盘',
|
||||
title: '每日客户复盘',
|
||||
value: `${locations.total.toLocaleString()} 位置`,
|
||||
detail: '每天交付位置历史、轨迹覆盖、里程统计和质量提示,便于运营早会复盘。',
|
||||
detail: '每天交付位置历史、轨迹覆盖、里程统计和质量提示,便于客户日报复盘。',
|
||||
action: '日报导出',
|
||||
color: locations.total > 0 ? 'green' as const : 'orange' as const,
|
||||
disabled: locations.items.length === 0,
|
||||
@@ -1403,9 +1403,9 @@ export function History({
|
||||
];
|
||||
const customerExportTemplateLibraryItems = [
|
||||
{
|
||||
title: '日报运营包',
|
||||
value: '运营早会',
|
||||
detail: '每天交付车辆位置覆盖、轨迹断点、字段证据和质量提示,适合早会复盘。',
|
||||
title: '日报复盘包',
|
||||
value: '客户日报',
|
||||
detail: '每天交付车辆位置覆盖、轨迹断点、字段证据和质量提示,适合客户日报复盘。',
|
||||
action: '导出日报',
|
||||
color: locations.total > 0 ? 'green' as const : 'orange' as const,
|
||||
disabled: locations.items.length === 0,
|
||||
@@ -1442,7 +1442,7 @@ export function History({
|
||||
const customerReportPurposeItems = [
|
||||
{
|
||||
title: '日报复盘',
|
||||
value: '运营早会',
|
||||
value: '客户日报',
|
||||
detail: '每天快速交付车辆位置、轨迹覆盖、区间里程和质量提示。',
|
||||
action: '生成日报',
|
||||
color: locations.total > 0 ? 'green' as const : 'orange' as const,
|
||||
@@ -1499,7 +1499,7 @@ export function History({
|
||||
{
|
||||
title: '交付节奏',
|
||||
value: '日报/周报',
|
||||
detail: '日报用于运营复盘,周报用于客户对账,临时导出用于问题解释。',
|
||||
detail: '日报用于客户复盘,周报用于客户对账,临时导出用于问题解释。',
|
||||
action: '复制计划',
|
||||
color: 'blue' as const,
|
||||
disabled: false,
|
||||
@@ -1508,7 +1508,7 @@ export function History({
|
||||
{
|
||||
title: '快速分享',
|
||||
value: '可复制',
|
||||
detail: '复制当前视图链接和字段模板,交给客户或内部运营复用。',
|
||||
detail: '复制当前视图链接和字段模板,交给客户或内部服务团队复用。',
|
||||
action: '复制链接',
|
||||
color: 'blue' as const,
|
||||
disabled: false,
|
||||
@@ -1683,7 +1683,7 @@ export function History({
|
||||
currentAddress: currentPlaybackAddress,
|
||||
anomalySummary: trajectoryAnomalies
|
||||
}),
|
||||
'轨迹复盘交接包'
|
||||
'客户轨迹复盘包'
|
||||
);
|
||||
};
|
||||
const copyTrajectoryImpactPackage = () => {
|
||||
@@ -1702,7 +1702,7 @@ export function History({
|
||||
anomalySummary: trajectoryAnomalies,
|
||||
amapConfigured
|
||||
}),
|
||||
'轨迹运营影响'
|
||||
'客户轨迹影响'
|
||||
);
|
||||
};
|
||||
const copyTrajectoryDecision = () => {
|
||||
@@ -2735,7 +2735,7 @@ export function History({
|
||||
</Space>
|
||||
<Typography.Title heading={5} style={{ margin: 0 }}>参考 Fleetio、Samsara、Geotab 的报表能力,把导出从一次性下载升级为可复用的交付节奏。</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
日报用于运营复盘,周报用于客户对账,临时导出用于问题解释,字段模板用于稳定复用。
|
||||
日报用于客户复盘,周报用于客户对账,临时导出用于问题解释,字段模板用于稳定复用。
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="vp-history-report-cadence-grid">
|
||||
@@ -2765,7 +2765,7 @@ export function History({
|
||||
{fieldEvidenceCount.toLocaleString()} 字段
|
||||
</Tag>
|
||||
</Space>
|
||||
<Typography.Title heading={5} style={{ margin: 0 }}>把一次性 CSV 下载变成客户可复用的报表模板:日报看运营、周报做对账、临时解释处理质疑、字段模板保持口径稳定。</Typography.Title>
|
||||
<Typography.Title heading={5} style={{ margin: 0 }}>把一次性 CSV 下载变成客户可复用的报表模板:日报看车辆、周报做对账、临时解释处理质疑、字段模板保持口径稳定。</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
每个模板都绑定同一辆车、同一时间窗和同一套证据口径,客户后续拿到的是稳定报表,而不是临时拼出来的查询结果。
|
||||
</Typography.Text>
|
||||
@@ -3302,7 +3302,7 @@ export function History({
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered title={mode === 'query' ? '历史数据质量' : '轨迹运营影响'} style={{ marginTop: 16 }}>
|
||||
<Card bordered title={mode === 'query' ? '历史数据质量' : '客户轨迹影响'} style={{ marginTop: 16 }}>
|
||||
<div className="vp-trajectory-impact-board">
|
||||
<div className="vp-trajectory-impact-summary">
|
||||
<Space wrap>
|
||||
@@ -3313,10 +3313,10 @@ export function History({
|
||||
</Space>
|
||||
<Typography.Text strong>{currentVehicleKeyword || '全部车辆'}</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
轨迹、历史查询导出、明细证据和统计查询按同一车辆范围联动,用于调度复盘、客户问询和断链定位。
|
||||
轨迹、历史查询导出、明细证据和统计查询按同一车辆范围联动,用于客户复盘、客户问询和断链说明。
|
||||
</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button size="small" icon={<IconCopy />} onClick={copyTrajectoryImpactPackage}>复制轨迹影响</Button>
|
||||
<Button size="small" icon={<IconCopy />} onClick={copyTrajectoryImpactPackage}>复制客户影响</Button>
|
||||
<Button size="small" disabled={!currentVehicleKeyword} onClick={() => onOpenVehicle(currentVehicleKeyword, currentProtocol)}>轨迹车辆服务</Button>
|
||||
<Button size="small" disabled={!onOpenMileage} onClick={() => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })}>统计查询</Button>
|
||||
</Space>
|
||||
|
||||
@@ -6887,9 +6887,9 @@ test('copies trajectory playback summary from history page', async () => {
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('统计查询:http://localhost:3000/#/mileage?keyword=VIN-HISTORY-SUMMARY&protocol=JT808&dateFrom=2026-07-03&dateTo=2026-07-04'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务:http://localhost:3000/#/detail?keyword=VIN-HISTORY-SUMMARY&protocol=JT808'));
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /复制轨迹影响/ }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /复制客户影响/ }));
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【轨迹运营影响】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户轨迹影响】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆:VIN-HISTORY-SUMMARY'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据通道:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('业务状态:需要复核'));
|
||||
@@ -6903,7 +6903,7 @@ test('copies trajectory playback summary from history page', async () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /复制轨迹复盘包/ }));
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【轨迹复盘交接包】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户轨迹复盘包】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆:VIN-HISTORY-SUMMARY'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹规模:位置 2 / 有效定位 2 / 明细证据 1 / 字段裁剪 2'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹质量:定位覆盖率 100% / 回放跨度 60 分钟 / 采样间隔 60 分钟/点'));
|
||||
@@ -8272,17 +8272,17 @@ test('shows parsed field history as a flattened evidence table', async () => {
|
||||
expect(screen.getByText('参考 Fleetio、Samsara、Geotab 的报表能力,把导出从一次性下载升级为可复用的交付节奏。')).toBeInTheDocument();
|
||||
expect(screen.getByText('客户报表用途导航')).toBeInTheDocument();
|
||||
expect(screen.getByText('先选择客户要解决的问题,再决定导出位置、字段、统计或说明。')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表用途导航 日报复盘 运营早会 生成日报' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表用途导航 日报复盘 客户日报 生成日报' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表用途导航 周报对账 客户对账 生成周报' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表用途导航 临时解释 质量说明 复制说明' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表用途导航 字段模板 稳定字段 字段配置' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表交付节奏 每日运营复盘 日报导出' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表交付节奏 每日客户复盘 日报导出' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表交付节奏 每周客户对账 周报计划' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表交付节奏 临时问题解释 复制说明' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户报表交付节奏 字段模板复用 字段配置' })).toBeInTheDocument();
|
||||
expect(screen.getByText('客户导出包模板库')).toBeInTheDocument();
|
||||
expect(screen.getByText('把一次性 CSV 下载变成客户可复用的报表模板:日报看运营、周报做对账、临时解释处理质疑、字段模板保持口径稳定。')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户导出包模板库 日报运营包 运营早会 导出日报' })).toBeInTheDocument();
|
||||
expect(screen.getByText('把一次性 CSV 下载变成客户可复用的报表模板:日报看车辆、周报做对账、临时解释处理质疑、字段模板保持口径稳定。')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户导出包模板库 日报复盘包 客户日报 导出日报' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户导出包模板库 周报对账包 客户对账 复制周报计划' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户导出包模板库 问题解释包 客户问询 复制说明' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户导出包模板库 字段稳定包 数据对接 字段配置' })).toBeInTheDocument();
|
||||
@@ -8315,7 +8315,7 @@ test('shows parsed field history as a flattened evidence table', async () => {
|
||||
await waitFor(() => {
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【历史数据周期交付计划】'));
|
||||
});
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('推荐频率:日报用于运营复盘,周报用于客户对账,临时导出用于问题解释。'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('推荐频率:日报用于客户复盘,周报用于客户对账,临时导出用于问题解释。'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('交付内容:轨迹报告 / 位置历史 CSV / 明细证据 CSV / 字段裁剪 CSV / 统计查询链接 / 质量提示。'));
|
||||
fireEvent.click(screen.getByRole('button', { name: '保存报表视图 当前筛选 VIN-FIELDS-001 保存视图' }));
|
||||
await waitFor(() => {
|
||||
@@ -8488,7 +8488,7 @@ test('shows trajectory playback workspace from history locations', async () => {
|
||||
expect(screen.getByText('位置导出')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('历史明细').length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText('字段裁剪').length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText('轨迹运营影响')).toBeInTheDocument();
|
||||
expect(screen.getByText('客户轨迹影响')).toBeInTheDocument();
|
||||
expect(screen.getByText('轨迹范围')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('定位覆盖').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('异常影响')).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user