feat(vehicle-tracking): 动态调整车辆轨迹动画时长并优化位置同步逻辑
- 基于推送间隔动态计算平移动画时长,适配1-2秒频率的轨迹推送
- 移除moveAlong路径动画逻辑,统一使用translateMarker进行车辆移动
- 优化车辆位置同步策略,优先使用已服务路段的最后一个点作为车辆位置
- 简化useVehicleTracking依赖项,提升代码可维护性
| | |
| | | const [trunkStatus, setTrunkStatus] = useState(false); |
| | | const lastVehiclePointRef = useRef<{ latitude: number; longitude: number } | null>(null); |
| | | const lastVehicleUpdateAtRef = useRef<number>(0); |
| | | const translateDurationRef = useRef<number>(900); |
| | | |
| | | // 更新车辆位置并维护最近位置引用 |
| | | const updateVehicleLocationThrottled = (lat: number, lng: number, force = false) => { |
| | | const now = Date.now(); |
| | | const prevUpdateAt = lastVehicleUpdateAtRef.current; |
| | | if (prevUpdateAt > 0) { |
| | | // 基于推送间隔动态调整平移时长,适配 1-2 秒频率的轨迹推送 |
| | | const interval = now - prevUpdateAt; |
| | | translateDurationRef.current = Math.max(350, Math.min(1600, Math.floor(interval * 0.85))); |
| | | } |
| | | if (Number.isFinite(lat) && Number.isFinite(lng)) { |
| | | lastVehiclePointRef.current = { latitude: lat, longitude: lng }; |
| | | lastVehicleUpdateAtRef.current = now; |
| | |
| | | |
| | | useEffect(() => { |
| | | if (!statusRule.translateCar) return; |
| | | if (vehicleMoveAlongPoints.length > 1) return; |
| | | if (!vehicleLocation) return; |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const timer = setTimeout(() => { |
| | |
| | | autoRotate: false, |
| | | moveWithRotate: true, |
| | | rotate: typeof vehicleDirection === 'number' ? ((vehicleDirection % 360) + 360) % 360 : 0, |
| | | duration: 100 |
| | | duration: translateDurationRef.current |
| | | }); |
| | | }, 100); |
| | | return () => clearTimeout(timer); |
| | | }, [statusRule.translateCar, vehicleLocation, vehicleDirection, vehicleMarkerId, vehicleMoveAlongPoints]); |
| | | |
| | | useEffect(() => { |
| | | if (!statusRule.translateCar) return; |
| | | if (vehicleMoveAlongPoints.length < 2) return; |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const duration = Math.min(2000, Math.max(300, vehicleMoveAlongPoints.length * 100)); |
| | | const timer = setTimeout(() => { |
| | | mapCtx.moveAlong({ |
| | | markerId: vehicleMarkerId, |
| | | path: vehicleMoveAlongPoints, |
| | | autoRotate: true, |
| | | duration |
| | | }); |
| | | }, 50); |
| | | return () => clearTimeout(timer); |
| | | }, [statusRule.translateCar, vehicleMoveAlongPoints, vehicleMarkerId]); |
| | | }, [statusRule.translateCar, vehicleLocation, vehicleDirection, vehicleMarkerId]); |
| | | |
| | | return { |
| | | vehicleLocation, |
| | |
| | | } |
| | | }; |
| | | |
| | | const syncVehiclePointWithRoute = (points: any[], deps: UseWebSocketEventsDeps) => { |
| | | const resolveVehiclePoint = (points: any[], servedPoints: any[] = []) => { |
| | | const servedLastPoint = Array.isArray(servedPoints) && servedPoints.length > 0 |
| | | ? servedPoints[servedPoints.length - 1] |
| | | : null; |
| | | if (servedLastPoint && typeof servedLastPoint.latitude === 'number' && typeof servedLastPoint.longitude === 'number') { |
| | | return servedLastPoint; |
| | | } |
| | | const firstPoint = points?.[0]; |
| | | if (!firstPoint || typeof firstPoint.latitude !== 'number' || typeof firstPoint.longitude !== 'number') { |
| | | if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') { |
| | | return firstPoint; |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const syncVehiclePointWithRoute = (points: any[], deps: UseWebSocketEventsDeps, servedPoints: any[] = []) => { |
| | | const targetPoint = resolveVehiclePoint(points, servedPoints); |
| | | if (!targetPoint) { |
| | | return; |
| | | } |
| | | deps.updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true); |
| | | deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true); |
| | | }; |
| | | |
| | | const handleEraseRouteStatus = (points: any[], servedPoints: any[], deps: UseWebSocketEventsDeps) => { |
| | | const firstPoint = points?.[0]; |
| | | const targetPoint = resolveVehiclePoint(points, servedPoints); |
| | | if (deps.orderRouteInitializedRef.current && deps.currentFullRouteRef.current.length > 0) { |
| | | deps.erasePassedRouteSegments(points, servedPoints); |
| | | } else { |
| | |
| | | deps.applyRoutePoints(points); |
| | | }); |
| | | } |
| | | if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') { |
| | | if (targetPoint) { |
| | | setTimeout(() => { |
| | | deps.updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true); |
| | | deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true); |
| | | }, 50); |
| | | } |
| | | }; |
| | | |
| | | const handleNormalRouteStatus = (points: any[], deps: UseWebSocketEventsDeps) => { |
| | | const handleNormalRouteStatus = (points: any[], deps: UseWebSocketEventsDeps, servedPoints: any[] = []) => { |
| | | Taro.nextTick(() => { |
| | | const updated = deps.applyRoutePoints(points); |
| | | if (!updated) return; |
| | | if (![4, 5, 6].includes(deps.tripStatusRef.current)) return; |
| | | syncVehiclePointWithRoute(points, deps); |
| | | syncVehiclePointWithRoute(points, deps, servedPoints); |
| | | }); |
| | | }; |
| | | |
| | | const handleForceFullRouteRedraw = (points: any[], deps: UseWebSocketEventsDeps) => { |
| | | const handleForceFullRouteRedraw = (points: any[], deps: UseWebSocketEventsDeps, servedPoints: any[] = []) => { |
| | | deps.setRoutePoints([]); |
| | | deps.setPolyline([]); |
| | | deps.orderRouteInitializedRef.current = false; |
| | |
| | | deps.currentFullRouteRef.current = [...points]; |
| | | deps.lastErasedIndexRef.current = 0; |
| | | deps.applyRoutePoints(points); |
| | | syncVehiclePointWithRoute(points, deps); |
| | | syncVehiclePointWithRoute(points, deps, servedPoints); |
| | | }); |
| | | }; |
| | | |
| | |
| | | deps.setVehicleMoveAlongPoints(servedPoints); |
| | | } |
| | | if (forceFullRedraw) { |
| | | handleForceFullRouteRedraw(points, deps); |
| | | handleForceFullRouteRedraw(points, deps, servedPoints); |
| | | return; |
| | | } |
| | | const isEraseStatus = [4, 6].includes(deps.tripStatusRef.current); |
| | |
| | | handleEraseRouteStatus(points, servedPoints, deps); |
| | | return; |
| | | } |
| | | handleNormalRouteStatus(points, deps); |
| | | handleNormalRouteStatus(points, deps, servedPoints); |
| | | }; |
| | | |
| | | export default function useWebSocketEvents(deps: UseWebSocketEventsDeps) { |