import { IconCopy } from '@douyinfe/semi-icons'; import { Button, Card, Form, Select, Space, Table, Tag, Toast } from '@douyinfe/semi-ui'; import { useEffect, useMemo, useState } from 'react'; import { api } from '../api/client'; import type { VehicleCoverageRow, VehicleCoverageSummary } from '../api/types'; import { DataEmpty } from '../components/DataEmpty'; import { PageHeader } from '../components/PageHeader'; import { SourceStatusTags } from '../components/SourceStatusTags'; import { StatusTag } from '../components/StatusTag'; function vehicleServiceStatus(row: VehicleCoverageRow) { if (row.serviceStatus) { return { label: row.serviceStatus.title, color: row.serviceStatus.severity === 'ok' ? 'green' as const : row.serviceStatus.severity === 'error' ? 'red' as const : 'orange' as const }; } if (row.bindingStatus !== 'bound') { return { label: '身份未绑定', color: 'orange' as const }; } if (row.sourceCount <= 0) { return { label: '暂无数据来源', color: 'orange' as const }; } if (row.onlineSourceCount <= 0) { return { label: '车辆离线', color: 'red' as const }; } if (row.onlineSourceCount < row.sourceCount) { return { label: '来源不完整', color: 'orange' as const }; } return { label: '服务正常', color: 'green' as const }; } function sourceEvidenceText(row: VehicleCoverageRow) { return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`; } function sourceDiagnosisText(row: VehicleCoverageRow) { const parts = [sourceEvidenceText(row)]; if ((row.missingProtocols ?? []).length > 0) { parts.push(`缺 ${row.missingProtocols.join('、')}`); } else if (row.sourceCount > 0) { parts.push('来源完整'); } else { parts.push('暂无来源'); } return parts.join(','); } function sourceConsistencyAction(row: VehicleCoverageRow, onFilter: (filters: Record) => void) { const consistency = row.sourceConsistency; if (!consistency) { return 未诊断; } const color = consistency.severity === 'ok' ? 'green' as const : consistency.severity === 'error' ? 'red' as const : 'orange' as const; const label = consistency.title || consistency.status; if ((consistency.missingProtocols ?? []).length > 0) { return ( ); } return {label}; } async function copyVehicleShareURL() { try { await navigator.clipboard.writeText(`${window.location.origin}${window.location.pathname}${window.location.hash}`); Toast.success('已复制筛选链接'); } catch { Toast.error('复制筛选链接失败'); } } export function Vehicles({ onOpenVehicle, onFiltersChange, initialFilters = {} }: { onOpenVehicle: (vin: string, protocol?: string) => void; onFiltersChange?: (filters: Record) => void; initialFilters?: Record; }) { const [rows, setRows] = useState([]); const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(true); const [filters, setFilters] = useState>(initialFilters); const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 20, total: 0 }); const resultSummary = useMemo }>>(() => { const items: Array<{ label: string; value: string; filters: Record }> = [ { label: '过滤车辆', value: (summary?.totalVehicles ?? pagination.total).toLocaleString(), filters: {} }, { label: '在线车辆', value: (summary?.onlineVehicles ?? 0).toLocaleString(), filters: { online: 'online' } }, { label: '单源车辆', value: (summary?.singleSourceVehicles ?? 0).toLocaleString(), filters: { coverage: 'single' } }, { label: '多源车辆', value: (summary?.multiSourceVehicles ?? 0).toLocaleString(), filters: { coverage: 'multi' } }, { label: '暂无来源车辆', value: (summary?.noDataVehicles ?? 0).toLocaleString(), filters: { serviceStatus: 'no_data' } }, { label: '待绑定', value: (summary?.unboundVehicles ?? 0).toLocaleString(), filters: { bindingStatus: 'unbound' } } ]; for (const source of summary?.missingSources ?? []) { if (source.count <= 0) continue; items.push({ label: `缺 ${source.protocol}`, value: source.count.toLocaleString(), filters: { missingProtocol: source.protocol } }); } return items; }, [pagination.total, summary]); const load = (values: Record = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => { setLoading(true); const params = new URLSearchParams({ limit: String(pageSize), offset: String((page - 1) * pageSize) }); if (values?.keyword) params.set('keyword', values.keyword); if (values?.protocol) params.set('protocol', values.protocol); if (values?.coverage) params.set('coverage', values.coverage); if (values?.missingProtocol) params.set('missingProtocol', values.missingProtocol); if (values?.online) params.set('online', values.online); if (values?.bindingStatus) params.set('bindingStatus', values.bindingStatus); if (values?.serviceStatus) params.set('serviceStatus', values.serviceStatus); const summaryParams = new URLSearchParams(params); summaryParams.delete('limit'); summaryParams.delete('offset'); Promise.all([api.vehicleCoverage(params), api.vehicleCoverageSummary(summaryParams)]) .then(([nextPage, nextSummary]) => { setRows(nextPage.items); setSummary(nextSummary); setPagination({ currentPage: page, pageSize, total: nextPage.total }); }) .catch((error: Error) => Toast.error(error.message)) .finally(() => setLoading(false)); }; const applyFilters = (nextFilters: Record) => { setFilters(nextFilters); onFiltersChange?.(nextFilters); load(nextFilters, 1, pagination.pageSize); }; useEffect(() => { setFilters(initialFilters); load(initialFilters, 1, pagination.pageSize); }, [JSON.stringify(initialFilters)]); const columns = useMemo( () => [ { title: 'VIN', dataIndex: 'vin', width: 190 }, { title: '车牌', dataIndex: 'plate', width: 120 }, { title: '手机号', dataIndex: 'phone', width: 130 }, { title: 'OEM', dataIndex: 'oem', width: 120 }, { title: '车辆服务状态', width: 130, render: (_: unknown, row: VehicleCoverageRow) => { const status = vehicleServiceStatus(row); return {status.label}; } }, { title: '来源证据', width: 300, render: (_: unknown, row: VehicleCoverageRow) => ( ) }, { title: '缺失来源', width: 220, render: (_: unknown, row: VehicleCoverageRow) => ( {(row.missingProtocols ?? []).length > 0 ? row.missingProtocols.map((protocol) => ( )) : 完整} ) }, { title: '来源一致性', width: 140, render: (_: unknown, row: VehicleCoverageRow) => sourceConsistencyAction(row, (nextFilters) => applyFilters({ ...filters, ...nextFilters })) }, { title: '诊断摘要', width: 240, render: (_: unknown, row: VehicleCoverageRow) => sourceDiagnosisText(row) }, { title: '证据覆盖', width: 120, render: (_: unknown, row: VehicleCoverageRow) => sourceEvidenceText(row) }, { title: '在线', width: 90, render: (_: unknown, row: VehicleCoverageRow) => }, { title: '最后在线', dataIndex: 'lastSeen', width: 170 }, { title: '绑定', width: 90, render: (_: unknown, row: VehicleCoverageRow) => {row.bindingStatus === 'bound' ? '已绑定' : '未绑定'} }, { title: '操作', width: 110, render: (_: unknown, row: VehicleCoverageRow) => } ], [filters, onOpenVehicle] ); return (
{ const nextFilters = values as Record; applyFilters(nextFilters); }}> GB32960 JT808 YUTONG_MQTT 单源 多源 缺 GB32960 缺 JT808 缺 YUTONG_MQTT 服务正常 来源不完整 车辆离线 暂无数据来源 身份未绑定 在线 离线 已绑定 未绑定
{resultSummary.map((item) => ( ))}
{rows.length === 0 && !loading ? ( ) : ( load(filters, page, pagination.pageSize), onPageSizeChange: (pageSize) => load(filters, 1, pageSize) }} /> )} ); }