feat(platform-web): preserve protocol in vehicle links
This commit is contained in:
@@ -17,6 +17,7 @@ export default function App() {
|
|||||||
const [activePage, setActivePage] = useState<PageKey>(initialRoute.page ?? 'dashboard');
|
const [activePage, setActivePage] = useState<PageKey>(initialRoute.page ?? 'dashboard');
|
||||||
const [activeVin, setActiveVin] = useState(initialVehicleKey);
|
const [activeVin, setActiveVin] = useState(initialVehicleKey);
|
||||||
const [analysisVin, setAnalysisVin] = useState(initialVehicleKey);
|
const [analysisVin, setAnalysisVin] = useState(initialVehicleKey);
|
||||||
|
const [activeProtocol, setActiveProtocol] = useState(initialRoute.protocol ?? '');
|
||||||
const [linkIssueCount, setLinkIssueCount] = useState<number | null>(null);
|
const [linkIssueCount, setLinkIssueCount] = useState<number | null>(null);
|
||||||
|
|
||||||
const refreshOpsHealth = useCallback((showError = true) => {
|
const refreshOpsHealth = useCallback((showError = true) => {
|
||||||
@@ -55,13 +56,14 @@ export default function App() {
|
|||||||
setAnalysisVin(route.keyword);
|
setAnalysisVin(route.keyword);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setActiveProtocol(route.protocol ?? '');
|
||||||
};
|
};
|
||||||
window.addEventListener('hashchange', applyHashRoute);
|
window.addEventListener('hashchange', applyHashRoute);
|
||||||
return () => window.removeEventListener('hashchange', applyHashRoute);
|
return () => window.removeEventListener('hashchange', applyHashRoute);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const replaceHash = (page: PageKey, keyword?: string) => {
|
const replaceHash = (page: PageKey, keyword?: string, protocol?: string) => {
|
||||||
const nextHash = buildAppHash({ page, keyword });
|
const nextHash = buildAppHash({ page, keyword, protocol });
|
||||||
if (window.location.hash !== nextHash) {
|
if (window.location.hash !== nextHash) {
|
||||||
window.history.replaceState(null, '', nextHash);
|
window.history.replaceState(null, '', nextHash);
|
||||||
}
|
}
|
||||||
@@ -70,11 +72,11 @@ export default function App() {
|
|||||||
const navigatePage = (page: PageKey) => {
|
const navigatePage = (page: PageKey) => {
|
||||||
setActivePage(page);
|
setActivePage(page);
|
||||||
if (page === 'detail') {
|
if (page === 'detail') {
|
||||||
replaceHash(page, activeVin);
|
replaceHash(page, activeVin, activeProtocol);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (page === 'history' || page === 'mileage') {
|
if (page === 'history' || page === 'mileage') {
|
||||||
replaceHash(page, analysisVin);
|
replaceHash(page, analysisVin, activeProtocol);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
replaceHash(page);
|
replaceHash(page);
|
||||||
@@ -89,6 +91,7 @@ export default function App() {
|
|||||||
const resolved = await api.vehicleResolve(new URLSearchParams({ keyword: lookupKey }));
|
const resolved = await api.vehicleResolve(new URLSearchParams({ keyword: lookupKey }));
|
||||||
const nextKey = resolved.resolved && resolved.vin ? resolved.vin : lookupKey;
|
const nextKey = resolved.resolved && resolved.vin ? resolved.vin : lookupKey;
|
||||||
setActiveVin(nextKey);
|
setActiveVin(nextKey);
|
||||||
|
setActiveProtocol('');
|
||||||
setActivePage('detail');
|
setActivePage('detail');
|
||||||
replaceHash('detail', nextKey);
|
replaceHash('detail', nextKey);
|
||||||
if (!resolved.resolved) {
|
if (!resolved.resolved) {
|
||||||
@@ -106,7 +109,7 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
setAnalysisVin(nextVin);
|
setAnalysisVin(nextVin);
|
||||||
setActivePage('history');
|
setActivePage('history');
|
||||||
replaceHash('history', nextVin);
|
replaceHash('history', nextVin, activeProtocol);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openMileageForVehicle = (vin: string) => {
|
const openMileageForVehicle = (vin: string) => {
|
||||||
@@ -116,14 +119,25 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
setAnalysisVin(nextVin);
|
setAnalysisVin(nextVin);
|
||||||
setActivePage('mileage');
|
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<PageKey, JSX.Element> = {
|
const pages: Record<PageKey, JSX.Element> = {
|
||||||
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={() => navigatePage('quality')} />,
|
dashboard: <Dashboard onOpenVehicle={openVehicle} onOpenQuality={() => navigatePage('quality')} />,
|
||||||
vehicles: <Vehicles onOpenVehicle={openVehicle} />,
|
vehicles: <Vehicles onOpenVehicle={openVehicle} />,
|
||||||
realtime: <Realtime onOpenVehicle={openVehicle} />,
|
realtime: <Realtime onOpenVehicle={openVehicle} />,
|
||||||
detail: <VehicleDetail vin={activeVin} onOpenHistory={openHistoryForVehicle} onOpenMileage={openMileageForVehicle} />,
|
detail: <VehicleDetail vin={activeVin} protocol={activeProtocol} onOpenHistory={openHistoryForVehicle} onOpenMileage={openMileageForVehicle} onQueryChange={updateVehicleDetailQuery} />,
|
||||||
history: <History initialVin={analysisVin} onOpenVehicle={openVehicle} />,
|
history: <History initialVin={analysisVin} onOpenVehicle={openVehicle} />,
|
||||||
mileage: <Mileage initialVin={analysisVin} onOpenVehicle={openVehicle} />,
|
mileage: <Mileage initialVin={analysisVin} onOpenVehicle={openVehicle} />,
|
||||||
quality: <Quality onOpenVehicle={openVehicle} onHealthLoaded={(health) => setLinkIssueCount(health.linkHealth.filter((item) => item.status !== 'ok').length)} />
|
quality: <Quality onOpenVehicle={openVehicle} onHealthLoaded={(health) => setLinkIssueCount(health.linkHealth.filter((item) => item.status !== 'ok').length)} />
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { buildAppHash, parseAppHash } from './appRoute';
|
|||||||
|
|
||||||
describe('parseAppHash', () => {
|
describe('parseAppHash', () => {
|
||||||
test('parses detail page keyword from hash query', () => {
|
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',
|
page: 'detail',
|
||||||
keyword: '粤AG18312'
|
keyword: '粤AG18312',
|
||||||
|
protocol: 'JT808'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ describe('parseAppHash', () => {
|
|||||||
|
|
||||||
describe('buildAppHash', () => {
|
describe('buildAppHash', () => {
|
||||||
test('builds shareable vehicle page hash with encoded keyword', () => {
|
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', () => {
|
test('builds page-only hash when keyword is empty', () => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const pageKeys = new Set<PageKey>(['dashboard', 'vehicles', 'realtime', 'detail'
|
|||||||
export type AppRoute = {
|
export type AppRoute = {
|
||||||
page?: PageKey;
|
page?: PageKey;
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
|
protocol?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parseAppHash(hash: string): AppRoute {
|
export function parseAppHash(hash: string): AppRoute {
|
||||||
@@ -18,7 +19,8 @@ export function parseAppHash(hash: string): AppRoute {
|
|||||||
}
|
}
|
||||||
const params = new URLSearchParams(queryPart);
|
const params = new URLSearchParams(queryPart);
|
||||||
const keyword = params.get('keyword')?.trim() || undefined;
|
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 {
|
export function buildAppHash(route: AppRoute): string {
|
||||||
@@ -28,6 +30,10 @@ export function buildAppHash(route: AppRoute): string {
|
|||||||
if (keyword) {
|
if (keyword) {
|
||||||
params.set('keyword', keyword);
|
params.set('keyword', keyword);
|
||||||
}
|
}
|
||||||
|
const protocol = route.protocol?.trim();
|
||||||
|
if (protocol) {
|
||||||
|
params.set('protocol', protocol);
|
||||||
|
}
|
||||||
const query = params.toString();
|
const query = params.toString();
|
||||||
return query ? `#/${page}?${query}` : `#/${page}`;
|
return query ? `#/${page}?${query}` : `#/${page}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,18 @@ const sourceCapabilities: Array<{ key: keyof Pick<VehicleSourceStatus, 'hasRealt
|
|||||||
|
|
||||||
export function VehicleDetail({
|
export function VehicleDetail({
|
||||||
vin,
|
vin,
|
||||||
|
protocol,
|
||||||
onOpenHistory,
|
onOpenHistory,
|
||||||
onOpenMileage
|
onOpenMileage,
|
||||||
|
onQueryChange
|
||||||
}: {
|
}: {
|
||||||
vin: string;
|
vin: string;
|
||||||
|
protocol?: string;
|
||||||
onOpenHistory: (vin: string) => void;
|
onOpenHistory: (vin: string) => void;
|
||||||
onOpenMileage: (vin: string) => void;
|
onOpenMileage: (vin: string) => void;
|
||||||
|
onQueryChange?: (keyword: string, protocol?: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const [query, setQuery] = useState<VehicleQuery>({ keyword: vin });
|
const [query, setQuery] = useState<VehicleQuery>({ keyword: vin, protocol });
|
||||||
const [detail, setDetail] = useState<VehicleDetailData | null>(null);
|
const [detail, setDetail] = useState<VehicleDetailData | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const requestSeq = useRef(0);
|
const requestSeq = useRef(0);
|
||||||
@@ -65,10 +69,10 @@ export function VehicleDetail({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const nextQuery = { keyword: vin };
|
const nextQuery = { keyword: vin, protocol };
|
||||||
setQuery(nextQuery);
|
setQuery(nextQuery);
|
||||||
load(nextQuery);
|
load(nextQuery);
|
||||||
}, [vin]);
|
}, [vin, protocol]);
|
||||||
|
|
||||||
const identity = detail?.identity;
|
const identity = detail?.identity;
|
||||||
const summary = detail?.realtimeSummary;
|
const summary = detail?.realtimeSummary;
|
||||||
@@ -107,6 +111,7 @@ export function VehicleDetail({
|
|||||||
<Form key={formKey} initValues={query} layout="horizontal" onSubmit={(values) => {
|
<Form key={formKey} initValues={query} layout="horizontal" onSubmit={(values) => {
|
||||||
const nextQuery = { keyword: String(values.keyword ?? ''), protocol: String(values.protocol ?? '') };
|
const nextQuery = { keyword: String(values.keyword ?? ''), protocol: String(values.protocol ?? '') };
|
||||||
setQuery(nextQuery);
|
setQuery(nextQuery);
|
||||||
|
onQueryChange?.(nextQuery.keyword, nextQuery.protocol);
|
||||||
load(nextQuery);
|
load(nextQuery);
|
||||||
}}>
|
}}>
|
||||||
<Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号" style={{ width: 280 }} />
|
<Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号" style={{ width: 280 }} />
|
||||||
|
|||||||
Reference in New Issue
Block a user