feat(platform): route vehicle searches to detail
This commit is contained in:
@@ -8,20 +8,31 @@ import { Realtime } from './pages/Realtime';
|
||||
import { VehicleDetail } from './pages/VehicleDetail';
|
||||
import { Vehicles } from './pages/Vehicles';
|
||||
|
||||
const pages: Record<PageKey, JSX.Element> = {
|
||||
dashboard: <Dashboard />,
|
||||
vehicles: <Vehicles />,
|
||||
realtime: <Realtime />,
|
||||
detail: <VehicleDetail />,
|
||||
history: <History />,
|
||||
mileage: <Mileage />,
|
||||
quality: <Quality />
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
const [activePage, setActivePage] = useState<PageKey>('dashboard');
|
||||
const [activeVin, setActiveVin] = useState('LB9A32A24R0LS1426');
|
||||
|
||||
const openVehicle = (vin: string) => {
|
||||
const nextVin = vin.trim();
|
||||
if (!nextVin) {
|
||||
return;
|
||||
}
|
||||
setActiveVin(nextVin);
|
||||
setActivePage('detail');
|
||||
};
|
||||
|
||||
const pages: Record<PageKey, JSX.Element> = {
|
||||
dashboard: <Dashboard />,
|
||||
vehicles: <Vehicles onOpenVehicle={openVehicle} />,
|
||||
realtime: <Realtime />,
|
||||
detail: <VehicleDetail vin={activeVin} />,
|
||||
history: <History />,
|
||||
mileage: <Mileage />,
|
||||
quality: <Quality />
|
||||
};
|
||||
|
||||
return (
|
||||
<AppShell activePage={activePage} onChange={setActivePage}>
|
||||
<AppShell activePage={activePage} onChange={setActivePage} onVehicleSearch={openVehicle}>
|
||||
{pages[activePage]}
|
||||
</AppShell>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Badge, Button, Input, Nav, Space, Tag, Typography } from '@douyinfe/semi-ui';
|
||||
import { Badge, Button, Input, Nav, Space, Tag, Toast, Typography } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconActivity,
|
||||
IconBarChartHStroked,
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
IconSetting
|
||||
} from '@douyinfe/semi-icons';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type PageKey = 'dashboard' | 'vehicles' | 'realtime' | 'detail' | 'history' | 'mileage' | 'quality';
|
||||
|
||||
@@ -26,12 +27,25 @@ const navItems = [
|
||||
export function AppShell({
|
||||
activePage,
|
||||
onChange,
|
||||
onVehicleSearch,
|
||||
children
|
||||
}: {
|
||||
activePage: PageKey;
|
||||
onChange: (page: PageKey) => void;
|
||||
onVehicleSearch: (keyword: string) => void;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
|
||||
const search = () => {
|
||||
const value = keyword.trim();
|
||||
if (!value) {
|
||||
Toast.warning('请输入 VIN / 车牌 / 手机号');
|
||||
return;
|
||||
}
|
||||
onVehicleSearch(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="vp-app">
|
||||
<div className="vp-shell">
|
||||
@@ -50,8 +64,15 @@ export function AppShell({
|
||||
<main className="vp-main">
|
||||
<header className="vp-topbar">
|
||||
<Space spacing={12}>
|
||||
<Input prefix={<IconSearch />} placeholder="搜索 VIN / 车牌 / 手机号" style={{ width: 320 }} />
|
||||
<Button theme="solid" type="primary">查询车辆</Button>
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder="搜索 VIN / 车牌 / 手机号"
|
||||
value={keyword}
|
||||
onChange={setKeyword}
|
||||
onEnterPress={search}
|
||||
style={{ width: 320 }}
|
||||
/>
|
||||
<Button theme="solid" type="primary" onClick={search}>查询车辆</Button>
|
||||
</Space>
|
||||
<Space spacing={12}>
|
||||
<Tag color="blue">生产环境</Tag>
|
||||
|
||||
@@ -11,12 +11,8 @@ type VehicleQuery = {
|
||||
protocol?: string;
|
||||
};
|
||||
|
||||
const defaultQuery: VehicleQuery = {
|
||||
vin: 'LB9A32A24R0LS1426'
|
||||
};
|
||||
|
||||
export function VehicleDetail() {
|
||||
const [query, setQuery] = useState<VehicleQuery>(defaultQuery);
|
||||
export function VehicleDetail({ vin }: { vin: string }) {
|
||||
const [query, setQuery] = useState<VehicleQuery>({ vin });
|
||||
const [detail, setDetail] = useState<VehicleDetailData | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -38,8 +34,10 @@ export function VehicleDetail() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
load(defaultQuery);
|
||||
}, []);
|
||||
const nextQuery = { vin };
|
||||
setQuery(nextQuery);
|
||||
load(nextQuery);
|
||||
}, [vin]);
|
||||
|
||||
const identity = detail?.identity;
|
||||
const latest = detail?.realtime[0];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Card, Form, Select, SideSheet, Space, Table, TextArea, Toast } from '@douyinfe/semi-ui';
|
||||
import { Button, Card, Form, Select, Space, Table, Toast } from '@douyinfe/semi-ui';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import type { VehicleRow } from '../api/types';
|
||||
@@ -6,10 +6,9 @@ import { DataEmpty } from '../components/DataEmpty';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
import { StatusTag } from '../components/StatusTag';
|
||||
|
||||
export function Vehicles() {
|
||||
export function Vehicles({ onOpenVehicle }: { onOpenVehicle: (vin: string) => void }) {
|
||||
const [rows, setRows] = useState<VehicleRow[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selected, setSelected] = useState<VehicleRow | null>(null);
|
||||
|
||||
const load = (values?: Record<string, string>) => {
|
||||
setLoading(true);
|
||||
@@ -35,9 +34,9 @@ export function Vehicles() {
|
||||
{ title: '最后在线', dataIndex: 'lastSeen', width: 170 },
|
||||
{ title: '位置', dataIndex: 'locationText' },
|
||||
{ title: '绑定分', dataIndex: 'bindingScore', width: 90 },
|
||||
{ title: '操作', render: (_: unknown, row: VehicleRow) => <Button onClick={() => setSelected(row)}>详情</Button> }
|
||||
{ title: '操作', render: (_: unknown, row: VehicleRow) => <Button onClick={() => onOpenVehicle(row.vin)}>车辆服务</Button> }
|
||||
],
|
||||
[]
|
||||
[onOpenVehicle]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -64,9 +63,6 @@ export function Vehicles() {
|
||||
<Table loading={loading} rowKey="vin" dataSource={rows} columns={columns} pagination={false} />
|
||||
)}
|
||||
</Card>
|
||||
<SideSheet title="车辆详情" visible={Boolean(selected)} onCancel={() => setSelected(null)} width={520}>
|
||||
<TextArea value={JSON.stringify(selected, null, 2)} autosize readOnly />
|
||||
</SideSheet>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user