import { IconRefresh, IconSearch, IconTickCircle } from '@douyinfe/semi-icons'; import { Button, Card, CardGroup, Checkbox, Descriptions, Empty, Input, Progress, SideSheet, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { FormEvent, useDeferredValue, useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { api } from '../../api/client'; import type { VehicleCoverageRow, VehicleLocationSourceEvidence, VehicleSourceDiagnostic } from '../../api/types'; import { InlineError } from '../shared/AsyncState'; import { PlatformTime } from '../shared/PlatformTime'; import { SegmentedTabs } from '../shared/SegmentedTabs'; import { VehicleCandidateList } from '../shared/VehicleCandidateList'; import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; import { LIVE_QUERY_POLICY, QUERY_MEMORY } from '../queryPolicy'; import { useMobileLayout } from '../hooks/useMobileLayout'; import { useSideSheetA11y } from '../hooks/useSideSheetA11y'; import ReconciliationCenter from './ReconciliationCenter'; type OperationsWorkspace = 'reconciliation' | 'diagnostic' | 'health'; const operationsWorkspaces = new Set(['reconciliation', 'diagnostic', 'health']); function statusLabel(status: string) { return { ok: '正常', warning: '关注', error: '异常' }[status] ?? status; } const opsLinkLabels: Record = { 'MySQL realtime': 'MySQL 实时连接', vehicle_realtime_snapshot: '车辆实时快照', vehicle_realtime_location: '车辆实时位置', 'TDengine raw_frames': 'TDengine 原始帧', 'Capacity check': '容量与消费积压', 'Redis online keys': 'Redis 在线状态', 'Alert Kafka stream': '告警消费链路' }; function opsLinkLabel(name: string) { return opsLinkLabels[name] ?? name; } function HealthTag({ status, children }: { status: string; children?: string }) { const color = status === 'ok' ? 'green' : status === 'warning' ? 'orange' : status === 'error' ? 'red' : 'grey'; return {children ?? statusLabel(status)}; } function number(value?: number, digits = 1) { return value == null || !Number.isFinite(value) ? '—' : value.toLocaleString('zh-CN', { maximumFractionDigits: digits }); } function OpsMetric({ label, value, detail, tone = 'neutral' }: { label: string; value: string; detail: string; tone?: 'neutral' | 'success' | 'warning' | 'danger'; }) { return
{label} {value} {detail}
; } function sourceOnlineRate(online: number, total: number, onlineRate?: number) { if (onlineRate != null && Number.isFinite(onlineRate)) return Math.max(0, Math.min(100, onlineRate)); return total > 0 ? Math.max(0, Math.min(100, (online / total) * 100)) : 0; } function SourcePolicyEditor({ vin, source, diagnostic, editable, onSaved }: { vin: string; source: VehicleLocationSourceEvidence; diagnostic: VehicleSourceDiagnostic; editable: boolean; onSaved: (next: VehicleSourceDiagnostic) => void; }) { const [enabled, setEnabled] = useState(source.enabled); const [priority, setPriority] = useState(source.priority); const [providerName, setProviderName] = useState(source.providerOverride || ''); const [providerEvidence, setProviderEvidence] = useState(''); const [remark, setRemark] = useState(source.policyRemark || ''); useEffect(() => { setEnabled(source.enabled); setPriority(source.priority); setProviderName(source.providerOverride || ''); setProviderEvidence(''); setRemark(source.policyRemark || ''); }, [source.enabled, source.priority, source.providerOverride, source.policyRemark, source.sourceRef]); const save = useMutation({ mutationFn: () => api.updateVehicleSourcePolicy(vin, { version: diagnostic.policy.version, sourceRef: source.sourceRef ?? '', providerName, providerEvidence, enabled, priority, remark }), onSuccess: onSaved }); const providerChanged = providerName.trim() !== (source.providerOverride || ''); const policyEditable = source.sourceKind !== 'CANONICAL'; const policyChanged = enabled !== source.enabled || priority !== source.priority || remark.trim() !== (source.policyRemark || ''); const changed = (policyEditable && policyChanged) || providerChanged; return
setEnabled(Boolean(event.target.checked))} aria-label="启用">启用 setPriority(Number(value))} /> {save.isError ? {save.error instanceof Error ? save.error.message : '保存失败'} : null}
; } function SourceIdentity({ source }: { source: VehicleLocationSourceEvidence }) { return
{source.sourceLabel}{source.recommended ? 推荐 : null}{source.terminalLabel || source.sourceKind || '未维护终端'}
; } function SourceProtocol({ source }: { source: VehicleLocationSourceEvidence }) { return
{source.protocol}{source.selectedWithinProtocol ? '协议内已选' : '协议内候选'}
; } function SourceHealth({ source }: { source: VehicleLocationSourceEvidence }) { return
{source.online ? '在线' : '离线'}{source.qualityStatus || '未知'}{source.qualityReason ? ` · ${source.qualityReason}` : ''}
; } function SourceTiming({ source }: { source: VehicleLocationSourceEvidence }) { return
最近
; } function SourceInterval({ source }: { source: VehicleLocationSourceEvidence }) { return
{source.reportIntervalSec == null ? '—' : `${source.reportIntervalSec}s`}{source.reportSampleCount ? `${source.reportSampleCount.toLocaleString('zh-CN')} 个里程样本` : '暂无累计样本'}
; } function SourcePosition({ source }: { source: VehicleLocationSourceEvidence }) { return
{source.longitude == null || source.latitude == null ? '—' : `${source.longitude.toFixed(6)}, ${source.latitude.toFixed(6)}`}{number(source.speedKmh)} km/h · {number(source.totalMileageKm)} km
; } function SourceDecision({ source }: { source: VehicleLocationSourceEvidence }) { return
{source.recommended ? '当前推荐' : source.selectedWithinProtocol ? '协议首选' : '备用来源'}{source.selectionReason || '等待选举说明'}
; } function sourceRowKey(source?: VehicleLocationSourceEvidence) { return source?.sourceRef || `${source?.protocol ?? ''}-${source?.sourceLabel ?? ''}-${source?.terminalLabel ?? ''}`; } function SourcePolicySummary({ source, onEdit }: { source: VehicleLocationSourceEvidence; onEdit: () => void; }) { return
{source.enabled ? '已启用' : '已禁用'}优先级 {source.priority} {source.providerOverride || '提供方未维护'}
; } function SourcePolicySheet({ vin, source, diagnostic, editable, onClose, onSaved }: { vin: string; source?: VehicleLocationSourceEvidence; diagnostic: VehicleSourceDiagnostic; editable: boolean; onClose: () => void; onSaved: (next: VehicleSourceDiagnostic) => void; }) { useSideSheetA11y(Boolean(source), '.v2-source-policy-sidesheet', 'v2-source-policy-sheet', '车辆来源策略', '关闭车辆来源策略'); return {source.sourceLabel}{source.terminalLabel || source.protocol} · {source.sourceKind === 'CANONICAL' ? '仅维护提供方' : '来源策略'} : '来源策略'} onCancel={onClose} footer={null} > {source ?
{ onSaved(next); onClose(); }} />
: null}
; } function SourceDiagnosticTable({ vin, sources, diagnostic, editable, onSaved }: { vin: string; sources: VehicleLocationSourceEvidence[]; diagnostic: VehicleSourceDiagnostic; editable: boolean; onSaved: (next: VehicleSourceDiagnostic) => void; }) { const [editingSource, setEditingSource] = useState(); const columns = [ { title: '来源 / 终端', dataIndex: 'sourceLabel', width: 180, render: (_: unknown, source: VehicleLocationSourceEvidence) => }, { title: '协议', dataIndex: 'protocol', width: 120, render: (_: unknown, source: VehicleLocationSourceEvidence) => }, { title: '在线 / 质量', dataIndex: 'online', width: 190, render: (_: unknown, source: VehicleLocationSourceEvidence) => }, { title: '首次 / 最近上报', dataIndex: 'firstSeenAt', width: 220, render: (_: unknown, source: VehicleLocationSourceEvidence) => }, { title: '上报周期', dataIndex: 'reportIntervalSec', width: 145, render: (_: unknown, source: VehicleLocationSourceEvidence) => }, { title: '位置 / 里程', dataIndex: 'longitude', width: 230, render: (_: unknown, source: VehicleLocationSourceEvidence) => }, { title: '选举结论', dataIndex: 'recommended', width: 220, render: (_: unknown, source: VehicleLocationSourceEvidence) => }, { title: '运维策略', dataIndex: 'policy', width: 200, render: (_: unknown, source: VehicleLocationSourceEvidence) => setEditingSource(source)} /> } ]; return <>
source ? ({ className: source.recommended ? 'is-recommended' : '' }) : ({})} /> setEditingSource(undefined)} onSaved={onSaved} /> ; } function SourceDiagnosticCards({ vin, sources, diagnostic, editable, onSaved }: { vin: string; sources: VehicleLocationSourceEvidence[]; diagnostic: VehicleSourceDiagnostic; editable: boolean; onSaved: (next: VehicleSourceDiagnostic) => void; }) { const [editingSource, setEditingSource] = useState(); return <>
{sources.map((source) =>
}, { key: '首次 / 最近上报', value: }, { key: '上报周期', value: }, { key: '位置 / 里程', value: }, { key: '选举结论', value: } ]} />
运维策略 setEditingSource(source)} />
)}
setEditingSource(undefined)} onSaved={onSaved} /> ; } function SourceDiagnosticWorkspace() { const queryClient = useQueryClient(); const mobileLayout = useMobileLayout(); const [keyword, setKeyword] = useState(''); const deferredKeyword = useDeferredValue(keyword.trim()); const [selected, setSelected] = useState(); const [candidateOffset, setCandidateOffset] = useState(0); const [filtersCollapsed, setFiltersCollapsed] = useState(false); useEffect(() => setCandidateOffset(0), [deferredKeyword]); const candidates = useQuery({ queryKey: ['ops-source-candidates', deferredKeyword, candidateOffset], queryFn: ({ signal }) => api.vehicleCoverage(new URLSearchParams({ keyword: deferredKeyword, bindingStatus: 'bound', limit: '20', offset: String(candidateOffset) }), signal), enabled: deferredKeyword.length > 0, staleTime: 15_000, gcTime: QUERY_MEMORY.optionGcTime }); const session = useQuery({ queryKey: ['ops-session'], queryFn: ({ signal }) => api.session(signal), staleTime: 60_000 }); const diagnostic = useQuery({ queryKey: ['ops-source-diagnostic', selected?.vin], queryFn: ({ signal }) => api.vehicleSourceDiagnostic(selected!.vin, signal), enabled: Boolean(selected?.vin), staleTime: 5_000, gcTime: QUERY_MEMORY.highVolumeGcTime }); const choose = (vehicle: VehicleCoverageRow) => { setSelected(vehicle); setKeyword(vehicle.plate || vehicle.vin); if (mobileLayout) setFiltersCollapsed(true); }; const submit = (event: FormEvent) => { event.preventDefault(); const first = candidates.data?.items[0]; if (first) choose(first); }; const data = diagnostic.data; const editable = session.data?.role === 'admin'; const selectedLabel = selected ? selected.plate || selected.vin : ''; const filterStatus = selectedLabel ? `已选 ${selectedLabel}` : deferredKeyword ? candidates.isFetching ? '正在查找' : `${candidates.data?.total ?? 0} 辆候选` : '等待选择车辆'; const onSourceSaved = (next: VehicleSourceDiagnostic) => { queryClient.setQueryData(['ops-source-diagnostic', next.evidence.vin], next); void Promise.all([ queryClient.invalidateQueries({ queryKey: ['access-summary'] }), queryClient.invalidateQueries({ queryKey: ['access-vehicles'] }), queryClient.invalidateQueries({ queryKey: ['ops-source-readiness-v2'] }) ]); }; return <> setFiltersCollapsed((value) => !value)} >
} value={keyword} onChange={(value) => { setKeyword(value); setSelected(undefined); }} placeholder="输入车牌或 VIN,支持模糊搜索" /> {deferredKeyword && !selected ? 20 ? <>第 {Math.floor(candidateOffset / 20) + 1} / {Math.ceil((candidates.data?.total ?? 0) / 20)} 页
: undefined} /> : null}
{data ? `${data.evidence.locationSources.length} 个来源` : selected ? '正在读取证据' : '等待选择车辆'}} actions={selected ? : null} /> {diagnostic.isError ? diagnostic.refetch()} /> : null} {!selected ? : null} {selected && diagnostic.isPending ?
正在读取来源证据只查询当前车辆,不会扫描整车队。
: null} {data ? <>
车辆{data.evidence.plate || selected?.plate || '未绑定车牌'}{data.evidence.vin} 当前推荐{data.evidence.recommendedLocationLabel || '暂无推荐'}{data.evidence.recommendedLocationProtocol || '—'} 来源状态{data.evidence.locationSources.filter((item) => item.online).length} / {data.evidence.locationSources.length} 在线{data.evidence.locationConflict ? `位置冲突 ${number(data.evidence.conflictDistanceM)}m` : '未发现实时位置冲突'} 策略版本v{data.policy.version}{data.policy.updatedAt ? <>{data.policy.updatedBy} · : '尚无人工调整'}
推荐说明{data.refreshHint}

{data.recommendationReason}

{!editable ?
只读权限当前账号可查看诊断证据;只有管理员可以调整启停和优先级。
: null} {mobileLayout ? : } {data.policy.audit.length ?
    {data.policy.audit.map((item) =>
  1. v{item.version}{item.summary}{item.changeType === 'provider' ? '提供方' : '策略'} · {item.actor} ·
  2. )}
:

该车辆尚无人工来源策略或提供方变更。

}
: null}
; } export default function OperationsPage() { const [searchParams, setSearchParams] = useSearchParams(); const requestedWorkspace = searchParams.get('view') as OperationsWorkspace | null; const workspace = requestedWorkspace && operationsWorkspaces.has(requestedWorkspace) ? requestedWorkspace : 'reconciliation'; const setWorkspace = (next: OperationsWorkspace) => { const copy = new URLSearchParams(searchParams); if (next === 'reconciliation') copy.delete('view'); else copy.set('view', next); setSearchParams(copy, { replace: true }); }; const health = useQuery({ queryKey: ['ops-health-v2'], queryFn: ({ signal }) => api.opsHealth(signal), refetchInterval: 15_000, staleTime: 8_000, gcTime: QUERY_MEMORY.summaryGcTime, ...LIVE_QUERY_POLICY }); const readiness = useQuery({ queryKey: ['ops-source-readiness-v2'], queryFn: ({ signal }) => api.sourceReadiness(signal), refetchInterval: 30_000, staleTime: 15_000, gcTime: QUERY_MEMORY.summaryGcTime, ...LIVE_QUERY_POLICY }); const reconciliation = useQuery({ queryKey: ['reconciliation-summary', 30], queryFn: ({ signal }) => api.reconciliationSummary(30, signal), staleTime: 60_000, gcTime: QUERY_MEMORY.summaryGcTime }); const data = health.data; const sources = readiness.data; const refresh = () => Promise.all([health.refetch(), readiness.refetch(), reconciliation.refetch()]); const linkIssueCount = data?.linkHealth.filter((item) => item.status !== 'ok').length ?? 0; const runtimeIssueCount = data ? Number(!data.mysqlWritable) + Number(!data.tdengineWritable) + Number(data.runtime.dataMode !== 'production') : 0; const capacityIssueCount = data?.capacityFindings.length ?? 0; const infrastructureIssueCount = linkIssueCount + runtimeIssueCount + capacityIssueCount + Number((data?.kafkaLag ?? 0) > 0); const sourceIssueCount = sources?.sources.filter((item) => item.severity !== 'ok').length ?? 0; const sourceErrorCount = sources?.sources.filter((item) => item.severity === 'error').length ?? 0; const sourceHealthyCount = sources?.sources.filter((item) => item.severity === 'ok').length ?? 0; const readinessUnavailable = readiness.isError; const healthIssueCount = infrastructureIssueCount + sourceIssueCount + Number(readinessUnavailable); const healthEvidencePending = !data || readiness.isPending; const overallStatus = healthEvidencePending ? 'unknown' : readinessUnavailable || sourceErrorCount > 0 ? 'error' : healthIssueCount > 0 ? 'warning' : 'ok'; const overallValue = healthEvidencePending ? '读取中' : readinessUnavailable ? '证据缺失' : healthIssueCount > 0 ? '需要处理' : '运行正常'; const overallDetail = healthEvidencePending ? '等待运行与协议覆盖证据' : readinessUnavailable ? '协议来源就绪度暂不可用' : healthIssueCount > 0 ? `协议覆盖 ${sourceIssueCount} 项 · 链路运行 ${infrastructureIssueCount} 项` : '链路、写入与协议覆盖均正常'; const sortedLinks = useMemo(() => [...(data?.linkHealth ?? [])].sort((left, right) => { const priority = { error: 0, warning: 1, ok: 2 } as Record; return (priority[left.status] ?? 3) - (priority[right.status] ?? 3); }), [data?.linkHealth]); const sortedSources = useMemo(() => [...(sources?.sources ?? [])].sort((left, right) => { const priority = { error: 0, warning: 1, ok: 2 } as Record; return (priority[left.severity] ?? 3) - (priority[right.severity] ?? 3); }), [sources?.sources]); const workspaceContext = workspace === 'reconciliation' ? { description: '每日自动对账', status: reconciliation.data ? `${reconciliation.data.pending.toLocaleString('zh-CN')} 项待复核` : '读取差异证据', color: reconciliation.data?.overSla ? 'orange' as const : 'blue' as const } : workspace === 'diagnostic' ? { description: '按需读取单车来源', status: '精确诊断', color: 'cyan' as const } : { description: '15 秒刷新运行证据', status: healthEvidencePending ? '读取中' : readinessUnavailable ? '协议证据缺失' : healthIssueCount > 0 ? `${healthIssueCount} 项关注` : '运行正常', color: healthEvidencePending ? 'grey' as const : readinessUnavailable || sourceErrorCount > 0 ? 'red' as const : healthIssueCount > 0 ? 'orange' as const : 'green' as const }; return
上海时间 · {data?.runtime.platformRelease ? `版本 ${data.runtime.platformRelease}` : '正在读取运行版本'}} actions={} />
{workspace === 'reconciliation' ? : null} {workspace === 'diagnostic' ? : null} {workspace === 'health' ? <> {health.isError ? : null} {readiness.isError ? readiness.refetch()} /> : null} {!data ? '正在读取' : healthIssueCount > 0 ? `${healthIssueCount} 项需关注` : '全部正常'}15 秒自动刷新} />
0 ? 'danger' : healthIssueCount > 0 ? 'warning' : 'success'} /> 0 ? `${sourceIssueCount} 个协议需要处置` : sources?.sources.length ? '全部协议达到当前口径' : '等待协议覆盖证据'} tone={readinessUnavailable || sourceErrorCount > 0 ? 'danger' : sourceIssueCount > 0 ? 'warning' : sources?.sources.length ? 'success' : 'neutral'} />
0 ? 'warning' : sources ? 'ok' : 'unknown'}>{readinessUnavailable ? '证据不可用' : sources ? sourceIssueCount > 0 ? `${sourceIssueCount} 个协议需关注` : `${sources.sources.length} 个协议正常` : '正在读取'}} />{sources ? sortedSources.length ? {sortedSources.map((source) => { const onlineRate = sourceOnlineRate(source.online, source.total, source.onlineRate); return {source.protocol}{source.role}} headerExtraContent={{source.status || statusLabel(source.severity)}} headerLine>
在线覆盖{source.online.toLocaleString('zh-CN')} / {source.total.toLocaleString('zh-CN')}{number(onlineRate)}% 在线{source.missingVehicles ? ` · ${source.missingVehicles.toLocaleString('zh-CN')} 辆待恢复` : ''}
当前证据

{source.evidence}

建议动作

{source.action}

验收标准{source.acceptance}
; })}
: : readinessUnavailable ? :
}
0 ? 'warning' : 'ok'}>{!data ? '读取中' : linkIssueCount > 0 ? `${linkIssueCount} 条异常` : '链路正常'}} />
{sortedLinks.length ? sortedLinks.map((item) => { const label = opsLinkLabel(item.name); return
{label}{label !== item.name ? {item.name} : null}

{item.detail || '无补充信息'}

; }) : }
0 ? 'warning' : 'ok'}>{!data ? '读取中' : runtimeIssueCount + capacityIssueCount > 0 ? `${runtimeIssueCount + capacityIssueCount} 项关注` : '配置正常'}} />{data?.runtime.dataMode === 'production' ? '已启用' : '未启用'} }, { key: 'MySQL 写探针', value: {data?.mysqlWritable ? '正常' : '异常'} }, { key: 'TDengine 写探针', value: {data?.tdengineWritable ? '正常' : '异常'} }, { key: '请求超时', value: `${data?.runtime.requestTimeoutMs ?? '—'} ms` }, { key: '高德安全代理', value: {data?.runtime.amapSecurityProxyEnabled ? '服务端代理' : '未启用'} } ]} />{data?.capacityFindings?.length ?
{data.capacityFindings.map((item) => 待处理

{item}

)}
: } title="容量检查通过" description="当前没有需要处理的容量风险。" />}
: null}
; }