Merge branch 'lucl/auto-scale-v6' into feature_v2.0
| | |
| | | arrivedMarkerReady: boolean; |
| | | arrivedTooltipReady: boolean; |
| | | onVehicleMarkerReady: () => void; |
| | | onCarMarkerWillRebuild: (newMarkerId: number) => void; |
| | | }; |
| | | |
| | | const defaultDeps: Deps = { |
| | |
| | | vehicleMarkerId: 3, |
| | | arrivedMarkerReady: false, |
| | | arrivedTooltipReady: false, |
| | | onVehicleMarkerReady: () => {} |
| | | onVehicleMarkerReady: () => { }, |
| | | onCarMarkerWillRebuild: (_newMarkerId: number) => { } |
| | | }; |
| | | |
| | | export default function useMapMarkers(rawDeps?: Partial<Deps>) { |
| | | const deps = { ...defaultDeps, ...(rawDeps || {}) } as Deps; |
| | | // 地图 marker 集合 |
| | | // 内部"逻辑 markers"——真相态,所有内部判断(签名、是否含车标等)以此为准 |
| | | const [markers, setMarkers] = useState<any[]>([]); |
| | | // 对外"展示 markers"——返回给页面,通过 rebuild dance 制造短暂"无车标"窗口以重建原生 marker |
| | | const [displayMarkers, setDisplayMarkers] = useState<any[]>([]); |
| | | const markersRef = useRef<any[]>([]); |
| | | const markersSignatureRef = useRef<string>(""); |
| | | const carMarkerInitialized = useRef(false); |
| | | const tripStatus6MarkerRefreshedRef = useRef(false); |
| | | const rebuildCarMarkerTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| | | // 每次 rebuild 递增,用于派生新的 car marker id,避免原生层因 id 相同跳过销毁/重建导致旋转矩阵残留 |
| | | const carMarkerEpochRef = useRef(0); |
| | | |
| | | /** |
| | | * 派生新的 car marker id:base + ((epoch % 1000) + 1) * 1000 |
| | | * - 与业务 base id(3/30)和其他 marker(pickup=1, dropoff=2, beforeUser=31)不冲突 |
| | | * - epoch 取模 1000 防止无限增长 |
| | | */ |
| | | const deriveNewCarMarkerId = (baseId: number) => { |
| | | return baseId + ((carMarkerEpochRef.current % 1000) + 1) * 1000; |
| | | }; |
| | | |
| | | /** |
| | | * 当逻辑 markers 变化时,对展示 markers 做"先移除车标 → 100ms 后用新 id 恢复"的两阶段提交。 |
| | | * 关键:恢复时给车标分配一个全新 markerId(base + epoch 派生),强制原生层创建新 marker 实例, |
| | | * 不会因 id 相同被原生层复用,translateMarker 残留的旋转矩阵随之清除。 |
| | | */ |
| | | useEffect(() => { |
| | | if (rebuildCarMarkerTimerRef.current) { |
| | | clearTimeout(rebuildCarMarkerTimerRef.current); |
| | | rebuildCarMarkerTimerRef.current = null; |
| | | } |
| | | const hasCarMarker = markers.some((m: any) => m.id === deps.vehicleMarkerId); |
| | | if (!hasCarMarker) { |
| | | setDisplayMarkers(markers); |
| | | return; |
| | | } |
| | | carMarkerEpochRef.current += 1; |
| | | const newCarId = deriveNewCarMarkerId(deps.vehicleMarkerId); |
| | | // 通知外部:CarMarker 即将以 newCarId 重建,暂停 translateMarker 并切换到新 id |
| | | deps.onCarMarkerWillRebuild(newCarId); |
| | | // 第一步:展示层先去掉车标(按业务 base id 过滤),原生层销毁旧 car marker |
| | | setDisplayMarkers(markers.filter((m: any) => m.id !== deps.vehicleMarkerId)); |
| | | // 第二步:100ms 后用 newCarId 重新挂载车标,原生层创建全新 marker 实例 |
| | | rebuildCarMarkerTimerRef.current = setTimeout(() => { |
| | | rebuildCarMarkerTimerRef.current = null; |
| | | setDisplayMarkers(markers.map((m: any) => m.id === deps.vehicleMarkerId ? { ...m, id: newCarId } : m)); |
| | | }, 100); |
| | | return () => { |
| | | if (rebuildCarMarkerTimerRef.current) { |
| | | clearTimeout(rebuildCarMarkerTimerRef.current); |
| | | rebuildCarMarkerTimerRef.current = null; |
| | | } |
| | | }; |
| | | }, [markers, deps.vehicleMarkerId]); |
| | | |
| | | // 行驶中/接驾中/到达上车点 车辆 marker 统一交由 translateMarker 托管位置与角度 |
| | | const shouldUsePureTranslateCarMarker = |
| | |
| | | const verticalExtraOffset = hasBeforeUserOrderTip ? 22 : 0; |
| | | const anchorY = 80 + verticalExtraOffset; |
| | | const anchorX = 0; |
| | | |
| | | |
| | | return { |
| | | id: deps.vehicleMarkerId, |
| | |
| | | return prevMarkers; |
| | | } |
| | | const prevCar = prevMarkers[targetCarIndex] || {}; |
| | | const shouldClearCarRotate = Object.prototype.hasOwnProperty.call( |
| | | prevCar, |
| | | "rotate" |
| | | ); |
| | | if ( |
| | | prevMarkersSignature === nextMarkersSignature && |
| | | !shouldClearCarRotate |
| | | ) { |
| | | // const shouldClearCarRotate = Object.prototype.hasOwnProperty.call(prevCar, 'rotate'); |
| | | if (prevMarkersSignature === nextMarkersSignature) { |
| | | return prevMarkers; |
| | | } |
| | | markersSignatureRef.current = nextMarkersSignature; |
| | |
| | | const latestVehicleLongitude = Number(deps.vehicleLocation?.longitude); |
| | | // 车辆在签名中以 'translate' 占位,签名差异只可能来自非车辆 marker(如终点从新目的地回退到原目的地)。 |
| | | // 因此仅当非车辆 marker 结构未变、车辆结构未变且无需清理朝向时才保留旧 marker,否则必须应用最新 marker,避免终点/上车点残留。 |
| | | if ( |
| | | !shouldRefreshCarMarkerStructure && |
| | | !shouldClearCarRotate && |
| | | prevMarkersSignature === nextMarkersSignature |
| | | ) { |
| | | if (!shouldRefreshCarMarkerStructure) { |
| | | return prevMarkers; |
| | | } |
| | | mergedMarkers[nextCarIndex] = { |
| | |
| | | }, [markers]); |
| | | |
| | | useEffect(() => { |
| | | const hasVehicleMarker = |
| | | deps.statusRule.showCar && |
| | | markers.some((item: any) => item.id === deps.vehicleMarkerId); |
| | | // 这里使用 displayMarkers + name 匹配(而非 id),因为 displayMarkers 中 car id 每次 rebuild 都会变成新 id |
| | | const hasVehicleMarker = deps.statusRule.showCar && displayMarkers.some((item: any) => item.name === '车辆位置'); |
| | | if (!hasVehicleMarker) { |
| | | return; |
| | | } |
| | | Taro.nextTick(() => { |
| | | deps.onVehicleMarkerReady(); |
| | | }); |
| | | }, [ |
| | | markers, |
| | | deps.statusRule.showCar, |
| | | deps.vehicleMarkerId, |
| | | deps.onVehicleMarkerReady |
| | | ]); |
| | | }, [displayMarkers, deps.statusRule.showCar, deps.onVehicleMarkerReady]); |
| | | |
| | | useEffect(() => { |
| | | const pickupSite = |
| | |
| | | ]); |
| | | |
| | | return { |
| | | markers, |
| | | // 对外返回经过 rebuild dance 的展示态 markers;内部逻辑(applyMarkerState 等)仍读取 markers(逻辑态) |
| | | markers: displayMarkers, |
| | | setMarkers, |
| | | markersRef, |
| | | markersSignatureRef, |
| | |
| | | /** 地图视口顶部附加安全区内边距。 */ |
| | | export const ROUTE_VIEWPORT_TOP_EXTRA_PADDING = 50; |
| | | /** 地图视口底部附加安全区内边距,因为地图的高度是 87vh,到时底部 padding 偏高,要减去一点 */ |
| | | export const ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING = -30; |
| | | export const ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING = 0; |
| | | /** 地图视口左右内边距。 */ |
| | | export const ROUTE_VIEWPORT_SIDE_PADDING = 80; |
| | | export const ROUTE_VIEWPORT_RIGHT_SIDE_PADDING = 80; |
| | |
| | | * 间隔太短会让多次 fit 的 race 窗口叠加。这里取 1500ms 让每次 fit 有充裕 |
| | | * 的同步时间,相当于把抖动从"持续震"压成"偶尔轻微闪一下"。 |
| | | */ |
| | | const MIN_FIT_INTERVAL_MS = 1500; |
| | | const MIN_FIT_INTERVAL_MS = 5000; |
| | | |
| | | const buildRouteSignature = (points: { latitude: number; longitude: number }[]) => { |
| | | if (!Array.isArray(points) || points.length === 0) return ''; |
| | |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const bottomPadding = computeBottomPadding(points, 'route', tripStatus, true) + ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING ; |
| | | const topPadding = Math.max(ROUTE_VIEWPORT_MIN_TOP_PADDING, resolveSafeTopInset() + ROUTE_VIEWPORT_TOP_EXTRA_PADDING); |
| | | console.log('[lucl] fitRouteIntoView ---> includePoints', JSON.stringify({ points, padding: [topPadding, ROUTE_VIEWPORT_RIGHT_SIDE_PADDING, bottomPadding, ROUTE_VIEWPORT_SIDE_PADDING] })); |
| | | mapCtx.includePoints({ |
| | | points, |
| | | padding: [topPadding, ROUTE_VIEWPORT_RIGHT_SIDE_PADDING, bottomPadding, ROUTE_VIEWPORT_SIDE_PADDING] |
| | |
| | | const TRANSLATE_DURATION_MS = 100; |
| | | const ROTATE_ONLY_DURATION_MS = 0; |
| | | const VEHICLE_LOCATION_EPSILON = 0.000001; |
| | | const FIRST_VEHICLE_MARKER_READY_DELAY_MS = 120; |
| | | const FIRST_VEHICLE_MARKER_READY_DELAY_MS = 300; |
| | | |
| | | /** |
| | | * 计算两个角度之间的最小夹角,范围 0~180。 |
| | |
| | | // 兜底定时器,防止回调丢失导致动画队列卡死 |
| | | fallbackTimer = setTimeout(() => { |
| | | finishTranslate(); |
| | | }, TRANSLATE_DURATION_MS + 50); |
| | | }, 1500); |
| | | |
| | | Taro.nextTick(() => { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const duration =Math.min(Math.max(task.duration, 100), 1000); |
| | | |
| | | mapCtx.translateMarker({ |
| | | markerId: task.markerId, |
| | | destination: { |
| | |
| | | }, |
| | | autoRotate: false, |
| | | moveWithRotate: false, |
| | | rotate: task.rotate, |
| | | duration: Math.max(task.duration, 100), |
| | | rotate: Math.round(task.rotate), |
| | | duration, |
| | | animationEnd: () => { |
| | | clearTimeout(fallbackTimer); |
| | | if (translateSessionId !== translateSessionIdRef.current) { |
| | | return; |
| | | } |
| | |
| | | rotate: task.rotate |
| | | }; |
| | | finishTranslate(); |
| | | |
| | | }, |
| | | fail: () => { |
| | | clearTimeout(fallbackTimer); |
| | | if (translateSessionId !== translateSessionIdRef.current) { |
| | | return; |
| | | } |
| | |
| | | longitude: task.longitude, |
| | | rotate: task.rotate |
| | | }; |
| | | finishTranslate(); |
| | | finishTranslate(); |
| | | } |
| | | }); |
| | | }); |
| | |
| | | latitude: vehicleLocation.latitude, |
| | | longitude: vehicleLocation.longitude, |
| | | rotate: targetRotate, |
| | | markerId: vehicleMarkerId, |
| | | // 用 ref 而非 props:rebuild 期间 suspendVehicleMarker(newId) 已经把 ref 切到新 id, |
| | | // 必须读最新 ref 才能让 pending 的 markerId 与原生层新 marker 一致 |
| | | markerId: vehicleMarkerIdRef.current, |
| | | duration: hasLocationDelta ? TRANSLATE_DURATION_MS : ROTATE_ONLY_DURATION_MS |
| | | }; |
| | | flushTranslateTask(); |
| | |
| | | clearVehicleMarkerReadyTimer(); |
| | | }, []); |
| | | |
| | | /** |
| | | * 暂停 translateMarker 并切换到新的 markerId。 |
| | | * 在 CarMarker 重建期间调用:原生层即将销毁旧 marker、用 newMarkerId 创建新 marker, |
| | | * 这里同步把 vehicleMarkerIdRef 切到新 id,pending 任务的 markerId 也一并更新, |
| | | * 确保 ready 恢复后 translateMarker 作用于新 marker。 |
| | | * |
| | | * 关键:保留 pendingTranslateRef.current(只改 markerId), |
| | | * 若 suspend 前已有 pending(最近一次推送写入但尚未 flush),ready 恢复后会直接消费它。 |
| | | */ |
| | | const suspendVehicleMarker = useCallback((newMarkerId: number) => { |
| | | isTranslatingRef.current = false; |
| | | lastAppliedTranslateTaskRef.current = null; |
| | | vehicleMarkerReadyRef.current = false; |
| | | clearVehicleMarkerReadyTimer(); |
| | | translateSessionIdRef.current += 1; |
| | | vehicleMarkerIdRef.current = newMarkerId; |
| | | if (pendingTranslateRef.current) { |
| | | pendingTranslateRef.current = { |
| | | ...pendingTranslateRef.current, |
| | | markerId: newMarkerId |
| | | }; |
| | | } |
| | | }, []); |
| | | |
| | | return { |
| | | vehicleLocation, |
| | | setVehicleLocation, |
| | |
| | | lastVehiclePointRef, |
| | | lastVehicleUpdateAtRef, |
| | | updateVehicleLocationThrottled, |
| | | notifyVehicleMarkerReady |
| | | notifyVehicleMarkerReady, |
| | | suspendVehicleMarker |
| | | }; |
| | | } |
| | |
| | | deps.lastSocketDirectionAtRef.current = Date.now(); |
| | | } |
| | | deps.setVehicleDirection(normalizedDirection); |
| | | if (deps.tripStatusRef.current === TripStatus.InService) { |
| | | console.log('[开始行程][送驾方向][Socket]', { |
| | | 订单ID: getOrderId(deps.orderId), |
| | | 原始方向: rawDirection, |
| | | 归一化方向: normalizedDirection, |
| | | 车辆点位数量: Array.isArray(payload?.content?.vehicleMoveAlongPoints) ? payload.content.vehicleMoveAlongPoints.length : undefined, |
| | | Socket原始数据: payload, |
| | | }); |
| | | } |
| | | // if (deps.tripStatusRef.current === TripStatus.InService) { |
| | | // console.log('[开始行程][送驾方向][Socket]', { |
| | | // 订单ID: getOrderId(deps.orderId), |
| | | // 原始方向: rawDirection, |
| | | // 归一化方向: normalizedDirection, |
| | | // 车辆点位数量: Array.isArray(payload?.content?.vehicleMoveAlongPoints) ? payload.content.vehicleMoveAlongPoints.length : undefined, |
| | | // Socket原始数据: payload, |
| | | // }); |
| | | // } |
| | | } |
| | | const route = safeParse(payload?.content?.route, {}); |
| | | const distanceMeters = Number(route?.distance); |
| | |
| | | import CreditPayPopup from './components/credit-pay-popup'; |
| | | import CouponSelectPopup from '../../components/coupon-select-popup'; |
| | | // 调试面板:模拟完整行程流转(仅 dev 构建可见) |
| | | import DebugOrderSimulatorPanel from './components/debug-order-simulator-panel'; |
| | | // import DebugOrderSimulatorPanel from './components/debug-order-simulator-panel'; |
| | | // 弹窗状态管理 |
| | | import usePopupManager from './hooks/use-popup-manager'; |
| | | // WebSocket 事件编排 |
| | |
| | | beforeUserEndSite: isPickupRouteChangeMapMode ? null : beforeUserEndSite, |
| | | arrivedMarkerReady, |
| | | arrivedTooltipReady, |
| | | onVehicleMarkerReady: vehicleState.notifyVehicleMarkerReady |
| | | onVehicleMarkerReady: vehicleState.notifyVehicleMarkerReady, |
| | | onCarMarkerWillRebuild: vehicleState.suspendVehicleMarker |
| | | }); // 地图标记状态容器 |
| | | const { |
| | | markers, |
| | |
| | | vehicleMarkerId: number; |
| | | arrivedMarkerReady: boolean; |
| | | onVehicleMarkerReady: () => void; |
| | | onCarMarkerWillRebuild: (newMarkerId: number) => void; |
| | | }; |
| | | |
| | | const defaultDeps: Deps = { |
| | |
| | | vehicleDirection: null, |
| | | vehicleMarkerId: 3, |
| | | arrivedMarkerReady: false, |
| | | onVehicleMarkerReady: () => { } |
| | | onVehicleMarkerReady: () => { }, |
| | | onCarMarkerWillRebuild: (_newMarkerId: number) => { } |
| | | }; |
| | | |
| | | export default function useMapMarkers(rawDeps?: Partial<Deps>) { |
| | | const deps = { ...defaultDeps, ...(rawDeps || {}) } as Deps; |
| | | // 内部"逻辑 markers"——真相态,所有内部判断(签名、是否含车标等)以此为准 |
| | | const [markers, setMarkers] = useState<any[]>([]); |
| | | // 对外"展示 markers"——返回给页面,通过 rebuild dance 制造短暂"无车标"窗口以重建原生 marker |
| | | const [displayMarkers, setDisplayMarkers] = useState<any[]>([]); |
| | | const markersRef = useRef<any[]>([]); |
| | | const markersSignatureRef = useRef<string>(""); |
| | | const carMarkerInitialized = useRef(false); |
| | | const tripStatus6MarkerRefreshedRef = useRef(false); |
| | | const rebuildCarMarkerTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| | | // 每次 rebuild 递增,用于派生新的 car marker id,避免原生层因 id 相同跳过销毁/重建导致旋转矩阵残留 |
| | | const carMarkerEpochRef = useRef(0); |
| | | |
| | | /** |
| | | * 派生新的 car marker id:base + ((epoch % 1000) + 1) * 1000 |
| | | * - 与业务 base id(3/30)和其他 marker(pickup=1, dropoff=2)不冲突 |
| | | * - epoch 取模 1000 防止无限增长 |
| | | */ |
| | | const deriveNewCarMarkerId = (baseId: number) => { |
| | | return baseId + ((carMarkerEpochRef.current % 1000) + 1) * 1000; |
| | | }; |
| | | |
| | | /** |
| | | * 当逻辑 markers 变化时,对展示 markers 做"先移除车标 → 100ms 后用新 id 恢复"的两阶段提交。 |
| | | * 关键:恢复时给车标分配一个全新 markerId(base + epoch 派生),强制原生层创建新 marker 实例, |
| | | * 不会因 id 相同被原生层复用,translateMarker 残留的旋转矩阵随之清除。 |
| | | */ |
| | | useEffect(() => { |
| | | if (rebuildCarMarkerTimerRef.current) { |
| | | clearTimeout(rebuildCarMarkerTimerRef.current); |
| | | rebuildCarMarkerTimerRef.current = null; |
| | | } |
| | | const hasCarMarker = markers.some((m: any) => m.id === deps.vehicleMarkerId); |
| | | if (!hasCarMarker) { |
| | | setDisplayMarkers(markers); |
| | | return; |
| | | } |
| | | carMarkerEpochRef.current += 1; |
| | | const newCarId = deriveNewCarMarkerId(deps.vehicleMarkerId); |
| | | // 通知外部:CarMarker 即将以 newCarId 重建,暂停 translateMarker 并切换到新 id |
| | | deps.onCarMarkerWillRebuild(newCarId); |
| | | // 第一步:展示层先去掉车标(按业务 base id 过滤),原生层销毁旧 car marker |
| | | setDisplayMarkers(markers.filter((m: any) => m.id !== deps.vehicleMarkerId)); |
| | | // 第二步:100ms 后用 newCarId 重新挂载车标,原生层创建全新 marker 实例 |
| | | rebuildCarMarkerTimerRef.current = setTimeout(() => { |
| | | rebuildCarMarkerTimerRef.current = null; |
| | | setDisplayMarkers(markers.map((m: any) => m.id === deps.vehicleMarkerId ? { ...m, id: newCarId } : m)); |
| | | }, 100); |
| | | return () => { |
| | | if (rebuildCarMarkerTimerRef.current) { |
| | | clearTimeout(rebuildCarMarkerTimerRef.current); |
| | | rebuildCarMarkerTimerRef.current = null; |
| | | } |
| | | }; |
| | | }, [markers, deps.vehicleMarkerId]); |
| | | |
| | | const shouldUsePureTranslateCarMarker = |
| | | [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) |
| | | && deps.statusRule.showCar; |
| | |
| | | }, [markers]); |
| | | |
| | | useEffect(() => { |
| | | const hasVehicleMarker = deps.statusRule.showCar && markers.some((item: any) => item.id === deps.vehicleMarkerId); |
| | | // 这里使用 displayMarkers + name 匹配(而非 id),因为 displayMarkers 中 car id 每次 rebuild 都会变成新 id |
| | | const hasVehicleMarker = deps.statusRule.showCar && displayMarkers.some((item: any) => item.name === '车辆位置'); |
| | | if (!hasVehicleMarker) { |
| | | return; |
| | | } |
| | | Taro.nextTick(() => { |
| | | deps.onVehicleMarkerReady(); |
| | | }); |
| | | }, [markers, deps.statusRule.showCar, deps.vehicleMarkerId, deps.onVehicleMarkerReady]); |
| | | }, [displayMarkers, deps.statusRule.showCar, deps.onVehicleMarkerReady]); |
| | | |
| | | useEffect(() => { |
| | | const pickupSite = |
| | |
| | | ]); |
| | | |
| | | return { |
| | | markers, |
| | | // 对外返回经过 rebuild dance 的展示态 markers;内部逻辑(applyMarkerState 等)仍读取 markers(逻辑态) |
| | | markers: displayMarkers, |
| | | setMarkers, |
| | | markersRef, |
| | | markersSignatureRef, |
| | |
| | | latitude: vehicleLocation.latitude, |
| | | longitude: vehicleLocation.longitude, |
| | | rotate: targetRotate, |
| | | markerId: vehicleMarkerId, |
| | | // 用 ref 而非 props:rebuild 期间 suspendVehicleMarker(newId) 已经把 ref 切到新 id, |
| | | // 必须读最新 ref 才能让 pending 的 markerId 与原生层新 marker 一致 |
| | | markerId: vehicleMarkerIdRef.current, |
| | | duration: hasLocationDelta ? TRANSLATE_DURATION_MS : ROTATE_ONLY_DURATION_MS |
| | | }; |
| | | flushTranslateTask(); |
| | |
| | | clearVehicleMarkerReadyTimer(); |
| | | }, []); |
| | | |
| | | /** |
| | | * 暂停 translateMarker 并切换到新的 markerId。 |
| | | * 在 CarMarker 重建期间调用:原生层即将销毁旧 marker、用 newMarkerId 创建新 marker, |
| | | * 这里同步把 vehicleMarkerIdRef 切到新 id,pending 任务的 markerId 也一并更新, |
| | | * 确保 ready 恢复后 translateMarker 作用于新 marker。 |
| | | * |
| | | * 关键:保留 pendingTranslateRef.current(只改 markerId)。 |
| | | * - 若 suspend 前已有 pending(最近一次推送写入但尚未 flush),ready 恢复后会直接消费它。 |
| | | * - 若 suspend 后再有新推送,translateCar useEffect 会用最新值覆盖 pending(markerId 用 ref 取最新)。 |
| | | */ |
| | | const suspendVehicleMarker = useCallback((newMarkerId: number) => { |
| | | isTranslatingRef.current = false; |
| | | lastAppliedTranslateTaskRef.current = null; |
| | | vehicleMarkerReadyRef.current = false; |
| | | clearVehicleMarkerReadyTimer(); |
| | | translateSessionIdRef.current += 1; |
| | | vehicleMarkerIdRef.current = newMarkerId; |
| | | if (pendingTranslateRef.current) { |
| | | pendingTranslateRef.current = { |
| | | ...pendingTranslateRef.current, |
| | | markerId: newMarkerId |
| | | }; |
| | | } |
| | | }, []); |
| | | |
| | | return { |
| | | vehicleLocation, |
| | | setVehicleLocation, |
| | |
| | | lastVehiclePointRef, |
| | | lastVehicleUpdateAtRef, |
| | | updateVehicleLocationThrottled, |
| | | notifyVehicleMarkerReady |
| | | notifyVehicleMarkerReady, |
| | | suspendVehicleMarker |
| | | }; |
| | | } |
| | |
| | | setVehicleMoveAlongPoints, |
| | | setVehicleDirection, |
| | | updateVehicleLocationThrottled, |
| | | notifyVehicleMarkerReady |
| | | notifyVehicleMarkerReady, |
| | | suspendVehicleMarker |
| | | } = useVehicleTracking({ |
| | | statusRule, |
| | | vehicleMarkerId, |
| | |
| | | vehicleMarkerId, |
| | | arrivedMarkerReady, |
| | | onVehicleMarkerReady: notifyVehicleMarkerReady, |
| | | onCarMarkerWillRebuild: suspendVehicleMarker, |
| | | }); // 地图 marker 状态容器 |
| | | |
| | | const { |
| | |
| | | return; |
| | | } |
| | | |
| | | console.log('收到 WebSocket 消息:', rawData); |
| | | // console.log('收到 WebSocket 消息:', rawData); |
| | | const data = this.normalizeIncomingMessage(rawData); |
| | | if (this.isPongMessage(data)) { |
| | | this.handlePong(connectionId); |