| | |
| | | }; |
| | | |
| | | // 使用 eraseLines 擦除已行驶路段(状态4/6专用) |
| | | const erasePassedRouteSegments = (newPoints: any[], served?: number) => { |
| | | const erasePassedRouteSegments = (newPoints: any[]) => { |
| | | const oldPoints = currentFullRouteRef.current; |
| | | if (!oldPoints?.length || !newPoints?.length) return newPoints?.[0]; |
| | | if (!oldPoints?.length || !newPoints?.length) return; |
| | | |
| | | const newFirst = newPoints[0]; |
| | | let bestMatchIndex = -1; |
| | | let bestDistance = Infinity; |
| | | const startIndex = lastErasedIndexRef.current; |
| | | |
| | | // 如果后端提供了明确的已行驶点数 served (作为增量:这次少了几个点) |
| | | if (typeof served === 'number' && !Number.isNaN(served) && served >= 0) { |
| | | bestMatchIndex = startIndex + served; |
| | | bestMatchIndex = Math.min(bestMatchIndex, oldPoints.length - 1); |
| | | } else { |
| | | let bestDistance = Infinity; |
| | | // 降级使用距离匹配逻辑,移除不合理的退出条件,使匹配更加敏感 |
| | | for (let i = startIndex; i < oldPoints.length; i++) { |
| | | const dist = getDistance( |
| | | oldPoints[i].latitude, oldPoints[i].longitude, |
| | | newFirst.latitude, newFirst.longitude |
| | | ); |
| | | if (dist < bestDistance) { |
| | | bestDistance = dist; |
| | | bestMatchIndex = i; |
| | | } |
| | | // 如果找到了非常近的点(例如10米内),就可以认为这就是车辆当前位置所在的索引,提前结束查找 |
| | | if (bestDistance < 10) break; |
| | | for (let i = startIndex; i < oldPoints.length; i++) { |
| | | const dist = getDistance( |
| | | oldPoints[i].latitude, oldPoints[i].longitude, |
| | | newFirst.latitude, newFirst.longitude |
| | | ); |
| | | if (dist < bestDistance) { |
| | | bestDistance = dist; |
| | | bestMatchIndex = i; |
| | | } |
| | | // 已找到足够近的匹配点且距离开始增大,提前退出 |
| | | if (dist > bestDistance * 3 && bestDistance < 100) break; |
| | | } |
| | | |
| | | if (bestMatchIndex < startIndex) { |
| | | // 路线发生较大变化,回退为全量重绘 |
| | | currentFullRouteRef.current = [...newPoints]; |
| | | lastErasedIndexRef.current = 0; |
| | | lastRoutePointRef.current = null; |
| | | lastRouteUpdateAtRef.current = 0; |
| | | setRoutePoints([...newPoints]); |
| | | return newPoints[0]; |
| | | } |
| | | |
| | | // 只要找到了比之前更大的索引,就进行擦除(无论距离多远,只要是往后走就应该擦除) |
| | | if (bestMatchIndex > startIndex) { |
| | | if (bestMatchIndex >= startIndex && bestDistance < 500) { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | |
| | | const eraseIndex = Math.max(0, bestMatchIndex - 1); |
| | | const erasePoint = oldPoints[bestMatchIndex]; |
| | | |
| | | mapCtx.eraseLines({ |
| | | lines: [{ |
| | | id: 2001, |
| | | index: eraseIndex, |
| | | point: { |
| | | latitude: erasePoint.latitude, |
| | | longitude: erasePoint.longitude, |
| | | latitude: newFirst.latitude, |
| | | longitude: newFirst.longitude, |
| | | }, |
| | | clear: true |
| | | }], |
| | |
| | | }); |
| | | |
| | | lastErasedIndexRef.current = bestMatchIndex; |
| | | } else { |
| | | // 路线发生较大变化,回退为全量重绘 |
| | | currentFullRouteRef.current = [...newPoints]; |
| | | lastErasedIndexRef.current = 0; |
| | | lastRoutePointRef.current = null; |
| | | lastRouteUpdateAtRef.current = 0; |
| | | setRoutePoints([...newPoints]); |
| | | } |
| | | |
| | | const nextIndex = Math.min(bestMatchIndex + 1, oldPoints.length - 1); |
| | | const pointA = oldPoints[bestMatchIndex]; |
| | | const pointB = oldPoints[nextIndex]; |
| | | |
| | | return getProjectedPoint(newFirst, pointA, pointB); |
| | | }; |
| | | |
| | | // 更新车辆位置(移除节流,实时更新) |
| | |
| | | }; |
| | | |
| | | orderRouteHandlerRef.current = (payload) => { |
| | | if ([5, 8, 9].includes(tripStatusRef.current)) { |
| | | if (tripStatusRef.current === 5) { |
| | | return; |
| | | } |
| | | const points = payload?.points || []; |
| | |
| | | // 状态4/6:使用 eraseLines 擦除已行驶路段,不再全量重绘 |
| | | const isEraseStatus = [4, 6].includes(tripStatusRef.current); |
| | | if (isEraseStatus) { |
| | | let vehiclePoint = points?.[0]; |
| | | const firstPoint = points?.[0]; |
| | | if (orderRouteInitializedRef.current && currentFullRouteRef.current.length > 0) { |
| | | // 后续更新:擦除已行驶路段 |
| | | let servedCount = 0; |
| | | try { |
| | | const servedData = JSON.parse(payload?.content?.route || '{}')?.served; |
| | | // 尝试将 served 解析为数组,获取其长度,否则如果是数字则直接使用,或者是字符串数组再解析 |
| | | if (Array.isArray(servedData)) { |
| | | servedCount = servedData.length; |
| | | } else if (typeof servedData === 'string') { |
| | | const parsedServed = JSON.parse(servedData); |
| | | if (Array.isArray(parsedServed)) { |
| | | servedCount = parsedServed.length; |
| | | } |
| | | } else if (typeof servedData === 'number') { |
| | | servedCount = servedData; |
| | | } |
| | | } catch (e) { |
| | | console.error('解析 served 数据失败', e); |
| | | } |
| | | vehiclePoint = erasePassedRouteSegments(points, servedCount) || points?.[0]; |
| | | erasePassedRouteSegments(points); |
| | | } else { |
| | | // 首次绘制:全量渲染并存储完整路线 |
| | | orderRouteInitializedRef.current = true; |
| | |
| | | applyRoutePoints(points); |
| | | }); |
| | | } |
| | | if (vehiclePoint && typeof vehiclePoint.latitude === 'number' && typeof vehiclePoint.longitude === 'number') { |
| | | updateVehicleLocationThrottled(vehiclePoint.latitude, vehiclePoint.longitude, true); |
| | | |
| | | // 延迟执行车辆位置更新,确保路线擦除(同步调用或极短时间生效)先于小车平移发生 |
| | | if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') { |
| | | setTimeout(() => { |
| | | updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true); |
| | | }, 50); // 50ms 延迟足以让擦除指令先发给底层地图 |
| | | } |
| | | return; |
| | | } |
| | |
| | | useEffect(() => { |
| | | const pickupSite = startSite?.latitude && startSite?.longitude ? startSite : Taro.getStorageSync('startSite'); |
| | | const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite'); |
| | | const useAndroidNativeTrip1Callout = isAndroid && [1, 8, 9, 10].includes(tripStatus); |
| | | const useAndroidNativeTrip1Callout = isAndroid && [1, 5, 8, 9, 10].includes(tripStatus); |
| | | const pickupMarker = pickupSite?.latitude && pickupSite?.longitude ? { |
| | | id: 1, |
| | | latitude: pickupSite.latitude, |
| | |
| | | if (!carMarkerInitialized.current) return; |
| | | if (!vehicleLocation) return; |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | // Taro.nextTick(() => { |
| | | mapCtx.translateMarker({ |
| | | markerId: vehicleMarkerId, |
| | | destination: { |
| | | latitude: vehicleLocation.latitude, |
| | | longitude: vehicleLocation.longitude, |
| | | }, |
| | | autoRotate: false, |
| | | rotate: typeof vehicleDirection === 'number' ? vehicleDirection : 0, |
| | | duration: 100, |
| | | fail: (err) => { |
| | | console.log(err); |
| | | |
| | | // 增加延迟,确保 markers 已经在原生视图中完成渲染,避免 translateMarker 找不到 markerId 导致 fail |
| | | const timer = setTimeout(() => { |
| | | mapCtx.translateMarker({ |
| | | markerId: vehicleMarkerId, |
| | | destination: { |
| | | latitude: vehicleLocation.latitude, |
| | | longitude: vehicleLocation.longitude, |
| | | }, |
| | | autoRotate: false, |
| | | rotate: typeof vehicleDirection === 'number' ? vehicleDirection : 0, |
| | | duration: 100, |
| | | fail: (err) => { |
| | | console.log('translateMarker fail:', err, 'markerId:', vehicleMarkerId); |
| | | // 如果移动失败,可能是 marker 被意外卸载,强制解除一次拦截以重绘 |
| | | tripStatus6MarkerRefreshedRef.current = false; |
| | | }, |
| | | }); |
| | | }, 100); |
| | | |
| | | }, |
| | | }); |
| | | // }); |
| | | return () => clearTimeout(timer); |
| | | }, [tripStatus, vehicleLocation, vehicleDirection, vehicleMarkerId]); |
| | | |
| | | const bindSocketListeners = () => { |
| | |
| | | }) |
| | | } |
| | | |
| | | // 返回当前位置 |
| | | // 返回当前位置或车辆位置 |
| | | const toOrigin = () => { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | mapCtx.moveToLocation(); |
| | | // 如果是状态6(车辆行驶中)且有车辆位置,则回到车辆位置 |
| | | if (tripStatus === 6 && vehicleLocation?.latitude && vehicleLocation?.longitude) { |
| | | mapCtx.moveToLocation({ |
| | | latitude: vehicleLocation.latitude, |
| | | longitude: vehicleLocation.longitude, |
| | | }); |
| | | // 恢复视口锁定状态,让地图重新跟随车辆 |
| | | tripStatus6ViewportLockedRef.current = false; |
| | | } else { |
| | | mapCtx.moveToLocation(); |
| | | } |
| | | } |
| | | |
| | | const setPopupVisible = (setter: (value: boolean) => void, visible: boolean) => () => setter(visible); |
| | |
| | | > |
| | | {/* 自定义callout */} |
| | | { |
| | | !(isAndroid && [1, 8, 9].includes(tripStatus)) && markers.map((item, index) => { |
| | | !(isAndroid && [1, 5, 8, 9, 10].includes(tripStatus)) && markers.map((item, index) => { |
| | | const calloutKey = `callout-${item.id}-${item.name || ''}`; |
| | | const isWaiting = [2, 3].includes(tripStatus) && index === 0; |
| | | |