| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | setLongitude, |
| | | ensureSelectedCityReady, |
| | | lastSelectedCityCodeRef |
| | | }); // 地图回中动作 |
| | | |
| | | }); |
| | | const bootstrap = useHomeBootstrap({ |
| | | wsHost: _ROBOTAXI_WS_HOST, |
| | | getServicePhone, |
| | |
| | | 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, |
| | |
| | | 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, |
| | |
| | | handleVehicleRegionSettled(payload); |
| | | syncStationFenceVisibility(payload.lat, payload.lon, payload.isInside); |
| | | } |
| | | }); // 地图 region-change 编排 |
| | | }); |
| | | const { |
| | | ensureNetwork, |
| | | toSelectSitePage, |
| | | toCoupon, |
| | | toOrderDetail, |
| | |
| | | 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> |
| | | ) |
| | | ); |
| | | } |