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'; import { buildAppHash } from '../domain/appRoute'; import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport'; import { summarizeVehicleService, type VehicleServiceVerdict } from '../domain/vehicleService'; 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 }; } const verdict = summarizeVehicleService({ bindingStatus: row.bindingStatus, sourceCount: row.sourceCount, onlineSourceCount: row.onlineSourceCount, missingProtocols: row.missingProtocols, mileageDeltaKm: row.sourceConsistency?.mileageDeltaKm }); return { label: verdict.status, color: verdict.color }; } function rowServiceVerdict(row: VehicleCoverageRow): VehicleServiceVerdict { return summarizeVehicleService({ bindingStatus: row.bindingStatus, sourceCount: row.sourceCount, onlineSourceCount: row.onlineSourceCount, missingProtocols: row.missingProtocols, mileageDeltaKm: row.sourceConsistency?.mileageDeltaKm }); } function sourceEvidenceText(row: VehicleCoverageRow) { return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`; } function primaryRowProtocol(row: VehicleCoverageRow, fallbackProtocol?: string) { return (fallbackProtocol || row.sourceStatus?.find((source) => source.online)?.protocol || row.protocols?.[0] || '').trim(); } function rowWorkflowFilters(row: VehicleCoverageRow, fallbackProtocol?: string) { return { keyword: row.vin, ...(primaryRowProtocol(row, fallbackProtocol) ? { protocol: primaryRowProtocol(row, fallbackProtocol) } : {}) }; } function vehicleArchiveCompleteness(row: VehicleCoverageRow) { const fields = [row.vin, row.plate, row.phone, row.oem]; const completed = fields.filter((item) => Boolean(String(item ?? '').trim())).length; return { completed, total: fields.length, label: `${completed}/${fields.length}` }; } function vehicleArchiveMissingLabels(row: VehicleCoverageRow) { return [ { value: row.vin, label: '缺VIN' }, { value: row.plate, label: '缺车牌' }, { value: row.phone, label: '缺手机号' }, { value: row.oem, label: '缺OEM' } ].filter((item) => !String(item.value ?? '').trim()).map((item) => item.label); } const coverageLabel: Record = { single: '单源', multi: '多源' }; const serviceStatusLabel: Record = { healthy: '服务正常', degraded: '来源不完整', offline: '车辆离线', no_data: '暂无来源证据', identity_required: '身份未绑定' }; const onlineLabel: Record = { online: '在线', offline: '离线' }; const bindingStatusLabel: Record = { bound: '已绑定', unbound: '未绑定' }; const archiveStatusLabel: Record = { complete: '完整', incomplete: '不完整' }; const archiveMissingLabel: Record = { plate: '缺车牌', phone: '缺手机号', oem: '缺OEM' }; 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(','); } const vehicleExportColumns: CsvColumn[] = [ { title: 'VIN', value: (row) => row.vin }, { title: '车牌', value: (row) => row.plate }, { title: '手机号', value: (row) => row.phone }, { title: 'OEM', value: (row) => row.oem }, { title: '来源', value: (row) => row.protocols?.join('|') }, { title: '缺失来源', value: (row) => row.missingProtocols?.join('|') }, { title: '车辆覆盖', value: (row) => sourceEvidenceText(row) }, { title: '服务状态', value: (row) => vehicleServiceStatus(row).label }, { title: '诊断摘要', value: (row) => sourceDiagnosisText(row) }, { title: '档案完整度', value: (row) => vehicleArchiveCompleteness(row).label }, { title: '档案缺项', value: (row) => vehicleArchiveMissingLabels(row).join('|') }, { title: '在线', value: (row) => row.online ? '在线' : '离线' }, { title: '最后在线', value: (row) => row.lastSeen }, { title: '绑定状态', value: (row) => row.bindingStatus }, { title: '来源一致性', value: (row) => row.sourceConsistency?.title ?? '' } ]; function exportFileName(filters: Record) { const keyword = filters.keyword?.trim() || 'all'; const protocol = filters.protocol?.trim() || 'all-source'; return `vehicle-coverage-${keyword}-${protocol}.csv`; } type VehicleActionQueueItem = { label: string; count: number; filters: Record; color: 'orange' | 'red'; priority: 'P0' | 'P1'; detail: string; }; function vehicleFilterSummary(filters: Record) { return [ filters.keyword ? `关键词:${filters.keyword}` : '', filters.protocol ? `数据通道:${filters.protocol}` : '', filters.coverage ? `车辆覆盖:${coverageLabel[filters.coverage] ?? filters.coverage}` : '', filters.missingProtocol ? `缺失来源:${filters.missingProtocol}` : '', filters.serviceStatus ? `服务状态:${serviceStatusLabel[filters.serviceStatus] ?? filters.serviceStatus}` : '', filters.online ? `在线:${onlineLabel[filters.online] ?? filters.online}` : '', filters.bindingStatus ? `绑定:${bindingStatusLabel[filters.bindingStatus] ?? filters.bindingStatus}` : '', filters.archiveStatus ? `档案:${archiveStatusLabel[filters.archiveStatus] ?? filters.archiveStatus}` : '', filters.archiveMissing ? `档案缺项:${archiveMissingLabel[filters.archiveMissing] ?? filters.archiveMissing}` : '' ].filter(Boolean); } function vehicleGovernanceSummaryText({ filters, summary, actionQueue }: { filters: Record; summary: VehicleCoverageSummary | null; actionQueue: VehicleActionQueueItem[]; }) { const archiveMissing = (summary?.archiveMissingFields ?? []) .filter((item) => item.count > 0) .map((item) => `${item.title} ${item.count.toLocaleString()}`) .join('、') || '-'; const missingSources = (summary?.missingSources ?? []) .filter((item) => item.count > 0) .map((item) => `${item.protocol} ${item.count.toLocaleString()}`) .join('、') || '-'; const actions = actionQueue.length > 0 ? actionQueue.map((item) => `[${item.priority}] ${item.label} ${item.count.toLocaleString()}:${item.detail}`).join('\n') : '暂无处置项'; return [ '【车辆治理摘要】', `当前筛选:${vehicleFilterSummary(filters).join(';') || '全部车辆'}`, `车辆总数:${(summary?.totalVehicles ?? 0).toLocaleString()},在线:${(summary?.onlineVehicles ?? 0).toLocaleString()}`, `单源:${(summary?.singleSourceVehicles ?? 0).toLocaleString()},多源:${(summary?.multiSourceVehicles ?? 0).toLocaleString()},暂无来源:${(summary?.noDataVehicles ?? 0).toLocaleString()}`, `待绑定:${(summary?.unboundVehicles ?? 0).toLocaleString()},档案不完整:${(summary?.archiveIncompleteVehicles ?? 0).toLocaleString()}`, `档案缺项:${archiveMissing}`, `缺失来源:${missingSources}`, '处置队列:', actions ].join('\n'); } function appURL(hash: string) { return `${window.location.origin}${window.location.pathname}${hash}`; } function vehicleDispatchListText({ filters, rows, total }: { filters: Record; rows: VehicleCoverageRow[]; total: number; }) { const lines = [ '【车辆处置清单】', `当前筛选:${vehicleFilterSummary(filters).join(';') || '全部车辆'}`, `当前页:${rows.length.toLocaleString()} 辆 / 总计 ${total.toLocaleString()} 辆`, '' ]; rows.forEach((row, index) => { const action = vehicleActionRecommendation(row); const status = vehicleServiceStatus(row); const protocol = primaryRowProtocol(row, filters.protocol); const identity = [ row.plate ? `车牌 ${row.plate}` : '车牌 -', row.vin ? `VIN ${row.vin}` : 'VIN -', row.phone ? `手机号 ${row.phone}` : '手机号 -', row.oem ? `OEM ${row.oem}` : 'OEM -' ].join(' / '); lines.push( `${index + 1}. ${identity}`, ` 状态:${status.label};在线:${row.online ? '在线' : '离线'};来源:${sourceDiagnosisText(row)};最后时间:${row.lastSeen || '-'}`, ` 建议动作:${action.label}`, ` 车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: row.vin, protocol }))}`, ` 实时监控:${appURL(buildAppHash({ page: 'realtime', keyword: row.vin, protocol }))}`, ` 轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: row.vin, protocol }))}`, ` 告警事件:${appURL(buildAppHash({ page: 'alert-events', keyword: row.vin, protocol }))}` ); }); return lines.join('\n'); } function vehicleIdentityMaintenanceText({ filters, rows, total }: { filters: Record; rows: VehicleCoverageRow[]; total: number; }) { const maintenanceRows = rows.filter((row) => row.bindingStatus !== 'bound' || vehicleArchiveMissingLabels(row).length > 0); const lines = [ '【车辆身份维护清单】', `当前筛选:${vehicleFilterSummary(filters).join(';') || '全部车辆'}`, `当前页待维护:${maintenanceRows.length.toLocaleString()} 辆 / 当前筛选 ${total.toLocaleString()} 辆`, '' ]; maintenanceRows.forEach((row, index) => { const missingLabels = vehicleArchiveMissingLabels(row); const lookupKeys = [ row.vin ? `vin=${row.vin}` : '', row.plate ? `plate=${row.plate}` : '', row.phone ? `phone=${row.phone}` : '', row.oem ? `oem=${row.oem}` : '' ].filter(Boolean).join(';') || '暂无可用关联键'; const action = row.bindingStatus !== 'bound' ? '先维护 vehicle_identity_binding 的车牌/手机号到 VIN 映射,再复核实时来源是否归并到车辆服务。' : `补齐档案字段:${missingLabels.join('、')}`; lines.push( `${index + 1}. ${row.plate || '-'} / ${row.vin || '-'} / ${row.phone || '-'} / ${row.oem || '-'}`, ` 状态:${vehicleServiceStatus(row).label};绑定:${row.bindingStatus === 'bound' ? '已绑定' : '未绑定'};档案缺项:${missingLabels.join('、') || '无'}`, ` 关联键:${lookupKeys}`, ` 建议动作:${action}`, ` 车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: row.vin, protocol: primaryRowProtocol(row, filters.protocol) }))}` ); }); return lines.join('\n'); } function vehicleCustomerLookupText({ filters, rows, total, summary }: { filters: Record; rows: VehicleCoverageRow[]; total: number; summary: VehicleCoverageSummary | null; }) { const serviceMode = filters.keyword?.trim() ? '单车服务' : '车辆池'; const scopeHash = buildAppHash({ page: 'vehicles', keyword: filters.keyword, protocol: filters.protocol, filters }); const singleVehicleFilters = filters.keyword?.trim() ? { keyword: filters.keyword.trim(), ...(filters.protocol ? { protocol: filters.protocol } : {}) } : {}; const mapFilters = { ...filters, online: filters.online || 'online' }; const lines = [ '【客户查车路径】', `服务模式:${serviceMode}`, `当前筛选:${vehicleFilterSummary(filters).join(';') || '全部车辆'}`, `当前页:${rows.length.toLocaleString()} 辆 / 当前筛选 ${total.toLocaleString()} 辆`, `在线车辆:${(summary?.onlineVehicles ?? 0).toLocaleString()};待绑定:${(summary?.unboundVehicles ?? 0).toLocaleString()};暂无来源:${(summary?.noDataVehicles ?? 0).toLocaleString()};档案不完整:${(summary?.archiveIncompleteVehicles ?? 0).toLocaleString()}`, '', serviceMode === '单车服务' ? '推荐路径:先看车辆档案和实时监控,再按需要进入轨迹回放、统计查询、历史数据导出。' : '推荐路径:先筛在线/离线/无来源/待绑定车辆,再进入地图态势或导出处置清单。', `车辆服务:${appURL(scopeHash)}`, `实时监控:${appURL(buildAppHash({ page: 'realtime', ...singleVehicleFilters }))}`, `车辆地图:${appURL(buildAppHash({ page: 'map', keyword: filters.keyword, protocol: filters.protocol, filters: mapFilters }))}`, `轨迹回放:${appURL(buildAppHash({ page: 'history', ...singleVehicleFilters, filters: { tab: 'location' } }))}`, `统计查询:${appURL(buildAppHash({ page: 'mileage', ...singleVehicleFilters }))}`, `历史数据:${appURL(buildAppHash({ page: 'history', ...singleVehicleFilters, filters: { tab: 'raw', includeFields: 'true' } }))}` ]; return lines.join('\n'); } function vehicleCustomerServicePackageText({ filters, rows, total, summary }: { filters: Record; rows: VehicleCoverageRow[]; total: number; summary: VehicleCoverageSummary | null; }) { const scope = filters.keyword?.trim() ? `单车 ${filters.keyword.trim()}` : '当前车辆池'; const singleVehicleFilters = filters.keyword?.trim() ? { keyword: filters.keyword.trim(), ...(filters.protocol ? { protocol: filters.protocol } : {}) } : {}; const mapFilters = { ...filters, online: filters.online || 'online' }; const pageVerdicts = currentPageVerdictStats(rows).map((item) => `${item.status} ${item.count.toLocaleString()}`).join(';') || '-'; const pageActions = currentPageActionStats(rows).map((item) => `${item.label} ${item.count.toLocaleString()}`).join(';') || '-'; const missingSources = (summary?.missingSources ?? []) .filter((item) => item.count > 0) .map((item) => `${item.protocol} ${item.count.toLocaleString()}`) .join(';') || '-'; return [ '【客户车辆服务交付包】', `服务范围:${scope}`, `当前筛选:${vehicleFilterSummary(filters).join(';') || '全部车辆'}`, `车辆规模:当前页 ${rows.length.toLocaleString()} 辆 / 当前筛选 ${total.toLocaleString()} 辆`, `服务概览:在线 ${(summary?.onlineVehicles ?? 0).toLocaleString()};单源 ${(summary?.singleSourceVehicles ?? 0).toLocaleString()};多源 ${(summary?.multiSourceVehicles ?? 0).toLocaleString()};暂无来源 ${(summary?.noDataVehicles ?? 0).toLocaleString()}`, `档案状态:待绑定 ${(summary?.unboundVehicles ?? 0).toLocaleString()};档案不完整 ${(summary?.archiveIncompleteVehicles ?? 0).toLocaleString()}`, `当前页判读:${pageVerdicts}`, `建议动作:${pageActions}`, `缺失来源:${missingSources}`, '', '客户入口:', `车辆地图:${appURL(buildAppHash({ page: 'map', keyword: filters.keyword, protocol: filters.protocol, filters: mapFilters }))}`, `实时监控:${appURL(buildAppHash({ page: 'realtime', ...singleVehicleFilters, filters: filters.keyword?.trim() ? undefined : { ...filters, online: filters.online || 'online' } }))}`, `轨迹回放:${appURL(buildAppHash({ page: 'history', ...singleVehicleFilters, filters: { tab: 'location' } }))}`, `统计查询:${appURL(buildAppHash({ page: 'mileage', ...singleVehicleFilters }))}`, `告警通知:${appURL(buildAppHash({ page: 'alert-events', ...singleVehicleFilters, filters }))}`, `历史数据导出:${appURL(buildAppHash({ page: 'history', ...singleVehicleFilters, filters: { tab: 'raw', includeFields: 'true' } }))}`, '', '交付口径:', '1. 客户先看地图与实时状态,判断车辆是否在线和位置是否可信', '2. 单车问题进入轨迹回放和统计查询,确认时间窗内发生了什么', '3. 异常车辆进入告警通知,带上原始记录和历史数据作为证据', '4. 对外交付时优先使用车辆口径,协议来源只作为证据说明' ].join('\n'); } type VehicleActionRecommendation = { label: string; color: 'green' | 'orange' | 'red'; filters: Record | null; }; type PageVerdictStat = { status: string; count: number; color: 'green' | 'orange' | 'red'; risk: string; nextStep: string; }; function vehicleActionRecommendation(row: VehicleCoverageRow): VehicleActionRecommendation { const verdict = summarizeVehicleService({ bindingStatus: row.bindingStatus, sourceCount: row.sourceCount, onlineSourceCount: row.onlineSourceCount, missingProtocols: row.missingProtocols, mileageDeltaKm: row.sourceConsistency?.mileageDeltaKm }); if (row.bindingStatus !== 'bound') { return { label: '维护身份绑定', color: verdict.color, filters: { bindingStatus: 'unbound' } }; } if ((row.missingProtocols ?? []).length > 0) { return { label: `补齐 ${row.missingProtocols.join('、')} 来源`, color: verdict.color, filters: { missingProtocol: row.missingProtocols[0] } }; } if (row.sourceCount <= 0) { return { label: '确认平台转发配置', color: verdict.color, filters: { serviceStatus: 'no_data' } }; } if (row.onlineSourceCount <= 0) { return { label: '排查离线链路', color: verdict.color, filters: { online: 'offline' } }; } return { label: '持续观察', color: verdict.color, filters: null }; } function currentPageVerdictStats(rows: VehicleCoverageRow[]): PageVerdictStat[] { const stats = new Map(); for (const row of rows) { const verdict = rowServiceVerdict(row); const current = stats.get(verdict.status); if (current) { current.count += 1; continue; } stats.set(verdict.status, { status: verdict.status, count: 1, color: verdict.color, risk: verdict.risk, nextStep: verdict.nextStep }); } const order = ['不可服务', '降级可服务', '可服务']; return [...stats.values()].sort((left, right) => order.indexOf(left.status) - order.indexOf(right.status)); } function currentPageActionStats(rows: VehicleCoverageRow[]) { const stats = new Map | null }>(); for (const row of rows) { const action = vehicleActionRecommendation(row); const current = stats.get(action.label); if (current) { current.count += 1; continue; } stats.set(action.label, { label: action.label, count: 1, color: action.color, filters: action.filters }); } return [...stats.values()].sort((left, right) => right.count - left.count); } 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 copyText(value: string, label: string) { try { await navigator.clipboard.writeText(value); Toast.success(`已复制${label}`); } catch { Toast.error(`复制${label}失败`); } } async function copyVehicleShareURL() { await copyText(`${window.location.origin}${window.location.pathname}${window.location.hash}`, '筛选链接'); } export function Vehicles({ onOpenVehicle, onOpenQuality, onOpenMap, onOpenRealtime, onOpenHistory, onOpenMileage, onFiltersChange, initialFilters = {} }: { onOpenVehicle: (vin: string, protocol?: string) => void; onOpenQuality?: (filters: Record) => void; onOpenMap?: (filters: Record) => void; onOpenRealtime?: (filters: Record) => void; onOpenHistory?: (filters: Record) => void; onOpenMileage?: (filters: Record) => 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' } }, { label: '档案不完整', value: (summary?.archiveIncompleteVehicles ?? 0).toLocaleString(), filters: { archiveStatus: 'incomplete' } } ]; for (const field of summary?.archiveMissingFields ?? []) { if (field.count <= 0) continue; items.push({ label: field.title, value: field.count.toLocaleString(), filters: { archiveMissing: field.field } }); } 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 actionQueue = useMemo(() => { const items: VehicleActionQueueItem[] = []; const unboundCount = summary?.unboundVehicles ?? 0; if (unboundCount > 0) { items.push({ label: '维护身份绑定', count: unboundCount, filters: { bindingStatus: 'unbound' }, color: 'orange', priority: 'P0', detail: '已有数据但无法归并到 VIN,先补绑定再看跨来源服务。' }); } const noDataCount = summary?.noDataVehicles ?? 0; if (noDataCount > 0) { items.push({ label: '确认平台转发', count: noDataCount, filters: { serviceStatus: 'no_data' }, color: 'orange', priority: 'P0', detail: '车辆没有任何来源证据,优先确认平台转发、端口和订阅配置。' }); } const archiveIncompleteCount = summary?.archiveIncompleteVehicles ?? 0; if (archiveIncompleteCount > 0) { items.push({ label: '完善车辆档案', count: archiveIncompleteCount, filters: { archiveStatus: 'incomplete' }, color: 'orange', priority: 'P1', detail: '车辆缺少车牌、手机号或 OEM 等基础档案,影响后续运营查询和治理。' }); } for (const field of summary?.archiveMissingFields ?? []) { if (field.count <= 0) continue; items.push({ label: `补齐${field.title}`, count: field.count, filters: { archiveMissing: field.field }, color: 'orange', priority: 'P1', detail: `${field.title}会影响车辆档案检索、绑定确认和运营侧筛选。` }); } for (const source of summary?.missingSources ?? []) { if (source.count <= 0) continue; items.push({ label: `补齐 ${source.protocol} 来源`, count: source.count, filters: { missingProtocol: source.protocol }, color: 'orange', priority: 'P1', detail: `${source.protocol} 来源缺失会降低跨来源定位、里程和实时判断可信度。` }); } return items; }, [summary]); const filterSummary = vehicleFilterSummary(filters); const pageVerdictStats = useMemo(() => currentPageVerdictStats(rows), [rows]); const pageActionStats = useMemo(() => currentPageActionStats(rows), [rows]); const serviceScopeFilters = useMemo(() => { const nextFilters: Record = {}; if (filters.keyword?.trim()) nextFilters.keyword = filters.keyword.trim(); if (filters.protocol?.trim()) nextFilters.protocol = filters.protocol.trim(); return nextFilters; }, [filters.keyword, filters.protocol]); 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?.archiveStatus) params.set('archiveStatus', values.archiveStatus); if (values?.archiveMissing) params.set('archiveMissing', values.archiveMissing); 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); }; const exportVehicles = () => { if (rows.length === 0) { Toast.warning('当前没有可导出的车辆结果'); return; } downloadCsv(exportFileName(filters), buildCsv(vehicleExportColumns, rows)); Toast.success(`已导出 ${rows.length} 条车辆结果`); }; const copyGovernanceSummary = () => { copyText(vehicleGovernanceSummaryText({ filters, summary, actionQueue }), '治理摘要'); }; const copyDispatchList = () => { if (rows.length === 0) { Toast.warning('当前没有可复制的车辆处置清单'); return; } copyText(vehicleDispatchListText({ filters, rows, total: pagination.total }), '车辆处置清单'); }; const copyIdentityMaintenanceList = () => { const maintenanceRows = rows.filter((row) => row.bindingStatus !== 'bound' || vehicleArchiveMissingLabels(row).length > 0); if (maintenanceRows.length === 0) { Toast.warning('当前页没有需要维护身份或档案的车辆'); return; } copyText(vehicleIdentityMaintenanceText({ filters, rows, total: pagination.total }), '车辆身份维护清单'); }; const copyCustomerLookupPath = () => { copyText(vehicleCustomerLookupText({ filters, rows, total: pagination.total, summary }), '客户查车路径'); }; const copyCustomerServicePackage = () => { copyText(vehicleCustomerServicePackageText({ filters, rows, total: pagination.total, summary }), '客户车辆服务交付包'); }; const customerLookupSteps = [ { label: '车辆身份', value: filters.keyword?.trim() || 'VIN / 车牌 / 手机号', detail: '确认车辆归属、车牌、手机号和 OEM,客户问题先落到一辆车。' }, { label: '在线状态', value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`, detail: '判断车辆是否仍在上报,离线车辆进入断链和来源证据排查。' }, { label: '轨迹与位置', value: filters.keyword?.trim() ? '可回放' : '先选车辆', detail: '单车进入轨迹回放,车辆池进入地图态势查看位置分布。' }, { label: '数据交付', value: `${rows.length.toLocaleString()} 当前页`, detail: '导出车辆清单,再进入历史页交付轨迹、RAW 和字段证据。' } ]; const serviceDeskActions = useMemo(() => [ { label: '实时监控', value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`, detail: '查看车辆在线、速度、最后上报和来源状态。', color: 'green' as const, disabled: !onOpenRealtime, onClick: () => onOpenRealtime?.({ ...serviceScopeFilters, online: 'online' }) }, { label: '车辆地图', value: '地图态势', detail: '把当前筛选车辆带入实时地图,优先看位置分布。', color: 'blue' as const, disabled: !onOpenMap, onClick: () => onOpenMap?.({ ...serviceScopeFilters, online: 'online' }) }, { label: '轨迹回放', value: filters.keyword?.trim() ? '单车轨迹' : '选择车辆', detail: filters.keyword?.trim() ? '按当前车辆进入历史轨迹复盘。' : '先选择车辆后进入精确轨迹回放。', color: filters.keyword?.trim() ? 'blue' as const : 'grey' as const, disabled: !onOpenHistory || !filters.keyword?.trim(), onClick: () => onOpenHistory?.({ ...serviceScopeFilters, tab: 'location' }) }, { label: '统计查询', value: filters.keyword?.trim() ? '区间核对' : '车辆口径', detail: filters.keyword?.trim() ? '按当前车辆核对区间里程、日里程和统计结果。' : '输入车辆后查看时间窗统计结果。', color: filters.keyword?.trim() ? 'green' as const : 'grey' as const, disabled: !onOpenMileage || !filters.keyword?.trim(), onClick: () => onOpenMileage?.(serviceScopeFilters) }, { label: '数据导出', value: `${rows.length.toLocaleString()} 当前页`, detail: '导出当前页车辆清单,后续可继续导出轨迹和原始记录。', color: rows.length > 0 ? 'blue' as const : 'grey' as const, disabled: rows.length === 0, onClick: exportVehicles } ], [exportVehicles, filters.keyword, onOpenHistory, onOpenMap, onOpenMileage, onOpenRealtime, rows.length, serviceScopeFilters, summary?.onlineVehicles]); const primaryAction = actionQueue[0]; const vehicleAssetTasks = [ { title: '最近上报', value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`, detail: '按最近仍在上报的车辆进入实时监控和地图态势。', action: '只看在线', color: 'green' as const, onClick: () => applyFilters({ ...filters, online: 'online' }) }, { title: '待关注车辆', value: `${((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)).toLocaleString()} 待处理`, detail: '无来源、未绑定或来源缺失的车辆先进入处置队列。', action: '查看异常', color: ((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)) > 0 ? 'orange' as const : 'green' as const, onClick: () => applyFilters(primaryAction ? { ...filters, ...primaryAction.filters } : { ...filters, serviceStatus: 'degraded' }) }, { title: '身份待维护', value: `${(summary?.unboundVehicles ?? 0).toLocaleString()} 未绑定`, detail: '复制当前页身份维护清单,用于补齐车牌、手机号、VIN 和 OEM。', action: '维护清单', color: (summary?.unboundVehicles ?? 0) > 0 || (summary?.archiveIncompleteVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const, onClick: copyIdentityMaintenanceList }, { title: '报表导出', value: `${rows.length.toLocaleString()} 当前页`, detail: '导出车辆资产清单,后续按车辆进入轨迹、统计和历史证据。', action: '导出车辆', color: rows.length > 0 ? 'blue' as const : 'grey' as const, onClick: exportVehicles } ]; const vehicleAssetOperations = [ { title: '资产健康', value: `${(summary?.totalVehicles ?? pagination.total).toLocaleString()} 车辆`, detail: '当前筛选车辆池,可继续按车牌、手机号、OEM、在线状态和服务状态缩小范围。', action: '查看车辆池', color: 'blue' as const, onClick: () => applyFilters(filters) }, { title: '在线车辆', value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`, detail: '优先进入仍在上报的车辆,查看实时位置、最后时间和来源证据。', action: '打开实时', color: (summary?.onlineVehicles ?? 0) > 0 ? 'green' as const : 'orange' as const, onClick: () => onOpenRealtime?.({ ...serviceScopeFilters, online: 'online' }) }, { title: '待处理', value: `${((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)).toLocaleString()} 待处理`, detail: '无来源和未绑定车辆优先处理,否则客户无法稳定查车、看位置和出报表。', action: '异常优先', color: ((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)) > 0 ? 'orange' as const : 'green' as const, onClick: () => applyFilters(primaryAction ? { ...filters, ...primaryAction.filters } : { ...filters, serviceStatus: 'no_data' }) }, { title: '服务交付', value: `${rows.length.toLocaleString()} 当前页`, detail: '把当前车辆池打包成客户能用的地图、实时、轨迹、统计、告警和导出入口。', action: '复制交付包', color: rows.length > 0 ? 'blue' as const : 'grey' as const, onClick: copyCustomerServicePackage } ]; const vehicleDecisionItems = [ { title: '在线车辆', value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 辆`, detail: '先看当前仍在上报的车辆,直接进入地图和实时状态。', color: 'green' as const, primaryAction: '实时监控', secondaryAction: '车辆地图', onPrimary: () => onOpenRealtime?.({ ...serviceScopeFilters, online: 'online' }), onSecondary: () => onOpenMap?.({ ...serviceScopeFilters, online: 'online' }), disabled: !onOpenRealtime }, { title: '异常车辆', value: `${(summary?.noDataVehicles ?? 0).toLocaleString()} 无来源`, detail: primaryAction ? primaryAction.detail : '当前没有必须立即处理的车辆服务事项。', color: (summary?.noDataVehicles ?? 0) > 0 || primaryAction ? 'orange' as const : 'green' as const, primaryAction: primaryAction?.label ?? '查看结果', secondaryAction: '告警事件', onPrimary: () => primaryAction ? applyFilters({ ...filters, ...primaryAction.filters }) : applyFilters(filters), onSecondary: () => onOpenQuality?.(serviceScopeFilters), disabled: !primaryAction }, { title: '单车服务', value: filters.keyword?.trim() || '输入车辆', detail: filters.keyword?.trim() ? '当前已进入单车范围,可查看车辆详情、轨迹和里程。' : '输入 VIN、车牌或手机号后进入完整车辆服务。', color: filters.keyword?.trim() ? 'blue' as const : 'grey' as const, primaryAction: '车辆详情', secondaryAction: '轨迹回放', onPrimary: () => filters.keyword?.trim() && onOpenVehicle(filters.keyword.trim(), filters.protocol), onSecondary: () => onOpenHistory?.({ ...serviceScopeFilters, tab: 'location' }), disabled: !filters.keyword?.trim() }, { title: '数据交付', value: `${rows.length.toLocaleString()} 当前页`, detail: '导出当前车辆清单,继续进入历史页交付轨迹、RAW 和字段证据。', color: rows.length > 0 ? 'blue' as const : 'grey' as const, primaryAction: '导出清单', secondaryAction: '历史导出', onPrimary: exportVehicles, onSecondary: () => onOpenHistory?.({ ...serviceScopeFilters, tab: 'raw', includeFields: 'true' }), disabled: rows.length === 0 } ]; const servicePackageItems = [ { label: '车辆地图', value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`, detail: '先看当前筛选车辆的位置分布和在线状态。', color: 'blue' as const, action: '打开地图', disabled: !onOpenMap, onClick: () => onOpenMap?.({ ...serviceScopeFilters, online: 'online' }) }, { label: '轨迹回放', value: filters.keyword?.trim() ? '单车时间线' : '先选车辆', detail: filters.keyword?.trim() ? '按当前车辆回放定位轨迹和断点。' : '客户追问某车时,从车辆列表进入单车回放。', color: filters.keyword?.trim() ? 'green' as const : 'grey' as const, action: '回放轨迹', disabled: !onOpenHistory || !filters.keyword?.trim(), onClick: () => onOpenHistory?.({ ...serviceScopeFilters, tab: 'location' }) }, { label: '统计查询', value: filters.keyword?.trim() ? '区间复核' : '单车后可用', detail: '核对时间窗里程、日里程和累计口径,支撑 BI 与客户问询。', color: filters.keyword?.trim() ? 'orange' as const : 'grey' as const, action: '统计查询', disabled: !onOpenMileage || !filters.keyword?.trim(), onClick: () => onOpenMileage?.(serviceScopeFilters) }, { label: '告警通知', value: `${(summary?.noDataVehicles ?? 0).toLocaleString()} 无来源`, detail: '异常车辆进入告警闭环,确认责任方、通知渠道和证据。', color: (summary?.noDataVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const, action: '查看告警', disabled: !onOpenQuality, onClick: () => onOpenQuality?.(serviceScopeFilters) }, { label: '历史导出', value: `${rows.length.toLocaleString()} 当前页`, detail: '对外交付车辆清单、轨迹、RAW 和字段证据。', color: rows.length > 0 ? 'blue' as const : 'grey' as const, action: '导出清单', disabled: rows.length === 0, onClick: exportVehicles } ]; const vehicleCustomerQuestions = [ { question: '客户要找某辆车怎么办?', answer: filters.keyword?.trim() ? filters.keyword.trim() : '先输入 VIN/车牌/手机号', evidence: filters.keyword?.trim() ? '已进入单车范围' : `当前筛选 ${pagination.total.toLocaleString()} 辆`, action: '聚焦找车', color: filters.keyword?.trim() ? 'green' as const : 'blue' as const, disabled: false, onClick: () => document.querySelector('input[name="keyword"]')?.focus() }, { question: '现在有哪些车在线?', answer: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 辆在线`, evidence: filters.online === 'online' ? '已筛在线车辆' : '可切换到在线车辆池', action: '只看在线', color: (summary?.onlineVehicles ?? 0) > 0 ? 'green' as const : 'orange' as const, disabled: false, onClick: () => applyFilters({ ...filters, online: 'online' }) }, { question: '哪些车暂时不能服务?', answer: `${((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)).toLocaleString()} 辆待处理`, evidence: `无来源 ${(summary?.noDataVehicles ?? 0).toLocaleString()} / 待绑定 ${(summary?.unboundVehicles ?? 0).toLocaleString()}`, action: '异常车辆', color: (summary?.noDataVehicles ?? 0) > 0 || (summary?.unboundVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const, disabled: false, onClick: () => primaryAction ? applyFilters({ ...filters, ...primaryAction.filters }) : applyFilters({ ...filters, serviceStatus: 'no_data' }) }, { question: '车辆清单能交付吗?', answer: rows.length > 0 ? `${rows.length.toLocaleString()} 辆当前页` : '暂无可交付车辆', evidence: `筛选总计 ${pagination.total.toLocaleString()} 辆`, action: '复制交付包', color: rows.length > 0 ? 'blue' as const : 'grey' as const, disabled: false, onClick: () => copyCustomerServicePackage() } ]; const customerVehicleServiceItems = [ { title: '车辆池', value: `${(summary?.totalVehicles ?? pagination.total).toLocaleString()} 车辆`, detail: '按 VIN、车牌、手机号、OEM 和服务状态找到客户要看的车。', action: '找车', color: 'blue' as const, disabled: false, onClick: () => document.querySelector('input[name="keyword"]')?.focus() }, { title: '实时地图', value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`, detail: '直接查看当前在线车辆的位置分布、最后上报和在线状态。', action: '打开地图', color: (summary?.onlineVehicles ?? 0) > 0 ? 'green' as const : 'orange' as const, disabled: !onOpenMap, onClick: () => onOpenMap?.({ ...serviceScopeFilters, online: 'online' }) }, { title: '轨迹回放', value: filters.keyword?.trim() ? '单车时间窗' : '先选车辆', detail: filters.keyword?.trim() ? '围绕当前车辆复盘历史位置和断点。' : '先选中一辆车,再进入时间窗轨迹回放。', action: '回放', color: filters.keyword?.trim() ? 'green' as const : 'grey' as const, disabled: !onOpenHistory || !filters.keyword?.trim(), onClick: () => onOpenHistory?.({ ...serviceScopeFilters, tab: 'location' }) }, { title: '统计查询', value: filters.keyword?.trim() ? '区间统计' : '单车后可用', detail: '按车辆和时间窗核对里程、位置和业务统计口径。', action: '查询统计', color: filters.keyword?.trim() ? 'orange' as const : 'grey' as const, disabled: !onOpenMileage || !filters.keyword?.trim(), onClick: () => onOpenMileage?.(serviceScopeFilters) }, { title: '告警通知', value: `${((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)).toLocaleString()} 待处理`, detail: '把不可服务车辆、离线断链和身份缺口进入告警处置。', action: '查看告警', color: ((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)) > 0 ? 'orange' as const : 'green' as const, disabled: !onOpenQuality, onClick: () => onOpenQuality?.(serviceScopeFilters) } ]; const totalVehicleCount = summary?.totalVehicles ?? pagination.total; const onlineRate = totalVehicleCount > 0 ? `${(((summary?.onlineVehicles ?? 0) / totalVehicleCount) * 100).toFixed(1)}%` : '-'; const currentPageServiceableCount = rows.filter((row) => row.bindingStatus === 'bound' && row.sourceCount > 0).length; const pendingVehicleCount = (summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0); const fleetCommandItems = [ { label: '在线率', value: onlineRate, detail: `${(summary?.onlineVehicles ?? 0).toLocaleString()} / ${totalVehicleCount.toLocaleString()} 辆在线,先进入地图看车辆分布。`, action: '车辆地图', color: (summary?.onlineVehicles ?? 0) > 0 ? 'green' as const : 'orange' as const, disabled: !onOpenMap, onClick: () => onOpenMap?.({ ...serviceScopeFilters, online: 'online' }) }, { label: '可服务车辆', value: `${currentPageServiceableCount.toLocaleString()} 辆`, detail: '当前页可归并到车辆服务的车辆,可直接查看实时状态和来源证据。', action: '实时监控', color: currentPageServiceableCount > 0 ? 'blue' as const : 'orange' as const, disabled: !onOpenRealtime, onClick: () => onOpenRealtime?.({ ...serviceScopeFilters, online: 'online' }) }, { label: '待处理车辆', value: `${pendingVehicleCount.toLocaleString()} 辆`, detail: '无来源或未绑定车辆优先进入告警闭环,避免客户查不到车。', action: '告警通知', color: pendingVehicleCount > 0 ? 'orange' as const : 'green' as const, disabled: !onOpenQuality, onClick: () => onOpenQuality?.(serviceScopeFilters) }, { label: '当前交付', value: `${rows.length.toLocaleString()} 辆`, detail: '把当前筛选结果导出为车辆清单,再按时间窗交付轨迹和历史证据。', action: '数据导出', color: rows.length > 0 ? 'blue' as const : 'grey' as const, disabled: rows.length === 0, onClick: exportVehicles }, { label: '轨迹复盘', value: filters.keyword?.trim() ? '单车可回放' : '先选车辆', detail: filters.keyword?.trim() ? '当前已进入单车范围,可直接复盘历史轨迹。' : '先锁定 VIN、车牌或手机号,再进入轨迹回放。', action: '轨迹回放', color: filters.keyword?.trim() ? 'green' as const : 'grey' as const, disabled: !onOpenHistory || !filters.keyword?.trim(), onClick: () => onOpenHistory?.({ ...serviceScopeFilters, tab: 'location' }) } ]; const amapServiceFoundationItems = [ { title: '实时看车', value: `${(summary?.onlineVehicles ?? 0).toLocaleString()} 在线`, detail: '打开车辆地图,先看车辆是否仍在上报、位置是否有效、是否需要调度。', action: '打开地图', color: (summary?.onlineVehicles ?? 0) > 0 ? 'green' as const : 'orange' as const, disabled: !onOpenMap, onClick: () => onOpenMap?.({ ...serviceScopeFilters, online: 'online' }) }, { title: '轨迹复盘', value: filters.keyword?.trim() ? '单车可回放' : '先选车辆', detail: filters.keyword?.trim() ? '按当前车辆进入轨迹回放,解释时间窗内怎么跑。' : '从车辆池先锁定 VIN、车牌或手机号,再回放轨迹。', action: '回放轨迹', color: filters.keyword?.trim() ? 'green' as const : 'grey' as const, disabled: !onOpenHistory || !filters.keyword?.trim(), onClick: () => onOpenHistory?.({ ...serviceScopeFilters, tab: 'location' }) }, { title: '围栏告警', value: `${pendingVehicleCount.toLocaleString()} 待处理`, detail: '围绕车辆位置、断链、身份缺口和后续围栏规则,把异常转成通知闭环。', action: '告警通知', color: pendingVehicleCount > 0 ? 'orange' as const : 'green' as const, disabled: !onOpenQuality, onClick: () => onOpenQuality?.(serviceScopeFilters) }, { title: '数据交付', value: `${rows.length.toLocaleString()} 当前页`, detail: '把车辆清单、地图筛选、轨迹证据和历史数据导出给客户或运营人员。', action: '导出清单', color: rows.length > 0 ? 'blue' as const : 'grey' as const, disabled: rows.length === 0, onClick: exportVehicles } ]; 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: 190, render: (_: unknown, row: VehicleCoverageRow) => { const archive = vehicleArchiveCompleteness(row); const missingLabels = vehicleArchiveMissingLabels(row); return ( {archive.label} {missingLabels.map((label) => ( {label} ))} ); } }, { 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: 180, render: (_: unknown, row: VehicleCoverageRow) => { const action = vehicleActionRecommendation(row); if (action.filters) { return ( ); } return {action.label}; } }, { 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: 300, render: (_: unknown, row: VehicleCoverageRow) => ( ) } ], [filters, onOpenHistory, onOpenQuality, onOpenRealtime, onOpenVehicle] ); return (
客户车队指挥台 {filters.keyword?.trim() ? '单车服务' : '车辆池'} 0 ? 'orange' : 'green'}>{pendingVehicleCount.toLocaleString()} 待处理 从车辆池直接判断运营状态:哪些车在线、哪些车可服务、哪些车要回放、统计、导出或触发告警。 车队指挥台把实时地图、轨迹回放、里程统计、历史导出和告警通知放在同一组入口里;协议来源只保留为证据和筛选条件。
{fleetCommandItems.map((item) => ( ))}
高德地图车辆服务底座 {filters.keyword?.trim() ? '单车地图' : '车辆池地图'} 用地图把车辆监控、轨迹回放、围栏告警、统计查询和历史导出串成客户能理解的一条服务路径。 车辆列表只负责锁定服务对象;地图能力负责解释位置、路线、区域、时间窗和异常通知。
{amapServiceFoundationItems.map((item) => ( ))}
客户车辆服务台 {filters.keyword?.trim() ? '单车服务' : '车辆池服务'} {filters.keyword?.trim() || `${(summary?.totalVehicles ?? pagination.total).toLocaleString()} 辆车`} 三个接入源最终服务同一辆车,客户从这里进入找车、实时、轨迹、统计、导出和告警。
{customerVehicleServiceItems.map((item) => ( ))}
车辆资产运营台 {filters.keyword?.trim() ? '单车范围' : '车辆池'} 0 ? 'orange' : 'green'}> {((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)).toLocaleString()} 待处理 像车队资产系统一样,先判断车辆能不能服务,再进入地图、实时、轨迹、统计、导出和告警。 车辆中心面向客户运营:先看资产健康和在线车辆,再处理异常车辆,最后形成可交付的车辆服务包。
{vehicleAssetOperations.map((item) => ( ))}
客户视角 {filters.keyword?.trim() ? '单车服务' : '车辆池'} {filters.keyword?.trim() || '全部车辆'} 当前筛选覆盖 {(summary?.totalVehicles ?? pagination.total).toLocaleString()} 辆车,在线 {(summary?.onlineVehicles ?? 0).toLocaleString()} 辆。 客户先看车辆能不能服务,再进入地图、轨迹、里程和导出。
{serviceDeskActions.map((item) => ( ))}
{filters.keyword?.trim() ? '单车服务' : '车辆池服务'} {vehicleFilterSummary(filters).join(' / ') || '全部车辆'} {filters.keyword?.trim() ? '围绕一辆车完成监控、回放、统计和导出' : '从车辆池筛出客户要看的车,再进入单车服务'} 客户侧只关心车辆服务结果,接入来源只作为证据参与筛选和可信度判断。
{customerLookupSteps.map((step) => (
{step.label} {step.value} {step.detail}
))}
资产管理 {filters.keyword?.trim() ? '单车' : '车辆池'} 像车队资产系统一样,把车辆清单直接转成最近上报、待关注、身份维护和报表导出。 车辆中心不只是查表,客户要能从这里直接完成看车、筛异常、维护身份和导出车辆报表。
{vehicleAssetTasks.map((item) => ( ))}
客户问题 {filters.keyword?.trim() ? '单车范围' : '车辆池'} 0 || (summary?.unboundVehicles ?? 0) > 0 ? 'orange' : 'green'}> {((summary?.noDataVehicles ?? 0) + (summary?.unboundVehicles ?? 0)).toLocaleString()} 辆待处理 把车辆池问题翻译成找车、在线、异常和交付动作 客户从车辆池开始时,不需要先理解数据源;先定位车辆、确认在线、处理不可服务车辆,再交付车辆清单。
{vehicleCustomerQuestions.map((item) => ( ))}
{vehicleDecisionItems.map((item) => (
{item.title} {item.value}
{item.detail}
))}
客户交付 {filters.keyword?.trim() ? '单车包' : '车辆池包'} {filters.keyword?.trim() || `${(summary?.totalVehicles ?? pagination.total).toLocaleString()} 辆车`} 把车辆地图、实时监控、轨迹回放、统计查询、告警通知和历史导出放在同一份服务包里,客户不用理解 GB32960、808 或 MQTT 的差异。
{servicePackageItems.map((item) => ( ))}
{ const nextFilters = values as Record; applyFilters(nextFilters); }}> GB32960 JT808 YUTONG_MQTT 单源 多源 缺 GB32960 缺 JT808 缺 YUTONG_MQTT 服务正常 来源不完整 车辆离线 暂无来源证据 身份未绑定 在线 离线 已绑定 未绑定 完整 不完整 缺车牌 缺手机号 缺OEM
{filterSummary.length > 0 ? ( {filterSummary.map((item) => ( {item} ))} ) : null} 当前车辆结果 )} style={{ marginTop: 16 }} > {rows.length > 0 ? (
当前页服务判读
{pageVerdictStats.map((item) => (
{item.status} {item.count.toLocaleString()} 辆
{item.risk}
{item.nextStep}
))}
建议下一步
{pageActionStats.map((item) => ( item.filters ? ( ) : ( {item.label} {item.count.toLocaleString()} ) ))}
) : null}
{resultSummary.map((item) => ( ))}
{actionQueue.length > 0 ? (
{actionQueue.map((item) => (
{item.priority} {item.label} {item.count.toLocaleString()}
{item.detail}
))}
) : null} {rows.length === 0 && !loading ? ( ) : ( <>
当前页 {rows.length.toLocaleString()} 辆 总计 {pagination.total.toLocaleString()} 辆
load(filters, page, pagination.pageSize), onPageSizeChange: (pageSize) => load(filters, 1, pageSize) }} /> )} ); }