feat(platform): open analysis from vehicle detail
This commit is contained in:
@@ -13,6 +13,7 @@ import { Vehicles } from './pages/Vehicles';
|
|||||||
export default function App() {
|
export default function App() {
|
||||||
const [activePage, setActivePage] = useState<PageKey>('dashboard');
|
const [activePage, setActivePage] = useState<PageKey>('dashboard');
|
||||||
const [activeVin, setActiveVin] = useState('LB9A32A24R0LS1426');
|
const [activeVin, setActiveVin] = useState('LB9A32A24R0LS1426');
|
||||||
|
const [analysisVin, setAnalysisVin] = useState('LB9A32A24R0LS1426');
|
||||||
const [linkIssueCount, setLinkIssueCount] = useState<number | null>(null);
|
const [linkIssueCount, setLinkIssueCount] = useState<number | null>(null);
|
||||||
|
|
||||||
const refreshOpsHealth = useCallback((showError = true) => {
|
const refreshOpsHealth = useCallback((showError = true) => {
|
||||||
@@ -45,13 +46,31 @@ export default function App() {
|
|||||||
setActivePage('detail');
|
setActivePage('detail');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openHistoryForVehicle = (vin: string) => {
|
||||||
|
const nextVin = vin.trim();
|
||||||
|
if (!nextVin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAnalysisVin(nextVin);
|
||||||
|
setActivePage('history');
|
||||||
|
};
|
||||||
|
|
||||||
|
const openMileageForVehicle = (vin: string) => {
|
||||||
|
const nextVin = vin.trim();
|
||||||
|
if (!nextVin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAnalysisVin(nextVin);
|
||||||
|
setActivePage('mileage');
|
||||||
|
};
|
||||||
|
|
||||||
const pages: Record<PageKey, JSX.Element> = {
|
const pages: Record<PageKey, JSX.Element> = {
|
||||||
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={() => setActivePage('quality')} />,
|
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={() => setActivePage('quality')} />,
|
||||||
vehicles: <Vehicles onOpenVehicle={openVehicle} />,
|
vehicles: <Vehicles onOpenVehicle={openVehicle} />,
|
||||||
realtime: <Realtime onOpenVehicle={openVehicle} />,
|
realtime: <Realtime onOpenVehicle={openVehicle} />,
|
||||||
detail: <VehicleDetail vin={activeVin} />,
|
detail: <VehicleDetail vin={activeVin} onOpenHistory={openHistoryForVehicle} onOpenMileage={openMileageForVehicle} />,
|
||||||
history: <History onOpenVehicle={openVehicle} />,
|
history: <History initialVin={analysisVin} onOpenVehicle={openVehicle} />,
|
||||||
mileage: <Mileage onOpenVehicle={openVehicle} />,
|
mileage: <Mileage initialVin={analysisVin} onOpenVehicle={openVehicle} />,
|
||||||
quality: <Quality onHealthLoaded={(health) => setLinkIssueCount(health.linkHealth.filter((item) => item.status !== 'ok').length)} />
|
quality: <Quality onHealthLoaded={(health) => setLinkIssueCount(health.linkHealth.filter((item) => item.status !== 'ok').length)} />
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ function canOpenVehicle(vin?: string) {
|
|||||||
return Boolean(value && value !== 'unknown');
|
return Boolean(value && value !== 'unknown');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function History({ onOpenVehicle }: { onOpenVehicle: (vin: string) => void }) {
|
export function History({ initialVin, onOpenVehicle }: { initialVin: string; onOpenVehicle: (vin: string) => void }) {
|
||||||
const [filters, setFilters] = useState<HistoryFilters>(defaultFilters);
|
const [filters, setFilters] = useState<HistoryFilters>({ ...defaultFilters, vin: initialVin || defaultFilters.vin });
|
||||||
const [locations, setLocations] = useState<Page<HistoryLocationRow>>(defaultPage);
|
const [locations, setLocations] = useState<Page<HistoryLocationRow>>(defaultPage);
|
||||||
const [rawFrames, setRawFrames] = useState<Page<RawFrameRow>>(defaultPage);
|
const [rawFrames, setRawFrames] = useState<Page<RawFrameRow>>(defaultPage);
|
||||||
const [selectedRaw, setSelectedRaw] = useState<RawFrameRow | null>(null);
|
const [selectedRaw, setSelectedRaw] = useState<RawFrameRow | null>(null);
|
||||||
@@ -84,15 +84,18 @@ export function History({ onOpenVehicle }: { onOpenVehicle: (vin: string) => voi
|
|||||||
};
|
};
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
setFilters(defaultFilters);
|
const nextFilters = { ...defaultFilters, vin: initialVin || defaultFilters.vin };
|
||||||
loadLocations(defaultFilters, 1, locationPagination.pageSize);
|
setFilters(nextFilters);
|
||||||
loadRawFrames(defaultFilters, 1, rawPagination.pageSize);
|
loadLocations(nextFilters, 1, locationPagination.pageSize);
|
||||||
|
loadRawFrames(nextFilters, 1, rawPagination.pageSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadLocations(defaultFilters, 1, locationPagination.pageSize);
|
const nextFilters = { ...defaultFilters, vin: initialVin || defaultFilters.vin };
|
||||||
loadRawFrames(defaultFilters, 1, rawPagination.pageSize);
|
setFilters(nextFilters);
|
||||||
}, []);
|
loadLocations(nextFilters, 1, locationPagination.pageSize);
|
||||||
|
loadRawFrames(nextFilters, 1, rawPagination.pageSize);
|
||||||
|
}, [initialVin]);
|
||||||
|
|
||||||
const rawFieldCount = useMemo(() => Object.keys(selectedRaw?.parsedFields ?? {}).length, [selectedRaw]);
|
const rawFieldCount = useMemo(() => Object.keys(selectedRaw?.parsedFields ?? {}).length, [selectedRaw]);
|
||||||
|
|
||||||
@@ -100,7 +103,7 @@ export function History({ onOpenVehicle }: { onOpenVehicle: (vin: string) => voi
|
|||||||
<div className="vp-page">
|
<div className="vp-page">
|
||||||
<PageHeader title="历史查询" description="按车辆查询位置历史和 RAW 帧历史,协议作为来源过滤和诊断维度" />
|
<PageHeader title="历史查询" description="按车辆查询位置历史和 RAW 帧历史,协议作为来源过滤和诊断维度" />
|
||||||
<Card bordered>
|
<Card bordered>
|
||||||
<Form initValues={filters} layout="horizontal" onSubmit={(values) => submit(values)}>
|
<Form key={`${filters.vin ?? ''}-${filters.protocol ?? ''}-${filters.includeFields ? 'fields' : 'light'}`} initValues={filters} layout="horizontal" onSubmit={(values) => submit(values)}>
|
||||||
<Form.Input field="vin" label="VIN" placeholder="输入 VIN" style={{ width: 260 }} />
|
<Form.Input field="vin" label="VIN" 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>
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ function canOpenVehicle(vin?: string) {
|
|||||||
return Boolean(value && value !== 'unknown');
|
return Boolean(value && value !== 'unknown');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Mileage({ onOpenVehicle }: { onOpenVehicle: (vin: string) => void }) {
|
export function Mileage({ initialVin, onOpenVehicle }: { initialVin: string; onOpenVehicle: (vin: string) => void }) {
|
||||||
const [rows, setRows] = useState<DailyMileageRow[]>([]);
|
const [rows, setRows] = useState<DailyMileageRow[]>([]);
|
||||||
const [summary, setSummary] = useState<MileageSummary>(emptySummary);
|
const [summary, setSummary] = useState<MileageSummary>(emptySummary);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [summaryLoading, setSummaryLoading] = useState(true);
|
const [summaryLoading, setSummaryLoading] = useState(true);
|
||||||
const [filters, setFilters] = useState<Record<string, string>>({});
|
const [filters, setFilters] = useState<Record<string, string>>({ vin: initialVin });
|
||||||
const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 20, total: 0 });
|
const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 20, total: 0 });
|
||||||
|
|
||||||
const loadSummary = (values: Record<string, string> = filters) => {
|
const loadSummary = (values: Record<string, string> = filters) => {
|
||||||
@@ -60,15 +60,17 @@ export function Mileage({ onOpenVehicle }: { onOpenVehicle: (vin: string) => voi
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSummary({});
|
const nextFilters = { vin: initialVin };
|
||||||
load({}, 1, pagination.pageSize);
|
setFilters(nextFilters);
|
||||||
}, []);
|
loadSummary(nextFilters);
|
||||||
|
load(nextFilters, 1, pagination.pageSize);
|
||||||
|
}, [initialVin]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vp-page">
|
<div className="vp-page">
|
||||||
<PageHeader title="里程分析" description="每日里程、区间里程和异常差值分析" />
|
<PageHeader title="里程分析" description="每日里程、区间里程和异常差值分析" />
|
||||||
<Card bordered>
|
<Card bordered>
|
||||||
<Form layout="horizontal" onSubmit={(values) => {
|
<Form key={filters.vin ?? ''} initValues={filters} layout="horizontal" onSubmit={(values) => {
|
||||||
const nextFilters = values as Record<string, string>;
|
const nextFilters = values as Record<string, string>;
|
||||||
setFilters(nextFilters);
|
setFilters(nextFilters);
|
||||||
loadSummary(nextFilters);
|
loadSummary(nextFilters);
|
||||||
@@ -85,9 +87,10 @@ export function Mileage({ onOpenVehicle }: { onOpenVehicle: (vin: string) => voi
|
|||||||
<Space>
|
<Space>
|
||||||
<Button htmlType="submit" theme="solid" type="primary">查询</Button>
|
<Button htmlType="submit" theme="solid" type="primary">查询</Button>
|
||||||
<Button onClick={() => {
|
<Button onClick={() => {
|
||||||
setFilters({});
|
const nextFilters = { vin: initialVin };
|
||||||
loadSummary({});
|
setFilters(nextFilters);
|
||||||
load({}, 1, pagination.pageSize);
|
loadSummary(nextFilters);
|
||||||
|
load(nextFilters, 1, pagination.pageSize);
|
||||||
}}>重置</Button>
|
}}>重置</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -11,7 +11,15 @@ type VehicleQuery = {
|
|||||||
protocol?: string;
|
protocol?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function VehicleDetail({ vin }: { vin: string }) {
|
export function VehicleDetail({
|
||||||
|
vin,
|
||||||
|
onOpenHistory,
|
||||||
|
onOpenMileage
|
||||||
|
}: {
|
||||||
|
vin: string;
|
||||||
|
onOpenHistory: (vin: string) => void;
|
||||||
|
onOpenMileage: (vin: string) => void;
|
||||||
|
}) {
|
||||||
const [query, setQuery] = useState<VehicleQuery>({ vin });
|
const [query, setQuery] = useState<VehicleQuery>({ vin });
|
||||||
const [detail, setDetail] = useState<VehicleDetailData | null>(null);
|
const [detail, setDetail] = useState<VehicleDetailData | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -66,6 +74,8 @@ export function VehicleDetail({ vin }: { vin: string }) {
|
|||||||
<Space>
|
<Space>
|
||||||
<Button icon={<IconSearch />} htmlType="submit" theme="solid" type="primary">查询车辆</Button>
|
<Button icon={<IconSearch />} htmlType="submit" theme="solid" type="primary">查询车辆</Button>
|
||||||
<Button icon={<IconRefresh />} onClick={() => load(query)} loading={loading}>刷新</Button>
|
<Button icon={<IconRefresh />} onClick={() => load(query)} loading={loading}>刷新</Button>
|
||||||
|
<Button onClick={() => onOpenHistory(query.vin)}>查看历史</Button>
|
||||||
|
<Button onClick={() => onOpenMileage(query.vin)}>查看里程</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user