feat(platform): refine history query workbench

This commit is contained in:
lingniu
2026-07-05 01:29:45 +08:00
parent 72a4761bdb
commit f5d38253fe
2 changed files with 123 additions and 2 deletions

View File

@@ -903,6 +903,17 @@ export function History({
...(day ? { dateFrom: day, dateTo: nextDate(day) } : {})
});
};
const openQueryTab = (tab: HistoryTabKey) => {
const nextFilters = tab === 'fields' ? { ...filters, includeFields: true } : filters;
setActiveTab(tab);
setFilters(nextFilters);
onFiltersChange?.(nextFilters, tab);
if (tab === 'location') {
loadLocations(nextFilters, 1, locationPagination.pageSize);
return;
}
loadRawFrames(nextFilters, 1, rawPagination.pageSize, tab === 'fields');
};
return (
<div className="vp-page">
@@ -923,6 +934,72 @@ export function History({
<Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol || '全部来源'}</Tag>
<Typography.Text type="tertiary">{scopeDescription}</Typography.Text>
</div>
{mode === 'query' ? (
<Card bordered title="历史查询与导出工作台">
<div className="vp-history-query-workbench">
<div className="vp-history-query-summary">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={activeTab === 'location' ? 'green' : 'blue'}>
{activeTab === 'location' ? '位置历史' : activeTab === 'raw' ? '原始记录' : '字段明细'}
</Tag>
</Space>
<Typography.Text strong>{currentVehicleKeyword || '全部车辆'}</Typography.Text>
<Typography.Text type="secondary">
BI
</Typography.Text>
<Space wrap>
<Button size="small" theme="solid" type="primary" onClick={() => openQueryTab('location')}></Button>
<Button size="small" onClick={() => openQueryTab('raw')}></Button>
<Button size="small" onClick={() => openQueryTab('fields')}></Button>
</Space>
</div>
<div className="vp-history-query-grid">
{[
{
label: '位置记录',
value: locations.total.toLocaleString(),
detail: `${validLocations.length.toLocaleString()} 个有效坐标,可用于轨迹和定位复盘。`,
action: '导出位置',
color: 'green' as const,
onClick: exportLocations
},
{
label: '原始记录',
value: `${(rawFrames.total ?? 0).toLocaleString()}`,
detail: '保存协议接入后的原始证据,可按车辆和时间追溯。',
action: '导出原始',
color: 'blue' as const,
onClick: exportRawFrames
},
{
label: '字段明细',
value: rawFieldRows.length.toLocaleString(),
detail: selectedFieldCount > 0 ? `已裁剪 ${selectedFieldCount.toLocaleString()} 个字段。` : '可勾选字段明细或填写字段裁剪后查询。',
action: '导出字段',
color: rawFieldRows.length > 0 ? 'green' as const : 'grey' as const,
onClick: exportRawFields
},
{
label: '服务联动',
value: currentVehicleKeyword ? '单车' : '批量',
detail: currentVehicleKeyword ? '可进入车辆服务、轨迹回放和里程核对。' : '输入 VIN、车牌或手机号后可进入单车服务。',
action: '车辆服务',
color: currentVehicleKeyword ? 'blue' as const : 'grey' as const,
onClick: () => currentVehicleKeyword && onOpenVehicle(currentVehicleKeyword, currentProtocol)
}
].map((item) => (
<div key={item.label} className="vp-history-query-item">
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
<Button size="small" disabled={item.label === '服务联动' && !currentVehicleKeyword} onClick={item.onClick}>{item.action}</Button>
</div>
))}
</div>
</div>
</Card>
) : null}
<Card bordered>
<Form key={JSON.stringify(filters)} initValues={filters} layout="horizontal" onSubmit={(values) => submit(values)}>
<Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号" style={{ width: 260 }} />
@@ -959,7 +1036,7 @@ export function History({
</Space>
</Card>
) : null}
<Card bordered title="轨迹运营影响" 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>
@@ -1024,7 +1101,7 @@ export function History({
bordered
title={(
<Space>
<span></span>
<span>{mode === 'query' ? '轨迹预览与证据' : '轨迹回放作业台'}</span>
<Button size="small" theme="light" icon={<IconCopy />} onClick={copyTrajectorySummary}></Button>
<Button size="small" theme="light" icon={<IconCopy />} onClick={copyHistoryEvidencePackage}></Button>
<Button size="small" theme="light" icon={<IconCopy />} onClick={copyTrajectoryReviewPackage}></Button>

View File

@@ -1996,6 +1996,48 @@ button.vp-realtime-command-item:focus-visible {
word-break: break-word;
}
.vp-history-query-workbench {
display: grid;
grid-template-columns: minmax(260px, 0.68fr) minmax(0, 1.32fr);
gap: 16px;
}
.vp-history-query-summary {
min-height: 176px;
padding: 12px;
border: 1px solid rgba(58, 104, 240, 0.24);
border-radius: var(--vp-radius);
background: #f6f8ff;
display: grid;
gap: 10px;
align-content: start;
}
.vp-history-query-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.vp-history-query-item {
min-height: 176px;
padding: 12px;
border: 1px solid var(--vp-border);
border-radius: var(--vp-radius);
background: #fbfcff;
display: grid;
gap: 10px;
align-content: start;
}
.vp-history-query-item strong {
color: var(--vp-text);
font-size: 20px;
line-height: 26px;
font-weight: 700;
word-break: break-word;
}
.vp-playback-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 260px;
@@ -2961,6 +3003,8 @@ button.vp-realtime-command-item:focus-visible {
.vp-trajectory-anomaly-grid,
.vp-trajectory-impact-board,
.vp-trajectory-impact-grid,
.vp-history-query-workbench,
.vp-history-query-grid,
.vp-source-fusion-grid,
.vp-source-fusion-facts,
.vp-vehicle-map-layout,