diff --git a/vehicle-data-platform/apps/web/src/App.tsx b/vehicle-data-platform/apps/web/src/App.tsx index 4478e07a..b5372ca0 100644 --- a/vehicle-data-platform/apps/web/src/App.tsx +++ b/vehicle-data-platform/apps/web/src/App.tsx @@ -17,6 +17,7 @@ export default function App() { const [activePage, setActivePage] = useState(initialRoute.page ?? 'dashboard'); const [activeVin, setActiveVin] = useState(initialVehicleKey); const [analysisVin, setAnalysisVin] = useState(initialVehicleKey); + const [activeProtocol, setActiveProtocol] = useState(initialRoute.protocol ?? ''); const [linkIssueCount, setLinkIssueCount] = useState(null); const refreshOpsHealth = useCallback((showError = true) => { @@ -55,13 +56,14 @@ export default function App() { setAnalysisVin(route.keyword); } } + setActiveProtocol(route.protocol ?? ''); }; window.addEventListener('hashchange', applyHashRoute); return () => window.removeEventListener('hashchange', applyHashRoute); }, []); - const replaceHash = (page: PageKey, keyword?: string) => { - const nextHash = buildAppHash({ page, keyword }); + const replaceHash = (page: PageKey, keyword?: string, protocol?: string) => { + const nextHash = buildAppHash({ page, keyword, protocol }); if (window.location.hash !== nextHash) { window.history.replaceState(null, '', nextHash); } @@ -70,11 +72,11 @@ export default function App() { const navigatePage = (page: PageKey) => { setActivePage(page); if (page === 'detail') { - replaceHash(page, activeVin); + replaceHash(page, activeVin, activeProtocol); return; } if (page === 'history' || page === 'mileage') { - replaceHash(page, analysisVin); + replaceHash(page, analysisVin, activeProtocol); return; } replaceHash(page); @@ -89,6 +91,7 @@ export default function App() { const resolved = await api.vehicleResolve(new URLSearchParams({ keyword: lookupKey })); const nextKey = resolved.resolved && resolved.vin ? resolved.vin : lookupKey; setActiveVin(nextKey); + setActiveProtocol(''); setActivePage('detail'); replaceHash('detail', nextKey); if (!resolved.resolved) { @@ -106,7 +109,7 @@ export default function App() { } setAnalysisVin(nextVin); setActivePage('history'); - replaceHash('history', nextVin); + replaceHash('history', nextVin, activeProtocol); }; const openMileageForVehicle = (vin: string) => { @@ -116,14 +119,25 @@ export default function App() { } setAnalysisVin(nextVin); setActivePage('mileage'); - replaceHash('mileage', nextVin); + replaceHash('mileage', nextVin, activeProtocol); + }; + + const updateVehicleDetailQuery = (keyword: string, protocol?: string) => { + const nextKeyword = keyword.trim(); + const nextProtocol = protocol?.trim() ?? ''; + if (!nextKeyword) { + return; + } + setActiveVin(nextKeyword); + setActiveProtocol(nextProtocol); + replaceHash('detail', nextKeyword, nextProtocol); }; const pages: Record = { dashboard: navigatePage('quality')} />, vehicles: , realtime: , - detail: , + detail: , history: , mileage: , quality: setLinkIssueCount(health.linkHealth.filter((item) => item.status !== 'ok').length)} /> diff --git a/vehicle-data-platform/apps/web/src/domain/appRoute.test.ts b/vehicle-data-platform/apps/web/src/domain/appRoute.test.ts index 967cc7bf..5ce41ec4 100644 --- a/vehicle-data-platform/apps/web/src/domain/appRoute.test.ts +++ b/vehicle-data-platform/apps/web/src/domain/appRoute.test.ts @@ -3,9 +3,10 @@ import { buildAppHash, parseAppHash } from './appRoute'; describe('parseAppHash', () => { test('parses detail page keyword from hash query', () => { - expect(parseAppHash('#/detail?keyword=%E7%B2%A4AG18312')).toEqual({ + expect(parseAppHash('#/detail?keyword=%E7%B2%A4AG18312&protocol=JT808')).toEqual({ page: 'detail', - keyword: '粤AG18312' + keyword: '粤AG18312', + protocol: 'JT808' }); }); @@ -16,7 +17,7 @@ describe('parseAppHash', () => { describe('buildAppHash', () => { test('builds shareable vehicle page hash with encoded keyword', () => { - expect(buildAppHash({ page: 'history', keyword: '粤AG18312' })).toBe('#/history?keyword=%E7%B2%A4AG18312'); + expect(buildAppHash({ page: 'history', keyword: '粤AG18312', protocol: 'GB32960' })).toBe('#/history?keyword=%E7%B2%A4AG18312&protocol=GB32960'); }); test('builds page-only hash when keyword is empty', () => { diff --git a/vehicle-data-platform/apps/web/src/domain/appRoute.ts b/vehicle-data-platform/apps/web/src/domain/appRoute.ts index a7bb4bf6..ab2060a5 100644 --- a/vehicle-data-platform/apps/web/src/domain/appRoute.ts +++ b/vehicle-data-platform/apps/web/src/domain/appRoute.ts @@ -5,6 +5,7 @@ const pageKeys = new Set(['dashboard', 'vehicles', 'realtime', 'detail' export type AppRoute = { page?: PageKey; keyword?: string; + protocol?: string; }; export function parseAppHash(hash: string): AppRoute { @@ -18,7 +19,8 @@ export function parseAppHash(hash: string): AppRoute { } const params = new URLSearchParams(queryPart); const keyword = params.get('keyword')?.trim() || undefined; - return { page: pagePart as PageKey, keyword }; + const protocol = params.get('protocol')?.trim() || undefined; + return { page: pagePart as PageKey, keyword, protocol }; } export function buildAppHash(route: AppRoute): string { @@ -28,6 +30,10 @@ export function buildAppHash(route: AppRoute): string { if (keyword) { params.set('keyword', keyword); } + const protocol = route.protocol?.trim(); + if (protocol) { + params.set('protocol', protocol); + } const query = params.toString(); return query ? `#/${page}?${query}` : `#/${page}`; } diff --git a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx index 28f4efff..def22c78 100644 --- a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx +++ b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx @@ -21,14 +21,18 @@ const sourceCapabilities: Array<{ key: keyof Pick void; onOpenMileage: (vin: string) => void; + onQueryChange?: (keyword: string, protocol?: string) => void; }) { - const [query, setQuery] = useState({ keyword: vin }); + const [query, setQuery] = useState({ keyword: vin, protocol }); const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(false); const requestSeq = useRef(0); @@ -65,10 +69,10 @@ export function VehicleDetail({ }; useEffect(() => { - const nextQuery = { keyword: vin }; + const nextQuery = { keyword: vin, protocol }; setQuery(nextQuery); load(nextQuery); - }, [vin]); + }, [vin, protocol]); const identity = detail?.identity; const summary = detail?.realtimeSummary; @@ -107,6 +111,7 @@ export function VehicleDetail({
{ const nextQuery = { keyword: String(values.keyword ?? ''), protocol: String(values.protocol ?? '') }; setQuery(nextQuery); + onQueryChange?.(nextQuery.keyword, nextQuery.protocol); load(nextQuery); }}>