feat(platform-web): support shareable vehicle routes

This commit is contained in:
lingniu
2026-07-04 01:13:07 +08:00
parent beb5034b9b
commit 8e5cb5b8c7
4 changed files with 154 additions and 6 deletions

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { Toast } from '@douyinfe/semi-ui';
import { api } from './api/client';
import { buildAppHash, parseAppHash } from './domain/appRoute';
import { AppShell, type PageKey } from './layout/AppShell';
import { Dashboard } from './pages/Dashboard';
import { History } from './pages/History';
@@ -11,9 +12,11 @@ import { VehicleDetail } from './pages/VehicleDetail';
import { Vehicles } from './pages/Vehicles';
export default function App() {
const [activePage, setActivePage] = useState<PageKey>('dashboard');
const [activeVin, setActiveVin] = useState('LB9A32A24R0LS1426');
const [analysisVin, setAnalysisVin] = useState('LB9A32A24R0LS1426');
const initialRoute = parseAppHash(window.location.hash);
const initialVehicleKey = initialRoute.keyword || 'LB9A32A24R0LS1426';
const [activePage, setActivePage] = useState<PageKey>(initialRoute.page ?? 'dashboard');
const [activeVin, setActiveVin] = useState(initialVehicleKey);
const [analysisVin, setAnalysisVin] = useState(initialVehicleKey);
const [linkIssueCount, setLinkIssueCount] = useState<number | null>(null);
const refreshOpsHealth = useCallback((showError = true) => {
@@ -37,6 +40,46 @@ export default function App() {
return () => window.clearInterval(timer);
}, [refreshOpsHealth]);
useEffect(() => {
const applyHashRoute = () => {
const route = parseAppHash(window.location.hash);
if (!route.page) {
return;
}
setActivePage(route.page);
if (route.keyword) {
if (route.page === 'detail') {
setActiveVin(route.keyword);
}
if (route.page === 'history' || route.page === 'mileage') {
setAnalysisVin(route.keyword);
}
}
};
window.addEventListener('hashchange', applyHashRoute);
return () => window.removeEventListener('hashchange', applyHashRoute);
}, []);
const replaceHash = (page: PageKey, keyword?: string) => {
const nextHash = buildAppHash({ page, keyword });
if (window.location.hash !== nextHash) {
window.history.replaceState(null, '', nextHash);
}
};
const navigatePage = (page: PageKey) => {
setActivePage(page);
if (page === 'detail') {
replaceHash(page, activeVin);
return;
}
if (page === 'history' || page === 'mileage') {
replaceHash(page, analysisVin);
return;
}
replaceHash(page);
};
const openVehicle = async (keyword: string) => {
const lookupKey = keyword.trim();
if (!lookupKey) {
@@ -47,6 +90,7 @@ export default function App() {
const nextKey = resolved.resolved && resolved.vin ? resolved.vin : lookupKey;
setActiveVin(nextKey);
setActivePage('detail');
replaceHash('detail', nextKey);
if (!resolved.resolved) {
Toast.warning('未匹配到车辆身份,已打开问题排查视图');
}
@@ -62,6 +106,7 @@ export default function App() {
}
setAnalysisVin(nextVin);
setActivePage('history');
replaceHash('history', nextVin);
};
const openMileageForVehicle = (vin: string) => {
@@ -71,10 +116,11 @@ export default function App() {
}
setAnalysisVin(nextVin);
setActivePage('mileage');
replaceHash('mileage', nextVin);
};
const pages: Record<PageKey, JSX.Element> = {
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={() => setActivePage('quality')} />,
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={() => navigatePage('quality')} />,
vehicles: <Vehicles onOpenVehicle={openVehicle} />,
realtime: <Realtime onOpenVehicle={openVehicle} />,
detail: <VehicleDetail vin={activeVin} onOpenHistory={openHistoryForVehicle} onOpenMileage={openMileageForVehicle} />,
@@ -84,7 +130,7 @@ export default function App() {
};
return (
<AppShell activePage={activePage} linkIssueCount={linkIssueCount} onChange={setActivePage} onVehicleSearch={openVehicle}>
<AppShell activePage={activePage} linkIssueCount={linkIssueCount} onChange={navigatePage} onVehicleSearch={openVehicle}>
{pages[activePage]}
</AppShell>
);