feat(platform): export history query results
This commit is contained in:
@@ -6,6 +6,7 @@ import type { HistoryLocationRow, Page, RawFrameRow } from '../api/types';
|
||||
import { VehicleMap, type VehicleMapPoint } from '../components/VehicleMap';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
import { isAMapConfigured } from '../config/appConfig';
|
||||
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
|
||||
|
||||
type HistoryFilters = {
|
||||
keyword?: string;
|
||||
@@ -68,6 +69,36 @@ function mergeInitialFilters(initialVin: string, initialProtocol?: string, initi
|
||||
};
|
||||
}
|
||||
|
||||
const locationExportColumns: CsvColumn<HistoryLocationRow>[] = [
|
||||
{ title: 'VIN', value: (row) => row.vin },
|
||||
{ title: '车牌', value: (row) => row.plate },
|
||||
{ title: '数据来源', value: (row) => row.protocol },
|
||||
{ title: '经度', value: (row) => row.longitude },
|
||||
{ title: '纬度', value: (row) => row.latitude },
|
||||
{ title: '速度km/h', value: (row) => row.speedKmh },
|
||||
{ title: '总里程km', value: (row) => row.totalMileageKm },
|
||||
{ title: '设备时间', value: (row) => row.deviceTime },
|
||||
{ title: '入库时间', value: (row) => row.serverTime }
|
||||
];
|
||||
|
||||
const rawExportColumns: CsvColumn<RawFrameRow>[] = [
|
||||
{ title: 'ID', value: (row) => row.id },
|
||||
{ title: 'VIN', value: (row) => row.vin },
|
||||
{ title: '车牌', value: (row) => row.plate },
|
||||
{ title: '数据来源', value: (row) => row.protocol },
|
||||
{ title: '帧类型', value: (row) => row.frameType },
|
||||
{ title: '大小B', value: (row) => row.rawSizeBytes },
|
||||
{ title: '设备时间', value: (row) => row.deviceTime },
|
||||
{ title: '入库时间', value: (row) => row.serverTime },
|
||||
{ title: '解析字段', value: (row) => row.parsedFields ?? {} }
|
||||
];
|
||||
|
||||
function exportFileName(prefix: string, filters: HistoryFilters) {
|
||||
const keyword = filters.keyword?.trim() || 'all';
|
||||
const protocol = filters.protocol?.trim() || 'all-source';
|
||||
return `${prefix}-${keyword}-${protocol}.csv`;
|
||||
}
|
||||
|
||||
export function History({
|
||||
initialVin,
|
||||
initialProtocol,
|
||||
@@ -211,6 +242,22 @@ export function History({
|
||||
const clearRangeFilters = () => {
|
||||
applyFilters(currentVehicleKeyword ? { keyword: currentVehicleKeyword } : {});
|
||||
};
|
||||
const exportLocations = () => {
|
||||
if (locations.items.length === 0) {
|
||||
Toast.warning('当前没有可导出的位置历史');
|
||||
return;
|
||||
}
|
||||
downloadCsv(exportFileName('history-locations', filters), buildCsv(locationExportColumns, locations.items));
|
||||
Toast.success(`已导出 ${locations.items.length} 条位置历史`);
|
||||
};
|
||||
const exportRawFrames = () => {
|
||||
if (rawFrames.items.length === 0) {
|
||||
Toast.warning('当前没有可导出的 RAW 帧');
|
||||
return;
|
||||
}
|
||||
downloadCsv(exportFileName('raw-frames', filters), buildCsv(rawExportColumns, rawFrames.items));
|
||||
Toast.success(`已导出 ${rawFrames.items.length} 条 RAW 帧`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="vp-page">
|
||||
@@ -320,6 +367,12 @@ export function History({
|
||||
<Card bordered style={{ marginTop: 16 }}>
|
||||
<Tabs activeKey={activeTab} onChange={(key) => changeTab(String(key))}>
|
||||
<Tabs.TabPane tab="位置历史" itemKey="location">
|
||||
<div className="vp-table-toolbar">
|
||||
<Space wrap>
|
||||
<Tag color="blue">当前页 {locations.items.length.toLocaleString()} 条</Tag>
|
||||
<Button size="small" onClick={exportLocations}>导出位置当前页 CSV</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<Table
|
||||
rowKey="deviceTime"
|
||||
dataSource={locations.items}
|
||||
@@ -352,6 +405,15 @@ export function History({
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="RAW 帧" itemKey="raw">
|
||||
<div className="vp-table-toolbar">
|
||||
<Space wrap>
|
||||
<Tag color="blue">当前页 {rawFrames.items.length.toLocaleString()} 条</Tag>
|
||||
<Tag color={isIncludeFieldsEnabled(filters.includeFields) || splitFields(filters.fields).length > 0 ? 'green' : 'orange'}>
|
||||
{isIncludeFieldsEnabled(filters.includeFields) || splitFields(filters.fields).length > 0 ? '包含解析字段' : '未请求解析字段'}
|
||||
</Tag>
|
||||
<Button size="small" onClick={exportRawFrames}>导出 RAW 当前页 CSV</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<Table
|
||||
rowKey="id"
|
||||
dataSource={rawFrames.items}
|
||||
|
||||
Reference in New Issue
Block a user