feat(platform): harden telemetry pipeline and unify Semi UI workspaces

This commit is contained in:
lingniu
2026-07-18 00:26:36 +08:00
parent 65b4e4f055
commit 159c80b0ae
136 changed files with 21616 additions and 1785 deletions

View File

@@ -344,7 +344,9 @@ test('recovers in place after a transient AMap failure and renders data that arr
);
expect(await screen.findByText(/地图加载失败/)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /重新加载地图/ }));
const retry = screen.getByRole('button', { name: /重新加载地图/ });
expect(retry).toHaveClass('semi-button', 'v2-map-retry-action');
fireEvent.click(retry);
expect(setData).not.toHaveBeenCalled();
await act(async () => {
@@ -520,6 +522,23 @@ test('converts AMap GCJ-02 bounds back to WGS-84 before requesting monitor data'
expect(Math.abs(bounds[3] - 24)).toBeLessThan(0.0005);
});
test('drops a wrapped world viewport instead of emitting invalid WGS-84 bounds', async () => {
getZoom.mockReturnValue(3);
getBounds.mockReturnValue({
getSouthWest: () => ({ getLng: () => -80, getLat: () => -20 }),
getNorthEast: () => ({ getLng: () => 260, getLat: () => 70 })
});
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
window.AMapLoader = { load: vi.fn(async () => amapMock()) };
const onViewportChange = vi.fn();
render(<FleetMap vehicles={[]} monitorMap={pointMap} onSelect={() => undefined} onViewportChange={onViewportChange} />);
await waitFor(() => expect(onViewportChange).toHaveBeenCalled());
const viewport = onViewportChange.mock.calls[onViewportChange.mock.calls.length - 1]?.[0] as { zoom: number; bounds: string };
expect(viewport).toEqual({ zoom: 3, bounds: '' });
});
test('renders one selected plate and smoothly follows it until the map is dragged', async () => {
useFastAnimationFrames();
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
@@ -702,7 +721,8 @@ test('renders crisp province totals and drills into the existing cluster layer',
expect(setData).toHaveBeenLastCalledWith([]);
const provinceIndex = markerOptions.findIndex((options) => String(options.content).includes('广东省'));
expect(String(markerOptions[provinceIndex]?.content)).toContain('<strong>3</strong>');
expect(String(markerOptions[provinceIndex]?.content)).toContain('在线 2');
expect(String(markerOptions[provinceIndex]?.content)).toContain('<span>广东</span>');
expect(String(markerOptions[provinceIndex]?.content)).not.toContain('在线');
markerInstances[provinceIndex]?.handlers.get('click')?.();
expect(setZoomAndCenter).toHaveBeenLastCalledWith(7, [113.266, 23.132]);

View File

@@ -1,4 +1,4 @@
import { IconEyeClosed, IconEyeOpened, IconMapPin, IconRefresh } from '@douyinfe/semi-icons';
import { IconEyeClosed, IconEyeOpened, IconMapPin } from '@douyinfe/semi-icons';
import { useEffect, useMemo, useRef, useState } from 'react';
import { getAMapConfig, isAMapConfigured } from '../../config/appConfig';
import {
@@ -16,8 +16,9 @@ import {
type AMapOverlay
} from '../../integrations/amap';
import type { MonitorMapProvincePoint, MonitorMapResponse, VehicleRealtimeRow } from '../../api/types';
import { vehicleStatus } from '../domain/monitor';
import { normalizeMonitorBounds, vehicleStatus } from '../domain/monitor';
import type { MonitorViewport } from '../hooks/useMonitorData';
import { MapRetryAction } from '../shared/RecoveryActions';
const COLORS = ['#12a46f', '#9aa6b7', '#1677ff', '#f59e0b', '#ef4444'];
const CLUSTER_VISUAL_CACHE_LIMIT = 256;
@@ -78,11 +79,9 @@ function viewportFromMap(map: AMapMap): MonitorViewport | null {
];
const longitudes = wgsCorners.map(([longitude]) => longitude);
const latitudes = wgsCorners.map(([, latitude]) => latitude);
return {
zoom,
bounds: [Math.min(...longitudes), Math.min(...latitudes), Math.max(...longitudes), Math.max(...latitudes)]
.map((value) => value.toFixed(6)).join(',')
};
const viewportBounds = [Math.min(...longitudes), Math.min(...latitudes), Math.max(...longitudes), Math.max(...latitudes)]
.map((value) => value.toFixed(6)).join(',');
return { zoom, bounds: normalizeMonitorBounds(viewportBounds) };
}
function viewportCenter(viewport?: MonitorViewport) {
@@ -757,13 +756,12 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
existing.marker.off?.('click', existing.click);
existing.marker.setMap?.(null);
}
const onlinePercent = province.count ? Math.round(province.online / province.count * 100) : 0;
const displayName = compactMarkers ? compactProvinceName(province.name) : province.name;
const displayName = compactProvinceName(province.name);
const marker = new AMap.Marker({
position: [province.longitude, province.latitude],
offset: new AMap.Pixel(0, 0),
zIndex: 180,
content: `<div class="v2-province-placement" style="--province-x:${placement.dx}px;--province-y:${placement.dy}px;--province-line:${connectorLength}px;--province-angle:${connectorAngle}deg"><i class="v2-province-anchor"></i>${connectorLength ? '<i class="v2-province-connector"></i>' : ''}<button type="button" tabindex="-1" class="v2-province-marker${compactMarkers ? ' is-compact' : ''}" aria-label="${escapeHtml(province.name)} ${province.count} 辆"><span>${escapeHtml(displayName)}</span><strong>${province.count.toLocaleString('zh-CN')}</strong><small>在线 ${province.online.toLocaleString('zh-CN')}</small><i><b style="width:${onlinePercent}%"></b></i></button></div>`
content: `<div class="v2-province-placement" style="--province-x:${placement.dx}px;--province-y:${placement.dy}px;--province-line:${connectorLength}px;--province-angle:${connectorAngle}deg"><i class="v2-province-anchor"></i>${connectorLength ? '<i class="v2-province-connector"></i>' : ''}<button type="button" tabindex="-1" class="v2-province-marker${compactMarkers ? ' is-compact' : ''}" aria-label="${escapeHtml(province.name)} ${province.count} 辆"><span>${escapeHtml(displayName)}</span><strong>${province.count.toLocaleString('zh-CN')}</strong></button></div>`
});
const click = () => map.setZoomAndCenter?.(PROVINCE_DRILL_ZOOM, [province.longitude, province.latitude]);
marker.on?.('click', click);
@@ -1053,7 +1051,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
<div className={`v2-map-state is-${state}`}>
{state === 'loading' ? <><span className="v2-spinner" /></> : null}
{state === 'fallback' ? `地图未配置,当前已载入 ${renderedPointCount} 个有效坐标` : null}
{state === 'error' ? <><span> Key</span><button type="button" onClick={() => setLoadAttempt((value) => value + 1)}><IconRefresh /></button></> : null}
{state === 'error' ? <><span> Key</span><MapRetryAction onRetry={() => setLoadAttempt((value) => value + 1)} /></> : null}
</div>
) : null}
<div className="v2-map-legend" aria-label="车辆状态图例">

View File

@@ -128,7 +128,9 @@ test('defers the map runtime until valid data exists, retries a transient failur
onFollowChange={() => undefined}
/>);
expect(await screen.findByText(/地图加载失败/)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /重新加载地图/ }));
const retry = screen.getByRole('button', { name: /重新加载地图/ });
expect(retry).toHaveClass('semi-button', 'v2-map-retry-action');
fireEvent.click(retry);
await waitFor(() => expect(mapInstances).toHaveLength(1));
expect(load).toHaveBeenCalledTimes(2);
expect(screen.queryByText(/地图加载失败/)).not.toBeInTheDocument();

View File

@@ -1,8 +1,8 @@
import { IconRefresh } from '@douyinfe/semi-icons';
import { useEffect, useMemo, useRef, useState } from 'react';
import type { HistoryLocationRow, TrackStop } from '../../api/types';
import { getAMapConfig, isAMapConfigured } from '../../config/appConfig';
import { isValidAMapCoordinate, loadAMap, wgs84ToGcj02, type AMapLike, type AMapMap, type AMapOverlay } from '../../integrations/amap';
import { MapRetryAction } from '../shared/RecoveryActions';
function markerContent(kind: 'start' | 'end' | 'stop' | 'current', label?: string) {
if (kind === 'current') return '<div class="v2-track-current-marker" aria-hidden="true"><i></i><span></span></div>';
@@ -153,7 +153,7 @@ export function TrackMap({ points, stops, activeIndex, showStops, follow, follow
{state === 'loading' ? <><span className="v2-spinner" /></> : null}
{state === 'idle' ? '当前轨迹没有可展示的有效坐标' : null}
{state === 'fallback' ? `地图未配置,已载入 ${valid.length} 个有效轨迹点` : null}
{state === 'error' ? <><span></span><button type="button" onClick={() => setLoadAttempt((value) => value + 1)}><IconRefresh /></button></> : null}
{state === 'error' ? <><span></span><MapRetryAction onRetry={() => setLoadAttempt((value) => value + 1)} /></> : null}
</div> : null}
</div>;
}