import {
IconBarChartVStroked,
IconBox,
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconClock,
IconClose,
IconCopy,
IconFilter,
IconHelpCircle,
IconHome,
IconMore,
IconRefresh,
IconRoute,
IconSearch
} from '@douyinfe/semi-icons';
import {
type CSSProperties,
type FormEvent,
type KeyboardEvent,
type PointerEvent,
useDeferredValue,
useEffect,
useMemo,
useRef,
useState
} from 'react';
type DirectoryView = 'all' | 'online' | 'offline' | 'multi';
type VehicleStatus = 'online' | 'offline';
type ProtocolKind = 'JT/T 808' | 'GB/T 32960' | '宇通 MQTT';
type VehicleRecord = {
plate: string;
vin: string;
brand: string;
terminal: string;
status: VehicleStatus;
updatedAt: string;
protocols: ProtocolKind[];
department: string;
owner: string;
customer: string;
};
const vehicles: VehicleRecord[] = [
{
plate: '粤AG18312',
vin: 'LB9A32A24R0LS1426',
brand: 'G7s',
terminal: '13307795425',
status: 'online',
updatedAt: '07-03 20:12',
protocols: ['JT/T 808', 'GB/T 32960'],
department: '华南运营部',
owner: '陈思远',
customer: '羚牛示范车队'
},
{
plate: '川AHTW01',
vin: 'LNXNEGRR7SR318212',
brand: 'Hyundai',
terminal: '暂无终端手机号',
status: 'online',
updatedAt: '07-03 20:12',
protocols: ['GB/T 32960'],
department: '西南交付部',
owner: '王凌',
customer: '川渝联合车队'
},
{
plate: '豫A88888',
vin: 'LMRKH9AC2R1004087',
brand: '宇通',
terminal: '暂无终端手机号',
status: 'online',
updatedAt: '07-03 20:11',
protocols: ['宇通 MQTT'],
department: '中原运营部',
owner: '刘真',
customer: '宇通测试车队'
},
{
plate: '粤AFF7936',
vin: 'LB9A32A24P0LS1230',
brand: '广安车联',
terminal: '13307795426',
status: 'offline',
updatedAt: '07-03 19:58',
protocols: ['JT/T 808'],
department: '华南运营部',
owner: '陈思远',
customer: '羚牛示范车队'
}
];
const navigation = [
{ key: 'monitor', label: '全局监控', icon: IconHome },
{ key: 'vehicles', label: '车辆查询', icon: IconSearch, active: true },
{ key: 'tracks', label: '轨迹回放', icon: IconRoute },
{ key: 'history', label: '历史数据', icon: IconClock },
{ key: 'statistics', label: '里程查询', icon: IconBarChartVStroked }
];
const governanceNavigation = [
{ key: 'alerts', label: '事件中心', icon: IconClock },
{ key: 'access', label: '接入管理', icon: IconBox },
{ key: 'users', label: '账号管理', icon: IconMore }
];
const viewOptions: Array<{ key: DirectoryView; label: string }> = [
{ key: 'all', label: '全部车辆' },
{ key: 'online', label: '当前在线' },
{ key: 'offline', label: '当前离线' },
{ key: 'multi', label: '多源车辆' }
];
function ProtocolTag({ protocol }: { protocol: ProtocolKind }) {
const tone = protocol === 'JT/T 808' ? 'cyan' : protocol === 'GB/T 32960' ? 'blue' : 'violet';
return {protocol};
}
function StatusTag({ status }: { status: VehicleStatus }) {
return {status === 'online' ? '在线' : '离线'};
}
function Sidebar() {
const navigate = (key: string) => {
if (key !== 'vehicles') window.location.href = `/platform-design-lab.html#${key}`;
};
return (
);
}
function TopBar({ onHelp }: { onHelp: () => void }) {
return (
);
}
type CommandSurfaceProps = {
keyword: string;
view: DirectoryView;
department: string;
owner: string;
customer: string;
status: string;
onKeywordChange: (value: string) => void;
onViewChange: (view: DirectoryView) => void;
onDepartmentChange: (value: string) => void;
onOwnerChange: (value: string) => void;
onCustomerChange: (value: string) => void;
onStatusChange: (value: string) => void;
onSubmit: (event: FormEvent) => void;
};
function CommandSurface({
keyword,
view,
department,
owner,
customer,
status,
onKeywordChange,
onViewChange,
onDepartmentChange,
onOwnerChange,
onCustomerChange,
onStatusChange,
onSubmit
}: CommandSurfaceProps) {
return (
{viewOptions.map((option) => (
))}
);
}
function MobileCommandSurface({
keyword,
onKeywordChange,
onSubmit,
activeFilterCount,
onOpenFilters,
onBatch
}: {
keyword: string;
onKeywordChange: (value: string) => void;
onSubmit: (event: FormEvent) => void;
activeFilterCount: number;
onOpenFilters: () => void;
onBatch: () => void;
}) {
return (
<>
车辆范围
4 辆授权车辆
>
);
}
function MetricRail({ records }: { records: VehicleRecord[] }) {
const online = records.filter((vehicle) => vehicle.status === 'online').length;
const multi = records.filter((vehicle) => vehicle.protocols.length > 1).length;
const metrics = [
{ label: '授权车辆', value: records.length, tone: 'primary' },
{ label: '本页在线', value: online, tone: 'success' },
{ label: '本页多源', value: multi, tone: 'primary' }
];
return (
{metrics.map((metric) => (
{metric.label}
{metric.value}辆
))}
);
}
function DesktopDirectory({
records,
selectedVin,
onSelect
}: {
records: VehicleRecord[];
selectedVin?: string;
onSelect: (vehicle: VehicleRecord) => void;
}) {
const selectByKeyboard = (event: KeyboardEvent, vehicle: VehicleRecord) => {
if (event.key !== 'Enter' && event.key !== ' ') return;
event.preventDefault();
onSelect(vehicle);
};
return (
| 车辆 |
品牌 / 终端 |
实时状态 |
协议来源 |
最后上报时间 |
操作 |
{records.map((vehicle) => (
onSelect(vehicle)}
onKeyDown={(event) => selectByKeyboard(event, vehicle)}
>
|
{vehicle.plate}
{vehicle.vin}
|
{vehicle.brand}
{vehicle.terminal}
|
|
{vehicle.protocols.map((protocol) => )}
|
{vehicle.updatedAt}:12 |
|
))}
);
}
function MobileDirectory({
records,
selectedVin,
onSelect
}: {
records: VehicleRecord[];
selectedVin?: string;
onSelect: (vehicle: VehicleRecord) => void;
}) {
return (
{records.map((vehicle) => (
-
))}
);
}
function VehicleInspector({
vehicle,
mobile,
sheetOffset,
onClose,
onCopyVin,
onDragStart,
onDragMove,
onDragEnd
}: {
vehicle: VehicleRecord;
mobile?: boolean;
sheetOffset?: number;
onClose: () => void;
onCopyVin: () => void;
onDragStart?: (event: PointerEvent) => void;
onDragMove?: (event: PointerEvent) => void;
onDragEnd?: (event: PointerEvent) => void;
}) {
const style = mobile
? ({ '--lab-sheet-offset': `${sheetOffset ?? 0}px` } as CSSProperties)
: undefined;
return (
);
}
function MobileFilterSheet({
view,
status,
department,
onViewChange,
onStatusChange,
onDepartmentChange,
onReset,
onClose
}: {
view: DirectoryView;
status: string;
department: string;
onViewChange: (view: DirectoryView) => void;
onStatusChange: (value: string) => void;
onDepartmentChange: (value: string) => void;
onReset: () => void;
onClose: () => void;
}) {
return (
{
if (event.currentTarget === event.target) onClose();
}}>
);
}
function BottomNavigation() {
const items = [
{ key: 'monitor', label: '全局监控', icon: IconHome },
{ key: 'vehicles', label: '车辆查询', icon: IconSearch, active: true },
{ key: 'tracks', label: '轨迹回放', icon: IconRoute },
{ key: 'statistics', label: '里程查询', icon: IconBarChartVStroked },
{ key: 'alerts', label: '更多', icon: IconMore }
];
return (
);
}
export function VehicleDesignLab() {
const [keyword, setKeyword] = useState('');
const deferredKeyword = useDeferredValue(keyword.trim().toLowerCase());
const [view, setView] = useState('all');
const [department, setDepartment] = useState('');
const [owner, setOwner] = useState('');
const [customer, setCustomer] = useState('');
const [status, setStatus] = useState('');
const [selectedVin, setSelectedVin] = useState(vehicles[0].vin);
const [filterOpen, setFilterOpen] = useState(false);
const [notice, setNotice] = useState('');
const [sheetOffset, setSheetOffset] = useState(0);
const dragStartRef = useRef(undefined);
const filteredVehicles = useMemo(() => vehicles.filter((vehicle) => {
const matchesKeyword = !deferredKeyword || [
vehicle.plate,
vehicle.vin,
vehicle.brand,
vehicle.terminal
].some((value) => value.toLowerCase().includes(deferredKeyword));
const matchesView = view === 'all'
|| view === 'online' && vehicle.status === 'online'
|| view === 'offline' && vehicle.status === 'offline'
|| view === 'multi' && vehicle.protocols.length > 1;
return matchesKeyword
&& matchesView
&& (!department || vehicle.department === department)
&& (!owner || vehicle.owner === owner)
&& (!customer || vehicle.customer === customer)
&& (!status || vehicle.status === status);
}), [customer, deferredKeyword, department, owner, status, view]);
const selectedVehicle = vehicles.find((vehicle) => vehicle.vin === selectedVin);
const activeFilterCount = [view !== 'all', !!department, !!owner, !!customer, !!status].filter(Boolean).length;
useEffect(() => {
if (!notice) return;
const timeout = window.setTimeout(() => setNotice(''), 2400);
return () => window.clearTimeout(timeout);
}, [notice]);
useEffect(() => {
if (!filterOpen) return;
const onKeyDown = (event: globalThis.KeyboardEvent) => {
if (event.key === 'Escape') setFilterOpen(false);
};
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, [filterOpen]);
const submitSearch = (event: FormEvent) => {
event.preventDefault();
setNotice(`已找到 ${filteredVehicles.length} 辆车辆`);
};
const resetFilters = () => {
setView('all');
setDepartment('');
setOwner('');
setCustomer('');
setStatus('');
};
const copySelectedVin = async () => {
if (!selectedVehicle) return;
try {
await navigator.clipboard.writeText(selectedVehicle.vin);
setNotice('VIN 已复制');
} catch {
setNotice('当前浏览器未开放剪贴板权限');
}
};
const onDragStart = (event: PointerEvent) => {
dragStartRef.current = event.clientY;
event.currentTarget.setPointerCapture(event.pointerId);
};
const onDragMove = (event: PointerEvent) => {
if (dragStartRef.current === undefined) return;
setSheetOffset(Math.max(0, event.clientY - dragStartRef.current));
};
const onDragEnd = (event: PointerEvent) => {
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
dragStartRef.current = undefined;
if (sheetOffset > 110) setSelectedVin('');
setSheetOffset(0);
};
return (
setNotice('帮助:搜索车辆后选择一行查看档案')} />
setFilterOpen(true)}
onBatch={() => setNotice('已创建 4 辆车辆的主档同步预览')}
/>
授权车辆目录
按照最新上报时间排序
{filteredVehicles.length} 辆
{filteredVehicles.length > 0 ? (
<>
setSelectedVin(vehicle.vin)} />
setSelectedVin(vehicle.vin)} />
>
) : (
没有匹配车辆
调整关键词或清除筛选后重试。
)}
{selectedVehicle ? (
setSelectedVin('')}
onCopyVin={copySelectedVin}
/>
) : null}
{selectedVehicle ? (
setSelectedVin('')}
onCopyVin={copySelectedVin}
onDragStart={onDragStart}
onDragMove={onDragMove}
onDragEnd={onDragEnd}
/>
) : null}
{filterOpen ? (
setFilterOpen(false)}
/>
) : null}
{notice ? {notice}
: null}
);
}