feat(platform): add vehicle service desk

This commit is contained in:
lingniu
2026-07-05 01:36:46 +08:00
parent f5d38253fe
commit f45cf90b70
4 changed files with 171 additions and 2 deletions

View File

@@ -383,15 +383,19 @@ async function copyVehicleShareURL() {
export function Vehicles({
onOpenVehicle,
onOpenQuality,
onOpenMap,
onOpenRealtime,
onOpenHistory,
onOpenMileage,
onFiltersChange,
initialFilters = {}
}: {
onOpenVehicle: (vin: string, protocol?: string) => void;
onOpenQuality?: (filters: Record<string, string>) => void;
onOpenMap?: (filters: Record<string, string>) => void;
onOpenRealtime?: (filters: Record<string, string>) => void;
onOpenHistory?: (filters: Record<string, string>) => void;
onOpenMileage?: (filters: Record<string, string>) => void;
onFiltersChange?: (filters: Record<string, string>) => void;
initialFilters?: Record<string, string>;
}) {
@@ -490,6 +494,12 @@ export function Vehicles({
const filterSummary = vehicleFilterSummary(filters);
const pageVerdictStats = useMemo(() => currentPageVerdictStats(rows), [rows]);
const pageActionStats = useMemo(() => currentPageActionStats(rows), [rows]);
const serviceScopeFilters = useMemo(() => {
const nextFilters: Record<string, string> = {};
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<string, string> = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => {
setLoading(true);
@@ -547,6 +557,48 @@ export function Vehicles({
}
copyText(vehicleIdentityMaintenanceText({ filters, rows, total: pagination.total }), '车辆身份维护清单');
};
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]);
useEffect(() => {
setFilters(initialFilters);
@@ -654,7 +706,42 @@ export function Vehicles({
return (
<div className="vp-page">
<PageHeader title="车辆服务" description="以 VIN 为主对象维护车辆身份、车辆覆盖、在线状态和绑定状态" />
<PageHeader title="车辆服务" description="围绕车辆完成实时监控、地图查看、轨迹回放、里程统计和数据导出,协议来源只作为证据过滤条件" />
<Card bordered title="车辆服务台">
<div className="vp-vehicle-service-desk">
<div className="vp-vehicle-service-summary">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={filters.keyword?.trim() ? 'green' : 'grey'}>{filters.keyword?.trim() ? '单车服务' : '车辆池'}</Tag>
</Space>
<strong>{filters.keyword?.trim() || '全部车辆'}</strong>
<span>
{(summary?.totalVehicles ?? pagination.total).toLocaleString()} 线 {(summary?.onlineVehicles ?? 0).toLocaleString()}
</span>
<Space wrap>
<Button size="small" theme="solid" type="primary" disabled={!onOpenRealtime} onClick={() => onOpenRealtime?.({ ...serviceScopeFilters, online: 'online' })}>线</Button>
<Button size="small" disabled={!onOpenMap} onClick={() => onOpenMap?.({ ...serviceScopeFilters, online: 'online' })}></Button>
<Button size="small" onClick={copyDispatchList}></Button>
</Space>
</div>
<div className="vp-vehicle-service-grid">
{serviceDeskActions.map((item) => (
<button
key={item.label}
className="vp-vehicle-service-action"
type="button"
disabled={item.disabled}
onClick={item.onClick}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</button>
))}
</div>
</div>
</Card>
<Card bordered>
<Form key={JSON.stringify(filters)} initValues={filters} layout="horizontal" onSubmit={(values) => {
const nextFilters = values as Record<string, string>;