hejianhao
2026-04-27 fe96791de5cc7bd67d41eb63647998f40e2ce4a4
Merge branch 'feature_last_fix' into feature_v1.0
8个文件已修改
1172 ■■■■■ 已修改文件
src/app.tsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-actions.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 1091 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app.tsx
@@ -16,6 +16,7 @@
import { getStorage } from './utils/tools';
import { jumpTo } from './utils/jump';
import { clearSubscribeCount } from './utils/subscribe';
import wsManager from './utils/websocket';
import './app.less';
import { dispatchPreRouterUpdate } from './actions/router';
@@ -93,6 +94,7 @@
  }
  componentDidShow() {
    wsManager.onAppShow();
    if (ENV === 'weapp' && Taro.canIUse('onBeforePageUnload') && Taro.canIUse('offBeforePageUnload')) {
      // @ts-ignore
      wx.onBeforePageUnload(this.beforePageUnload);
@@ -126,6 +128,7 @@
  }
  componentDidHide() {
    wsManager.onAppHide();
    if (ENV === 'weapp' && Taro.canIUse('offBeforePageUnload')) {
      // @ts-ignore
      wx.offBeforePageUnload(this.beforePageUnload);
src/pages/robotaxi/home/index.less
@@ -693,6 +693,8 @@
  }
  .product-name {
    position: relative;
    z-index: 10;
    margin-top: 10px;
    text-align: center;
    font-size: 24px;
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
@@ -87,7 +87,11 @@
            return () => { active = false; };
        }
        const startSite = Taro.getStorageSync('startSite');
        getWaitTime({ latitude: startSite?.latitude, longitude: startSite?.longitude })
        getWaitTime({
            latitude: startSite?.latitude,
            longitude: startSite?.longitude,
            startPointId: startSite?.id ?? ''
        })
            .then((res) => {
                const minutes = Number(res);
                if (active && !Number.isNaN(minutes)) {
src/robotaxi/pages/begin-route/hooks/use-order-actions.ts
@@ -61,7 +61,14 @@
  /** 更新步行路线 */
  routeUpdateWalkingRouteForStart: (startSite: any) => void;
  /** 请求预估路线 */
  routeRequestRouteAndFare: (originLat: number, originLng: number, destLat: number, destLng: number, force?: boolean) => void;
  routeRequestRouteAndFare: (
    originLat: number,
    originLng: number,
    destLat: number,
    destLng: number,
    startPointId: string | number,
    force?: boolean
  ) => void;
};
/**
@@ -311,7 +318,7 @@
    const s = Taro.getStorageSync('startSite');
    const e = Taro.getStorageSync('endSite');
    if (s?.latitude && s?.longitude && e?.longitude && e?.latitude) {
      deps.routeRequestRouteAndFare(s.latitude, s.longitude, e.latitude, e.longitude, true);
      deps.routeRequestRouteAndFare(s.latitude, s.longitude, e.latitude, e.longitude, s?.id, true);
    }
  };
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts
@@ -17,8 +17,15 @@
  setLatitude: (value: number | null) => void;
  setLongitude: (value: number | null) => void;
  getLocationInfo: () => void;
  routeRequestRouteAndFare: (originLat: number, originLng: number, destLat: number, destLng: number, force?: boolean) => void;
  routeRequestOrderRoute: (orderId: string, status: TripStatus) => void;
  routeRequestRouteAndFare: (
    originLat: number,
    originLng: number,
    destLat: number,
    destLng: number,
    startPointId: string | number,
    force?: boolean
  ) => void;
  routeRequestOrderRoute: (orderId: string, status: number) => void;
  routeUpdateWalkingRouteForStart: (startSite: any) => void;
  getCurrentTripStatus: () => TripStatus;
  getCurrentLongitude: () => number | null;
@@ -103,7 +110,7 @@
    const originLng = s?.longitude || deps.getCurrentLongitude();
    const originLat = s?.latitude || deps.getCurrentLatitude();
    if (cachedTripStatus === TripStatus.PublishRoute && originLng && originLat && e?.longitude && e?.latitude) {
      deps.routeRequestRouteAndFare(originLat, originLng, e.latitude, e.longitude, true);
      deps.routeRequestRouteAndFare(originLat, originLng, e.latitude, e.longitude, s?.id, true);
    }
    if (deps.getCurrentTripStatus() === TripStatus.PublishRoute && s?.latitude && s?.longitude) {
      deps.routeUpdateWalkingRouteForStart(s);
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts
@@ -28,6 +28,7 @@
  force?: boolean;
  refreshMode?: 'full' | 'price_only';
  couponId?: string;
  startPointId?: string | number;
};
const defaultDeps: Deps = {
@@ -373,19 +374,38 @@
    originLng: number,
    destLat: number,
    destLng: number,
    optionsOrForce: boolean | RequestRouteAndFareOptions = false
    startPointIdOrOptions: string | number | boolean | RequestRouteAndFareOptions = '',
    forceArg = false
  ) => {
    const resolvedOptions: RequestRouteAndFareOptions = typeof optionsOrForce === 'boolean'
      ? { force: optionsOrForce, refreshMode: 'full' }
      : { force: false, refreshMode: 'full', ...(optionsOrForce || {}) };
    // 兼容两种调用:旧版(startPointId, force) 与 新版(options)
    const resolvedOptions: RequestRouteAndFareOptions = {
      force: false,
      refreshMode: 'full',
      couponId: '',
      startPointId: '',
    };
    if (typeof startPointIdOrOptions === 'boolean') {
      resolvedOptions.force = startPointIdOrOptions;
    } else if (
      typeof startPointIdOrOptions === 'object'
      && startPointIdOrOptions !== null
      && !Array.isArray(startPointIdOrOptions)
    ) {
      Object.assign(resolvedOptions, startPointIdOrOptions);
    } else {
      resolvedOptions.startPointId = startPointIdOrOptions ?? '';
      resolvedOptions.force = !!forceArg;
    }
    const force = !!resolvedOptions.force;
    const refreshMode = resolvedOptions.refreshMode || 'full';
    const startPointId = resolvedOptions.startPointId ?? '';
    const now = Date.now();
    if (now - lastRouteAndFareRequestAtRef.current < 1000) {
      return;
    }
    lastRouteAndFareRequestAtRef.current = now;
    const requestKey = `${originLng},${originLat}-${destLng},${destLat}`;
    const requestKey = `${originLng},${originLat}-${destLng},${destLat}-${startPointId ?? ''}`;
    if (!force && requestKey === routeRequestKeyRef.current) {
      return;
    }
@@ -404,6 +424,7 @@
        latitude: originLat,
        longitude: originLng
      },
      startPointId,
    };
    if (couponId) {
      params.userCouponId = couponId;
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
@@ -33,6 +33,8 @@
  orderDetailRouteRequestIdRef: React.MutableRefObject<number>;
  /** 当前路线点 */
  routePoints: any[];
  /** 起点 */
  startSite: any;
  /** 结束点 */
  endSite: any;
  /** 当前经度 */
@@ -112,7 +114,14 @@
  /** 费用信息 */
  fareEstimate: any;
  /** 请求预估路线 */
  routeRequestRouteAndFare: (originLat: number, originLng: number, destLat: number, destLng: number, force?: boolean) => void;
  routeRequestRouteAndFare: (
    originLat: number,
    originLng: number,
    destLat: number,
    destLng: number,
    startPointId: string | number,
    force?: boolean
  ) => void;
  /** 更新步行路线 */
  routeUpdateWalkingRouteForStart: (startSite: any) => void;
  /** 应用路线点 */
@@ -167,7 +176,7 @@
      const s = Taro.getStorageSync('startSite') || {};
      const e = Taro.getStorageSync('endSite') || {};
      if (s?.latitude && s?.longitude && e?.latitude && e?.longitude) {
        deps.routeRequestRouteAndFare(s.latitude, s.longitude, e.latitude, e.longitude, true);
        deps.routeRequestRouteAndFare(s.latitude, s.longitude, e.latitude, e.longitude, s?.id, true);
      }
    };
    updateRequoteElapsed();
@@ -354,7 +363,7 @@
    const originLng = deps.startSite?.longitude || deps.longitude;
    const originLat = deps.startSite?.latitude || deps.latitude;
    if (deps.tripStatus === TripStatus.PublishRoute && originLng && originLat && deps.endSite?.longitude && deps.endSite?.latitude) {
      deps.routeRequestRouteAndFare(originLat, originLng, deps.endSite.latitude, deps.endSite.longitude);
      deps.routeRequestRouteAndFare(originLat, originLng, deps.endSite.latitude, deps.endSite.longitude, deps.startSite?.id, false);
    }
    if (deps.tripStatus === TripStatus.PublishRoute && deps.startSite?.latitude && deps.startSite?.longitude) {
      deps.routeUpdateWalkingRouteForStart(deps.startSite);
src/utils/websocket.ts
@@ -5,29 +5,38 @@
type MessageCallback = (data: any) => void;
enum SocketState {
  IDLE = 'idle',
  CONNECTING = 'connecting',
  CONNECTED = 'connected',
}
class WebSocketManager {
  private static instance: WebSocketManager;
  private socketTask: Taro.SocketTask | null = null;
  private url: string = '';
  private isConnected: boolean = false;
  private isConnecting: boolean = false;
  private isManualClose: boolean = false;
  private reconnectAttempts: number = 0;
  private maxReconnectAttempts: number = 5;
  private reconnectTimer: any = null;
  private heartbeatTimer: any = null;
  private heartbeatTimeoutTimer: any = null; // 用于检测心跳超时的定时器
  private heartbeatInterval: number = 10000; // 10秒心跳
  private socketTask: Taro.SocketTask | null = null; // 当前生效的 socketTask
  private url: string = ''; // 当前连接地址
  private socketState: SocketState = SocketState.IDLE; // 当前连接状态
  private isConnected: boolean = false; // 对外兼容的连接态
  private isConnecting: boolean = false; // 对外兼容的连接中标记
  private isManualClose: boolean = false; // 是否手动关闭
  private isAppActive: boolean = true; // 应用是否处于前台
  private shouldReconnectWhenActive: boolean = false; // 回前台后是否需要补重连
  private reconnectAttempts: number = 0; // 当前连续重连次数
  private reconnectTimer: any = null; // 重连定时器
  private heartbeatTimer: any = null; // 心跳发送定时器
  private heartbeatTimeoutTimer: any = null; // 单次等待 pong 的超时定时器
  private heartbeatInterval: number = 15000; // 心跳间隔
  private heartbeatTimeout: number = 10000; // 单次心跳等待 pong 的超时时间
  private maxHeartbeatMisses: number = 2; // 连续丢失 pong 的最大次数
  private locationReportTimer: any = null; // 位置上报定时器
  private locationReportInterval: number = 35000; // 35秒上报一次位置
  private heartbeatTimeout: number = 3000;   // 3秒收不到响应即视为掉线
  private listeners: Map<string, MessageCallback[]> = new Map();
  private globalEventsBound: boolean = false;
  private isHeartbeatMessage(data: any) {
    const type = String(data?.type || '').toLowerCase();
    return type === 'ping' || type === 'pong';
  }
  private locationReportInterval: number = 35000; // 35 秒上报一次位置
  private listeners: Map<string, MessageCallback[]> = new Map(); // 事件监听器
  private globalEventsBound: boolean = false; // 全局事件是否已绑定
  private connectionSeed: number = 0; // 连接代次种子
  private activeConnectionId: number = 0; // 当前生效连接代次
  private heartbeatSeq: number = 0; // 心跳序号
  private pendingHeartbeatSeq: number | null = null; // 当前等待 pong 的心跳序号
  private missedHeartbeatCount: number = 0; // 连续未收到 pong 的次数
  private constructor() { }
@@ -39,396 +48,157 @@
  }
  /**
   * 开始定时位置上报。
   * 应用回到前台时恢复心跳或补发重连。
   */
  private startLocationReport() {
    this.stopLocationReport();
    this.sendLocationMessage();
    this.locationReportTimer = setInterval(() => {
      this.sendLocationMessage();
    }, this.locationReportInterval);
  }
  /**
   * 停止定时位置上报。
   */
  private stopLocationReport() {
    if (this.locationReportTimer) {
      clearInterval(this.locationReportTimer);
      this.locationReportTimer = null;
    }
  }
  /**
   * 发送位置消息。
   */
  private async sendLocationMessage() {
    if (!this.isConnected) return;
    const uid = await getStorage(_UID) || '-1';
    const userId = uid.toString();
    if (!userId || userId === '-1') return;
    try {
      const locationRes = await Taro.getLocation({
        type: 'gcj02',
        isHighAccuracy: true,
        highAccuracyExpireTime: 3000
      } as any);
      const longitude = Number(locationRes?.longitude);
      const latitude = Number(locationRes?.latitude);
      if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) return;
      this.send({
        type: 'location',
        longitude,
        latitude,
        userId,
        timestamp: Date.now() * 1000
      });
    } catch (error) {
      // 定时定位失败静默处理,避免干扰业务流程
    }
  }
  /**
   * 初始化连接
   * @param url WebSocket地址
   */
  public async connect(url: string) {
    if (this.socketTask || this.isConnected || this.isConnecting) {
      console.log('WebSocket 已连接或正在连接中');
  public onAppShow() {
    this.isAppActive = true;
    this.shouldReconnectWhenActive = false;
    if (this.isConnected && this.activeConnectionId > 0) {
      this.startHeartbeat(this.activeConnectionId);
      this.startLocationReport(this.activeConnectionId);
      return;
    }
    this.url = url;
    this.isConnecting = true;
    this.isManualClose = false;
    console.log(`正在连接 WebSocket: ${url}`);
    const uid = await getStorage(_UID) || '-1';
    const task = Taro.connectSocket({
      header: {
        'XP-Uid': uid.toString(),
      },
      url: this.url,
      success: () => { },
      fail: (err) => {
        console.error('WebSocket 连接失败:', err);
        this.isConnecting = false;
        this.handleReconnect();
      }
    });
    const maybeTask: any = task as any;
    if (maybeTask && typeof maybeTask.onOpen === 'function') {
      this.socketTask = maybeTask;
      this.isConnecting = false;
      this.initEventListeners();
      return;
    }
    if (maybeTask && typeof maybeTask.then === 'function') {
      maybeTask
        .then((resolvedTask) => {
          this.socketTask = resolvedTask;
          this.isConnecting = false;
          this.initEventListeners();
        })
        .catch((err) => {
          console.error('WebSocket 连接失败:', err);
          this.isConnecting = false;
          this.handleReconnect();
        });
      return;
    }
    this.socketTask = null;
    this.isConnecting = false;
    this.initEventListeners();
  }
  public isConnectedStatus() {
    return this.isConnected;
  }
  /**
   * 初始化事件监听
   */
  private initEventListeners() {
    if (this.socketTask && typeof this.socketTask.onOpen === 'function') {
      this.socketTask.onOpen(async () => {
        console.log('WebSocket 连接已打开');
        this.isConnected = true;
        this.isConnecting = false;
        this.reconnectAttempts = 0;
        this.startHeartbeat();
        this.startLocationReport();
        const uid = await getStorage(_UID) || '-1';
        this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() });
        this.notifyListeners('open', { status: 'connected' });
      });
      this.socketTask.onMessage((res) => {
        this.resetHeartbeatTimeout();
        let data: any = {};
        try {
          data = safeParse(res.data as string, {});
          console.log('收到 WebSocket 消息:', res.data);
        } catch (e) {
          console.warn('解析 WebSocket data 失败:', res.data);
          this.notifyListeners('message', res.data);
          return;
        }
        const content = safeParse(data.content || '{}', {});
        try {
          this.handleIncomingMessage(data, content);
          if (data.type) {
            this.notifyListeners(data.type, data);
          }
          this.notifyListeners('message', data);
        } catch (e) {
          console.error('处理 WebSocket 消息出错:', e);
        }
      });
      this.socketTask.onClose((res) => {
        console.log('WebSocket 连接已关闭', res);
        this.isConnected = false;
        this.isConnecting = false;
        this.stopHeartbeat();
        this.stopLocationReport();
        this.socketTask = null;
        if (!this.isManualClose) {
          this.handleReconnect();
        }
        this.notifyListeners('close', res);
      });
      this.socketTask.onError((err) => {
        console.error('WebSocket 错误:', err);
        this.isConnected = false;
        this.isConnecting = false;
        this.socketTask = null;
        this.stopHeartbeat();
        this.stopLocationReport();
        if (!this.isManualClose) {
          this.handleReconnect();
        }
        this.notifyListeners('error', err);
      });
      return;
    }
    if (this.globalEventsBound) return;
    this.globalEventsBound = true;
    Taro.onSocketOpen(async () => {
      console.log('WebSocket 连接已打开');
      this.isConnected = true;
      this.isConnecting = false;
      this.reconnectAttempts = 0;
      this.startHeartbeat();
      this.startLocationReport();
      const uid = await getStorage(_UID) || '-1';
      this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() });
      this.notifyListeners('open', { status: 'connected' });
    });
    Taro.onSocketMessage((res) => {
      this.resetHeartbeatTimeout();
      let data: any = {};
      try {
        data = safeParse(res.data as string, {});
        console.log('收到 WebSocket 消息:', res.data);
      } catch (e) {
        console.warn('解析 WebSocket data 失败:', res.data);
        this.notifyListeners('message', res.data);
        return;
      }
      const content = safeParse(data.content || '{}', {});
      try {
        this.handleIncomingMessage(data, content);
        if (data.type) {
          this.notifyListeners(data.type, data);
        }
        this.notifyListeners('message', data);
      } catch (e) {
        console.error('处理 WebSocket 消息出错:', e);
      }
    });
    Taro.onSocketClose((res) => {
      console.log('WebSocket 连接已关闭', res);
      this.isConnected = false;
      this.isConnecting = false;
      this.stopHeartbeat();
      this.stopLocationReport();
      this.socketTask = null;
      if (!this.isManualClose) {
        this.handleReconnect();
      }
      this.notifyListeners('close', res);
    });
    Taro.onSocketError((err) => {
      console.error('WebSocket 错误:', err);
      this.isConnected = false;
      this.isConnecting = false;
      this.socketTask = null;
      this.stopHeartbeat();
      this.stopLocationReport();
      if (!this.isManualClose) {
        this.handleReconnect();
      }
      this.notifyListeners('error', err);
    });
  }
  /**
   * 发送消息
   * @param data 消息内容
   */
  public send(data: any) {
    if (!this.isConnected) {
      console.warn('WebSocket 未连接,消息未发送:', data);
      return;
    }
    const message = typeof data === 'string' ? data : JSON.stringify(data);
    if (this.socketTask) {
      this.socketTask.send({
        data: message,
        fail: (err) => {
          console.error('发送消息失败:', err);
        }
      });
    } else {
      Taro.sendSocketMessage({
        data: message,
        fail: (err) => {
          console.error('发送消息失败:', err);
        }
      });
    if (!this.isManualClose && this.url) {
      this.connect(this.url);
    }
  }
  /**
   * 关闭连接
   * 应用进入后台时暂停心跳,避免后台定时器抖动误判掉线。
   */
  public close() {
    this.isManualClose = true;
  public onAppHide() {
    this.isAppActive = false;
    this.stopHeartbeat();
    this.stopLocationReport();
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
      this.shouldReconnectWhenActive = true;
    }
    if (this.socketTask) {
      this.socketTask.close({
        code: 1000,
        reason: '用户主动关闭连接'
      });
      this.socketTask = null;
    }
    this.isConnected = false;
    this.isConnecting = false;
  }
  /**
   * 处理重连
   * 初始化连接。
   * @param url WebSocket 地址
   */
  private handleReconnect() {
    if (this.reconnectTimer) return;
  public async connect(url: string) {
    if (!url) {
      return;
    }
    if (this.isConnected && this.url === url) {
      return;
    }
    if (this.isConnecting && this.url === url) {
      return;
    }
    // 固定约 3 秒重连一次,并加上 0~1 秒的随机抖动
    const delay = 3000 + Math.floor(Math.random() * 1000);
    console.log(`正在尝试重连,延迟 ${delay}ms... (第 ${this.reconnectAttempts + 1} 次尝试)`);
    this.url = url;
    this.isManualClose = false;
    this.shouldReconnectWhenActive = false;
    this.isConnecting = true;
    this.socketState = SocketState.CONNECTING;
    this.clearReconnectTimer();
    this.reconnectTimer = setTimeout(() => {
      // 防止数字溢出(无限重试时)
      if (this.reconnectAttempts < 9999) {
        this.reconnectAttempts++;
    const connectionId = this.createConnectionId();
    console.log(`正在连接 WebSocket: ${url},连接代次:${connectionId}`);
    try {
      const uid = await this.getValidUid();
      if (!this.isCurrentConnection(connectionId)) {
        return;
      }
      this.reconnectTimer = null;
      this.connect(this.url);
    }, delay);
  }
      if (!uid) {
        console.warn('WebSocket 连接已取消,UID 无效');
        this.markConnectionClosed(connectionId);
        return;
      }
  /**
   * 开启心跳
   */
  private startHeartbeat() {
    this.stopHeartbeat();
    this.heartbeatTimer = setInterval(() => {
      if (this.isConnected) {
        this.send({ type: 'ping', timestamp: Date.now() });
        // 发送 ping 后,启动一个超时检测
        // 如果在配置的时间内没有收到任何消息(pong 或业务消息),则认为连接已假死
        if (this.heartbeatTimeoutTimer) {
          clearTimeout(this.heartbeatTimeoutTimer);
      const task = Taro.connectSocket({
        header: {
          'XP-Uid': uid,
        },
        url: this.url,
        success: () => { },
        fail: (err) => {
          if (!this.isCurrentConnection(connectionId)) {
            return;
          }
          console.error('WebSocket 连接失败:', err);
          this.markConnectionClosed(connectionId);
          this.scheduleReconnect(connectionId, 'connect_fail');
        }
        this.heartbeatTimeoutTimer = setTimeout(() => {
          console.warn('心跳超时,未收到服务器响应,主动断开 WebSocket 连接');
          this.closeAndReconnect();
        }, this.heartbeatTimeout);
      });
      const maybeTask: any = task as any;
      if (maybeTask && typeof maybeTask.onOpen === 'function') {
        this.attachTask(connectionId, maybeTask);
        return;
      }
    }, this.heartbeatInterval);
  }
  /**
   * 停止心跳
   */
  private stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
    if (this.heartbeatTimeoutTimer) {
      clearTimeout(this.heartbeatTimeoutTimer);
      this.heartbeatTimeoutTimer = null;
    }
  }
  /**
   * 收到消息时重置心跳超时检测
   */
  private resetHeartbeatTimeout() {
    if (this.heartbeatTimeoutTimer) {
      clearTimeout(this.heartbeatTimeoutTimer);
      this.heartbeatTimeoutTimer = null;
    }
  }
  /**
   * 主动关闭并重连 (处理假死状态)
   */
  private closeAndReconnect() {
    // 强制关闭当前连接
    if (this.socketTask) {
      try {
        this.socketTask.close({
          code: 1000,
          reason: '心跳超时主动关闭'
        });
      } catch (e) {
        console.error('强制关闭 Socket 失败:', e);
      if (maybeTask && typeof maybeTask.then === 'function') {
        maybeTask
          .then((resolvedTask: Taro.SocketTask) => {
            if (!this.isCurrentConnection(connectionId)) {
              this.closeTaskSilently(resolvedTask, '过期连接任务关闭');
              return;
            }
            this.attachTask(connectionId, resolvedTask);
          })
          .catch((err: any) => {
            if (!this.isCurrentConnection(connectionId)) {
              return;
            }
            console.error('WebSocket 连接失败:', err);
            this.markConnectionClosed(connectionId);
            this.scheduleReconnect(connectionId, 'connect_promise_fail');
          });
        return;
      }
      this.socketTask = null;
      this.bindGlobalEventListeners();
    } catch (error) {
      if (!this.isCurrentConnection(connectionId)) {
        return;
      }
      console.error('初始化 WebSocket 连接失败:', error);
      this.markConnectionClosed(connectionId);
      this.scheduleReconnect(connectionId, 'connect_exception');
    }
    this.isConnected = false;
    this.isConnecting = false;
  }
  /**
   * 对外暴露当前连接状态。
   */
  public isConnectedStatus() {
    return this.isConnected;
  }
  /**
   * 发送消息。
   * @param data 消息内容
   */
  public send(data: any) {
    this.sendInternal(data, { silentWhenDisconnected: false });
  }
  /**
   * 主动关闭连接。
   */
  public close() {
    const closedConnectionId = this.activeConnectionId;
    this.isManualClose = true;
    this.shouldReconnectWhenActive = false;
    this.clearReconnectTimer();
    this.stopHeartbeat();
    // 立即触发重连
    if (!this.isManualClose) {
      this.handleReconnect();
    }
    this.stopLocationReport();
    this.invalidateCurrentConnection();
    this.closeTaskSilently(this.socketTask, '用户主动关闭连接');
    this.socketTask = null;
    this.markConnectionClosed(closedConnectionId);
  }
  /**
   * 注册事件监听
   * 注册事件监听。
   * @param event 事件名称
   * @param callback 回调函数
   */
@@ -443,7 +213,7 @@
  }
  /**
   * 移除事件监听
   * 移除事件监听。
   * @param event 事件名称
   * @param callback 回调函数
   */
@@ -459,7 +229,541 @@
  }
  /**
   * 通知监听器
   * 创建新的连接代次,并让旧连接回调全部失效。
   */
  private createConnectionId() {
    this.connectionSeed += 1;
    this.activeConnectionId = this.connectionSeed;
    return this.activeConnectionId;
  }
  /**
   * 当前连接失效后让旧回调不再影响全局状态。
   */
  private invalidateCurrentConnection() {
    this.connectionSeed += 1;
    this.activeConnectionId = this.connectionSeed;
    this.pendingHeartbeatSeq = null;
  }
  /**
   * 判断回调是否仍然属于当前连接代次。
   */
  private isCurrentConnection(connectionId: number) {
    return connectionId > 0 && connectionId === this.activeConnectionId;
  }
  /**
   * 绑定当前 socketTask,并注册基于连接代次的回调。
   */
  private attachTask(connectionId: number, task: Taro.SocketTask) {
    if (!this.isCurrentConnection(connectionId)) {
      this.closeTaskSilently(task, '过期连接任务关闭');
      return;
    }
    this.socketTask = task;
    this.bindTaskEventListeners(connectionId, task);
  }
  /**
   * 绑定 task 级别事件,确保旧连接回调不会污染新连接。
   */
  private bindTaskEventListeners(connectionId: number, task: Taro.SocketTask) {
    task.onOpen(() => {
      this.handleSocketOpen(connectionId);
    });
    task.onMessage((res) => {
      this.handleSocketMessage(connectionId, res.data);
    });
    task.onClose((res) => {
      this.handleSocketClose(connectionId, res);
    });
    task.onError((err) => {
      this.handleSocketError(connectionId, err);
    });
  }
  /**
   * 兼容无 task 级事件的运行环境。
   */
  private bindGlobalEventListeners() {
    if (this.globalEventsBound) return;
    this.globalEventsBound = true;
    Taro.onSocketOpen(() => {
      this.handleSocketOpen(this.activeConnectionId);
    });
    Taro.onSocketMessage((res) => {
      this.handleSocketMessage(this.activeConnectionId, res.data);
    });
    Taro.onSocketClose((res) => {
      this.handleSocketClose(this.activeConnectionId, res);
    });
    Taro.onSocketError((err) => {
      this.handleSocketError(this.activeConnectionId, err);
    });
  }
  /**
   * 处理连接建立成功。
   */
  private async handleSocketOpen(connectionId: number) {
    if (!this.isCurrentConnection(connectionId)) {
      return;
    }
    console.log(`WebSocket 连接已打开,连接代次:${connectionId}`);
    this.isConnected = true;
    this.isConnecting = false;
    this.socketState = SocketState.CONNECTED;
    this.reconnectAttempts = 0;
    this.resetHeartbeatState();
    if (this.isAppActive) {
      this.startHeartbeat(connectionId);
      this.startLocationReport(connectionId);
    }
    const uid = await this.getValidUid();
    if (!this.isCurrentConnection(connectionId)) {
      return;
    }
    if (!uid) {
      console.warn('WebSocket 鉴权已取消,UID 无效');
      const expiredTask = this.socketTask;
      this.stopHeartbeat();
      this.invalidateCurrentConnection();
      this.socketTask = null;
      this.closeTaskSilently(expiredTask, 'UID 无效,关闭未鉴权连接');
      this.markConnectionClosed(connectionId);
      return;
    }
    this.sendInternal(
      { type: 'auth', appUserId: uid, timestamp: Date.now() },
      { silentWhenDisconnected: true }
    );
    this.notifyListeners('open', { status: 'connected' });
  }
  /**
   * 处理服务端消息。
   */
  private handleSocketMessage(connectionId: number, rawData: any) {
    if (!this.isCurrentConnection(connectionId)) {
      return;
    }
    console.log('收到 WebSocket 消息:', rawData);
    const data = this.normalizeIncomingMessage(rawData);
    if (this.isPongMessage(data)) {
      this.handlePong(connectionId);
    }
    const content = safeParse(data?.content || '{}', {});
    try {
      this.handleIncomingMessage(data, content);
      if (data?.type) {
        this.notifyListeners(data.type, data);
      }
      this.notifyListeners('message', data);
    } catch (error) {
      console.error('处理 WebSocket 消息出错:', error);
    }
  }
  /**
   * 处理连接关闭。
   */
  private handleSocketClose(connectionId: number, res: any) {
    if (!this.isCurrentConnection(connectionId)) {
      console.log('忽略过期连接关闭回调', res);
      return;
    }
    console.log('WebSocket 连接已关闭', res);
    this.markConnectionClosed(connectionId);
    this.notifyListeners('close', res);
    if (!this.isManualClose) {
      this.scheduleReconnect(connectionId, 'socket_close');
    }
  }
  /**
   * 处理连接异常。
   */
  private handleSocketError(connectionId: number, err: any) {
    if (!this.isCurrentConnection(connectionId)) {
      console.log('忽略过期连接错误回调', err);
      return;
    }
    console.error('WebSocket 错误:', err);
    this.markConnectionClosed(connectionId);
    this.notifyListeners('error', err);
    if (!this.isManualClose) {
      this.scheduleReconnect(connectionId, 'socket_error');
    }
  }
  /**
   * 标准化消息结构,兼容纯字符串 pong。
   */
  private normalizeIncomingMessage(rawData: any) {
    const parsedData = safeParse(rawData, rawData);
    if (typeof parsedData === 'string') {
      return {
        type: parsedData,
        raw: rawData,
      };
    }
    return parsedData || {};
  }
  /**
   * 获取有效 UID,约束连接头和鉴权消息都不能使用 -1 等无效值。
   */
  private async getValidUid() {
    const uid = await getStorage(_UID);
    return this.normalizeValidUid(uid);
  }
  /**
   * 标准化并校验 UID。
   */
  private normalizeValidUid(uid: any) {
    const normalizedUid = String(uid ?? '').trim();
    if (!normalizedUid || normalizedUid === '-1' || normalizedUid === 'undefined' || normalizedUid === 'null') {
      return null;
    }
    return normalizedUid;
  }
  /**
   * 判断消息是否为服务端 pong。
   */
  private isPongMessage(data: any) {
    const type = String(data?.type || '').toLowerCase();
    return type === 'pong';
  }
  /**
   * 根据 pong 清理等待状态,并恢复心跳健康计数。
   */
  private handlePong(connectionId: number) {
    if (!this.isCurrentConnection(connectionId)) {
      return;
    }
    this.pendingHeartbeatSeq = null;
    this.missedHeartbeatCount = 0;
    if (this.heartbeatTimeoutTimer) {
      clearTimeout(this.heartbeatTimeoutTimer);
      this.heartbeatTimeoutTimer = null;
    }
  }
  /**
   * 启动心跳轮询。
   */
  private startHeartbeat(connectionId: number) {
    this.stopHeartbeat();
    if (!this.isCurrentConnection(connectionId) || !this.isAppActive) {
      return;
    }
    this.heartbeatTimer = setInterval(() => {
      if (!this.isCurrentConnection(connectionId)) {
        return;
      }
      if (!this.isConnected || this.socketState !== SocketState.CONNECTED || !this.isAppActive) {
        return;
      }
      if (this.pendingHeartbeatSeq !== null) {
        return;
      }
      this.sendHeartbeat(connectionId);
    }, this.heartbeatInterval);
  }
  /**
   * 发送一轮 ping,并等待服务端回 pong。
   */
  private sendHeartbeat(connectionId: number) {
    if (!this.isCurrentConnection(connectionId) || !this.isAppActive) {
      return;
    }
    const currentHeartbeatSeq = ++this.heartbeatSeq;
    this.pendingHeartbeatSeq = currentHeartbeatSeq;
    const hasSent = this.sendInternal(
      { type: 'ping', timestamp: Date.now(), seq: currentHeartbeatSeq },
      { silentWhenDisconnected: true }
    );
    if (!hasSent) {
      this.handleHeartbeatMiss(connectionId, currentHeartbeatSeq, 'ping_send_fail');
      return;
    }
    this.heartbeatTimeoutTimer = setTimeout(() => {
      this.handleHeartbeatMiss(connectionId, currentHeartbeatSeq, 'pong_timeout');
    }, this.heartbeatTimeout);
  }
  /**
   * 处理单轮心跳未收到 pong 的场景。
   */
  private handleHeartbeatMiss(connectionId: number, heartbeatSeq: number, reason: string) {
    if (!this.isCurrentConnection(connectionId)) {
      return;
    }
    if (!this.isAppActive) {
      this.pendingHeartbeatSeq = null;
      return;
    }
    if (this.pendingHeartbeatSeq !== heartbeatSeq) {
      return;
    }
    this.pendingHeartbeatSeq = null;
    if (this.heartbeatTimeoutTimer) {
      clearTimeout(this.heartbeatTimeoutTimer);
      this.heartbeatTimeoutTimer = null;
    }
    this.missedHeartbeatCount += 1;
    console.warn(`WebSocket 心跳未收到 pong,原因:${reason},连续失败 ${this.missedHeartbeatCount} 次`);
    if (this.missedHeartbeatCount < this.maxHeartbeatMisses) {
      return;
    }
    this.safeReconnect(connectionId, reason);
  }
  /**
   * 安全关闭当前连接并发起重连,不让旧连接回调影响新连接。
   */
  private safeReconnect(connectionId: number, reason: string) {
    if (!this.isCurrentConnection(connectionId)) {
      return;
    }
    console.warn(`WebSocket 准备重连,原因:${reason},连接代次:${connectionId}`);
    const expiredTask = this.socketTask;
    this.stopHeartbeat();
    this.markConnectionClosed(connectionId);
    this.invalidateCurrentConnection();
    this.socketTask = null;
    this.closeTaskSilently(expiredTask, `重连前关闭旧连接:${reason}`);
    this.scheduleReconnect(this.activeConnectionId, reason);
  }
  /**
   * 停止心跳及其超时检测。
   */
  private stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
    if (this.heartbeatTimeoutTimer) {
      clearTimeout(this.heartbeatTimeoutTimer);
      this.heartbeatTimeoutTimer = null;
    }
    this.pendingHeartbeatSeq = null;
  }
  /**
   * 启动位置上报轮询。
   */
  private startLocationReport(connectionId: number) {
    this.stopLocationReport();
    if (!this.isCurrentConnection(connectionId) || !this.isAppActive || !this.isConnected) {
      return;
    }
    this.sendLocationMessage(connectionId);
    this.locationReportTimer = setInterval(() => {
      if (!this.isCurrentConnection(connectionId) || !this.isAppActive || !this.isConnected) {
        return;
      }
      this.sendLocationMessage(connectionId);
    }, this.locationReportInterval);
  }
  /**
   * 停止位置上报轮询。
   */
  private stopLocationReport() {
    if (this.locationReportTimer) {
      clearInterval(this.locationReportTimer);
      this.locationReportTimer = null;
    }
  }
  /**
   * 上报当前位置,失败时静默处理。
   */
  private async sendLocationMessage(connectionId: number) {
    if (!this.isCurrentConnection(connectionId) || !this.isConnected || !this.isAppActive) {
      return;
    }
    const userId = await this.getValidUid();
    if (!userId) {
      return;
    }
    try {
      const locationRes = await Taro.getLocation({
        type: 'gcj02',
        isHighAccuracy: true,
        highAccuracyExpireTime: 3000,
      } as any);
      if (!this.isCurrentConnection(connectionId) || !this.isConnected || !this.isAppActive) {
        return;
      }
      const longitude = Number(locationRes?.longitude);
      const latitude = Number(locationRes?.latitude);
      if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
        return;
      }
      this.sendInternal(
        {
          type: 'location',
          longitude,
          latitude,
          userId,
          timestamp: Date.now() * 1000,
        },
        { silentWhenDisconnected: true }
      );
    } catch (error) {
      // 定时定位失败静默处理,避免干扰业务流程
    }
  }
  /**
   * 重置心跳健康状态。
   */
  private resetHeartbeatState() {
    this.pendingHeartbeatSeq = null;
    this.missedHeartbeatCount = 0;
    if (this.heartbeatTimeoutTimer) {
      clearTimeout(this.heartbeatTimeoutTimer);
      this.heartbeatTimeoutTimer = null;
    }
  }
  /**
   * 调度下一次重连,后台期间延后到前台再处理。
   */
  private scheduleReconnect(connectionId: number, reason: string) {
    if (!this.isCurrentConnection(connectionId) || this.isManualClose || !this.url) {
      return;
    }
    if (!this.isAppActive) {
      this.shouldReconnectWhenActive = true;
      return;
    }
    if (this.reconnectTimer) {
      return;
    }
    const delay = this.getReconnectDelay();
    console.log(`正在尝试重连,原因:${reason},延迟 ${delay}ms... (第 ${this.reconnectAttempts + 1} 次尝试)`);
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      if (this.isManualClose || !this.isAppActive || !this.url) {
        return;
      }
      this.reconnectAttempts += 1;
      this.connect(this.url);
    }, delay);
  }
  /**
   * 获取退避后的重连延迟。
   */
  private getReconnectDelay() {
    const retryLevel = Math.min(this.reconnectAttempts, 4);
    const baseDelay = Math.min(1000 * Math.pow(2, retryLevel), 15000);
    const jitter = Math.floor(Math.random() * 500);
    return baseDelay + jitter;
  }
  /**
   * 清理已有重连任务。
   */
  private clearReconnectTimer() {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }
  /**
   * 发送消息的底层实现。
   */
  private sendInternal(data: any, options: { silentWhenDisconnected: boolean }) {
    if (!this.isConnected || this.socketState !== SocketState.CONNECTED) {
      if (!options.silentWhenDisconnected) {
        console.warn('WebSocket 未连接,消息未发送:', data);
      }
      return false;
    }
    const message = typeof data === 'string' ? data : JSON.stringify(data);
    if (this.socketTask) {
      this.socketTask.send({
        data: message,
        fail: (err) => {
          console.error('发送消息失败:', err);
        }
      });
      return true;
    }
    Taro.sendSocketMessage({
      data: message,
      fail: (err) => {
        console.error('发送消息失败:', err);
      }
    });
    return true;
  }
  /**
   * 关闭指定 task,不影响当前代次判断。
   */
  private closeTaskSilently(task: Taro.SocketTask | null | undefined, reason: string) {
    if (!task) {
      return;
    }
    try {
      task.close({
        code: 1000,
        reason,
      });
    } catch (error) {
      console.error('关闭 SocketTask 失败:', error);
    }
  }
  /**
   * 统一更新关闭态。
   */
  private markConnectionClosed(connectionId: number) {
    if (this.isCurrentConnection(connectionId)) {
      this.socketTask = null;
    }
    this.isConnected = false;
    this.isConnecting = false;
    this.socketState = SocketState.IDLE;
    this.stopHeartbeat();
    this.stopLocationReport();
  }
  /**
   * 通知监听器。
   * @param event 事件名称
   * @param data 数据
   */
@@ -476,7 +780,9 @@
    }
  }
  // 处理传入消息
  /**
   * 处理传入消息。
   */
  private handleIncomingMessage(data: any, content: any) {
    if (data?.type === 'ORDER_STATUS') {
      const status = content?.status;
@@ -487,12 +793,7 @@
          Taro.setStorageSync('tripStatus', mapped.tripStatus);
          Taro.setStorageSync('orderId', orderId);
        }
        this.notifyListeners('order_status', {
          status,
          ...mapped,
          data,
          content
        });
        this.notifyListeners('order_status', { status, ...mapped, data, content });
      }
    }
    if (data?.type === 'ORDER_ROUTE' || data?.type === 'ORDER_UPDATE_LINE') {
@@ -547,15 +848,23 @@
    }
  }
  // 归一化路线点
  /**
   * 归一化路线点。
   */
  private normalizeRoutePoints(content: any) {
    return this.normalizeRoutePointsByKey(content, 'polyline');
  }
  /**
   * 归一化已行驶路线点。
   */
  private normalizeServedPoints(content: any) {
    return this.normalizeRoutePointsByKey(content, 'served');
  }
  /**
   * 根据指定 key 归一化路线点。
   */
  private normalizeRoutePointsByKey(content: any, key: 'polyline' | 'served') {
    const route = safeParse(content?.route ?? '{}', {});
    const rawPoints = route?.[key] ?? [];
@@ -573,7 +882,9 @@
      .filter(Boolean);
  }
  // 映射订单状态到行程状态
  /**
   * 映射订单状态到行程状态。
   */
  private mapOrderStatusToTripStatus(status: number) {
    switch (status) {
      case 0: