import { IconRefresh, IconSearch } from '@douyinfe/semi-icons';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { FormEvent, useDeferredValue, useEffect, useState } from 'react';
import { api } from '../../api/client';
import type { VehicleCoverageRow, VehicleLocationSourceEvidence, VehicleSourceDiagnostic } from '../../api/types';
import { InlineError } from '../shared/AsyncState';
import { LIVE_QUERY_POLICY, QUERY_MEMORY } from '../queryPolicy';
import ReconciliationCenter from './ReconciliationCenter';
function statusLabel(status: string) {
return { ok: '正常', warning: '关注', error: '异常' }[status] ?? status;
}
function fmt(value?: string) {
if (!value) return '—';
const parsed = new Date(value.replace(' ', 'T'));
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString('zh-CN', { hour12: false });
}
function number(value?: number, digits = 1) {
return value == null || !Number.isFinite(value) ? '—' : value.toLocaleString('zh-CN', { maximumFractionDigits: digits });
}
function SourcePolicyRow({ 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
| {source.sourceLabel}{source.terminalLabel || source.sourceKind || '未维护终端'} |
{source.protocol}{source.selectedWithinProtocol ? '协议内已选' : '协议内候选'} |
{source.online ? '在线' : '离线'}{source.qualityStatus || '未知'}{source.qualityReason ? ` · ${source.qualityReason}` : ''} |
{fmt(source.firstSeenAt)}最近 {fmt(source.receivedAt || source.eventTime)} |
{source.reportIntervalSec == null ? '—' : `${source.reportIntervalSec}s`}{source.reportSampleCount ? `${source.reportSampleCount.toLocaleString('zh-CN')} 个里程样本` : '暂无累计样本'} |
{source.longitude == null || source.latitude == null ? '—' : `${source.longitude.toFixed(6)}, ${source.latitude.toFixed(6)}`}{number(source.speedKmh)} km/h · {number(source.totalMileageKm)} km |
{source.recommended ? '当前推荐' : source.selectedWithinProtocol ? '协议首选' : '备用来源'}{source.selectionReason || '等待选举说明'} |
setPriority(Number(event.target.value))} />
setProviderName(event.target.value)} placeholder="提供方,如 G7s" />
setProviderEvidence(event.target.value)} placeholder={providerChanged ? '权威终端清单、厂商确认记录等(必填)' : '修改提供方后填写核验依据'} />
setRemark(event.target.value)} placeholder={policyEditable ? '启停或优先级调整原因(可选)' : '协议融合快照不可调整策略'} />
{save.isError ? {save.error instanceof Error ? save.error.message : '保存失败'} : null}
|
;
}
function SourceDiagnosticWorkspace() {
const queryClient = useQueryClient();
const [keyword, setKeyword] = useState('');
const deferredKeyword = useDeferredValue(keyword.trim());
const [selected, setSelected] = useState();
const [candidateOffset, setCandidateOffset] = useState(0);
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);
};
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';
return
{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} · ${fmt(data.policy.updatedAt)}` : '尚无人工调整'}
推荐说明{data.recommendationReason}
{data.refreshHint}
{!editable ? 当前账号可查看诊断证据;只有管理员可以调整启停和优先级。
: null}
| 来源 / 终端 | 协议 | 在线 / 质量 | 首次 / 最近上报 | 上报周期 | 位置 / 里程 | 选举结论 | 运维策略 |
{data.evidence.locationSources.map((source) => {
queryClient.setQueryData(['ops-source-diagnostic', data.evidence.vin], next);
void Promise.all([
queryClient.invalidateQueries({ queryKey: ['access-summary'] }),
queryClient.invalidateQueries({ queryKey: ['access-vehicles'] }),
queryClient.invalidateQueries({ queryKey: ['ops-source-readiness-v2'] })
]);
}} />)}
最近策略审计仅记录实际变更,原始 source_key 不对前端暴露
{data.policy.audit.length ? {data.policy.audit.map((item) => - v{item.version}{item.summary}{item.changeType === 'provider' ? '提供方' : '策略'} · {item.actor} · {fmt(item.changedAt)}
)}
: 该车辆尚无人工来源策略或提供方变更。
}
> : null}
;
}
export default function OperationsPage() {
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 data = health.data; const sources = readiness.data;
const refresh = () => Promise.all([health.refetch(), readiness.refetch()]);
return
{health.isError ?
: null}
{readiness.isError ?
readiness.refetch()} /> : null}
运行版本{data?.runtime.platformRelease || '未注入'}{data?.runtime.dataMode || 'unknown'}
活跃连接{data?.activeConnections?.toLocaleString('zh-CN') ?? '—'}capacity-check
Kafka Lag{data?.kafkaLag?.toLocaleString('zh-CN') ?? '—'}{data?.kafkaLag === 0 ? '已回零' : '需检查'}
Redis 在线 Key{data?.redisOnlineKeys?.toLocaleString('zh-CN') ?? '—'}实时探针
服务身份 / 在线{sources ? `${sources.totalVehicles} / ${sources.onlineVehicles}` : '—'}{sources ? `已绑定 ${sources.boundVehicles} · 待绑定 ${sources.identityRequiredVehicles}` : '档案与快照并集'}
{data?.linkHealth.map((item) =>
{item.name}{item.detail || '无补充信息'}
{statusLabel(item.status)})}
- 生产数据模式
- {data?.runtime.dataMode === 'production' ? '已启用' : '未启用'}
- MySQL 写探针
- {data?.mysqlWritable ? '正常' : '异常'}
- TDengine 写探针
- {data?.tdengineWritable ? '正常' : '异常'}
- 请求超时
- {data?.runtime.requestTimeoutMs ?? '—'} ms
- 高德安全代理
- {data?.runtime.amapSecurityProxyEnabled ? '服务端代理' : '未启用'}
{data?.capacityFindings?.length ? {data.capacityFindings.map((item) =>
{item}
)}
: 容量检查无待处理项
}
{sources?.sources.map((source) =>
{source.protocol}{source.role}
{source.online} / {source.total} 在线{source.evidence}
{source.action}
{source.acceptance})}
;
}