hejianhao
2026-05-15 04f62cd2c47ee7760e968495c6ea96e817954926
refactor(robotaxi/home): 重构首页代码,拆分组件与自定义hook

将Robotaxi首页的臃肿业务逻辑抽离为独立的自定义hook与公共组件,新增常量文件统一管理首页配置,优化城市状态、订单WebSocket、天气、热修轮询等模块的封装,大幅简化首页页面代码,提升整体可维护性与可读性。
9个文件已添加
2个文件已修改
2025 ■■■■■ 已修改文件
src/pages/robotaxi/home/components/home-content-panel.tsx 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/home-map-panel.tsx 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/home-overlay-layer.tsx 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/constants.ts 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-city-state.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-fence-manager.ts 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-hotfix-check.ts 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-marketing.ts 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-order-ws.ts 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-weather.ts 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.tsx 967 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/home-content-panel.tsx
New file
@@ -0,0 +1,152 @@
import { View, Text, Image } from '@tarojs/components';
import OrderStatusCard from './order-status-card';
import rightIcon from '../../../../robotaxi/assets/right-icon.png';
import { TripStatus } from '../../../../robotaxi/pages/begin-route/constants/trip-status';
import { safeParse } from '../../../../utils/Robotaxi-public-method';
type Props = {
  /** Banner 列表。 */
  bannerList: any[];
  /** 底部容器高度。 */
  boxHeight: number;
  /** 是否拖拽中。 */
  isDragging: boolean;
  /** 是否展示订单状态卡片。 */
  shouldShowOrderStatusCard: boolean;
  /** 当前行程状态。 */
  tripStatus: TripStatus | 0;
  /** 当前订单数据。 */
  orderData: any;
  /** 订单状态文案映射。 */
  orderStatusMap: Partial<Record<TripStatus, string>>;
  /** 当前是否在运营区。 */
  isOperatingArea: boolean;
  /** 当前是否吸附站点。 */
  isAdsorbed: boolean;
  /** 当前站点。 */
  currentSite: any;
  /** 是否展示快捷胶囊。 */
  showQuickCapsule: boolean;
  /** 快捷胶囊文案。 */
  quickCapsuleText: string;
  /** 选择站点。 */
  onSelectSite: (type: string) => void;
  /** 快捷胶囊点击。 */
  onQuickCapsuleClick: () => void;
  /** 查看订单详情。 */
  onViewOrderDetail: () => void;
  /** Banner 点击。 */
  onBannerClick: (item: any) => void;
  /** 打开协议页。 */
  onToAgreement: () => void;
  /** 触摸开始。 */
  onTouchStart: (event: any) => void;
  /** 触摸移动。 */
  onTouchMove: (event: any) => void;
  /** 触摸结束。 */
  onTouchEnd: () => void;
};
/**
 * 保留文案末尾关键字的快捷胶囊样式。
 */
function renderTailPreserveText(text: string, tailCount = 3) {
  const safeText = String(text || '');
  if (!safeText) {
    return null;
  }
  if (safeText.length <= tailCount) {
    return <Text className='to-site-fast-full'>{safeText}</Text>;
  }
  const headText = safeText.slice(0, -tailCount);
  const tailText = safeText.slice(-tailCount);
  return (
    <>
      <Text className='to-site-fast-head'>{headText}</Text>
      <Text className='to-site-fast-tail'>{tailText}</Text>
    </>
  );
}
/**
 * 首页底部内容面板。
 */
export default function HomeContentPanel(props: Props) {
  return (
    <View
      className="content-box"
      style={{ height: props.bannerList.length > 1 ? `${props.boxHeight}px` : 'auto', transition: props.isDragging ? 'none' : 'height 0.3s ease-out' }}
      catchMove
      onTouchStart={(event) => {
        event.stopPropagation();
        props.onTouchStart(event);
      }}
      onTouchMove={(event) => {
        event.stopPropagation();
        props.onTouchMove(event);
      }}
      onTouchEnd={(event) => {
        event.stopPropagation();
        props.onTouchEnd();
      }}
    >
      <View className="content-body">
        <View className="select-site">
          <Image className="site-top-bg" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg-20260507.png' />
          <View className="site-boarding-point">
            <View onClick={() => props.onSelectSite('start')} className="now-site">
              <View className="now-site-dot"></View>
              <View className="now-site-address">
                <View className="now-site-address-content">
                  从
                  <Text className="now-site-address-text">
                    {(!props.isOperatingArea || props.isAdsorbed) ? (props.currentSite?.name || '请选择上车点') : '推荐上车点'}
                  </Text>
                  出发
                </View>
                <Image className="now-site-icon" src={rightIcon} />
              </View>
            </View>
            <View onClick={() => props.onSelectSite('end')} className="to-site">
              <View className="to-site-dot"></View>
              <View className="to-site-address">
                <View className="to-site-text">你想去哪?</View>
                {props.showQuickCapsule && (
                  <View
                    onClick={(event) => {
                      event.stopPropagation();
                      props.onQuickCapsuleClick();
                    }}
                    className="to-site-fast"
                  >
                    {renderTailPreserveText(props.quickCapsuleText)}
                  </View>
                )}
              </View>
            </View>
          </View>
          {props.shouldShowOrderStatusCard && (
            <OrderStatusCard
              tripStatus={props.tripStatus}
              orderData={props.orderData}
              onViewDetail={props.onViewOrderDetail}
              orderStatusMap={props.orderStatusMap}
            />
          )}
          {props.bannerList.length > 0 && (
            <View className="activity">
              {props.bannerList.map((item, index) => (
                <View key={item.id || index} className="activity-item" onClick={() => props.onBannerClick(item)}>
                  <Image mode="aspectFill" className="activity-img" src={safeParse(item.imageUrl, [])?.[0]?.url} />
                </View>
              ))}
            </View>
          )}
          <View onClick={props.onToAgreement} className="product-name">XPENG Robotaxi</View>
        </View>
      </View>
    </View>
  );
}
src/pages/robotaxi/home/components/home-map-panel.tsx
New file
@@ -0,0 +1,172 @@
import { View, Map, Text, Image } from '@tarojs/components';
import MapOverlayTips from './map-overlay-tips';
import MapPointer from './map-pointer';
import selectCityIcon from '../../../../robotaxi/assets/select-city.png';
import personalCenter from '../../../../robotaxi/assets/personal-center.png';
import coupon from '../../../../robotaxi/assets/coupon.png';
import contactService from '../../../../robotaxi/assets/contact-service.png';
import originIcon from '../../../../robotaxi/assets/origin-icon.png';
import pointer from '../../../../robotaxi/assets/pointer.png';
import rightIcon from '../../../../robotaxi/assets/right-icon.png';
import { getWeatherIconByDesc } from '../../../../utils/weather';
import { DEFAULT_LATITUDE, DEFAULT_LONGITUDE } from '../../../../robotaxi/constants/map';
import { HOME_MAP_HEIGHT, HOME_MAP_ID } from '../constants';
type Props = {
  /** 地图经度。 */
  longitude: number | null;
  /** 地图纬度。 */
  latitude: number | null;
  /** 地图缩放等级。 */
  mapScale: number;
  /** 地图 marker 列表。 */
  mergedMapMarkers: any[];
  /** 地图虚线围栏。 */
  fencePolylines: any[];
  /** 地图多边形围栏。 */
  polygons: any[];
  /** 是否拒绝定位。 */
  isLocationAuthDenied: boolean;
  /** 当前选中城市名称。 */
  selectedCityName?: string;
  /** 当前天气数据。 */
  weatherData: { weather: string; temperature: number | string } | null;
  /** 展示温度值。 */
  displayTemperature: string | number;
  /** 展示温度单位。 */
  displayTemperatureUnit: string;
  /** 回中按钮透明度。 */
  originOpacity: number;
  /** 浮层底部锚点。 */
  overlayAnchorBottom: number | null;
  /** 是否处于拖拽中。 */
  isDragging: boolean;
  /** 是否展示游客提示。 */
  shouldShowGuestTip: boolean;
  /** 是否在运营区。 */
  isOperatingArea: boolean;
  /** 是否维护中。 */
  isMaintain: boolean;
  /** 维护提示动效。 */
  maintainBounce: boolean;
  /** 维护恢复时间。 */
  hotfixEndText: string;
  /** 附近车辆是否加载中。 */
  nearbyLoading: boolean;
  /** 是否安卓设备。 */
  isAndroid: boolean;
  /** 是否展示站点 tooltip。 */
  showToolTip: boolean;
  /** 中心指针是否弹跳。 */
  bounce: boolean;
  /** 是否已吸附站点。 */
  isAdsorbed: boolean;
  /** 当前站点。 */
  currentSite: any;
  /** 等待态雷达资源。 */
  waitingRadarSrc: string;
  /** 地图区域变化回调。 */
  onRegionChange: (event: any) => void;
  /** Marker 点击回调。 */
  onMarkerTap: (event: any) => void;
  /** 去城市选择页。 */
  onSelectCity: () => void;
  /** 回到当前位置。 */
  onToOrigin: () => void;
  /** 展示隐私弹窗。 */
  onShowPrivacy: () => void;
  /** 打开系统设置。 */
  onOpenSetting: () => void;
  /** 查看运营区域。 */
  onViewOperatingArea: () => void;
  /** 打开个人中心。 */
  onToPersonalCenter: () => void;
  /** 打开客服。 */
  onOpenService: () => void;
  /** 打开优惠券。 */
  onToCoupon: () => void;
  /** 选择站点。 */
  onSelectSite: (type: string) => void;
};
/**
 * 首页地图区域与地图浮层。
 */
export default function HomeMapPanel(props: Props) {
  return (
    <Map
      id={HOME_MAP_ID}
      longitude={props.longitude ?? DEFAULT_LONGITUDE}
      latitude={props.latitude ?? DEFAULT_LATITUDE}
      scale={props.mapScale}
      markers={props.isLocationAuthDenied ? [] : props.mergedMapMarkers.map((item) => ({ ...item, title: '', name: '' }))}
      polyline={props.fencePolylines}
      polygons={props.polygons}
      showLocation={!!props.longitude}
      onRegionChange={props.onRegionChange}
      onMarkerTap={props.onMarkerTap}
      style={{ width: '100%', height: HOME_MAP_HEIGHT, flex: 'none' }}
    >
      <View className="select-city-container">
        <View onClick={props.onSelectCity} className="select-city">
          <Text>{props.selectedCityName || '北京市'}</Text>
          <Image className="select-city-icon" src={selectCityIcon} />
        </View>
        {props.weatherData && (
          <View className="weather-info">
            <Image className="weather-icon" src={getWeatherIconByDesc(props.weatherData.weather)} />
            <Text>{props.displayTemperature}{props.displayTemperatureUnit}</Text>
          </View>
        )}
      </View>
      <View
        className="origin-box"
        style={{
          opacity: props.originOpacity,
          visibility: props.originOpacity <= 0 ? 'hidden' : 'visible',
          pointerEvents: props.originOpacity <= 0 ? 'none' : 'auto',
          transition: props.isDragging ? 'none' : 'opacity 0.2s ease-out',
          top: typeof props.overlayAnchorBottom === 'number' ? 'auto' : undefined,
          bottom: typeof props.overlayAnchorBottom === 'number' ? `${props.overlayAnchorBottom}px` : undefined
        }}
      >
        <Image onClick={props.onToOrigin} className="origin-icon" src={originIcon} />
      </View>
      <MapOverlayTips
        isGuest={props.shouldShowGuestTip}
        isLocationAuthDenied={props.isLocationAuthDenied}
        isOperatingArea={props.isOperatingArea}
        isMaintain={props.isMaintain}
        maintainBounce={props.maintainBounce}
        hotfixEndText={props.hotfixEndText}
        anchorBottom={props.overlayAnchorBottom ?? undefined}
        opacity={props.originOpacity}
        isDragging={props.isDragging}
        onShowPrivacy={props.onShowPrivacy}
        onOpenSetting={props.onOpenSetting}
        onViewOperatingArea={props.onViewOperatingArea}
      />
      <View className="map-entry">
        <Image onClick={props.onToPersonalCenter} className="map-entry-icon" src={personalCenter} />
        <View className="split-line"></View>
        <Image onClick={props.onOpenService} className="map-entry-icon" src={contactService} />
        <View className="split-line"></View>
        <Image onClick={props.onToCoupon} className="map-entry-icon" src={coupon} />
      </View>
      <MapPointer
        showToolTip={props.showToolTip}
        bounce={props.bounce}
        nearbyLoading={props.nearbyLoading}
        isAndroid={props.isAndroid}
        isLocationAuthDenied={props.isLocationAuthDenied}
        isOperatingArea={props.isOperatingArea}
        isAdsorbed={props.isAdsorbed}
        currentSite={props.currentSite}
        pointerSrc={pointer}
        rightIconSrc={rightIcon}
        waitingRadarSrc={props.waitingRadarSrc}
        onSelectSite={props.onSelectSite}
      />
    </Map>
  );
}
src/pages/robotaxi/home/components/home-overlay-layer.tsx
New file
@@ -0,0 +1,85 @@
import { View, Image, Button } from '@tarojs/components';
import PrivacyPopup from './privacy-popup';
import CustomerServicePopup from '../../../../robotaxi/components/customer-service';
import MarketingPopup from './marketing-popup';
import { CDN } from '../../../../constants/api';
import { FLOAT_ACTIVITY_DEFAULT_IMAGE } from '../constants';
type Props = {
  /** 是否维护中。 */
  isMaintain: boolean;
  /** 触发维护提示回弹。 */
  onMaintainMaskClick: () => void;
  /** 是否展示登录入口。 */
  shouldShowLoginEntry: boolean;
  /** 去登录页。 */
  onToLogin: () => void;
  /** 是否展示隐私弹窗。 */
  showPrivacy: boolean;
  /** 关闭隐私弹窗。 */
  onClosePrivacy: () => void;
  /** 同意隐私协议。 */
  onAgreePrivacy: () => void;
  /** 是否展示客服弹窗。 */
  showCustomerService: boolean;
  /** 关闭客服弹窗。 */
  onCloseCustomerService: () => void;
  /** 是否展示营销弹窗。 */
  showMarketing: boolean;
  /** 营销数据。 */
  marketingData: Record<string, any>;
  /** 关闭营销弹窗。 */
  onCloseMarketing: () => void;
  /** 是否展示活动悬浮窗。 */
  showFloatActivity: boolean;
  /** 活动悬浮窗图片。 */
  floatActivityImg: string;
  /** 点击活动悬浮窗。 */
  onFloatActivityClick: () => void;
  /** 悬浮窗图片兜底。 */
  onFloatActivityError: () => void;
};
/**
 * 首页登录提示与弹层集合。
 */
export default function HomeOverlayLayer(props: Props) {
  return (
    <>
      {props.isMaintain && (
        <View
          onClick={props.onMaintainMaskClick}
          style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', zIndex: 9999 }}
        />
      )}
      {props.shouldShowLoginEntry && (
        <View className='login-bar'>
          <View className='text'>立即登录,即可体验Robotaxi行程服务</View>
          <View className='more' onClick={props.onToLogin}>
            <Button className='more-btn'>立即登录</Button>
            <Image src={`${CDN}/common/arrow-white-new.png`} className='more-icon'></Image>
          </View>
        </View>
      )}
      <PrivacyPopup
        show={props.showPrivacy}
        onClose={props.onClosePrivacy}
        onAgree={props.onAgreePrivacy}
      />
      <CustomerServicePopup
        show={props.showCustomerService}
        onClose={props.onCloseCustomerService}
      />
      <MarketingPopup show={props.showMarketing} data={props.marketingData} onClose={props.onCloseMarketing} />
      {props.showFloatActivity && (
        <View className='activity-float-window' onClick={props.onFloatActivityClick}>
          <Image
            className='activity-float-window-img'
            src={props.floatActivityImg || FLOAT_ACTIVITY_DEFAULT_IMAGE}
            onError={props.onFloatActivityError}
          />
        </View>
      )}
    </>
  );
}
src/pages/robotaxi/home/constants.ts
New file
@@ -0,0 +1,40 @@
import { TripStatus } from '../../../robotaxi/pages/begin-route/constants/trip-status';
/** 首页地图实例 ID。 */
export const HOME_MAP_ID = 'map';
/** 浮层默认上移距离。 */
export const OVERLAY_ANCHOR_OFFSET = 32;
/** 浮层初次量测延迟。 */
export const OVERLAY_ANCHOR_INIT_DELAY = 80;
/** Marker 点击后的吸附结算时长。 */
export const MARKER_TAP_SETTLE_DELAY = 600;
/** 维护提示点击回弹时长。 */
export const MAINTAIN_BOUNCE_DURATION = 300;
/** 热修轮询间隔。 */
export const HOTFIX_POLL_INTERVAL = 1000 * 30;
/** 活动悬浮窗默认图片。 */
export const FLOAT_ACTIVITY_DEFAULT_IMAGE = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/coverImage.png';
/** 等待态雷达资源。 */
export const WAITING_RADAR_SRC = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/waiting-radar.webp';
/** 地图内容高度。 */
export const HOME_MAP_HEIGHT = '85vh';
/** 场站围栏命中扩展距离。 */
export const STATION_GEOFENCE_MATCH_PADDING = 0;
/** 首页订单状态文案映射。 */
export const HOME_ORDER_STATUS: Partial<Record<TripStatus, string>> = {
  [TripStatus.VehicleApproaching]: '车辆正在赶来',
  [TripStatus.VehicleArrivedPickup]: '车辆已到达上车点,请尽快前往',
  [TripStatus.InService]: '正在前往目的地',
  [TripStatus.ArrivedDestination]: '车辆已到达目的地,请尽快结束行程',
  [TripStatus.TripFinished]: '行程已结束,请尽快支付'
};
src/pages/robotaxi/home/hooks/use-city-state.ts
@@ -7,12 +7,41 @@
} from '../../../../utils/Robotaxi-public-method';
/**
 * 首页选中城市类型。
 */
export type SelectedCity = {
  /** 城市 ID。 */
  id?: number;
  /** 城市名称。 */
  name: string;
  /** 城市编码。 */
  code: string;
};
const EMPTY_SELECTED_CITY: SelectedCity = {
  name: '',
  code: ''
}; // 空城市对象,作为类型兜底默认值
/**
 * 读取缓存中的选中城市,并兜底为空城市对象。
 */
const getInitialSelectedCity = (): SelectedCity => {
  const cachedSelectedCity = Taro.getStorageSync('selectedCity');
  if (cachedSelectedCity && typeof cachedSelectedCity === 'object') {
    return {
      ...EMPTY_SELECTED_CITY,
      ...cachedSelectedCity
    } as SelectedCity;
  }
  return EMPTY_SELECTED_CITY;
};
/**
 * 城市状态管理:维护 selectedCity 并提供城市识别与兜底同步能力。
 */
export default function useCityState() {
  const [selectedCity, setSelectedCity] = useState(
    Taro.getStorageSync('selectedCity') || { name: '', code: '' }
  );
  const [selectedCity, setSelectedCity] = useState<SelectedCity>(getInitialSelectedCity());
  const skipCityChangeEffectRef = useRef(false);
  /**
@@ -24,7 +53,7 @@
    skipCityEffect = false,
    forceRefresh = false
  ) => {
    const cachedCity = Taro.getStorageSync('selectedCity');
    const cachedCity = getInitialSelectedCity();
    if (!forceRefresh && cachedCity?.code && typeof cachedCity?.id === 'number') {
      return cachedCity;
    }
src/pages/robotaxi/home/hooks/use-fence-manager.ts
New file
@@ -0,0 +1,193 @@
import Taro from '@tarojs/taro';
import { useEffect, useRef, type MutableRefObject } from 'react';
import {
  filterRenderableGeofence,
  findMatchedPolygon,
  formatGeofenceByType,
  getCityLocation
} from '../../../../utils/Robotaxi-public-method';
import { getGeofence } from '../../../../robotaxi/api/home';
import { GeofenceType } from '../../../../robotaxi/constants/geofence';
import { HOME_MAP_ID, STATION_GEOFENCE_MATCH_PADDING } from '../constants';
import { type SelectedCity } from './use-city-state';
type GeofenceItem = Record<string, any>;
type Deps = {
  /** 当前选中城市。 */
  selectedCity: SelectedCity;
  /** 当前经度。 */
  longitude: number | null;
  /** 当前纬度。 */
  latitude: number | null;
  /** 是否拒绝定位。 */
  isLocationAuthDenied: boolean;
  /** 城市切换 effect 跳过标记。 */
  skipCityChangeEffectRef: MutableRefObject<boolean>;
  /** 跳过下一次定位回中的标记。 */
  skipNextLocationCenterRef: MutableRefObject<boolean>;
  /** 忽略下一次 region-change 的标记。 */
  shouldIgnoreRegionChange: MutableRefObject<boolean>;
  /** 当前是否存在活跃订单。 */
  hasActiveOrder: boolean;
  /** 设置地图多边形。 */
  setPolygons: (value: any[]) => void;
  /** 设置场站围栏虚线。 */
  setFencePolylines: (value: any[]) => void;
};
type FenceManagerState = {
  /** 按地图中心刷新场站围栏显隐。 */
  syncStationFenceVisibility: (lat: number, lng: number, isInside: boolean) => void;
};
/**
 * 首页电子围栏拉取、缓存与显隐管理。
 */
export default function useFenceManager(deps: Deps): FenceManagerState {
  const operatingGeofenceListRef = useRef<GeofenceItem[]>([]); // 运营区围栏缓存
  const stationGeofenceListRef = useRef<GeofenceItem[]>([]); // 场站围栏缓存
  const {
    selectedCity,
    longitude,
    latitude,
    isLocationAuthDenied,
    skipCityChangeEffectRef,
    skipNextLocationCenterRef,
    shouldIgnoreRegionChange,
    hasActiveOrder,
    setPolygons,
    setFencePolylines
  } = deps;
  /**
   * 重置当前围栏覆盖物与缓存。
   */
  const clearGeofenceLayers = () => {
    operatingGeofenceListRef.current = [];
    stationGeofenceListRef.current = [];
    setPolygons([]);
    setFencePolylines([]);
  };
  /**
   * 仅根据命中的场站围栏渲染当前地图覆盖物,运营区始终常驻。
   */
  const applyVisibleGeofence = (matchedStationGeofence: GeofenceItem | null, nextOperatingGeofenceList?: GeofenceItem[]) => {
    const operatingGeofenceSource = Array.isArray(nextOperatingGeofenceList)
      ? nextOperatingGeofenceList
      : operatingGeofenceListRef.current;
    const { polygons: operatingPolygons } = formatGeofenceByType(operatingGeofenceSource);
    const { polygons: stationPolygons, fencePolylines: stationFencePolylines } = formatGeofenceByType(
      matchedStationGeofence ? [matchedStationGeofence] : []
    );
    setPolygons([...operatingPolygons, ...stationPolygons]);
    setFencePolylines(stationFencePolylines);
  };
  /**
   * 根据地图中心点决定当前场站围栏是否需要展示。
   */
  const syncStationFenceVisibility = (lat: number, lng: number, isInside: boolean) => {
    const latestOperatingGeofenceList = operatingGeofenceListRef.current;
    const latestStationGeofenceList = stationGeofenceListRef.current;
    if (!Array.isArray(latestOperatingGeofenceList) || latestOperatingGeofenceList.length === 0) {
      return;
    }
    if (!isInside || !Array.isArray(latestStationGeofenceList) || latestStationGeofenceList.length === 0) {
      applyVisibleGeofence(null);
      return;
    }
    const matchedStationGeofence = findMatchedPolygon(
      { latitude: lat, longitude: lng },
      latestStationGeofenceList,
      STATION_GEOFENCE_MATCH_PADDING
    );
    applyVisibleGeofence(matchedStationGeofence);
  };
  /**
   * 在无围栏或围栏拉取失败时,将地图回退到当前城市中心点。
   */
  const moveToCityCenter = async () => {
    if (!selectedCity?.name || isLocationAuthDenied) {
      return;
    }
    try {
      const location = await getCityLocation(selectedCity.name);
      skipNextLocationCenterRef.current = true;
      shouldIgnoreRegionChange.current = true;
      Taro.createMapContext(HOME_MAP_ID).moveToLocation({
        latitude: location.latitude,
        longitude: location.longitude
      });
    } catch (error) {
      console.error('获取城市中心失败', error);
    }
  };
  useEffect(() => {
    if (skipCityChangeEffectRef.current) {
      skipCityChangeEffectRef.current = false;
      return;
    }
    if (!selectedCity?.code) {
      return;
    }
    /**
     * 拉取当前城市围栏并同步地图覆盖物。
     */
    const fetchGeofence = async () => {
      try {
        const response = await getGeofence({ cityCode: selectedCity.code });
        const geofenceList = filterRenderableGeofence(response);
        const nextOperatingGeofenceList = geofenceList.filter((item) => Number(item?.type) === GeofenceType.OPERATING);
        const nextStationGeofenceList = geofenceList.filter((item) => Number(item?.type) === GeofenceType.STATION);
        operatingGeofenceListRef.current = nextOperatingGeofenceList;
        stationGeofenceListRef.current = nextStationGeofenceList;
        if (geofenceList.length > 0) {
          const matchedStationGeofence = (
            typeof latitude === 'number' && typeof longitude === 'number'
          )
            ? findMatchedPolygon(
              { latitude, longitude },
              nextStationGeofenceList,
              STATION_GEOFENCE_MATCH_PADDING
            )
            : null;
          applyVisibleGeofence(matchedStationGeofence, nextOperatingGeofenceList);
          return;
        }
        clearGeofenceLayers();
        if (!hasActiveOrder) {
          await moveToCityCenter();
        }
      } catch (error) {
        console.error('获取电子围栏失败', error);
        clearGeofenceLayers();
        await moveToCityCenter();
      }
    };
    void fetchGeofence();
  }, [
    hasActiveOrder,
    isLocationAuthDenied,
    selectedCity?.code,
    selectedCity?.name
  ]);
  return {
    syncStationFenceVisibility
  };
}
src/pages/robotaxi/home/hooks/use-hotfix-check.ts
New file
@@ -0,0 +1,95 @@
import { useEffect, useRef, useState } from 'react';
import { getHotFixInfo } from '../../../../robotaxi/api/home';
import { HOTFIX_POLL_INTERVAL, MAINTAIN_BOUNCE_DURATION } from '../constants';
type HotfixState = {
  /** 是否维护中。 */
  isMaintain: boolean;
  /** 维护提示回弹状态。 */
  maintainBounce: boolean;
  /** 维护结束时间文案。 */
  hotfixEndText: string;
  /** 触发维护提示回弹。 */
  triggerMaintainBounce: () => void;
};
/**
 * 维护态轮询与提示动效管理。
 */
export default function useHotfixCheck(): HotfixState {
  const [isMaintain, setIsMaintain] = useState(false); // 当前是否处于维护中
  const [maintainBounce, setMaintainBounce] = useState(false); // 维护态提示是否执行回弹
  const [hotfixEndText, setHotfixEndText] = useState(''); // 维护恢复时间文案
  const bounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // 维护回弹计时器
  /**
   * 拉取热修配置并同步维护态。
   */
  const syncHotfixState = async () => {
    try {
      const response = await getHotFixInfo();
      const status = Number(response?.hotfixStatus);
      if (status === 1) {
        setIsMaintain(true);
        setHotfixEndText(response?.hotfixEndTime || '');
        return;
      }
      setIsMaintain(false);
      setHotfixEndText('');
    } catch (error) {
      // 热修接口失败时保持当前页面可用,不主动打断首页交互。
    }
  };
  /**
   * 触发维护提示轻量回弹,提示用户当前处于维护状态。
   */
  const triggerMaintainBounce = () => {
    setMaintainBounce(true);
    if (bounceTimerRef.current) {
      clearTimeout(bounceTimerRef.current);
    }
    bounceTimerRef.current = setTimeout(() => {
      setMaintainBounce(false);
      bounceTimerRef.current = null;
    }, MAINTAIN_BOUNCE_DURATION);
  };
  useEffect(() => {
    let isUnmounted = false;
    let intervalId: ReturnType<typeof setInterval> | null = null;
    /**
     * 包装轮询,避免卸载后继续落状态。
     */
    const startPolling = async () => {
      await syncHotfixState();
      if (isUnmounted) {
        return;
      }
      intervalId = setInterval(() => {
        void syncHotfixState();
      }, HOTFIX_POLL_INTERVAL);
    };
    void startPolling();
    return () => {
      isUnmounted = true;
      if (intervalId) {
        clearInterval(intervalId);
      }
      if (bounceTimerRef.current) {
        clearTimeout(bounceTimerRef.current);
        bounceTimerRef.current = null;
      }
    };
  }, []);
  return {
    isMaintain,
    maintainBounce,
    hotfixEndText,
    triggerMaintainBounce
  };
}
src/pages/robotaxi/home/hooks/use-marketing.ts
New file
@@ -0,0 +1,126 @@
import Taro from '@tarojs/taro';
import { useState } from 'react';
import { FLOAT_ACTIVITY_DEFAULT_IMAGE } from '../constants';
type MarketingData = Record<string, any>;
type Deps = {
  /** 首次进入营销弹窗查询。 */
  firstEnter: () => Promise<any>;
  /** 悬浮活动查询。 */
  getFloatActicity: () => Promise<any>;
  /** 运营活动券订阅。 */
  subscribeOperationCouponGroup: (bizId?: string | number) => Promise<unknown> | void;
  /** H5 跳转能力。 */
  toH5: (url: string, needLogin?: boolean) => void;
};
type MarketingState = {
  /** 是否展示营销弹窗。 */
  showMarketing: boolean;
  /** 营销弹窗数据。 */
  marketingData: MarketingData;
  /** 是否展示悬浮活动。 */
  showFloatActivity: boolean;
  /** 悬浮活动跳转地址。 */
  floatActivityUrl: string;
  /** 悬浮活动图片。 */
  floatActivityImg: string;
  /** 刷新营销活动状态。 */
  syncMarketingState: (hasLogin: boolean) => Promise<void>;
  /** 关闭营销弹窗。 */
  handleCloseMarketing: () => void;
  /** 点击活动悬浮窗。 */
  handleFloatActivityClick: () => void;
  /** 悬浮图兜底。 */
  handleFloatActivityError: () => void;
};
/**
 * 首页营销弹窗与活动悬浮窗管理。
 */
export default function useMarketing(deps: Deps): MarketingState {
  const [showMarketing, setShowMarketing] = useState(false); // 营销弹窗是否显示
  const [marketingData, setMarketingData] = useState<MarketingData>({}); // 营销弹窗数据
  const [showFloatActivity, setShowFloatActivity] = useState(false); // 活动悬浮窗是否显示
  const [floatActivityUrl, setFloatActivityUrl] = useState(''); // 活动悬浮窗跳转地址
  const [floatActivityImg, setFloatActivityImg] = useState(FLOAT_ACTIVITY_DEFAULT_IMAGE); // 活动悬浮窗图片
  /**
   * 重置所有营销展示态,避免游客态误展示。
   */
  const resetMarketingState = () => {
    setShowMarketing(false);
    setMarketingData({});
    setShowFloatActivity(false);
    setFloatActivityUrl('');
    setFloatActivityImg(FLOAT_ACTIVITY_DEFAULT_IMAGE);
  };
  /**
   * 刷新首页营销弹窗与悬浮活动状态。
   */
  const syncMarketingState = async (hasLogin: boolean) => {
    if (!hasLogin) {
      resetMarketingState();
      return;
    }
    try {
      const [firstEnterResponse, floatResponse] = await Promise.all([
        deps.firstEnter(),
        deps.getFloatActicity()
      ]);
      const firstEnterData = firstEnterResponse?.data ?? firstEnterResponse ?? {};
      const floatData = floatResponse?.data ?? floatResponse ?? {};
      const isFloatActivityEnabled = Number(floatData?.enableFloatWindow) === 1;
      setShowMarketing(Boolean(firstEnterData?.isFirst));
      setMarketingData(firstEnterData);
      setShowFloatActivity(isFloatActivityEnabled);
      setFloatActivityUrl(String(floatData?.floatWindowJumpUrl || ''));
      setFloatActivityImg(String(floatData?.coverImage || '') || FLOAT_ACTIVITY_DEFAULT_IMAGE);
    } catch (error) {
      resetMarketingState();
    }
  };
  /**
   * 关闭营销弹窗并触发活动券订阅。
   */
  const handleCloseMarketing = () => {
    const subscribeBizId = marketingData?.id || marketingData?.popupId || marketingData?.activityId;
    setShowMarketing(false);
    void deps.subscribeOperationCouponGroup(subscribeBizId);
  };
  /**
   * 点击活动悬浮窗后跳转 H5 活动页。
   */
  const handleFloatActivityClick = () => {
    if (!floatActivityUrl) {
      Taro.showToast({ title: '活动页面建设中', icon: 'none' });
      return;
    }
    deps.toH5(floatActivityUrl, true);
  };
  /**
   * 悬浮活动图加载失败时回退默认图。
   */
  const handleFloatActivityError = () => {
    setFloatActivityImg(FLOAT_ACTIVITY_DEFAULT_IMAGE);
  };
  return {
    showMarketing,
    marketingData,
    showFloatActivity,
    floatActivityUrl,
    floatActivityImg,
    syncMarketingState,
    handleCloseMarketing,
    handleFloatActivityClick,
    handleFloatActivityError
  };
}
src/pages/robotaxi/home/hooks/use-order-ws.ts
New file
@@ -0,0 +1,77 @@
import { useEffect, useRef } from 'react';
import wsManager from '../../../../utils/websocket';
import { TripStatus, toTripStatus } from '../../../../robotaxi/pages/begin-route/constants/trip-status';
type HomeTripStatus = TripStatus | 0;
type OrderStatusPayload = {
  /** ws 推送的行程状态。 */
  tripStatus?: number;
};
type Deps = {
  /** 认证校验。 */
  checkOtp: (silent?: boolean) => Promise<unknown>;
  /** 设置首页行程状态。 */
  setTripStatus: (value: HomeTripStatus) => void;
};
/**
 * 首页订单状态 WebSocket 监听。
 */
export default function useOrderWs(deps: Deps) {
  const orderStatusBoundRef = useRef(false); // 当前页面是否已绑定订单状态监听
  const { checkOtp, setTripStatus } = deps;
  useEffect(() => {
    /**
     * 处理订单状态推送并同步首页状态。
     */
    const orderStatusHandler = (payload?: OrderStatusPayload) => {
      const mappedTripStatus = payload?.tripStatus;
      if (typeof mappedTripStatus === 'number') {
        setTripStatus(mappedTripStatus === 0 ? 0 : toTripStatus(mappedTripStatus));
      }
    };
    let openHandler: (() => void) | null = null;
    /**
     * 只在当前页面生命周期内绑定一次订单状态事件。
     */
    const bindOrderStatus = () => {
      if (orderStatusBoundRef.current) {
        return;
      }
      wsManager.on('order_status', orderStatusHandler);
      orderStatusBoundRef.current = true;
    };
    void checkOtp(false).then((otp) => {
      if (!otp) {
        return;
      }
      if (wsManager.isConnectedStatus()) {
        bindOrderStatus();
        return;
      }
      openHandler = () => {
        bindOrderStatus();
        if (openHandler) {
          wsManager.off('open', openHandler);
        }
      };
      wsManager.on('open', openHandler);
    });
    return () => {
      if (openHandler) {
        wsManager.off('open', openHandler);
      }
      if (orderStatusBoundRef.current) {
        wsManager.off('order_status', orderStatusHandler);
        orderStatusBoundRef.current = false;
      }
    };
  }, [checkOtp, setTripStatus]);
}
src/pages/robotaxi/home/hooks/use-weather.ts
New file
@@ -0,0 +1,81 @@
import { useEffect, useMemo, useState } from 'react';
import {
  convertTemperatureByUnit,
  DEFAULT_TEMPERATURE_UNIT,
  normalizeTemperatureUnit
} from '../../../../utils/weather';
import { getWeatherInfo } from '../../../../utils/Robotaxi-public-method';
import { type SelectedCity } from './use-city-state';
type WeatherData = {
  /** 天气文案。 */
  weather: string;
  /** 原始温度值。 */
  temperature: number | string;
} | null;
type Deps = {
  /** 当前选中城市。 */
  selectedCity: SelectedCity;
  /** 温度单位配置。 */
  temperatureUnit?: string;
};
type WeatherState = {
  /** 原始天气数据。 */
  weatherData: WeatherData;
  /** 展示用温度值。 */
  displayTemperature: string | number;
  /** 展示用温度单位。 */
  displayTemperatureUnit: string;
};
/**
 * 首页天气与温度单位展示管理。
 */
export default function useWeather(deps: Deps): WeatherState {
  const [weatherData, setWeatherData] = useState<WeatherData>(null); // 当前城市天气数据
  const displayTemperatureUnit = useMemo(() => {
    return normalizeTemperatureUnit(deps.temperatureUnit || DEFAULT_TEMPERATURE_UNIT);
  }, [deps.temperatureUnit]);
  const displayTemperature = useMemo(() => {
    return convertTemperatureByUnit(weatherData?.temperature, displayTemperatureUnit);
  }, [displayTemperatureUnit, weatherData?.temperature]);
  useEffect(() => {
    if (!deps.selectedCity?.name) {
      setWeatherData(null);
      return;
    }
    /**
     * 拉取当前城市天气信息。
     */
    const fetchWeather = async () => {
      try {
        const response = await getWeatherInfo(deps.selectedCity.name as string);
        if (response?.weather && response?.temperature) {
          setWeatherData({
            weather: response.weather.data,
            temperature: response.temperature.data
          });
          return;
        }
        setWeatherData(null);
      } catch (error) {
        console.error('获取天气失败', error);
        setWeatherData(null);
      }
    };
    void fetchWeather();
  }, [deps.selectedCity?.name]);
  return {
    weatherData,
    displayTemperature,
    displayTemperatureUnit
  };
}
src/pages/robotaxi/home/index.tsx
@@ -1,106 +1,69 @@
import Taro, { useDidHide, useDidShow } from "@tarojs/taro"
import { useEffect, useState, useRef, useMemo } from 'react';
import { View, Map, Text, Image, Button } from '@tarojs/components';
import Taro, { useDidHide, useDidShow } from "@tarojs/taro";
import { useEffect, useMemo, useState } from 'react';
import { View } from '@tarojs/components';
import './index.less';
import PrivacyPopup from './components/privacy-popup';
import CustomerServicePopup from '../../../robotaxi/components/customer-service';
import OrderStatusCard from './components/order-status-card';
import MapOverlayTips from './components/map-overlay-tips';
import MapPointer from './components/map-pointer';
import MarketingPopup from './components/marketing-popup';
import selectCityIcon from '../../../robotaxi/assets/select-city.png'
import personalCenter from '../../../robotaxi/assets/personal-center.png'
import coupon from '../../../robotaxi/assets/coupon.png'
import contactService from '../../../robotaxi/assets/contact-service.png'
import {
  convertTemperatureByUnit,
  DEFAULT_TEMPERATURE_UNIT,
  getWeatherIconByDesc,
  normalizeTemperatureUnit
} from '../../../utils/weather';
import originIcon from '../../../robotaxi/assets/origin-icon.png'
import pointer from '../../../robotaxi/assets/pointer.png'
import rightIcon from '../../../robotaxi/assets/right-icon.png'
import wsManager from '../../../utils/websocket';
import {
  filterRenderableGeofence,
  findMatchedPolygon,
  formatGeofenceByType,
  getCityLocation,
  safeParse,
  getWeatherInfo
} from '../../../utils/Robotaxi-public-method';
import { _ROBOTAXI_WS_HOST, CDN } from '../../../constants/api';
// 城市切换状态同步
import HomeMapPanel from './components/home-map-panel';
import HomeContentPanel from './components/home-content-panel';
import HomeOverlayLayer from './components/home-overlay-layer';
import useCitySwitch from './hooks/use-city-switch';
// 地图状态管理
import useMapState from './hooks/use-map-state';
// 站点与吸附状态管理
import useSiteState from './hooks/use-site-state';
// 城市状态管理
import useCityState from './hooks/use-city-state';
// 定位流程编排
import useLocationFlow from './hooks/use-location-flow';
// 地图区域变化编排
import useRegionChange from './hooks/use-region-change';
// 底部卡片拖拽编排
import useDragGesture from './hooks/use-drag-gesture';
// 页面导航与操作编排
import useNavigation from './hooks/use-navigation';
// 地图回中动作编排
import useOriginAction from './hooks/use-origin-action';
// 首页启动流程编排
import useHomeBootstrap from './hooks/use-home-bootstrap';
// 快捷胶囊编排
import useQuickCapsule from './hooks/use-quick-capsule';
// 附近车辆编排
import useNearbyVehicles from './hooks/use-nearby-vehicles';
import useHotfixCheck from './hooks/use-hotfix-check';
import useOrderWs from './hooks/use-order-ws';
import useWeather from './hooks/use-weather';
import useMarketing from './hooks/use-marketing';
import useFenceManager from './hooks/use-fence-manager';
import { DEFAULT_TEMPERATURE_UNIT } from '../../../utils/weather';
import { _ROBOTAXI_WS_HOST } from '../../../constants/api';
import { DEFAULT_LATITUDE, DEFAULT_LONGITUDE, FIRST_ENTER_KEY } from '../../../robotaxi/constants/map';
import { TripStatus, toTripStatus } from '../../../robotaxi/pages/begin-route/constants/trip-status';
import { TripStatus } from '../../../robotaxi/pages/begin-route/constants/trip-status';
import {
  getGeofence,
  getBannerList,
  getAgreement,
  getOrderActive,
  getHotFixInfo,
  firstEnter,
  getFloatActicity
} from '../../../robotaxi/api/home';
import { getServicePhone } from '../../../robotaxi/api/customer-service';
import { getOrderDistance } from '../../../robotaxi/api/select-map-site';
import { checkOtp, toH5 } from '../../../utils/tools';
import { subscribeOperationCouponGroup } from '../../../utils/subscribe';
import { GeofenceType } from '../../../robotaxi/constants/geofence';
import { checkOtp, toH5 } from '../../../utils/tools';
import { getCityLocation } from '../../../utils/Robotaxi-public-method';
import {
  HOME_ORDER_STATUS,
  MARKER_TAP_SETTLE_DELAY,
  OVERLAY_ANCHOR_INIT_DELAY,
  OVERLAY_ANCHOR_OFFSET,
  WAITING_RADAR_SRC
} from './constants';
type HomeTripStatus = TripStatus | 0;
/**
 * Robotaxi 首页页面容器:仅负责组合领域 hook 与布局组件。
 */
export default function Home() {
  const renderTailPreserveText = (text: string, tailCount = 3) => {
    const safeText = String(text || '');
    if (!safeText) return null;
    if (safeText.length <= tailCount) {
      return <Text className='to-site-fast-full'>{safeText}</Text>;
    }
    const headText = safeText.slice(0, -tailCount);
    const tailText = safeText.slice(-tailCount);
    return (
      <>
        <Text className='to-site-fast-head'>{headText}</Text>
        <Text className='to-site-fast-tail'>{tailText}</Text>
      </>
    );
  };
  const floatDefaultImg = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/coverImage.png';
  const [showMarketing, setShowMarketing] = useState(false); // 营销弹窗是否显示
  const [marketingData, setMarketingData] = useState({}); // 营销弹窗数据
  const [showFloatActivity, setShowFloatActivity] = useState(false); // 活动悬浮窗是否显示
  const [floatActivityUrl, setFloatActivityUrl] = useState(''); // 活动悬浮窗跳转地址
  const [floatActivityImg, setFloatActivityImg] = useState(floatDefaultImg); // 活动悬浮窗图片
  const [showPrivacy, setShowPrivacy] = useState(false); // 隐私政策弹窗是否显示
  const [showPrivacy, setShowPrivacy] = useState(false); // 隐私协议弹窗是否显示
  const [showCustomerService, setShowCustomerService] = useState(false); // 客服弹窗是否显示
  const mapState = useMapState(); // 地图状态容器
  const siteState = useSiteState(); // 站点状态容器
  const cityState = useCityState(); // 城市状态容器
  const [tripStatus, setTripStatus] = useState<HomeTripStatus>(0); // 当前行程状态
  const [bannerList, setBannerList] = useState<any[]>([]); // Banner 数据
  const [orderData, setOrderData] = useState<any>({}); // 当前活跃订单数据
  const [needLogin, setNeedLogin] = useState(false); // 当前是否需要登录
  const [overlayAnchorBottom, setOverlayAnchorBottom] = useState<number | null>(null); // 地图浮层锚点
  const [temperatureUnit, setTemperatureUnit] = useState(DEFAULT_TEMPERATURE_UNIT); // 温度单位配置
  const mapState = useMapState(); // 地图领域状态
  const siteState = useSiteState(); // 站点吸附状态
  const cityState = useCityState(); // 城市选择状态
  const {
    longitude,
    latitude,
@@ -119,39 +82,31 @@
    setIsLocationAuthDenied,
    shouldIgnoreRegionChange,
    isReturningToOrigin,
    resetTimer,
    updateMapData,
    nearbyLoading
  } = mapState; // 地图状态解构
  } = mapState;
  const {
    currentSite,
    isAdsorbed,
    showToolTip,
    bounce,
    adsorptionDistance,
    setCurrentSite,
    setCurrentAddress,
    setIsAdsorbed,
    setShowToolTip,
    setBounce,
    setAdsorptionDistance
  } = siteState; // 站点状态解构
  const { selectedCity, setSelectedCity, skipCityChangeEffectRef, ensureSelectedCityReady } = cityState; // 城市状态解构
  const [isMaintain, setIsMaintain] = useState(false); // 是否维护中
  const [maintainBounce, setMaintainBounce] = useState(false);
  const [hotfixEndText, setHotfixEndText] = useState('');
  const [tripStatus, setTripStatus] = useState<HomeTripStatus>(0); // 行程状态(0 表示无进行中行程)
  const [bannerList, setBannerList] = useState<any[]>([]); // 轮播图列表
  const [weatherData, setWeatherData] = useState<any>(null); // 天气数据
  const [orderData, setOrderData] = useState<any>({}); // 订单数据
  const [needLogin, setNeedLogin] = useState(false); // 是否需要登录
  const [overlayAnchorBottom, setOverlayAnchorBottom] = useState<number | null>(null); // 浮层初始化锚点(距底部)
  const [temperatureUnit, setTemperatureUnit] = useState(DEFAULT_TEMPERATURE_UNIT); // 后台配置的温度单位
  const [operatingGeofenceList, setOperatingGeofenceList] = useState<any[]>([]); // 运营区围栏原始数据
  const [stationGeofenceList, setStationGeofenceList] = useState<any[]>([]); // 场站围栏原始数据
  const operatingGeofenceListRef = useRef<any[]>([]); // 运营区围栏最新缓存
  const stationGeofenceListRef = useRef<any[]>([]); // 场站围栏最新缓存
  const shouldShowOrderStatusCard = useMemo(() => tripStatus === 3 || Boolean(orderData?.id), [orderData?.id, tripStatus]); // 订单状态卡片展示时机
  } = siteState;
  const { selectedCity, setSelectedCity, skipCityChangeEffectRef, ensureSelectedCityReady } = cityState;
  const shouldShowOrderStatusCard = useMemo(() => {
    return tripStatus === TripStatus.CallingCar || Boolean(orderData?.id);
  }, [orderData?.id, tripStatus]);
  const isLoggedIn = !needLogin; // 当前是否已登录
  const isGuest = needLogin; // 当前是否游客态
  const shouldShowLoginEntry = needLogin; // 当前是否展示登录引导
  const shouldShowGuestTip = Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true' && isGuest; // 当前是否展示游客隐私提示
  const {
    boxHeight,
    originOpacity,
@@ -159,257 +114,42 @@
    handleTouchStart,
    handleTouchMove,
    handleTouchEnd
  } = useDragGesture({
    bannerList,
    tripStatus
  }); // 拖拽手势编排
  } = useDragGesture({ bannerList, tripStatus });
  const {
    skipNextLocationCenterRef,
    lastSelectedCityCodeRef,
    syncSelectedCityFromStorage,
    consumeCitySwitchFlag
  } = useCitySwitch(); // 切城同步编排
  const orderStatusBound = useRef(false);
  const waitingRadarSrc = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/waiting-radar.webp';
  } = useCitySwitch();
  const { isMaintain, maintainBounce, hotfixEndText, triggerMaintainBounce } = useHotfixCheck();
  const isAndroid = useMemo(() => {
    try {
      const info = Taro.getSystemInfoSync();
      return info?.platform === 'android' || /android/i.test(info?.system || '');
      const systemInfo = Taro.getSystemInfoSync();
      return systemInfo?.platform === 'android' || /android/i.test(systemInfo?.system || '');
    } catch (error) {
      return false;
    }
  }, []);
  const ORDER_STATUS: Partial<Record<TripStatus, string>> = {
    [TripStatus.VehicleApproaching]: '车辆正在赶来',
    [TripStatus.VehicleArrivedPickup]: '车辆已到达上车点,请尽快前往',
    [TripStatus.InService]: '正在前往目的地',
    [TripStatus.ArrivedDestination]: '车辆已到达目的地,请尽快结束行程',
    [TripStatus.TripFinished]: '行程已结束,请尽快支付'
  }; // 订单状态文案映射
  const displayTemperatureUnit = useMemo(() => normalizeTemperatureUnit(temperatureUnit), [temperatureUnit]); // 页面展示温度单位
  const displayTemperature = useMemo(() => convertTemperatureByUnit(weatherData?.temperature, displayTemperatureUnit), [weatherData?.temperature, displayTemperatureUnit]); // 页面展示温度值
  const isLoggedIn = !needLogin; // 当前是否已登录
  const isGuest = needLogin; // 当前是否游客态
  const shouldShowLoginEntry = needLogin; // 是否展示登录入口
  /**
   * 按当前命中的场站围栏刷新地图覆盖物,运营区始终常驻展示。
   */
  const applyVisibleGeofence = (matchedStationGeofence: any | null, nextOperatingGeofenceList?: any[]) => {
    const operatingGeofenceSource = Array.isArray(nextOperatingGeofenceList)
      ? nextOperatingGeofenceList
      : operatingGeofenceListRef.current;
    const { polygons: operatingPolygons } = formatGeofenceByType(operatingGeofenceSource);
    const { polygons: stationPolygons, fencePolylines: stationFencePolylines } = formatGeofenceByType(
      matchedStationGeofence ? [matchedStationGeofence] : []
    );
    setPolygons([...operatingPolygons, ...stationPolygons]);
    setFencePolylines(stationFencePolylines);
  };
  /**
   * 根据大头针中心点切换场站围栏显隐。
   */
  const syncStationFenceVisibility = (lat: number, lng: number, isInside: boolean) => {
    const latestOperatingGeofenceList = operatingGeofenceListRef.current;
    const latestStationGeofenceList = stationGeofenceListRef.current;
    if (!Array.isArray(latestOperatingGeofenceList) || latestOperatingGeofenceList.length === 0) {
      return;
    }
    if (!isInside || !Array.isArray(latestStationGeofenceList) || latestStationGeofenceList.length === 0) {
      applyVisibleGeofence(null);
      return;
    }
    const matchedStationGeofence = findMatchedPolygon(
      { latitude: lat, longitude: lng },
      latestStationGeofenceList,
      0
    );
    applyVisibleGeofence(matchedStationGeofence);
  };
  /**
   * 同步运营区围栏 ref,避免异步回调读到旧值。
   */
  useEffect(() => {
    operatingGeofenceListRef.current = operatingGeofenceList;
  }, [operatingGeofenceList]);
  /**
   * 同步场站围栏 ref,保证初始化与吸附结算拿到最新数据。
   */
  useEffect(() => {
    stationGeofenceListRef.current = stationGeofenceList;
  }, [stationGeofenceList]);
  useEffect(() => {
    let timer: any = null;
    const check = () => {
      getHotFixInfo().then((res: any) => {
        const status = Number(res?.hotfixStatus);
        if (status === 1) {
          setIsMaintain(true);
          setHotfixEndText(res?.hotfixEndTime);
        } else {
          setIsMaintain(false);
          setHotfixEndText('');
        }
      }).catch(() => { });
    };
    check();
    timer = setInterval(check, 1000 * 30);
    return () => {
      if (timer) clearInterval(timer);
    };
  }, []);
  // 监听隐私政策显示状态
  useEffect(() => {
    getBannerList().then((res: any) => {
      if (res && Array.isArray(res)) {
        setBannerList(res);
      }
    }).catch(err => {
      console.error('获取Banner列表失败', err);
    });
    getOrderDistance().then((res: any) => {
      if (res && res.adsorptionDistance) {
        setAdsorptionDistance(res.adsorptionDistance);
      }
      if (res && res.temperatureUnit) {
        setTemperatureUnit(res.temperatureUnit);
      }
    }).catch(err => {
      console.error('获取吸附距离失败', err);
    });
  }, []);
  // 监听订单状态变化
  useEffect(() => {
    const orderStatusHandler = (payload) => {
      const mappedTripStatus = payload?.tripStatus; // ws 推送的行程状态
      if (typeof mappedTripStatus === 'number') {
        setTripStatus(mappedTripStatus === 0 ? 0 : toTripStatus(mappedTripStatus));
      }
    };
    let openHandler: (() => void) | null = null;
    const bindOrderStatus = () => {
      if (orderStatusBound.current) return;
      wsManager.on('order_status', orderStatusHandler);
      orderStatusBound.current = true;
    };
    checkOtp(false).then((otp) => {
      if (!otp) return;
      if (wsManager.isConnectedStatus()) {
        bindOrderStatus();
        return;
      }
      openHandler = () => {
        bindOrderStatus();
        if (openHandler) {
          wsManager.off('open', openHandler);
        }
      };
      wsManager.on('open', openHandler);
    });
    return () => {
      if (openHandler) {
        wsManager.off('open', openHandler);
      }
      if (orderStatusBound.current) {
        wsManager.off('order_status', orderStatusHandler);
        orderStatusBound.current = false;
      }
    };
  }, []);
  // 监听城市变化,更新电子围栏
  useEffect(() => {
    if (skipCityChangeEffectRef.current) {
      skipCityChangeEffectRef.current = false;
      return;
    }
    if (!selectedCity.code) return;
    const allowMapMove = !isLocationAuthDenied;
    getGeofence({ cityCode: selectedCity.code }).then((res: any) => {
      const geofenceList = filterRenderableGeofence(res);
      const nextOperatingGeofenceList = geofenceList.filter((item) => Number(item?.type) === GeofenceType.OPERATING); // 运营区围栏集合
      const nextStationGeofenceList = geofenceList.filter((item) => Number(item?.type) === GeofenceType.STATION); // 场站围栏集合
      setOperatingGeofenceList(nextOperatingGeofenceList);
      setStationGeofenceList(nextStationGeofenceList);
      if (geofenceList.length > 0) {
        const matchedStationGeofence = (typeof latitude === 'number' && typeof longitude === 'number')
          ? findMatchedPolygon(
            { latitude, longitude },
            nextStationGeofenceList,
            0
          )
          : null;
        applyVisibleGeofence(matchedStationGeofence, nextOperatingGeofenceList);
      } else {
        setOperatingGeofenceList([]);
        setStationGeofenceList([]);
        setPolygons([]);
        setFencePolylines([]);
        if ((orderData || orderData?.id) && JSON.stringify(orderData) != '{}') return;
        // 定位到城市中心
        if (selectedCity.name && allowMapMove) {
          getCityLocation(selectedCity.name).then(loc => {
            skipNextLocationCenterRef.current = true;
            shouldIgnoreRegionChange.current = true;
            const mapCtx = Taro.createMapContext('map');
            mapCtx.moveToLocation({
              latitude: loc.latitude,
              longitude: loc.longitude,
            });
          }).catch(err => {
            console.error('获取城市中心失败', err);
          });
        }
      }
    }).catch(err => {
      console.error('获取电子围栏失败', err);
      setOperatingGeofenceList([]);
      setStationGeofenceList([]);
      setPolygons([]);
      setFencePolylines([]);
      // 定位到城市中心
      if (selectedCity.name && allowMapMove) {
        getCityLocation(selectedCity.name).then(loc => {
          skipNextLocationCenterRef.current = true;
          shouldIgnoreRegionChange.current = true;
          const mapCtx = Taro.createMapContext('map');
          mapCtx.moveToLocation({
            latitude: loc.latitude,
            longitude: loc.longitude,
          });
        }).catch(err => {
          console.error('获取城市中心失败', err);
        });
      }
    });
  }, [selectedCity.code, selectedCity.name, isLocationAuthDenied]);
  const { syncStationFenceVisibility } = useFenceManager({
    selectedCity,
    longitude,
    latitude,
    isLocationAuthDenied,
    skipCityChangeEffectRef,
    skipNextLocationCenterRef,
    shouldIgnoreRegionChange,
    hasActiveOrder: Boolean(orderData?.id),
    setPolygons,
    setFencePolylines
  });
  const { getLocationInfo } = useLocationFlow({
    mapState,
    siteState,
    cityState,
    isLoggedIn,
    onLocationSettled: syncStationFenceVisibility
  }); // 定位流程编排
  const handleClosePrivacy = () => {
    setShowPrivacy(false);
  };
  const handleAgreePrivacy = () => {
    // 处理用户同意逻辑
    setShowPrivacy(false);
  };
  });
  const { toOrigin } = useOriginAction({
    isLocationAuthDenied,
    shouldIgnoreRegionChange,
@@ -419,8 +159,7 @@
    setLongitude,
    ensureSelectedCityReady,
    lastSelectedCityCodeRef
  }); // 地图回中动作
  });
  const bootstrap = useHomeBootstrap({
    wsHost: _ROBOTAXI_WS_HOST,
    getServicePhone,
@@ -453,91 +192,34 @@
    defaultLatitude: DEFAULT_LATITUDE,
    defaultLongitude: DEFAULT_LONGITUDE,
    toOrigin
  }); // 首页启动编排
  });
  const {
    quickCapsuleText,
    showQuickCapsule,
    refreshQuickCapsule,
    handleQuickCapsuleClick
  } = useQuickCapsule({
    isGuest,
    checkOtp,
    currentSite
  }); // 快捷胶囊编排
  useEffect(() => {
    if (selectedCity && selectedCity.name) {
      getWeatherInfo(selectedCity.name).then(res => {
        console.log('天气天气', res);
        if (res && res.weather && res.temperature) {
          setWeatherData({
            weather: res.weather.data,
            temperature: res.temperature.data
          });
        }
      }).catch(err => {
        console.error('获取天气失败', err);
      });
    }
  }, [selectedCity]);
  useDidShow(() => {
    const authSyncTask = bootstrap.onDidShow();
    refreshQuickCapsule();
    authSyncTask
      .then((hasLogin) => {
        if (!hasLogin) {
          setShowMarketing(false);
          setShowFloatActivity(false);
          return;
        }
        return Promise.all([firstEnter(), getFloatActicity()])
          .then(([firstEnterRes, floatRes]: any[]) => {
            // 兼容 firstEnter 接口返回结构:可能是 res.data 或直接为业务对象
            const firstEnterData = firstEnterRes?.data ?? firstEnterRes ?? {};
            const show = Boolean(firstEnterData?.isFirst);
            setShowMarketing(show);
            setMarketingData(firstEnterData);
            const floatData = floatRes?.data ?? floatRes ?? {};
            const isFloatActivity = Boolean(
              floatData?.enableFloatWindow === 1
            );
            setShowFloatActivity(isFloatActivity);
            setFloatActivityUrl(
              String(
                floatData?.floatWindowJumpUrl ||
                ''
              )
            );
            const img = String(floatData?.coverImage || '');
            setFloatActivityImg(img || floatDefaultImg);
          })
          .catch(() => {
            setShowMarketing(false);
            setShowFloatActivity(false);
            setFloatActivityUrl('');
          });
      })
      .catch(() => {
        setShowMarketing(false);
        setShowFloatActivity(false);
        setFloatActivityUrl('');
      });
    if (typeof latitude === 'number' && typeof longitude === 'number') {
      tryStartPollingOnInit({
        lat: latitude,
        lon: longitude,
        isInside: isOperatingArea
      });
    }
    setTimeout(() => {
      initOverlayAnchor(boxHeight);
    }, 80);
  } = useQuickCapsule({ isGuest, checkOtp, currentSite });
  const { weatherData, displayTemperature, displayTemperatureUnit } = useWeather({
    selectedCity,
    temperatureUnit
  });
  const {
    showMarketing,
    marketingData,
    showFloatActivity,
    floatActivityImg,
    syncMarketingState,
    handleCloseMarketing,
    handleFloatActivityClick,
    handleFloatActivityError
  } = useMarketing({
    firstEnter,
    getFloatActicity,
    subscribeOperationCouponGroup,
    toH5
  });
  useDidHide(() => {
    disposeVehicles();
  });
  useOrderWs({ checkOtp, setTripStatus });
  const {
    vehicleMarkers,
@@ -545,24 +227,10 @@
    handleRegionSettled: handleVehicleRegionSettled,
    tryStartPollingOnInit,
    disposeVehicles
  } = useNearbyVehicles({
    isOperatingArea
  }); // 附近车辆编排
  useEffect(() => {
    if (nearbyLoading || isLocationAuthDenied) return;
    if (typeof latitude !== 'number' || typeof longitude !== 'number') return;
    tryStartPollingOnInit({
      lat: latitude,
      lon: longitude,
      isInside: isOperatingArea
    });
  }, [nearbyLoading, isLocationAuthDenied, latitude, longitude, isOperatingArea, tryStartPollingOnInit]);
  } = useNearbyVehicles({ isOperatingArea });
  const mergedMapMarkers = useMemo(() => {
    return [...siteMarkers, ...vehicleMarkers];
  }, [siteMarkers, vehicleMarkers]); // 地图合并 marker 列表
  }, [siteMarkers, vehicleMarkers]);
  const { handleRegionChange, queryPointerByCenter } = useRegionChange({
    mapState,
    siteState,
@@ -572,9 +240,8 @@
      handleVehicleRegionSettled(payload);
      syncStationFenceVisibility(payload.lat, payload.lon, payload.isInside);
    }
  }); // 地图 region-change 编排
  });
  const {
    ensureNetwork,
    toSelectSitePage,
    toCoupon,
    toOrderDetail,
@@ -602,284 +269,220 @@
    setMapScale,
    getLocationInfo,
    queryPointerByCenter
  }); // 首页导航与操作编排
  });
  /**
   * 初始化首页首屏配置。
   */
  useEffect(() => {
    void getBannerList().then((response: any) => {
      if (Array.isArray(response)) {
        setBannerList(response);
      }
    }).catch((error) => {
      console.error('获取Banner列表失败', error);
    });
    void getOrderDistance().then((response: any) => {
      if (response?.adsorptionDistance) {
        setAdsorptionDistance(response.adsorptionDistance);
      }
      if (response?.temperatureUnit) {
        setTemperatureUnit(response.temperatureUnit);
      }
    }).catch((error) => {
      console.error('获取吸附距离失败', error);
    });
  }, [setAdsorptionDistance]);
  /**
   * 统一计算地图浮层锚点。
   */
  const initOverlayAnchor = (customHeight?: number) => {
    const measuredHeight = typeof customHeight === 'number' ? customHeight : Number(boxHeight || 0);
    if (!Number.isFinite(measuredHeight) || measuredHeight <= 0) {
      return;
    }
    setOverlayAnchorBottom(Math.max(0, measuredHeight + OVERLAY_ANCHOR_OFFSET));
  };
  /**
   * 关闭隐私弹窗。
   */
  const handleClosePrivacy = () => {
    setShowPrivacy(false);
  };
  /**
   * 同意隐私协议后关闭弹窗。
   */
  const handleAgreePrivacy = () => {
    setShowPrivacy(false);
  };
  /**
   * 关闭客服弹窗。
   */
  const handleCloseCustomerService = () => {
    setShowCustomerService(false);
  };
  /**
   * 关闭营销活动弹窗并触发活动券订阅。
   * 点击站点 marker 后同步地图中心与吸附站点。
   */
  const handleCloseMarketing = () => {
    setShowMarketing(false);
    const subscribeBizId = marketingData?.id || marketingData?.popupId || marketingData?.activityId; // 优先使用活动主键做订阅频控
    void subscribeOperationCouponGroup(subscribeBizId);
  };
  const handleFloatActivityClick = () => {
    if (!floatActivityUrl) {
      Taro.showToast({ title: '活动页面建设中', icon: 'none' });
  const handleMarkerTap = (event: any) => {
    if (isLocationAuthDenied) {
      return;
    }
    // 预留 H5 出口,后续可按活动类型扩展埋点和鉴权逻辑
    toH5(floatActivityUrl, true);
    const markerId = event?.detail?.markerId;
    if (markerId === undefined) {
      return;
    }
    const targetMarker = mergedMapMarkers.find((item) => item.id === markerId);
    if (!targetMarker || targetMarker.markerType === 'vehicle') {
      return;
    }
    shouldIgnoreRegionChange.current = true;
    setShowToolTip(false);
    setBounce(true);
    setLatitude(targetMarker.latitude);
    setLongitude(targetMarker.longitude);
    setTimeout(() => {
      setBounce(false);
      setIsAdsorbed(true);
      setCurrentSite(targetMarker);
      Taro.setStorageSync('startSite', targetMarker);
      setShowToolTip(true);
    }, MARKER_TAP_SETTLE_DELAY);
  };
  // 初始化浮层锚点:固定在 content-box 顶部上方 32px,统一按实际渲染高度量测
  const initOverlayAnchor = (customHeight?: number) => {
    const measuredHeight = typeof customHeight === 'number' ? customHeight : Number(boxHeight || 0);
    if (!Number.isFinite(measuredHeight) || measuredHeight <= 0) return;
    const bottom = Math.max(0, measuredHeight + 32);
    setOverlayAnchorBottom(bottom);
  };
  useDidShow(() => {
    const authSyncTask = bootstrap.onDidShow();
  // 订单状态卡片显隐与底部容器高度变化时,都需要重新计算地图浮层锚点。
    refreshQuickCapsule();
    void authSyncTask
      .then((hasLogin) => syncMarketingState(Boolean(hasLogin)))
      .catch(() => syncMarketingState(false));
    if (typeof latitude === 'number' && typeof longitude === 'number') {
      tryStartPollingOnInit({
        lat: latitude,
        lon: longitude,
        isInside: isOperatingArea
      });
    }
    setTimeout(() => {
      initOverlayAnchor(boxHeight);
    }, OVERLAY_ANCHOR_INIT_DELAY);
  });
  useDidHide(() => {
    disposeVehicles();
  });
  useEffect(() => {
    if (nearbyLoading || isLocationAuthDenied) {
      return;
    }
    if (typeof latitude !== 'number' || typeof longitude !== 'number') {
      return;
    }
    tryStartPollingOnInit({
      lat: latitude,
      lon: longitude,
      isInside: isOperatingArea
    });
  }, [nearbyLoading, isLocationAuthDenied, latitude, longitude, isOperatingArea, tryStartPollingOnInit]);
  useEffect(() => {
    initOverlayAnchor(boxHeight);
  }, [bannerList.length, tripStatus, shouldShowOrderStatusCard, boxHeight]);
  // 处理点击地图 marker
  const handleMarkerTap = (e: any) => {
    if (isLocationAuthDenied) return;
    const markerId = e?.detail?.markerId;
    if (markerId === undefined) return;
    const targetMarker = mergedMapMarkers.find(m => m.id === markerId);
    if (targetMarker) {
      if (targetMarker.markerType === 'vehicle') {
        return;
      }
      // 忽略因调用 moveToLocation 引起的 region change,避免重复吸附计算
      shouldIgnoreRegionChange.current = true;
      // 先隐藏tooltip,并触发指针跳动动画
      setShowToolTip(false);
      setBounce(true);
      // 直接更新经纬度实现跳转
      setLatitude(targetMarker.latitude);
      setLongitude(targetMarker.longitude);
      // 等待指针动画(600ms)结束后,再展示tooltip和吸附状态
      setTimeout(() => {
        setBounce(false);
        setIsAdsorbed(true);
        setCurrentSite(targetMarker);
        Taro.setStorageSync('startSite', targetMarker);
        setShowToolTip(true);
      }, 600);
    }
  };
  }, [bannerList.length, boxHeight, shouldShowOrderStatusCard, tripStatus]);
  return (
    <View className="home-container">
      <Map
        id="map"
        longitude={longitude ?? DEFAULT_LONGITUDE}
        latitude={latitude ?? DEFAULT_LATITUDE}
        scale={mapScale}
        markers={isLocationAuthDenied ? [] : mergedMapMarkers.map(m => ({ ...m, title: '', name: '' }))}
        polyline={fencePolylines}
      <HomeMapPanel
        longitude={longitude}
        latitude={latitude}
        mapScale={mapScale}
        mergedMapMarkers={mergedMapMarkers}
        fencePolylines={fencePolylines}
        polygons={polygons}
        showLocation={!!longitude}
        isLocationAuthDenied={isLocationAuthDenied}
        selectedCityName={selectedCity?.name}
        weatherData={weatherData}
        displayTemperature={displayTemperature}
        displayTemperatureUnit={displayTemperatureUnit}
        originOpacity={originOpacity}
        overlayAnchorBottom={overlayAnchorBottom}
        isDragging={isDragging.current}
        shouldShowGuestTip={shouldShowGuestTip}
        isOperatingArea={isOperatingArea}
        isMaintain={isMaintain}
        maintainBounce={maintainBounce}
        hotfixEndText={hotfixEndText}
        nearbyLoading={nearbyLoading}
        isAndroid={isAndroid}
        showToolTip={showToolTip}
        bounce={bounce}
        isAdsorbed={isAdsorbed}
        currentSite={currentSite}
        waitingRadarSrc={WAITING_RADAR_SRC}
        onRegionChange={handleRegionChange}
        onMarkerTap={handleMarkerTap}
        style={{ width: '100%', height: '85vh', flex: 'none' }}
      >
        {/* 选择城市 */}
        <View className="select-city-container">
          <View onClick={toSelectCityPage} className="select-city">
            <Text>{selectedCity.name || '北京市'}</Text>
            <Image className="select-city-icon" src={selectCityIcon} />
          </View>
          {weatherData && (
            <View className="weather-info">
              <Image className="weather-icon" src={getWeatherIconByDesc(weatherData.weather)} />
              <Text>{displayTemperature}{displayTemperatureUnit}</Text>
            </View>
          )}
        </View>
        {/* 定位图标 */}
        <View
          className="origin-box"
          style={{
            opacity: originOpacity,
            visibility: originOpacity <= 0 ? 'hidden' : 'visible',
            pointerEvents: originOpacity <= 0 ? 'none' : 'auto',
            transition: isDragging.current ? 'none' : 'opacity 0.2s ease-out',
            top: typeof overlayAnchorBottom === 'number' ? 'auto' : undefined,
            bottom: typeof overlayAnchorBottom === 'number' ? `${overlayAnchorBottom}px` : undefined,
          }}
        >
          <Image onClick={toOrigin} className="origin-icon" src={originIcon} />
        </View>
        <MapOverlayTips
          isGuest={Taro.getStorageSync('has_show_privicy_Robotaxi') != 'true' && isGuest}
          isLocationAuthDenied={isLocationAuthDenied}
          isOperatingArea={isOperatingArea}
          isMaintain={isMaintain}
          maintainBounce={maintainBounce}
          hotfixEndText={hotfixEndText}
          anchorBottom={overlayAnchorBottom ?? undefined}
          opacity={originOpacity}
          isDragging={isDragging.current}
          onShowPrivacy={() => setShowPrivacy(true)}
          onOpenSetting={handleOpenSetting}
          onViewOperatingArea={handleViewOperatingArea}
        />
        {/* 地图浮层入口 */}
        <View className="map-entry">
          <Image onClick={toPersonalCenter} className="map-entry-icon" src={personalCenter} />
          <View className="split-line"></View>
          <Image onClick={openService} className="map-entry-icon" src={contactService} />
          <View className="split-line"></View>
          <Image onClick={toCoupon} className="map-entry-icon" src={coupon} />
        </View>
        <MapPointer
          showToolTip={showToolTip}
          bounce={bounce}
          nearbyLoading={nearbyLoading}
          isAndroid={isAndroid}
          isLocationAuthDenied={isLocationAuthDenied}
          isOperatingArea={isOperatingArea}
          isAdsorbed={isAdsorbed}
          currentSite={currentSite}
          pointerSrc={pointer}
          rightIconSrc={rightIcon}
          waitingRadarSrc={waitingRadarSrc}
          onSelectSite={(type) => toSelectSitePage(type)}
        />
      </Map>
      {/* 选择站点 */}
      <View
        className="content-box"
        style={{ height: bannerList.length > 1 ? `${boxHeight}px` : `auto`, transition: isDragging.current ? 'none' : 'height 0.3s ease-out' }}
        catchMove
        onTouchStart={(e) => {
          e.stopPropagation();
          handleTouchStart(e);
        }}
        onTouchMove={(e) => {
          e.stopPropagation();
          handleTouchMove(e);
        }}
        onTouchEnd={(e) => {
          e.stopPropagation();
          handleTouchEnd();
        }}
      >
        <View className="content-body">
          <View className="select-site">
            <Image className="site-top-bg" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg-20260507.png' />
            <View className="site-boarding-point">
              {/* 起点 */}
              <View onClick={() => toSelectSitePage('start')} className="now-site">
                <View className="now-site-dot"></View>
                <View className="now-site-address">
                  <View className="now-site-address-content">
                    从
                    <Text className="now-site-address-text">
                      {(!isOperatingArea || isAdsorbed) ? (currentSite.name || '请选择上车点') : '推荐上车点'}
                    </Text>
                    出发
                  </View>
                  <Image className="now-site-icon" src={rightIcon} />
                </View>
              </View>
              {/* 终点 */}
              <View onClick={() => toSelectSitePage('end')} className="to-site">
                <View className="to-site-dot"></View>
                <View className="to-site-address">
                  <View className="to-site-text">你想去哪?</View>
                  {showQuickCapsule && (
                    <View
                      onClick={(e) => {
                        e.stopPropagation();
                        handleQuickCapsuleClick();
                      }}
                      className="to-site-fast"
                    >
                      {renderTailPreserveText(quickCapsuleText)}
                    </View>
                  )}
                </View>
              </View>
            </View>
            {
              shouldShowOrderStatusCard && (
                <OrderStatusCard
                  tripStatus={tripStatus}
                  orderData={orderData}
                  onViewDetail={toOrderDetail}
                  orderStatusMap={ORDER_STATUS}
                />
              )
            }
            {/* 运营活动 */}
            {bannerList.length > 0 && (
              <View className="activity">
                {
                  bannerList.map((item, index) => (
                    <View key={item.id || index} className="activity-item" onClick={() => handleBannerClick(item)}>
                      <Image mode="aspectFill" className="activity-img" src={safeParse(item.imageUrl, [])?.[0]?.url} />
                    </View>
                  ))
                }
              </View>
            )}
            {/* 产品名 */}
            <View onClick={toAgreementPage} className="product-name">XPENG Robotaxi</View>
          </View>
        </View>
      </View>
      {
        isMaintain && (
          <View
            onClick={() => {
              setMaintainBounce(true);
              setTimeout(() => setMaintainBounce(false), 300);
            }}
            style={{ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', zIndex: 9999 }}
          />
        )
      }
      {/* 是否展示登录入口 */}
      {
        shouldShowLoginEntry &&
        <View className='login-bar'>
          <View className='text'>立即登录,即可体验Robotaxi行程服务</View>
          <View className='more'
            onClick={toLogin}
          >
            <Button className='more-btn'>立即登录</Button>
            <Image src={`${CDN}/common/arrow-white-new.png`} className='more-icon'></Image>
          </View>
        </View>
      }
      {/* 隐私政策弹窗 */}
      <PrivacyPopup
        show={showPrivacy}
        onClose={handleClosePrivacy}
        onAgree={handleAgreePrivacy}
        onSelectCity={toSelectCityPage}
        onToOrigin={toOrigin}
        onShowPrivacy={() => setShowPrivacy(true)}
        onOpenSetting={handleOpenSetting}
        onViewOperatingArea={handleViewOperatingArea}
        onToPersonalCenter={toPersonalCenter}
        onOpenService={openService}
        onToCoupon={toCoupon}
        onSelectSite={toSelectSitePage}
      />
      {/* 客服弹窗 */}
      <CustomerServicePopup
        show={showCustomerService}
        onClose={handleCloseCustomerService}
      <HomeContentPanel
        bannerList={bannerList}
        boxHeight={boxHeight}
        isDragging={isDragging.current}
        shouldShowOrderStatusCard={shouldShowOrderStatusCard}
        tripStatus={tripStatus}
        orderData={orderData}
        orderStatusMap={HOME_ORDER_STATUS}
        isOperatingArea={isOperatingArea}
        isAdsorbed={isAdsorbed}
        currentSite={currentSite}
        showQuickCapsule={showQuickCapsule}
        quickCapsuleText={quickCapsuleText}
        onSelectSite={toSelectSitePage}
        onQuickCapsuleClick={handleQuickCapsuleClick}
        onViewOrderDetail={toOrderDetail}
        onBannerClick={handleBannerClick}
        onToAgreement={toAgreementPage}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
        onTouchEnd={handleTouchEnd}
      />
      {/* 营销活动弹窗 */}
      <MarketingPopup show={showMarketing} data={marketingData} onClose={handleCloseMarketing} />
      {/* 悬浮窗 */}
      {showFloatActivity && (
        <View className='activity-float-window' onClick={handleFloatActivityClick}>
          <Image
            className='activity-float-window-img'
            src={floatActivityImg || floatDefaultImg}
            onError={() => setFloatActivityImg(floatDefaultImg)}
          />
        </View>
       )}
      <HomeOverlayLayer
        isMaintain={isMaintain}
        onMaintainMaskClick={triggerMaintainBounce}
        shouldShowLoginEntry={shouldShowLoginEntry}
        onToLogin={toLogin}
        showPrivacy={showPrivacy}
        onClosePrivacy={handleClosePrivacy}
        onAgreePrivacy={handleAgreePrivacy}
        showCustomerService={showCustomerService}
        onCloseCustomerService={handleCloseCustomerService}
        showMarketing={showMarketing}
        marketingData={marketingData}
        onCloseMarketing={handleCloseMarketing}
        showFloatActivity={showFloatActivity}
        floatActivityImg={floatActivityImg}
        onFloatActivityClick={handleFloatActivityClick}
        onFloatActivityError={handleFloatActivityError}
      />
    </View>
  )
  );
}