import Taro, { useDidShow } from "@tarojs/taro"
|
import { useEffect, useState, useRef, useMemo } from 'react';
|
import { View, Map, Text, Image, Button } from '@tarojs/components';
|
import './index.less';
|
import PrivacyPopup from './components/privacy-popup';
|
import CustomerServicePopup from '../../../robotaxi/components/customer-service';
|
import selectCityIcon from '../../../robotaxi/assets/select-city.png'
|
import contactService from '../../../robotaxi/assets/contact-service.png'
|
import coupon from '../../../robotaxi/assets/coupon.png'
|
import suggest from '../../../robotaxi/assets/suggest.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 wsManager from '../../../utils/websocket';
|
import {
|
findNearestMarker,
|
getAddressByLocation,
|
formatGeofencePolygons,
|
extractPointsFromPolygons,
|
convertAdcodeToCityCode,
|
handleBannerJump,
|
fetchMapData,
|
getCityLocation,
|
getCityInfoWithId,
|
getLocationByIP
|
} from '../../../utils/Robotaxi-public-method';
|
import { jumpTo } from '../../../utils/jump';
|
import { _ROBOTAXI_WS_HOST, CDN } from '../../../constants/api';
|
import {
|
getGeofence,
|
getBannerList,
|
getAgreement,
|
getOrderActive,
|
getHotFixInfo
|
} from '../../../robotaxi/api/home';
|
import { getServicePhone } from '../../../robotaxi/api/customer-service';
|
import { getOrderDistance } from '../../../robotaxi/api/select-map-site';
|
import { checkOtp } from '../../../utils/tools';
|
|
export default function Home() {
|
|
const [showPrivacy, setShowPrivacy] = useState(false); // 隐私政策弹窗是否显示
|
const [showCustomerService, setShowCustomerService] = useState(false); // 客服弹窗是否显示
|
const [longitude, setLongitude] = useState<number | null>(null); // 经度
|
const [latitude, setLatitude] = useState<number | null>(null); // 纬度
|
const [showToolTip, setShowToolTip] = useState(true); // 提示是否显示
|
const [bounce, setBounce] = useState(false); // 是否弹跳
|
const [isLocationAuthDenied, setIsLocationAuthDenied] = useState(false); // 定位权限是否被拒绝
|
const [markers, setMarkers] = useState<any[]>([]); // 标记点列表
|
const [isMaintain, setIsMaintain] = useState(false); // 是否维护中
|
const [maintainBounce, setMaintainBounce] = useState(false);
|
const [hotfixEndText, setHotfixEndText] = useState('');
|
const [isOperatingArea, setIsOperatingArea] = useState(true); // 是否在运营区域内
|
const [polygons, setPolygons] = useState<any[]>([]); // 地图围栏
|
const [tripStatus, setTripStatus] = useState<number>(0); // 行程状态
|
const [currentSite, setCurrentSite] = useState<any>({}); // 当前站点
|
const [currentAddress, setCurrentAddress] = useState(''); // 当前位置地址
|
const [isAdsorbed, setIsAdsorbed] = useState(true); // 是否吸附到站点
|
const [mapScale, setMapScale] = useState(14); // 地图缩放级别
|
const [bannerList, setBannerList] = useState<any[]>([]); // 轮播图列表
|
const [electronicAreaId, setElectronicAreaId] = useState(''); // 大头针所在电子围栏ID
|
const [adsorptionDistance, setAdsorptionDistance] = useState(null); // 吸附距离
|
const [orderData, setOrderData] = useState<any>({}); // 订单数据
|
const [isLogin, setIsLogin] = useState(false); // 是否登录
|
|
const [boxHeight, setBoxHeight] = useState<number>(280); // 活动区域高度
|
const [originOpacity, setOriginOpacity] = useState(1);
|
const startY = useRef(0); // 拖动开始时的Y坐标
|
const startHeight = useRef(0); // 拖动开始时的高度
|
const fullHeight = useRef(0); // 活动区域最大高度
|
const minHeight = useRef(0); // 活动区域最小高度
|
const isDragging = useRef(false); // 是否正在拖动
|
const dragFrame = useRef<any>(null);
|
const pendingHeight = useRef<number | null>(null);
|
const shouldIgnoreRegionChange = useRef(false); // 是否忽略地图区域变化
|
const resetTimer = useRef<any>(null); // 自动归位定时器
|
const isReturningToOrigin = useRef(false); // 是否正在自动归位
|
const skipNextLocationCenterRef = useRef(false);
|
const skipCityChangeEffectRef = useRef(false);
|
const isCitySwitchingRef = useRef(false);
|
const lastSelectedCityCodeRef = useRef<string | undefined>(Taro.getStorageSync('selectedCity')?.code);
|
const orderStatusBound = useRef(false);
|
|
const DEFAULT_LATITUDE = 39.916527; // 北京故宫纬度
|
const DEFAULT_LONGITUDE = 116.397128; // 北京故宫经度
|
const FIRST_ENTER_KEY = 'robotaxi_first_entered';
|
|
const [selectedCity, setSelectedCity] = useState(Taro.getStorageSync('selectedCity') || {
|
name: '',
|
code: '',
|
}); // 选中的城市
|
|
const ensureSelectedCityReady = async (lng: number, lat: number, skipCityEffect = false, forceRefresh = false) => {
|
const cachedCity = Taro.getStorageSync('selectedCity');
|
if (!forceRefresh && cachedCity?.code && typeof cachedCity?.id === 'number') {
|
return cachedCity;
|
}
|
try {
|
const data = await getAddressByLocation(lng, lat);
|
if (data?.regeocodeData?.addressComponent) {
|
const { city, province, adcode } = data.regeocodeData.addressComponent;
|
const cityName = (city && typeof city === 'string' && city.length > 0) ? city : province;
|
const cityCode = convertAdcodeToCityCode(adcode, cityName, province);
|
const cityInfo = await getCityInfoWithId(cityCode, cityName);
|
if (cityInfo?.code) {
|
if (skipCityEffect) {
|
skipCityChangeEffectRef.current = true;
|
}
|
setSelectedCity(cityInfo);
|
Taro.setStorageSync('selectedCity', cityInfo);
|
return cityInfo;
|
}
|
}
|
} catch (error) {
|
}
|
return null;
|
};
|
|
const ORDER_STATUS = {
|
4: '车辆正在赶来',
|
5: '车辆已到达上车点,请尽快前往',
|
6: '正在前往目的地',
|
7: '车辆已到达目的地,请尽快结束行程',
|
8: '行程已结束,请尽快支付'
|
}
|
|
const mapActiveStatusToTripStatus = (status: number) => {
|
if (status === 0) return 1;
|
if (status === 1) return 3;
|
if (status === 3 || status === 2) return 4;
|
if (status === 4) return 5;
|
if (status === 5) return 6;
|
if (status === 6) return 6;
|
if (status === 7) return 7;
|
if (status === 8) return 8;
|
if (status === 9) return 9;
|
if (status === 10) return 9;
|
return 0;
|
};
|
const skipFenceIncludePointsRef = useRef(false);
|
|
// 监听页面显示
|
useDidShow(() => {
|
getServicePhone().then((res) => {
|
Taro.setStorageSync('customerServicePhone', res?.customerServicePhone || '');
|
})
|
// 从本地存储中获取选中的城市
|
const selectedCity = Taro.getStorageSync('selectedCity');
|
if (selectedCity) {
|
const nextCode = selectedCity.code;
|
if (lastSelectedCityCodeRef.current && nextCode && lastSelectedCityCodeRef.current !== nextCode) {
|
isCitySwitchingRef.current = true;
|
Taro.removeStorageSync('startSite');
|
Taro.removeStorageSync('endSite');
|
setCurrentSite({});
|
setCurrentAddress('');
|
setIsAdsorbed(false);
|
}
|
lastSelectedCityCodeRef.current = nextCode;
|
setSelectedCity(selectedCity);
|
}
|
// 从本地存储中获取行程状态
|
const tripStatus = Taro.getStorageSync('tripStatus');
|
setTripStatus(Number(tripStatus) || 0);
|
checkOtp(false).then(otp => {
|
setIsLogin(!otp);
|
if (otp) {
|
wsManager.connect(_ROBOTAXI_WS_HOST);
|
getAgreement().then((res: any) => {
|
// 如果接口返回未同意,则显示弹窗
|
if (res === false) {
|
setShowPrivacy(true);
|
Taro.setStorageSync('has_show_privicy_Robotaxi', 'false');
|
} else {
|
setShowPrivacy(false);
|
Taro.setStorageSync('has_show_privicy_Robotaxi', 'true');
|
}
|
})
|
getOrderActive().then(res => {
|
if (!res) {
|
Taro.removeStorageSync('orderId');
|
Taro.removeStorageSync('tripStatus');
|
Taro.removeStorageSync('endSite');
|
setTripStatus(0);
|
setOrderData({});
|
return
|
}
|
setOrderData(res || {});
|
setTripStatus(mapActiveStatusToTripStatus(res.status || 0));
|
Taro.setStorageSync('tripStatus', mapActiveStatusToTripStatus(res.status || 0));
|
Taro.setStorageSync('orderId', res.id || '');
|
if (!Taro.getStorageSync('startSite')) {
|
Taro.setStorageSync('startSite', {
|
id: res.startPointId || '',
|
name: res.startPointName || '',
|
latitude: res.startLat || 0,
|
longitude: res.startLon || 0,
|
electronicAreaId: res.xpElectronicFenceAreaId || '',
|
time: res.startParkingTimeLimit || 0,
|
})
|
}
|
if (!Taro.getStorageSync('endSite')) {
|
Taro.setStorageSync('endSite', {
|
id: res.endPointId || '',
|
name: res.endPointName || '',
|
latitude: res.endLat || 0,
|
longitude: res.endLon || 0,
|
electronicAreaId: res.xpElectronicFenceAreaId || '',
|
time: res.endParkingTimeLimit || 0,
|
})
|
}
|
})
|
} else {
|
// 没有登录
|
console.log('用户未登录');
|
}
|
});
|
|
Taro.getSetting({
|
success: (res) => {
|
const auth = res.authSetting['scope.userLocation'];
|
console.log('getSetting获取scope.userLocation授权状态', auth);
|
if (auth == undefined) {
|
getLocationInfo();
|
// skipCityChangeEffectRef.current = true;
|
return;
|
}
|
if (auth === false) {
|
setIsLocationAuthDenied(true);
|
setMapScale(3);
|
setLatitude(DEFAULT_LATITUDE);
|
setLongitude(DEFAULT_LONGITUDE);
|
return;
|
} else {
|
setIsLocationAuthDenied(false);
|
// 检查缓存中是否有 startSite
|
const startSite = Taro.getStorageSync('startSite');
|
if (startSite && startSite.latitude && startSite.longitude) {
|
if (isCitySwitchingRef.current) {
|
isCitySwitchingRef.current = false;
|
return;
|
}
|
setLatitude(startSite.latitude);
|
setLongitude(startSite.longitude);
|
setCurrentSite(startSite);
|
setIsAdsorbed(true);
|
shouldIgnoreRegionChange.current = true;
|
const cachedCity = Taro.getStorageSync('selectedCity');
|
if (cachedCity?.code) {
|
updateMapData(startSite.latitude, startSite.longitude, cachedCity.code);
|
} else {
|
ensureSelectedCityReady(startSite.longitude, startSite.latitude, true).then((cityInfo) => {
|
if (cityInfo?.code) {
|
updateMapData(startSite.latitude, startSite.longitude, cityInfo.code);
|
}
|
});
|
}
|
} else {
|
if (isCitySwitchingRef.current) {
|
isCitySwitchingRef.current = false;
|
skipNextLocationCenterRef.current = true;
|
return;
|
}
|
const shouldUpdateCenter = !skipNextLocationCenterRef.current;
|
skipNextLocationCenterRef.current = false;
|
const hasEntered = Taro.getStorageSync(FIRST_ENTER_KEY) === 'true';
|
if (!hasEntered) {
|
skipFenceIncludePointsRef.current = true;
|
getLocationInfo(true);
|
Taro.setStorageSync(FIRST_ENTER_KEY, 'true');
|
return;
|
}
|
getLocationInfo(shouldUpdateCenter);
|
}
|
}
|
}
|
});
|
});
|
|
useEffect(() => {
|
let timer: any = null;
|
const parseTime = (s: string) => {
|
if (!s) return NaN;
|
return new Date(s.replace(/-/g, '/')).getTime();
|
};
|
const formatEnd = (s: string) => {
|
if (!s) return '';
|
const d = new Date(s.replace(/-/g, '/'));
|
if (isNaN(d.getTime())) return '';
|
const hh = String(d.getHours()).padStart(2, '0');
|
const mm = String(d.getMinutes()).padStart(2, '0');
|
return `${hh}:${mm}`;
|
};
|
const check = () => {
|
getHotFixInfo().then((res: any) => {
|
const status = Number(res?.hotfixStatus ?? res?.data?.hotfixStatus ?? 0);
|
const start = parseTime(res?.hotfixStartTime ?? res?.data?.hotfixStartTime ?? '');
|
const end = parseTime(res?.hotfixEndTime ?? res?.data?.hotfixEndTime ?? '');
|
const endRaw = res?.hotfixEndTime ?? res?.data?.hotfixEndTime ?? '';
|
const now = Date.now();
|
if (status === 1 && Number.isFinite(start) && Number.isFinite(end) && now >= start && now <= end) {
|
setIsMaintain(true);
|
setHotfixEndText(formatEnd(endRaw));
|
} else {
|
setIsMaintain(false);
|
setHotfixEndText('');
|
}
|
}).catch(() => { });
|
};
|
check();
|
timer = setInterval(check, 3000);
|
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);
|
}
|
}).catch(err => {
|
console.error('获取吸附距离失败', err);
|
});
|
}, []);
|
|
// 监听订单状态变化
|
useEffect(() => {
|
const orderStatusHandler = (payload) => {
|
const mappedTripStatus = payload?.tripStatus;
|
if (typeof mappedTripStatus === 'number') {
|
setTripStatus(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;
|
}
|
};
|
}, []);
|
|
// 监听展示项与 Banner 变化,重新计算高度
|
useEffect(() => {
|
calculateBoxHeight();
|
}, [bannerList, tripStatus]);
|
|
// 监听城市变化,更新电子围栏
|
useEffect(() => {
|
if (skipCityChangeEffectRef.current) {
|
skipCityChangeEffectRef.current = false;
|
return;
|
}
|
if (!selectedCity.code) return;
|
const allowMapMove = !isLocationAuthDenied;
|
getGeofence({ cityCode: selectedCity.code }).then((res: any) => {
|
|
if (res && Array.isArray(res) && res.length > 0) {
|
const newPolygons = formatGeofencePolygons(res);
|
setPolygons(newPolygons);
|
const shouldIncludeOperatingArea = allowMapMove && !Taro.getStorageSync('startSite');
|
if (shouldIncludeOperatingArea) {
|
if (skipFenceIncludePointsRef.current) {
|
skipFenceIncludePointsRef.current = false;
|
} else {
|
const mapCtx = Taro.createMapContext('map');
|
const points = extractPointsFromPolygons(newPolygons);
|
if (!isLocationAuthDenied) {
|
mapCtx.includePoints({
|
points: points,
|
padding: [100, 100, 100, 100]
|
});
|
}
|
}
|
}
|
} else {
|
setPolygons([]);
|
if ((orderData || orderData?.id) && JSON.stringify(orderData) != '{}') return;
|
// 定位到城市中心
|
if (selectedCity.name && allowMapMove) {
|
getCityLocation(selectedCity.name).then(loc => {
|
setLatitude(loc.latitude);
|
setLongitude(loc.longitude);
|
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);
|
setPolygons([]);
|
// 定位到城市中心
|
if (selectedCity.name && allowMapMove) {
|
getCityLocation(selectedCity.name).then(loc => {
|
setLatitude(loc.latitude);
|
setLongitude(loc.longitude);
|
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 calculateBoxHeight = () => {
|
// 延迟计算,确保 DOM 渲染完成
|
setTimeout(() => {
|
const query = Taro.createSelectorQuery();
|
// 获取内部内容容器的完整高度(不受父容器 overflow:hidden 影响)
|
query.select('.content-body').boundingClientRect(rect => {
|
if (rect) {
|
const contentHeight = rect.height;
|
const baseHeight = 280;
|
fullHeight.current = Math.max(contentHeight, baseHeight);
|
minHeight.current = baseHeight;
|
setBoxHeight(baseHeight);
|
} else {
|
// 兜底逻辑
|
minHeight.current = 280;
|
fullHeight.current = 280;
|
setBoxHeight(280);
|
}
|
}).exec();
|
}, 500);
|
}
|
|
// 更新地图数据
|
const updateMapData = async (lat: number, lng: number, cityCode?: string) => {
|
const code = cityCode || Taro.getStorageSync('selectedCity')?.code;
|
if (!code) {
|
return { isInside: false, electronicAreaId: '', newMarkers: [], sites: [] };
|
}
|
const { isInside, electronicAreaId, newMarkers, sites } = await fetchMapData(lat, lng, code);
|
|
setIsOperatingArea(isInside);
|
setElectronicAreaId(electronicAreaId);
|
setMarkers(newMarkers);
|
|
return { isInside, newMarkers, sites };
|
}
|
|
// 获取用户位置信息
|
const getLocationInfo = (updateCenter = true, forceCityRefresh = false) => {
|
console.log('getLocation获取用户位置信息');
|
Taro.getLocation({ type: 'gcj02' })
|
.then(async res => {
|
setIsLocationAuthDenied(false);
|
let addressData: any = null;
|
try {
|
addressData = await getAddressByLocation(res.longitude, res.latitude);
|
} catch (error) {
|
}
|
const cachedCity = Taro.getStorageSync('selectedCity');
|
if (isLogin && cachedCity?.code && addressData?.regeocodeData?.addressComponent) {
|
const { city, province, adcode } = addressData.regeocodeData.addressComponent;
|
const cityName = (city && typeof city === 'string' && city.length > 0) ? city : province;
|
const currentCityCode = convertAdcodeToCityCode(adcode, cityName, province);
|
if (currentCityCode && currentCityCode !== cachedCity.code) {
|
return;
|
}
|
}
|
if (updateCenter) {
|
setLatitude(res.latitude);
|
setLongitude(res.longitude);
|
}
|
|
try {
|
const cityInfo = await ensureSelectedCityReady(res.longitude, res.latitude, false, forceCityRefresh);
|
if (cityInfo?.code) {
|
const { newMarkers, sites } = await updateMapData(res.latitude, res.longitude, cityInfo.code);
|
const { targetMarker, minDistance } = findNearestMarker(res.latitude, res.longitude, sites.length > 0 ? sites : newMarkers);
|
if (minDistance < adsorptionDistance && targetMarker) {
|
setIsAdsorbed(true);
|
setCurrentSite(targetMarker);
|
setShowToolTip(true);
|
return;
|
}
|
}
|
} catch (error) {
|
}
|
|
if (addressData) {
|
const address = addressData.name || addressData.desc;
|
setCurrentAddress(address);
|
setCurrentSite({
|
name: address,
|
latitude: res.latitude,
|
longitude: res.longitude
|
});
|
setIsAdsorbed(false);
|
|
const cachedCity = Taro.getStorageSync('selectedCity');
|
if (!cachedCity && addressData.regeocodeData && addressData.regeocodeData.addressComponent) {
|
const { city, province, adcode } = addressData.regeocodeData.addressComponent;
|
const cityName = (city && typeof city === 'string' && city.length > 0) ? city : province;
|
const cityCode = convertAdcodeToCityCode(adcode, cityName, province);
|
|
getCityInfoWithId(cityCode, cityName).then(cityInfo => {
|
setSelectedCity(cityInfo);
|
Taro.setStorageSync('selectedCity', cityInfo);
|
});
|
}
|
} else {
|
setCurrentAddress('未知位置');
|
setCurrentSite({ name: '未知位置' });
|
}
|
})
|
.catch((err) => {
|
console.log('getLocation获取用户位置失败', err);
|
Taro.getSetting({
|
success: (res) => {
|
const auth = res.authSetting['scope.userLocation'];
|
if (auth === false || auth === undefined) {
|
setIsLocationAuthDenied(true);
|
setMapScale(3);
|
setLatitude(DEFAULT_LATITUDE);
|
setLongitude(DEFAULT_LONGITUDE);
|
return;
|
}
|
setIsLocationAuthDenied(false);
|
getLocationByIP().then(res => {
|
setLatitude(res.latitude);
|
setLongitude(res.longitude);
|
const cityName = res.city || res.province || '北京市';
|
const cityCode = convertAdcodeToCityCode(res.adcode, cityName, res.province);
|
const cityInfo = {
|
name: cityName,
|
code: cityCode
|
};
|
setSelectedCity(cityInfo);
|
Taro.setStorageSync('selectedCity', cityInfo);
|
setCurrentAddress(cityName);
|
setCurrentSite({
|
name: cityName,
|
latitude: res.latitude,
|
longitude: res.longitude
|
});
|
}).catch(() => {
|
setLatitude(DEFAULT_LATITUDE);
|
setLongitude(DEFAULT_LONGITUDE);
|
});
|
},
|
fail: () => {
|
setIsLocationAuthDenied(false);
|
getLocationByIP().then(res => {
|
setLatitude(res.latitude);
|
setLongitude(res.longitude);
|
const cityName = res.city || res.province || '北京市';
|
const cityCode = convertAdcodeToCityCode(res.adcode, cityName, res.province);
|
const cityInfo = {
|
name: cityName,
|
code: cityCode
|
};
|
setSelectedCity(cityInfo);
|
Taro.setStorageSync('selectedCity', cityInfo);
|
setCurrentAddress(cityName);
|
setCurrentSite({
|
name: cityName,
|
latitude: res.latitude,
|
longitude: res.longitude
|
});
|
}).catch(() => {
|
setLatitude(DEFAULT_LATITUDE);
|
setLongitude(DEFAULT_LONGITUDE);
|
});
|
}
|
});
|
});
|
}
|
|
const handleClosePrivacy = () => {
|
setShowPrivacy(false);
|
};
|
|
const handleAgreePrivacy = () => {
|
// 处理用户同意逻辑
|
setShowPrivacy(false);
|
};
|
|
// 返回当前位置
|
const toOrigin = async () => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
if (isLocationAuthDenied) return;
|
shouldIgnoreRegionChange.current = false;
|
isReturningToOrigin.current = true;
|
setMapScale(14);
|
const mapCtx = Taro.createMapContext('map');
|
mapCtx.moveToLocation();
|
}
|
|
// 地图区域变化时触发,判断是否吸附到最近的站点停靠点
|
const handleRegionChange = (e: any) => {
|
const type = e?.type || e?.detail?.type;
|
// 获取触发源: gesture (手势), update (接口调用)
|
const causedBy = e?.causedBy || e?.detail?.causedBy;
|
if (isLocationAuthDenied) return;
|
|
// 如果是用户手势拖动,则必须触发,且重置忽略标记
|
if (causedBy === 'gesture' || causedBy === 'drag' || causedBy === 'scale') {
|
shouldIgnoreRegionChange.current = false;
|
// 清除自动归位定时器
|
if (resetTimer.current) {
|
clearTimeout(resetTimer.current);
|
resetTimer.current = null;
|
}
|
} else if (isReturningToOrigin.current) {
|
shouldIgnoreRegionChange.current = false;
|
} else {
|
shouldIgnoreRegionChange.current = true;
|
return
|
}
|
|
if (type === 'begin') {
|
setShowToolTip(false);
|
} else if (type === 'end') {
|
if (isReturningToOrigin.current) {
|
isReturningToOrigin.current = false;
|
}
|
|
// 如果标记为忽略且非手势触发,则直接返回
|
if (shouldIgnoreRegionChange.current) {
|
shouldIgnoreRegionChange.current = false;
|
return;
|
}
|
|
// 检查是否需要吸附到最近的站点停靠点
|
setBounce(true);
|
|
const mapCtx = Taro.createMapContext('map');
|
|
// 检查缩放级别,如果达到极限值且停止操作超过6s,自动归位
|
mapCtx.getScale({
|
success: (res) => {
|
if (res.scale <= 3 || res.scale >= 20) {
|
if (!resetTimer.current) {
|
setMapScale(res.scale);
|
resetTimer.current = setTimeout(() => {
|
toOrigin();
|
}, 6000);
|
}
|
}
|
}
|
});
|
|
mapCtx.getCenterLocation({
|
success: async (res) => {
|
const { latitude: lat, longitude: lng } = res;
|
if (causedBy === 'gesture' || causedBy === 'drag') {
|
setLatitude(lat);
|
setLongitude(lng);
|
}
|
|
try {
|
// 更新地图数据
|
const { newMarkers, sites } = await updateMapData(lat, lng);
|
|
const nearestCandidates = sites.length > 0 ? sites : newMarkers;
|
const { targetMarker, minDistance } = findNearestMarker(lat, lng, nearestCandidates);
|
|
// 只有用户手势拖动触发的移动才进行吸附判断
|
if ((causedBy === 'gesture' || causedBy === 'drag') && targetMarker && minDistance < adsorptionDistance) {
|
// 使用 moveToLocation 实现吸附效果
|
mapCtx.moveToLocation({
|
latitude: targetMarker.latitude,
|
longitude: targetMarker.longitude,
|
});
|
}
|
|
// 根据距离决定是否显示 ToolTip
|
setTimeout(() => {
|
setBounce(false);
|
if (minDistance < adsorptionDistance) {
|
setIsAdsorbed(true);
|
if (targetMarker) {
|
setCurrentSite(targetMarker);
|
}
|
setShowToolTip(true);
|
} else {
|
setIsAdsorbed(false);
|
setCurrentSite({ name: '正在获取位置...' });
|
// 获取当前位置的逆地理编码
|
getAddressByLocation(lng, lat)
|
.then((data) => {
|
const address = data.name || data.desc;
|
setCurrentAddress(address);
|
setCurrentSite({ name: address, latitude: lat, longitude: lng });
|
})
|
.catch((info) => {
|
|
setCurrentAddress('未知位置');
|
setCurrentSite({ name: '未知位置' });
|
});
|
setShowToolTip(true);
|
}
|
}, 600);
|
} catch (error) {
|
setBounce(false);
|
}
|
}
|
});
|
}
|
}
|
|
const queryPointerByCenter = async (lat: number, lng: number) => {
|
setBounce(true);
|
setShowToolTip(false);
|
try {
|
const { isInside, newMarkers, sites } = await updateMapData(lat, lng);
|
if (!isInside) {
|
setBounce(false);
|
return;
|
}
|
|
const nearestCandidates = sites.length > 0 ? sites : newMarkers;
|
const { targetMarker, minDistance } = findNearestMarker(lat, lng, nearestCandidates);
|
|
setTimeout(() => {
|
setBounce(false);
|
if (minDistance < adsorptionDistance) {
|
setIsAdsorbed(true);
|
if (targetMarker) {
|
setCurrentSite(targetMarker);
|
}
|
setShowToolTip(true);
|
} else {
|
setIsAdsorbed(false);
|
setCurrentSite({ name: '正在获取位置...' });
|
getAddressByLocation(lng, lat)
|
.then((data) => {
|
const address = data.name || data.desc;
|
setCurrentAddress(address);
|
setCurrentSite({ name: address, latitude: lat, longitude: lng });
|
})
|
.catch(() => {
|
setCurrentAddress('未知位置');
|
setCurrentSite({ name: '未知位置' });
|
});
|
setShowToolTip(true);
|
}
|
}, 600);
|
} catch (error) {
|
setBounce(false);
|
}
|
}
|
|
const handleTouchStart = (e: any) => {
|
isDragging.current = true;
|
startY.current = e.touches[0].clientY;
|
startHeight.current = typeof boxHeight === 'number' ? boxHeight : fullHeight.current;
|
if (dragFrame.current) {
|
clearTimeout(dragFrame.current);
|
dragFrame.current = null;
|
}
|
pendingHeight.current = null;
|
}
|
|
const handleTouchMove = (e: any) => {
|
if (!isDragging.current) return;
|
const deltaY = e.touches[0].clientY - startY.current;
|
let newHeight = startHeight.current - deltaY;
|
|
if (newHeight < minHeight.current) newHeight = minHeight.current;
|
if (newHeight > fullHeight.current) newHeight = fullHeight.current;
|
|
pendingHeight.current = newHeight;
|
if (dragFrame.current) return;
|
dragFrame.current = setTimeout(() => {
|
if (pendingHeight.current !== null) {
|
setBoxHeight(pendingHeight.current);
|
}
|
pendingHeight.current = null;
|
dragFrame.current = null;
|
}, 16);
|
}
|
|
const handleTouchEnd = () => {
|
isDragging.current = false;
|
if (dragFrame.current) {
|
clearTimeout(dragFrame.current);
|
dragFrame.current = null;
|
}
|
if (pendingHeight.current !== null) {
|
setBoxHeight(pendingHeight.current);
|
pendingHeight.current = null;
|
}
|
const movableRange = fullHeight.current - minHeight.current;
|
if (typeof boxHeight === 'number' && boxHeight > minHeight.current + movableRange / 2) {
|
setBoxHeight(fullHeight.current);
|
} else {
|
setBoxHeight(minHeight.current);
|
}
|
}
|
|
useEffect(() => {
|
const range = fullHeight.current - minHeight.current;
|
if (range > 0 && typeof boxHeight === 'number') {
|
const factor = (boxHeight - minHeight.current) / range;
|
const next = 1 - Math.min(1, Math.max(0, factor));
|
setOriginOpacity(next);
|
}
|
}, [boxHeight]);
|
|
const handleCloseCustomerService = () => {
|
setShowCustomerService(false);
|
};
|
|
const ensureNetwork = async () => {
|
try {
|
const res = await Taro.getNetworkType();
|
if (res?.networkType === 'none') {
|
Taro.showToast({
|
title: '网络未链接',
|
icon: 'none'
|
});
|
return false;
|
}
|
return true;
|
} catch (e) {
|
Taro.showToast({
|
title: '网络未链接',
|
icon: 'none'
|
});
|
return false;
|
}
|
};
|
|
const openService = async () => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
const phoneNumber = Taro.getStorageSync('customerServicePhone');
|
Taro.makePhoneCall({
|
phoneNumber,
|
})
|
}
|
|
const handleOpenSetting = () => {
|
Taro.openSetting({
|
success: (res) => {
|
if (res.authSetting['scope.userLocation']) {
|
setIsLocationAuthDenied(false);
|
setMapScale(14);
|
getLocationInfo(true, true);
|
}
|
}
|
})
|
}
|
|
const toSeleteSitePage = async (type: string, isToMapStart: boolean) => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
if (isLocationAuthDenied) {
|
Taro.showToast({
|
title: '请先开启定位权限',
|
icon: 'none'
|
});
|
return;
|
}
|
if (!electronicAreaId) {
|
Taro.showToast({
|
title: '请先移动到运营区内',
|
icon: 'none'
|
});
|
return;
|
}
|
if (isToMapStart) {
|
Taro.setStorageSync('startSite', currentSite);
|
Taro.navigateTo({
|
url: '/robotaxi/pages/select-map-site/index?pageType=' + type,
|
})
|
return
|
}
|
if (type == 'end') {
|
if (!currentSite || !currentSite.id) {
|
Taro.showToast({
|
title: '请先选择起点站点',
|
icon: 'none'
|
});
|
return;
|
}
|
Taro.setStorageSync('startSite', currentSite);
|
}
|
Taro.navigateTo({
|
url: `/robotaxi/pages/site-search/index?pageType=${type}&electronicAreaId=${electronicAreaId}&longitude=${currentSite.longitude}&latitude=${currentSite.latitude}`
|
})
|
}
|
|
const toSeleteCityPage = async () => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
Taro.navigateTo({
|
url: '/robotaxi/pages/select-city/index',
|
})
|
}
|
|
const toOrderDetail = async () => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
Taro.navigateTo({
|
url: '/robotaxi/pages/begin-route/index',
|
})
|
}
|
|
const toBeginRoute = () => {
|
}
|
|
const toCoupon = async () => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
if (isLogin) {
|
jumpTo({ url: '/pages/user/login?hideBack=1' });
|
return
|
}
|
if (Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true') {
|
setShowPrivacy(true);
|
return;
|
}
|
Taro.navigateTo({
|
url: '/robotaxi/pages/coupon/index',
|
})
|
}
|
|
const toOrder = async () => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
if (isLogin) {
|
jumpTo({ url: '/pages/user/login?hideBack=1' });
|
return
|
}
|
if (Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true') {
|
setShowPrivacy(true);
|
return;
|
}
|
Taro.navigateTo({
|
url: '/robotaxi/pages/order/index',
|
})
|
}
|
|
const handleBannerClick = async (item: any) => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
handleBannerJump(item);
|
}
|
|
// 查看运营区域
|
const handleViewOperatingArea = async () => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
if (isLocationAuthDenied) return;
|
if (!selectedCity?.code) {
|
Taro.showToast({
|
title: '此城市暂未开通运营区域',
|
icon: 'none'
|
});
|
return;
|
}
|
let nextPolygons = polygons;
|
try {
|
const res: any = await getGeofence({ cityCode: selectedCity.code });
|
if (res && Array.isArray(res)) {
|
nextPolygons = formatGeofencePolygons(res);
|
setPolygons(nextPolygons);
|
} else {
|
nextPolygons = [];
|
setPolygons([]);
|
}
|
} catch (err) {
|
nextPolygons = [];
|
setPolygons([]);
|
}
|
|
if (!nextPolygons || nextPolygons.length === 0) {
|
Taro.showToast({
|
title: '此城市暂未开通运营区域',
|
icon: 'none'
|
});
|
return;
|
}
|
|
const points = extractPointsFromPolygons(nextPolygons);
|
if (points.length > 0) {
|
const mapCtx = Taro.createMapContext('map');
|
if (!isLocationAuthDenied) {
|
mapCtx.includePoints({
|
points: points,
|
padding: [100, 100, 100, 100]
|
});
|
}
|
setTimeout(() => {
|
mapCtx.getCenterLocation({
|
success: (res) => {
|
queryPointerByCenter(res.latitude, res.longitude);
|
}
|
});
|
}, 600);
|
} else {
|
Taro.showToast({
|
title: '此城市暂未开通运营区域',
|
icon: 'none'
|
});
|
}
|
}
|
|
const toAgreementPage = async () => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
Taro.navigateTo({
|
url: '/robotaxi/pages/agreement-page/index',
|
})
|
}
|
|
const toLogin = async () => {
|
const ok = await ensureNetwork();
|
if (!ok) return;
|
jumpTo({ url: '/pages/user/login?hideBack=1' });
|
}
|
|
return (
|
<View className="home-container">
|
<Map
|
id="map"
|
longitude={longitude ?? DEFAULT_LONGITUDE}
|
latitude={latitude ?? DEFAULT_LATITUDE}
|
scale={mapScale}
|
markers={isLocationAuthDenied ? [] : markers}
|
polygons={polygons}
|
showLocation={!!longitude}
|
onRegionChange={handleRegionChange}
|
style={{ width: '100%', height: '85vh', flex: 'none' }}
|
>
|
{/* 选择城市 */}
|
<View onClick={toSeleteCityPage} className="select-city">
|
<Text>{selectedCity.name || '北京市'}</Text>
|
<Image className="select-city-icon" src={selectCityIcon} />
|
</View>
|
{/* 定位图标 */}
|
<View className="origin-box" style={{ opacity: originOpacity, transition: isDragging.current ? 'none' : 'opacity 0.2s ease-out' }}>
|
<Image onClick={toOrigin} className="origin-icon" src={originIcon} />
|
</View>
|
{/* 地图浮层tip */}
|
<View className="content-box-top">
|
<View className="position-box">
|
{/* 游客模式展示同意协议 */}
|
{
|
(Taro.getStorageSync('has_show_privicy_Robotaxi') != 'true' && !isLogin) && <View className="visitor-stste ml-32">
|
<View className="visitor-stste-tip">同意隐私协议体验更多约车体验</View>
|
<View onClick={() => setShowPrivacy(true)} className="visitor-stste-go">去同意</View>
|
</View>
|
}
|
{/* 未开启位置权限 */}
|
{
|
isLocationAuthDenied && <View className="visitor-stste ml-32 mt-20">
|
<View className="visitor-stste-tip">未开启位置权限,开启后即可发布行程</View>
|
<View onClick={handleOpenSetting} className="visitor-stste-go">去开启</View>
|
</View>
|
}
|
{/* 系统维护 */}
|
{
|
isMaintain && <View className={`is-maintain ml-32 ${maintainBounce ? 'maintain-bounce' : ''}`}>系统维护中,预计今日{hotfixEndText || '-:-'}恢复服务</View>
|
}
|
{/* 查看运营区域 */}
|
{
|
!isOperatingArea && <View onClick={handleViewOperatingArea} className="is-operating-area mt-20">查看运营区域</View>
|
}
|
</View>
|
</View>
|
{/* 地图浮层入口 */}
|
<View className="map-entry">
|
<Image onClick={openService} className="map-entry-icon" src={contactService} />
|
<View className="split-line"></View>
|
<Image onClick={toCoupon} className="map-entry-icon" src={coupon} />
|
<View className="split-line"></View>
|
<Image onClick={toOrder} className="map-entry-icon" src={suggest} />
|
</View>
|
{/* 指针 */}
|
{
|
!isLocationAuthDenied &&
|
<View className="pointer-box">
|
{
|
showToolTip && (currentSite.name || !isOperatingArea) && (
|
<View className="toolTip">
|
{
|
(!isOperatingArea || (isAdsorbed && currentSite.time)) && (
|
<View className="toolTip-bg">
|
<Text className="toolTip-bg-text">{!isOperatingArea ? '不在运营区' : `限停${currentSite.time}分钟`}</Text>
|
</View>
|
)
|
}
|
<View onClick={() => toSeleteSitePage('start')} className="toolTip-txt" style={{ paddingRight: !(isAdsorbed && isOperatingArea) ? '10px' : '12px' }}>
|
<View className="toolTip-txt-content">{!isOperatingArea ? '请拖动至运营范围内' : (isAdsorbed ? currentSite.name : '请拖动至站点')}</View>
|
{isAdsorbed && isOperatingArea && <Image className="toolTip-icon" src={rightIcon} />}
|
</View>
|
</View>
|
)
|
}
|
<Image className={`pointer-icon ${bounce ? 'bounce' : ''}`} src={pointer} />
|
</View>
|
}
|
</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://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/cab0e5b2-0da0-4d2e-80e6-0fc2da8d2532.png' />
|
<View className="site-boarding-point">
|
{/* 起点 */}
|
<View onClick={() => toSeleteSitePage('start', true)} 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={() => toSeleteSitePage('end')} className="to-site">
|
<View className="to-site-dot"></View>
|
<View className="to-site-address">
|
<View className="to-site-text">你想去哪?</View>
|
{/* <View
|
onClick={(e) => {
|
e.stopPropagation();
|
toBeginRoute()
|
}}
|
className="to-site-fast"
|
>去:中国南方电网·3号门</View> */}
|
</View>
|
</View>
|
</View>
|
{/* 订单状态卡片 */}
|
{
|
[2, 3, 4, 5, 6, 7, 8].includes(tripStatus) &&
|
<View className="order-status">
|
{
|
[3].includes(tripStatus) &&
|
<View className="order-status-left">
|
<View className="order-status-left-item">
|
<Image className="order-status-left-item-icon" src={require('../../../robotaxi/assets/pointer.png')} />
|
<View></View>
|
<View></View>
|
</View>
|
<View className="order-status-left-text">正在为您呼叫...</View>
|
</View>
|
}
|
{
|
[3, 4, 5, 6, 7, 8].includes(tripStatus) &&
|
<View className="order-status-left">
|
{
|
orderData.vehiclePictures ?
|
<Image className="order-status-left-icon" src={JSON.parse(orderData.vehiclePictures)?.url || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png'} />
|
:
|
<Image className="order-status-left-icon" src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png' />
|
}
|
<View className="order-status-left-detail">
|
<View>{orderData.plateNumber}</View>
|
<View>{ORDER_STATUS[tripStatus]}</View>
|
</View>
|
</View>
|
}
|
|
<View onClick={toOrderDetail} className="order-status-btn">查看</View>
|
<Image className="order-status-bg" src={require('../../../robotaxi/assets/order-card-bg.png')} />
|
</View>
|
}
|
{/* 运营活动 */}
|
{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={item.imageUrl || item.imgUrl || ''} />
|
</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 }}
|
/>
|
)
|
}
|
{/* 是否登录 */}
|
{
|
isLogin &&
|
<View className={`login-bar ${isLogin ? '' : 'hide'}`}>
|
<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}
|
/>
|
{/* 客服弹窗 */}
|
<CustomerServicePopup
|
show={showCustomerService}
|
onClose={handleCloseCustomerService}
|
/>
|
</View>
|
)
|
}
|