feat(platform): frame history protocol as source evidence

This commit is contained in:
lingniu
2026-07-06 02:11:44 +08:00
parent 26eeba713d
commit 1d51c92d8c
2 changed files with 24 additions and 23 deletions

View File

@@ -903,7 +903,7 @@ export function History({
const pageTitle = mode === 'query' ? '历史查询导出' : '轨迹回放'; const pageTitle = mode === 'query' ? '历史查询导出' : '轨迹回放';
const pageDescription = mode === 'query' const pageDescription = mode === 'query'
? '按车辆和时间窗交付轨迹报告、位置记录、明细证据和字段裁剪,支持分页、字段裁剪和 CSV 导出' ? '按车辆和时间窗交付轨迹报告、位置记录、明细证据和字段裁剪,支持分页、字段裁剪和 CSV 导出'
: '按车辆查询历史位置和轨迹回放,明细证据只作为复核依据,数据通道仅用于过滤'; : '按车辆查询历史位置和轨迹回放,明细证据只作为复核依据,来源证据仅用于过滤';
const scopeDescription = mode === 'query' const scopeDescription = mode === 'query'
? '位置记录、明细证据和字段裁剪按当前车辆与时间范围分页查询。' ? '位置记录、明细证据和字段裁剪按当前车辆与时间范围分页查询。'
: '历史位置和明细证据按当前车辆与时间范围查询。'; : '历史位置和明细证据按当前车辆与时间范围查询。';
@@ -925,7 +925,7 @@ export function History({
})); }));
const filterSummary = [ const filterSummary = [
currentVehicleKeyword ? `车辆:${currentVehicleKeyword}` : '', currentVehicleKeyword ? `车辆:${currentVehicleKeyword}` : '',
currentProtocol ? `数据通道${currentProtocol}` : '', currentProtocol ? `来源证据${currentProtocol}` : '',
filters.dateFrom?.trim() ? `开始时间:${filters.dateFrom.trim()}` : '', filters.dateFrom?.trim() ? `开始时间:${filters.dateFrom.trim()}` : '',
filters.dateTo?.trim() ? `结束时间:${filters.dateTo.trim()}` : '', filters.dateTo?.trim() ? `结束时间:${filters.dateTo.trim()}` : '',
isIncludeFieldsEnabled(filters.includeFields) ? '返回字段明细' : '', isIncludeFieldsEnabled(filters.includeFields) ? '返回字段明细' : '',
@@ -940,7 +940,7 @@ export function History({
? anomalyTotal > 0 || (trajectoryCoverageRate ?? 100) < 80 ? '可复盘,需说明' : '可直接复盘' ? anomalyTotal > 0 || (trajectoryCoverageRate ?? 100) < 80 ? '可复盘,需说明' : '可直接复盘'
: '待查询数据'; : '待查询数据';
const trajectoryDecisionColor = trajectoryDecisionState === '可直接复盘' ? 'green' as const : trajectoryDecisionState === '待查询数据' ? 'grey' as const : 'orange' as const; const trajectoryDecisionColor = trajectoryDecisionState === '可直接复盘' ? 'green' as const : trajectoryDecisionState === '待查询数据' ? 'grey' as const : 'orange' as const;
const deliveryScopeText = `${currentVehicleKeyword || '全部车辆'} / ${currentProtocol || '全部数据通道'}`; const deliveryScopeText = `${currentVehicleKeyword || '全部车辆'} / ${currentProtocol || '全部来源证据'}`;
const trajectoryDecisionItems = [ const trajectoryDecisionItems = [
{ {
label: '复盘结论', label: '复盘结论',
@@ -1219,7 +1219,7 @@ export function History({
const customerEvidencePackageOverviewItems = [ const customerEvidencePackageOverviewItems = [
{ {
label: '交付范围', label: '交付范围',
value: `${currentVehicleKeyword || '全部车辆'} / ${currentProtocol || '全部数据通道'}`, value: `${currentVehicleKeyword || '全部车辆'} / ${currentProtocol || '全部来源证据'}`,
detail: `${filters.dateFrom || '-'}${filters.dateTo || '-'},先确认客户要查的车辆和时间窗。`, detail: `${filters.dateFrom || '-'}${filters.dateTo || '-'},先确认客户要查的车辆和时间窗。`,
action: '查看范围', action: '查看范围',
color: currentVehicleKeyword ? 'green' as const : 'blue' as const, color: currentVehicleKeyword ? 'green' as const : 'blue' as const,
@@ -1258,7 +1258,7 @@ export function History({
{ {
label: currentVehicleKeyword ? '范围已锁定' : '范围待选择', label: currentVehicleKeyword ? '范围已锁定' : '范围待选择',
value: currentVehicleKeyword || '全部车辆', value: currentVehicleKeyword || '全部车辆',
detail: `${currentProtocol || '全部数据通道'} / ${filters.dateFrom || '-'}${filters.dateTo || '-'}`, detail: `${currentProtocol || '全部来源证据'} / ${filters.dateFrom || '-'}${filters.dateTo || '-'}`,
action: '查看范围', action: '查看范围',
color: currentVehicleKeyword ? 'green' as const : 'orange' as const, color: currentVehicleKeyword ? 'green' as const : 'orange' as const,
disabled: false, disabled: false,
@@ -1410,7 +1410,7 @@ export function History({
{ {
title: '当前筛选', title: '当前筛选',
value: currentVehicleKeyword || '全部车辆', value: currentVehicleKeyword || '全部车辆',
detail: `${currentProtocol || '全部数据通道'} / ${filters.dateFrom || '-'}${filters.dateTo || '-'}`, detail: `${currentProtocol || '全部来源证据'} / ${filters.dateFrom || '-'}${filters.dateTo || '-'}`,
action: '保存视图', action: '保存视图',
color: currentVehicleKeyword ? 'green' as const : 'blue' as const, color: currentVehicleKeyword ? 'green' as const : 'blue' as const,
disabled: false, disabled: false,
@@ -1830,7 +1830,7 @@ export function History({
{ {
title: '选车和时间', title: '选车和时间',
value: currentVehicleKeyword || '待选择车辆', value: currentVehicleKeyword || '待选择车辆',
detail: `${currentProtocol || '全部数据通道'} / ${filters.dateFrom || '-'}${filters.dateTo || '-'}`, detail: `${currentProtocol || '全部来源证据'} / ${filters.dateFrom || '-'}${filters.dateTo || '-'}`,
action: '调整筛选', action: '调整筛选',
color: currentVehicleKeyword ? 'green' as const : 'orange' as const, color: currentVehicleKeyword ? 'green' as const : 'orange' as const,
disabled: false, disabled: false,
@@ -1881,7 +1881,7 @@ export function History({
/> />
<div className="vp-scope-bar"> <div className="vp-scope-bar">
<span className="vp-scope-label">{currentVehicleKeyword || '-'}</span> <span className="vp-scope-label">{currentVehicleKeyword || '-'}</span>
<Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol || '全部数据通道'}</Tag> <Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol || '全部来源证据'}</Tag>
<Typography.Text type="tertiary">{scopeDescription}</Typography.Text> <Typography.Text type="tertiary">{scopeDescription}</Typography.Text>
</div> </div>
<section className="vp-history-evidence-chain" aria-label="客户时间窗证据链"> <section className="vp-history-evidence-chain" aria-label="客户时间窗证据链">
@@ -1987,7 +1987,7 @@ export function History({
线 线
</Typography.Title> </Typography.Title>
<div className="vp-history-service-desk-meta"> <div className="vp-history-service-desk-meta">
<span>{currentVehicleKeyword || '全部车辆'} / {currentProtocol || '全部数据通道'}</span> <span>{currentVehicleKeyword || '全部车辆'} / {currentProtocol || '全部来源证据'}</span>
<span>{filters.dateFrom || '-'} {filters.dateTo || '-'}</span> <span>{filters.dateFrom || '-'} {filters.dateTo || '-'}</span>
<span></span> <span></span>
</div> </div>
@@ -2102,7 +2102,7 @@ export function History({
step: '01', step: '01',
title: '选择范围', title: '选择范围',
value: currentVehicleKeyword || '全部车辆', value: currentVehicleKeyword || '全部车辆',
detail: `${currentProtocol || '全部数据通道'} / ${filters.dateFrom || '-'}${filters.dateTo || '-'}`, detail: `${currentProtocol || '全部来源证据'} / ${filters.dateFrom || '-'}${filters.dateTo || '-'}`,
action: '调整筛选', action: '调整筛选',
color: currentVehicleKeyword ? 'green' as const : 'blue' as const, color: currentVehicleKeyword ? 'green' as const : 'blue' as const,
disabled: false, disabled: false,
@@ -2310,7 +2310,7 @@ export function History({
{selectedFieldCount > 0 ? `${selectedFieldCount.toLocaleString()} 个字段` : `${rawFieldRows.length.toLocaleString()} 个字段`} {selectedFieldCount > 0 ? `${selectedFieldCount.toLocaleString()} 个字段` : `${rawFieldRows.length.toLocaleString()} 个字段`}
</Tag> </Tag>
</Space> </Space>
<Typography.Title heading={5} style={{ margin: 0 }}></Typography.Title> <Typography.Title heading={5} style={{ margin: 0 }}></Typography.Title>
<Typography.Text type="secondary"> <Typography.Text type="secondary">
</Typography.Text> </Typography.Text>
@@ -2441,7 +2441,7 @@ export function History({
<div className="vp-trip-current-card"> <div className="vp-trip-current-card">
<Space wrap> <Space wrap>
<Tag color={currentPlayback ? 'green' : 'grey'}>{currentPlayback ? `${currentPlaybackIndex + 1}/${playbackRows.length}` : '暂无回放点'}</Tag> <Tag color={currentPlayback ? 'green' : 'grey'}>{currentPlayback ? `${currentPlaybackIndex + 1}/${playbackRows.length}` : '暂无回放点'}</Tag>
<Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol ? `通道 ${currentProtocol}` : '全部数据通道'}</Tag> <Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol ? `来源证据 ${currentProtocol}` : '全部来源证据'}</Tag>
</Space> </Space>
<Typography.Text strong>{currentPlayback?.plate || (currentPlayback ? '当前回放车辆' : currentVehicleKeyword ? '当前查询车辆' : '全部车辆')}</Typography.Text> <Typography.Text strong>{currentPlayback?.plate || (currentPlayback ? '当前回放车辆' : currentVehicleKeyword ? '当前查询车辆' : '全部车辆')}</Typography.Text>
<Typography.Text type="secondary">{currentPlayback?.deviceTime || currentPlayback?.serverTime || '请选择车辆和时间窗查询轨迹'}</Typography.Text> <Typography.Text type="secondary">{currentPlayback?.deviceTime || currentPlayback?.serverTime || '请选择车辆和时间窗查询轨迹'}</Typography.Text>
@@ -2593,7 +2593,7 @@ export function History({
<div className="vp-history-package-summary"> <div className="vp-history-package-summary">
<Space wrap> <Space wrap>
<Tag color={deliveryStateColor}>{deliveryState}</Tag> <Tag color={deliveryStateColor}>{deliveryState}</Tag>
<Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol || '全部数据通道'}</Tag> <Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol || '全部来源证据'}</Tag>
</Space> </Space>
<Typography.Text strong>{deliveryScopeText}</Typography.Text> <Typography.Text strong>{deliveryScopeText}</Typography.Text>
<Typography.Text type="secondary"> <Typography.Text type="secondary">
@@ -2700,7 +2700,7 @@ export function History({
<Card bordered> <Card bordered>
<Form key={JSON.stringify(filters)} initValues={filters} layout="horizontal" onSubmit={(values) => submit(values)}> <Form key={JSON.stringify(filters)} initValues={filters} layout="horizontal" onSubmit={(values) => submit(values)}>
<Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号" style={{ width: 260 }} /> <Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号" style={{ width: 260 }} />
<Form.Select field="protocol" label="数据通道" placeholder="全部数据通道" style={{ width: 190 }}> <Form.Select field="protocol" label="来源证据筛选" placeholder="全部来源证据" style={{ width: 190 }}>
<Select.Option value="GB32960">GB32960</Select.Option> <Select.Option value="GB32960">GB32960</Select.Option>
<Select.Option value="JT808">JT808</Select.Option> <Select.Option value="JT808">JT808</Select.Option>
<Select.Option value="YUTONG_MQTT">YUTONG_MQTT</Select.Option> <Select.Option value="YUTONG_MQTT">YUTONG_MQTT</Select.Option>
@@ -2890,7 +2890,7 @@ export function History({
<div className="vp-monitor-map-header"> <div className="vp-monitor-map-header">
<Space wrap> <Space wrap>
<Tag color="blue">{validLocations.length.toLocaleString()} </Tag> <Tag color="blue">{validLocations.length.toLocaleString()} </Tag>
<Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol || '全部数据通道'}</Tag> <Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol || '全部来源证据'}</Tag>
<Tag color="green">{formatNumber(mileageDelta, ' km')}</Tag> <Tag color="green">{formatNumber(mileageDelta, ' km')}</Tag>
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图配置就绪' : '高德地图待配置'}</Tag> <Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图配置就绪' : '高德地图待配置'}</Tag>
</Space> </Space>
@@ -2998,7 +2998,7 @@ export function History({
</button> </button>
))} ))}
{locationItems.length === 0 ? ( {locationItems.length === 0 ? (
<Typography.Text type="secondary"></Typography.Text> <Typography.Text type="secondary"></Typography.Text>
) : null} ) : null}
</div> </div>
<Card bordered title="轨迹复盘质量" style={{ marginTop: 12 }}> <Card bordered title="轨迹复盘质量" style={{ marginTop: 12 }}>
@@ -3096,7 +3096,7 @@ export function History({
}} }}
columns={[ columns={[
{ title: 'VIN', dataIndex: 'vin', width: 190 }, { title: 'VIN', dataIndex: 'vin', width: 190 },
{ title: '数据通道', dataIndex: 'protocol', width: 120 }, { title: '来源证据', dataIndex: 'protocol', width: 120 },
{ title: '经度', dataIndex: 'longitude', width: 120 }, { title: '经度', dataIndex: 'longitude', width: 120 },
{ title: '纬度', dataIndex: 'latitude', width: 120 }, { title: '纬度', dataIndex: 'latitude', width: 120 },
{ title: '速度 km/h', dataIndex: 'speedKmh', width: 120 }, { title: '速度 km/h', dataIndex: 'speedKmh', width: 120 },
@@ -3143,7 +3143,7 @@ export function History({
{ title: 'ID', dataIndex: 'id', width: 260 }, { title: 'ID', dataIndex: 'id', width: 260 },
{ title: 'VIN', dataIndex: 'vin', width: 190 }, { title: 'VIN', dataIndex: 'vin', width: 190 },
{ title: '车牌', dataIndex: 'plate', width: 120 }, { title: '车牌', dataIndex: 'plate', width: 120 },
{ title: '数据通道', dataIndex: 'protocol', width: 120 }, { title: '来源证据', dataIndex: 'protocol', width: 120 },
{ title: '帧类型', dataIndex: 'frameType', width: 190 }, { title: '帧类型', dataIndex: 'frameType', width: 190 },
{ title: '大小 B', dataIndex: 'rawSizeBytes', width: 100 }, { title: '大小 B', dataIndex: 'rawSizeBytes', width: 100 },
{ title: '设备时间', dataIndex: 'deviceTime', width: 190 }, { title: '设备时间', dataIndex: 'deviceTime', width: 190 },
@@ -3187,7 +3187,7 @@ export function History({
}, },
{ title: 'VIN', dataIndex: 'vin', width: 190 }, { title: 'VIN', dataIndex: 'vin', width: 190 },
{ title: '车牌', dataIndex: 'plate', width: 120 }, { title: '车牌', dataIndex: 'plate', width: 120 },
{ title: '数据通道', dataIndex: 'protocol', width: 120 }, { title: '来源证据', dataIndex: 'protocol', width: 120 },
{ title: '帧类型', dataIndex: 'frameType', width: 190 }, { title: '帧类型', dataIndex: 'frameType', width: 190 },
{ title: '设备时间', dataIndex: 'deviceTime', width: 190 }, { title: '设备时间', dataIndex: 'deviceTime', width: 190 },
{ title: '历史明细ID', dataIndex: 'rawId', width: 260 }, { title: '历史明细ID', dataIndex: 'rawId', width: 260 },

View File

@@ -7187,7 +7187,7 @@ test('applies protocol from shareable history hash to API requests', async () =>
body: expect.stringContaining('"protocol":"JT808"') body: expect.stringContaining('"protocol":"JT808"')
})); }));
expect(screen.getByText('当前车辆粤AG18312')).toBeInTheDocument(); expect(screen.getByText('当前车辆粤AG18312')).toBeInTheDocument();
expect(screen.getAllByText('数据通道JT808').length).toBeGreaterThan(0); expect(screen.getAllByText('来源证据JT808').length).toBeGreaterThan(0);
fireEvent.click(screen.getByRole('button', { name: '顶部统计查询' })); fireEvent.click(screen.getByRole('button', { name: '顶部统计查询' }));
expect(window.location.hash).toBe('#/mileage?keyword=%E7%B2%A4AG18312&protocol=JT808&dateFrom=2026-07-01&dateTo=2026-07-03'); expect(window.location.hash).toBe('#/mileage?keyword=%E7%B2%A4AG18312&protocol=JT808&dateFrom=2026-07-01&dateTo=2026-07-03');
@@ -7528,7 +7528,7 @@ test('shows and clears current history filters while keeping vehicle scope', asy
expect(await screen.findByText('当前历史筛选')).toBeInTheDocument(); expect(await screen.findByText('当前历史筛选')).toBeInTheDocument();
expect(screen.getByText('车辆VIN-HISTORY-001')).toBeInTheDocument(); expect(screen.getByText('车辆VIN-HISTORY-001')).toBeInTheDocument();
expect(screen.getAllByText('数据通道JT808').length).toBeGreaterThan(0); expect(screen.getAllByText('来源证据JT808').length).toBeGreaterThan(0);
expect(screen.getByText('开始时间2026-07-01 00:00:00')).toBeInTheDocument(); expect(screen.getByText('开始时间2026-07-01 00:00:00')).toBeInTheDocument();
expect(screen.getByText('结束时间2026-07-01 23:59:59')).toBeInTheDocument(); expect(screen.getByText('结束时间2026-07-01 23:59:59')).toBeInTheDocument();
expect(screen.getAllByText('返回字段明细').length).toBeGreaterThanOrEqual(2); expect(screen.getAllByText('返回字段明细').length).toBeGreaterThanOrEqual(2);
@@ -7625,6 +7625,7 @@ test('shows parsed field history as a flattened evidence table', async () => {
expect(screen.getByRole('button', { name: '客户证据包交付总览 明细字段 2 字段 字段导出' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '客户证据包交付总览 明细字段 2 字段 字段导出' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户证据包交付总览 交付说明 可交付 复制说明' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '客户证据包交付总览 交付说明 可交付 复制说明' })).toBeInTheDocument();
expect(screen.getByText('客户先选车辆和时间窗,再拿到路线、里程、原始证据、字段裁剪和告警说明。')).toBeInTheDocument(); expect(screen.getByText('客户先选车辆和时间窗,再拿到路线、里程、原始证据、字段裁剪和告警说明。')).toBeInTheDocument();
expect(screen.getAllByText('来源证据GB32960').length).toBeGreaterThan(0);
expect(screen.getByText('当前服务范围VIN-FIELDS-001 / GB32960')).toBeInTheDocument(); expect(screen.getByText('当前服务范围VIN-FIELDS-001 / GB32960')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '车辆历史服务台 路线复盘 0 点 查询位置' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '车辆历史服务台 路线复盘 0 点 查询位置' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '车辆历史服务台 里程核对 - 统计查询' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '车辆历史服务台 里程核对 - 统计查询' })).toBeInTheDocument();
@@ -7687,7 +7688,7 @@ test('shows parsed field history as a flattened evidence table', async () => {
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('保存报表视图')).toBeInTheDocument();
expect(screen.getByText('把当前车辆、时间窗、数据通道、字段裁剪和交付节奏保存成客户可复用视图,后续不用重新拼查询条件。')).toBeInTheDocument(); expect(screen.getByText('把当前车辆、时间窗、来源证据、字段裁剪和交付节奏保存成客户可复用视图,后续不用重新拼查询条件。')).toBeInTheDocument();
expect(screen.getByRole('button', { name: '保存报表视图 当前筛选 VIN-FIELDS-001 保存视图' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '保存报表视图 当前筛选 VIN-FIELDS-001 保存视图' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '保存报表视图 字段模板 2 字段 复用字段' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '保存报表视图 字段模板 2 字段 复用字段' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '保存报表视图 交付节奏 日报/周报 复制计划' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '保存报表视图 交付节奏 日报/周报 复制计划' })).toBeInTheDocument();
@@ -10065,7 +10066,7 @@ test('opens current vehicle service from history header with current source evid
render(<App />); render(<App />);
expect((await screen.findAllByText('数据通道JT808')).length).toBeGreaterThan(0); expect((await screen.findAllByText('来源证据JT808')).length).toBeGreaterThan(0);
fireEvent.click(screen.getByRole('button', { name: '当前车辆服务' })); fireEvent.click(screen.getByRole('button', { name: '当前车辆服务' }));
await waitFor(() => { await waitFor(() => {