feat(robotaxi): 更新高德地图密钥、优化订单与地图交互逻辑
- 更换高德地图 API 密钥
- 新增获取活跃订单接口及状态映射逻辑
- 优化电子围栏显示,自动定位到运营区域或城市中心
- 改进站点搜索,集成高德 POI 并校验起终点区域一致性
- 优化步行路线计算,返回时长用于显示
- 统一费用明细展示,增加展开/收起功能
- 将教学视频替换为静态图片,提升加载性能
- 修复客服电话字段映射问题
- 更新开发环境配置,切换回本地测试服务
- 优化订单列表和优惠券页面的空状态显示
- 删除无用文件并修正文档拼写错误
| | |
| | | const COMMON_OSS_HOST = '"https://spre.xiaopeng.com"'; // 官渠通用 oss 资源前缀 |
| | | const ADMIN_OSS_HOST = '"https://pre-xps01.xiaopeng.com"'; // 运营后台上传的额文件的 oss 域名 |
| | | |
| | | // const ROBOTAXI_HOST = '"http://192.168.110.80:8000"'; // 打车接口域名 |
| | | // const ROBOTAXI_WS_HOST = '"ws://192.168.110.34:8888/websocket"'; |
| | | const ROBOTAXI_HOST = '"http://221.182.45.100:8000"'; |
| | | const ROBOTAXI_WS_HOST = '"ws://221.182.45.100:8888/websocket"'; |
| | | const ROBOTAXI_HOST = '"http://192.168.110.80:8000"'; // 打车接口域名 |
| | | const ROBOTAXI_WS_HOST = '"ws://192.168.110.34:8888/websocket"'; |
| | | // const ROBOTAXI_HOST = '"http://221.182.45.100:8000"'; |
| | | // const ROBOTAXI_WS_HOST = '"ws://221.182.45.100:8888/websocket"'; |
| | | |
| | | const XP_APPID_MAP = { |
| | | weapp: '"xxxxxxx"', // 用户中心登录appId |
| | |
| | | |
| | | |
| | | // 高德地图KEY |
| | | export const AMAP_KEY = 'e5212ad7656a00a09b9968dcbe551726' |
| | | export const AMAP_KEY = '8157fdfa13878d3f7bf69c560c8a846a' |
| | | |
| | | |
| | |
| | | convertAdcodeToCityCode, |
| | | handleBannerJump, |
| | | fetchMapData, |
| | | getLocationByIP |
| | | getLocationByIP, |
| | | getCityLocation, |
| | | getCityInfoWithId |
| | | } from '../../../utils/Robotaxi-public-method'; |
| | | import { _ROBOTAXI_WS_HOST } from '../../../constants/api'; |
| | | import { getGeofence, getBannerList, getAgreement } from '../../../robotaxi/api/home'; |
| | | import { getGeofence, getBannerList, getAgreement, getOrderActive } from '../../../robotaxi/api/home'; |
| | | import { getOrderDistance } from '../../../robotaxi/api/select-map-site'; |
| | | import { checkOtp } from '../../../utils/tools'; |
| | | |
| | |
| | | const [bannerList, setBannerList] = useState<any[]>([]); // 轮播图列表 |
| | | const [electronicAreaId, setElectronicAreaId] = useState(''); // 大头针所在电子围栏ID |
| | | const [adsorptionDistance, setAdsorptionDistance] = useState(null); // 吸附距离 |
| | | const [orderData, setOrderData] = useState<any>({}); // 订单数据 |
| | | |
| | | const [boxHeight, setBoxHeight] = useState<number | string>('auto'); // 活动区域高度 |
| | | const startY = useRef(0); // 拖动开始时的Y坐标 |
| | |
| | | 7: '车辆已到达目的地,请尽快结束行程', |
| | | } |
| | | |
| | | const mapActiveStatusToTripStatus = (status: number) => { |
| | | if (status === 0) return 1; |
| | | if (status === 1 || status === 2) return 3; |
| | | if (status === 3) return 4; |
| | | if (status === 4) return 5; |
| | | if (status === 5) return 6; |
| | | if (status === 6) return 7; |
| | | if (status === 7) return 7; |
| | | if (status === 8) return 8; |
| | | if (status === 9) return 9; |
| | | if (status === 10) return 9; |
| | | return 0; |
| | | }; |
| | | |
| | | // 监听页面显示 |
| | | useDidShow(() => { |
| | | |
| | |
| | | checkOtp(false).then(otp => { |
| | | if (otp) { |
| | | wsManager.connect(_ROBOTAXI_WS_HOST); |
| | | getOrderActive().then(res => { |
| | | 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 || '', |
| | | }) |
| | | } |
| | | if (!Taro.getStorageSync('endSite')) { |
| | | Taro.setStorageSync('endSite', { |
| | | id: res.endPointId || '', |
| | | name: res.endPointName || '', |
| | | latitude: res.endLat || 0, |
| | | longitude: res.endLon || 0, |
| | | electronicAreaId: res.xpElectronicFenceAreaId || '', |
| | | }) |
| | | } |
| | | }) |
| | | } else { |
| | | // 没有登录 |
| | | console.log('用户未登录'); |
| | |
| | | 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); |
| | | getCityInfoWithId(cityCode, cityName).then(cityInfo => { |
| | | setSelectedCity(cityInfo); |
| | | Taro.setStorageSync('selectedCity', cityInfo); |
| | | }); |
| | | }).catch(() => { |
| | | // IP 获取失败,使用默认值 |
| | | setLatitude(DEFAULT_LATITUDE); |
| | |
| | | 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 = { |
| | | name: cityName, |
| | | code: cityCode |
| | | }; |
| | | setSelectedCity(cityInfo); |
| | | Taro.setStorageSync('selectedCity', cityInfo); |
| | | getCityInfoWithId(cityCode, cityName).then(cityInfo => { |
| | | setSelectedCity(cityInfo); |
| | | Taro.setStorageSync('selectedCity', cityInfo); |
| | | }); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | |
| | | |
| | | getGeofence({ cityCode: selectedCity.code }).then((res: any) => { |
| | | |
| | | if (res && Array.isArray(res)) { |
| | | if (res && Array.isArray(res) && res.length > 0) { |
| | | const newPolygons = formatGeofencePolygons(res); |
| | | |
| | | setPolygons(newPolygons); |
| | | |
| | | // 定位到第一个运营区域的第一个点 |
| | | const firstPolygon = res[0]; |
| | | if (firstPolygon && firstPolygon.points && firstPolygon.points.length > 0) { |
| | | const firstPoint = firstPolygon.points[0]; |
| | | setLatitude(Number(firstPoint.lat)); |
| | | setLongitude(Number(firstPoint.lng)); |
| | | } |
| | | } else { |
| | | setPolygons([]); |
| | | // 定位到城市中心 |
| | | if (selectedCity.name) { |
| | | getCityLocation(selectedCity.name).then(loc => { |
| | | setLatitude(loc.latitude); |
| | | setLongitude(loc.longitude); |
| | | }).catch(err => { |
| | | console.error('获取城市中心失败', err); |
| | | }); |
| | | } |
| | | } |
| | | }).catch(err => { |
| | | console.error('获取电子围栏失败', err); |
| | | setPolygons([]); |
| | | // 定位到城市中心 |
| | | if (selectedCity.name) { |
| | | getCityLocation(selectedCity.name).then(loc => { |
| | | setLatitude(loc.latitude); |
| | | setLongitude(loc.longitude); |
| | | }).catch(err => { |
| | | console.error('获取城市中心失败', err); |
| | | }); |
| | | } |
| | | }); |
| | | }, [selectedCity.code]); |
| | | }, [selectedCity.code, selectedCity.name]); |
| | | |
| | | const calculateBoxHeight = () => { |
| | | // 延迟计算,确保 DOM 渲染完成 |
| | |
| | | // 将区级 adcode 转换为市级 adcode |
| | | const cityCode = convertAdcodeToCityCode(adcode, cityName, province); |
| | | |
| | | const cityInfo = { |
| | | name: cityName, |
| | | code: cityCode |
| | | }; |
| | | setSelectedCity(cityInfo); |
| | | Taro.setStorageSync('selectedCity', cityInfo); |
| | | getCityInfoWithId(cityCode, cityName).then(cityInfo => { |
| | | setSelectedCity(cityInfo); |
| | | Taro.setStorageSync('selectedCity', cityInfo); |
| | | }); |
| | | } |
| | | }) |
| | | .catch((info) => { |
| | |
| | | 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); |
| | | getCityInfoWithId(cityCode, cityName).then(cityInfo => { |
| | | setSelectedCity(cityInfo); |
| | | Taro.setStorageSync('selectedCity', cityInfo); |
| | | }); |
| | | setCurrentAddress(cityName); |
| | | setCurrentSite({ |
| | | name: cityName, |
| | |
| | | mapCtx.getCenterLocation({ |
| | | success: async (res) => { |
| | | const { latitude: lat, longitude: lng } = res; |
| | | if (causedBy === 'gesture' || causedBy === 'drag' || causedBy === 'scale') { |
| | | setLatitude(lat); |
| | | setLongitude(lng); |
| | | } |
| | | |
| | | try { |
| | | // 更新地图数据 |
| | |
| | | <View className="order-status-left"> |
| | | <Image className="order-status-left-icon" src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e76e8fd6-1f52-482f-b9c0-6a026f4d2797.png' /> |
| | | <View className="order-status-left-detail"> |
| | | <View>粤PXV7029</View> |
| | | <View>{orderData.plateNumber}</View> |
| | | <View>{ORDER_STATUS[tripStatus]}</View> |
| | | </View> |
| | | </View> |
| | |
| | | // 导航-获取路线和费用 |
| | | export const queryRouteAndFare = async (payload) => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/operation/base/v1/mini-program/navigation/route-and-fare', |
| | | url: _ROBOTAXI_HOST + '/itinerary/base/v1/mini-program/navigation/route-and-fare', |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | |
| | | // 保存订单 |
| | | export const saveOrder = async (payload) => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/operation/base/v1/mini-program/order/save', |
| | | url: _ROBOTAXI_HOST + '/itinerary/base/v1/mini-program/order/save', |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | |
| | | // 获取等待时间 |
| | | export const getWaitTime = async (payload) => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/operation/base/v1/mini-program/pricing/getWaitTime', |
| | | url: _ROBOTAXI_HOST + '/itinerary/base/v1/mini-program/pricing/getWaitTime', |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | |
| | | url: _ROBOTAXI_HOST + '/operation/api/v1/mini-program/privacy/getAgreement', |
| | | method: 'POST' |
| | | }); |
| | | } |
| | | |
| | | // 获取订单状态 |
| | | export const getOrderActive = async () => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/active`, |
| | | method: 'GET' |
| | | }); |
| | | } |
| | |
| | | */ |
| | | export const getPricingRules = async (openCityId: string | number) => { |
| | | return request({ |
| | | url: `${_ROBOTAXI_HOST}/operation/base/v1/mini-program/pricing/rules/${openCityId}`, |
| | | url: `${_ROBOTAXI_HOST}/itinerary/base/v1/mini-program/pricing/rules/${openCityId}`, |
| | | method: 'GET' |
| | | }); |
| | | }; |
| | |
| | | useEffect(() => { |
| | | if (show && !visible) { |
| | | getServicePhone().then((res) => { |
| | | setPhoneNumber(res?.msg); |
| | | setPhoneNumber(res?.customerServicePhone); |
| | | setTimeout(() => { |
| | | Taro.hideTabBar({ animation: true }); |
| | | setVisible(true); |
| | |
| | | |
| | | tripStatus不同状态标点和线路渲染逻辑,路线都是从socket消息来,车辆点位也是 |
| | | 4:渲染上车点、车辆点位、车辆位置公里、路线 |
| | | 5、6:渲染车辆点位、车辆位置公里、下车点、路线 |
| | | 5、6:渲染车辆点位、车辆位置公里、下#车点、路线 |
| | | 7:渲染车辆点位、下车点 |
| | | 8、9::渲染车辆上车点、下车点、路线 |
| | |
| | | export default function AnnouncementsPopup({ show = false, onClose }: Props) { |
| | | const [visible, setVisible] = useState(false); |
| | | const [animationData, setAnimationData] = useState<any>(); |
| | | const [video1Error, setVideo1Error] = useState(false); |
| | | const [video2Error, setVideo2Error] = useState(false); |
| | | const [doorImgError, setDoorImgError] = useState(false); |
| | | const [seatbeltImgError, setSeatbeltImgError] = useState(false); |
| | | |
| | | const video1 = 'https://www.psciio.com/idcard/a9f71103ce7a497495cfdaf81363b6f5.mp4' |
| | | const video2 = 'https://www.psciio.com/idcard/7d82b80f20bf4791915855f05667cecc.mp4' |
| | | const doorImage = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/f4a18c3f-a2db-45e6-8967-1510355b5e3b.webp' |
| | | const seatbeltImage = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/54cb5215-7603-443c-95a9-2558f54b6cb5.webp' |
| | | |
| | | useEffect(() => { |
| | | if (show && !visible) { |
| | | setVisible(true); |
| | | setVideo1Error(false); |
| | | setVideo2Error(false); |
| | | setDoorImgError(false); |
| | | setSeatbeltImgError(false); |
| | | const animation = Taro.createAnimation({ |
| | | duration: 300, |
| | | timingFunction: 'ease-out' |
| | |
| | | <Image onClick={onClose} className='announcements-icon' src={require('../../../assets/close-icon.png')} /> |
| | | </View> |
| | | { |
| | | video1Error ? ( |
| | | doorImgError ? ( |
| | | <Image className='demo-img' src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/fd2b5c95-ba7b-405c-b566-98357ec9c3a6.png' /> |
| | | ) : ( |
| | | <Video |
| | | <Image |
| | | className='demo-img' |
| | | src={video1} |
| | | autoplay |
| | | loop |
| | | controls={false} |
| | | showCenterPlayBtn={false} |
| | | enablePlayGesture={false} |
| | | onError={() => setVideo1Error(true)} |
| | | src={doorImage} |
| | | onError={() => setDoorImgError(true)} |
| | | /> |
| | | ) |
| | | } |
| | |
| | | <View className='announcements-text'>第二步:系上安全带</View> |
| | | </View> |
| | | { |
| | | video2Error ? ( |
| | | seatbeltImgError ? ( |
| | | <Image className='demo-img' src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/d4aa9610-c1b6-45b6-b6ae-affd2e9cc8ef.png' /> |
| | | ) : ( |
| | | <Video |
| | | <Image |
| | | className='demo-img' |
| | | src={video2} |
| | | autoplay |
| | | loop |
| | | controls={false} |
| | | showCenterPlayBtn={false} |
| | | enablePlayGesture={false} |
| | | onError={() => setVideo2Error(true)} |
| | | src={seatbeltImage} |
| | | onError={() => setSeatbeltImgError(true)} |
| | | /> |
| | | ) |
| | | } |
| | |
| | | const [pickupCountdownSeconds, setPickupCountdownSeconds] = useState<number | null>(null); // 上车倒计时 |
| | | const [finishCountdownSeconds, setFinishCountdownSeconds] = useState<number | null>(null); // 到达目的地倒计时 |
| | | const [fareBreakdown, setFareBreakdown] = useState<any>(null); |
| | | const [isEvaluated, setIsEvaluated] = useState(false); // 是否已评价 |
| | | const [showDetail, setShowDetail] = useState(false); // 是否显示费用明细 |
| | | const countdownTimerRef = useRef<any>(null); // 倒计时定时器引用 |
| | | const finishCountdownTimerRef = useRef<any>(null); // 结束倒计时定时器引用 |
| | | const tempTimerRef = useRef<any>(null); // 温度定时器引用 |
| | |
| | | const pickupEndTimeRef = useRef<number | null>(null); // 上车时间 |
| | | const finishEndTimeRef = useRef<number | null>(null); // 到达目的地时间 |
| | | const lastFareOrderIdRef = useRef<string | null>(null); |
| | | const hasShownAnnouncementsRef = useRef(false); // 是否已展示过公告弹窗 |
| | | |
| | | const formatCountdown = (seconds: number) => { |
| | | const safeSeconds = Math.max(0, seconds); |
| | |
| | | if (type === 3) return '节假日时段'; |
| | | return '普通时段'; |
| | | }; |
| | | const isArriveSoon = tripStatus === 4 && typeof displayRouteDuration === 'number' && !Number.isNaN(displayRouteDuration) && displayRouteDuration < 1; |
| | | const titleMap = { |
| | | 3: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>, |
| | | 4: <>车辆还有<View className="time">{displayRouteDuration}</View>分钟到达</>, |
| | |
| | | |
| | | const descMap = { |
| | | 3: { text: '正全力为您叫车中,请耐心等待...', className: 'calling-car-content-desc' }, |
| | | 4: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: 'calling-car-content-warning' } : null, |
| | | 4: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: isArriveSoon ? 'calling-car-content-desc-red' : 'calling-car-content-warning' } : null, |
| | | 5: { text: '此处限停,请在倒计时结束前到达上车点', className: 'calling-car-content-desc' }, |
| | | 6: { text: '行程已开始,请全程系好安全带', className: 'calling-car-content-desc' }, |
| | | 7: { text: '倒计时结束时车辆将自动驶离,请带好物品安全下车', className: 'calling-car-content-desc' }, |
| | |
| | | } |
| | | } else if (tripStatus === 5) { |
| | | // 已到达上车点:根据限停时间启动倒计时并处理超时 |
| | | // 2-12 新增 |
| | | const duration = Number(Taro.getStorageSync('orderRouteDuration')); |
| | | // 2-12 新增 |
| | | const duration = Number(Taro.getStorageSync('orderRouteDuration')); |
| | | if (!Number.isNaN(duration)) { |
| | | setRouteDuration(duration); |
| | | } |
| | |
| | | if (isFailure) { |
| | | setTempStatus(lastConfirmedTempRef.current); |
| | | Taro.showToast({ |
| | | title: '网络连接不稳定,设置失败,请重试', |
| | | title: res?.msg || '网络连接不稳定,设置失败,请重试', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | |
| | | }).catch(() => { |
| | | setTempStatus(lastConfirmedTempRef.current); |
| | | Taro.showToast({ |
| | | title: '网络连接不稳定,设置失败,请重试', |
| | | title: res?.msg || '网络连接不稳定,设置失败,请重试', |
| | | icon: 'none' |
| | | }); |
| | | }); |
| | |
| | | const isFailure = res?.success === false || (typeof res?.code === 'number' && res.code !== 0); |
| | | if (isFailure) { |
| | | Taro.showToast({ |
| | | title: '网络连接不稳定,解锁失败,请重试', |
| | | title: res?.msg || '网络连接不稳定,解锁失败,请重试', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | onSuccess?.(); |
| | | if (doorType === 'door') { |
| | | handleOpenAnnouncementsPopup?.(); |
| | | if (!hasShownAnnouncementsRef.current) { |
| | | handleOpenAnnouncementsPopup?.(); |
| | | hasShownAnnouncementsRef.current = true; |
| | | } |
| | | } |
| | | }).catch(() => { |
| | | Taro.showToast({ |
| | | title: '网络连接不稳定,解锁失败,请重试', |
| | | title: res?.msg || '网络连接不稳定,解锁失败,请重试', |
| | | icon: 'none' |
| | | }); |
| | | }); |
| | |
| | | |
| | | // 切换评价选项 |
| | | const changeEvaluate = (index: number) => { |
| | | if (isEvaluated) return; |
| | | setActiveIndex(index); |
| | | if (index == 1) { |
| | | handleOpenEvaluatePopup?.(); |
| | |
| | | orderRating: index, |
| | | orderId: targetOrderId |
| | | }).then((res: any) => { |
| | | const isFailure = res?.success === false || (typeof res?.code === 'number' && res.code !== 0); |
| | | if (isFailure) { |
| | | Taro.showToast({ |
| | | title: '评价提交失败,请重试', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | setIsEvaluated(true); |
| | | Taro.showToast({ |
| | | title: '评价已提交', |
| | | icon: 'none' |
| | |
| | | 车身编码: {vehicleInfo?.vehicleCode} |
| | | </View> |
| | | </View> |
| | | <Image className="car-card-detail-img" src={vehicleInfo?.vehiclePictures?.[0].url || ''} /> |
| | | <Image className="car-card-detail-img" src={vehicleInfo?.vehiclePictures?.[0].url || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/825d7fa2-6cf6-4a31-aaac-3a4de806e9a3.png'} /> |
| | | </View> |
| | | { |
| | | tripStatus == 4 && <View> |
| | |
| | | <View>{formatMoney(fareBreakdown?.orderMoney - fareBreakdown?.redPacketMoney - fareBreakdown?.couponMoney)}元</View> |
| | | <View>已优惠{formatMoney(fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney)}元</View> |
| | | </View> |
| | | <View className="car-card-end-particulars-view"> |
| | | <View>查看明细</View> |
| | | <Image src={require('../../../assets/ccc-icon-right.png')} /> |
| | | <View className="car-card-end-particulars-view" onClick={() => setShowDetail(!showDetail)}> |
| | | <View>{showDetail ? '收起明细' : '查看明细'}</View> |
| | | <Image style={{ transform: showDetail ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.3s' }} src={require('../../../assets/ccc-icon-right.png')} /> |
| | | </View> |
| | | </View> |
| | | <View className="particulars-detail"> |
| | | {Number(fareBreakdown?.orderMoney) > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>订单总额</View> |
| | | <View>{formatMoney(fareBreakdown?.orderMoney)}元</View> |
| | | </View> |
| | | </View> |
| | | } |
| | | {Array.isArray(fareBreakdown?.start) && fareBreakdown.start.length > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>起步价</View> |
| | | <View>{formatMoney(fareBreakdown.start.reduce((sum: number, item: any) => sum + (Number(item?.money) || 0), 0))}元</View> |
| | | </View> |
| | | {fareBreakdown.start.filter((item: any) => item?.money || item?.time).map((item: any, index: number) => ( |
| | | <View key={`start-${index}`} className="particulars-detail-item-content"> |
| | | <View>{item?.time || '--'}</View> |
| | | <View>{formatMoney(item?.money)}元</View> |
| | | {showDetail && |
| | | <View className="particulars-detail"> |
| | | {Number(fareBreakdown?.orderMoney) > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>订单总额</View> |
| | | <View>{formatMoney(fareBreakdown?.orderMoney)}元</View> |
| | | </View> |
| | | ))} |
| | | </View> |
| | | } |
| | | {Array.isArray(fareBreakdown?.mileage) && fareBreakdown.mileage.length > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>里程费</View> |
| | | <View>{formatMoney(fareBreakdown.mileage.reduce((sum: number, item: any) => sum + (Number(item?.money) || 0), 0))}元</View> |
| | | </View> |
| | | {fareBreakdown.mileage.filter((item: any) => item?.money || item?.mileage).map((item: any, index: number) => ( |
| | | <View key={`mileage-${index}`} className="particulars-detail-item-content"> |
| | | <View>{getTypeLabel(item?.type)}({formatNumber(item?.mileage)}公里)</View> |
| | | <View>{formatMoney(item?.money)}元</View> |
| | | } |
| | | {Array.isArray(fareBreakdown?.start) && fareBreakdown.start.length > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>起步价</View> |
| | | <View>{formatMoney(fareBreakdown.start.reduce((sum: number, item: any) => sum + (Number(item?.money) || 0), 0))}元</View> |
| | | </View> |
| | | ))} |
| | | </View> |
| | | } |
| | | {Array.isArray(fareBreakdown?.duration) && fareBreakdown.duration.length > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>时长费</View> |
| | | <View>{formatMoney(fareBreakdown.duration.reduce((sum: number, item: any) => sum + (Number(item?.money) || 0), 0))}元</View> |
| | | {fareBreakdown.start.filter((item: any) => item?.money || item?.time).map((item: any, index: number) => ( |
| | | <View key={`start-${index}`} className="particulars-detail-item-content"> |
| | | <View>{item?.time || '--'}</View> |
| | | <View>{formatMoney(item?.money)}元</View> |
| | | </View> |
| | | ))} |
| | | </View> |
| | | {fareBreakdown.duration.filter((item: any) => item?.money || item?.duration).map((item: any, index: number) => ( |
| | | <View key={`duration-${index}`} className="particulars-detail-item-content"> |
| | | <View>{getTypeLabel(item?.type)}({formatNumber(item?.duration, 0)}分钟)</View> |
| | | <View>{formatMoney(item?.money)}元</View> |
| | | } |
| | | {Array.isArray(fareBreakdown?.mileage) && fareBreakdown.mileage.length > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>里程费</View> |
| | | <View>{formatMoney(fareBreakdown.mileage.reduce((sum: number, item: any) => sum + (Number(item?.money) || 0), 0))}元</View> |
| | | </View> |
| | | ))} |
| | | </View> |
| | | } |
| | | {Array.isArray(fareBreakdown?.longDistance) && fareBreakdown.longDistance.length > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>远途费</View> |
| | | <View>{formatMoney(fareBreakdown.longDistance.reduce((sum: number, item: any) => sum + (Number(item?.money) || 0), 0))}元</View> |
| | | {fareBreakdown.mileage.filter((item: any) => item?.money || item?.mileage).map((item: any, index: number) => ( |
| | | <View key={`mileage-${index}`} className="particulars-detail-item-content"> |
| | | <View>{getTypeLabel(item?.type)}({formatNumber(item?.mileage)}公里)</View> |
| | | <View>{formatMoney(item?.money)}元</View> |
| | | </View> |
| | | ))} |
| | | </View> |
| | | {fareBreakdown.longDistance.filter((item: any) => item?.money || item?.mileage).map((item: any, index: number) => ( |
| | | <View key={`long-${index}`} className="particulars-detail-item-content"> |
| | | <View>{getTypeLabel(item?.type)}({formatNumber(item?.mileage)}公里)</View> |
| | | <View>{formatMoney(item?.money)}元</View> |
| | | } |
| | | {Array.isArray(fareBreakdown?.duration) && fareBreakdown.duration.length > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>时长费</View> |
| | | <View>{formatMoney(fareBreakdown.duration.reduce((sum: number, item: any) => sum + (Number(item?.money) || 0), 0))}元</View> |
| | | </View> |
| | | ))} |
| | | </View> |
| | | } |
| | | {Number(fareBreakdown?.redPacketMoney) > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>红包</View> |
| | | <View className="color1">-{formatMoney(fareBreakdown?.redPacketMoney)}元</View> |
| | | {fareBreakdown.duration.filter((item: any) => item?.money || item?.duration).map((item: any, index: number) => ( |
| | | <View key={`duration-${index}`} className="particulars-detail-item-content"> |
| | | <View>{getTypeLabel(item?.type)}({formatNumber(item?.duration, 0)}分钟)</View> |
| | | <View>{formatMoney(item?.money)}元</View> |
| | | </View> |
| | | ))} |
| | | </View> |
| | | </View> |
| | | } |
| | | {Number(fareBreakdown?.couponMoney) > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>优惠券</View> |
| | | <View className="color1">-{formatMoney(fareBreakdown?.couponMoney)}元</View> |
| | | } |
| | | {Array.isArray(fareBreakdown?.longDistance) && fareBreakdown.longDistance.length > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>远途费</View> |
| | | <View>{formatMoney(fareBreakdown.longDistance.reduce((sum: number, item: any) => sum + (Number(item?.money) || 0), 0))}元</View> |
| | | </View> |
| | | {fareBreakdown.longDistance.filter((item: any) => item?.money || item?.mileage).map((item: any, index: number) => ( |
| | | <View key={`long-${index}`} className="particulars-detail-item-content"> |
| | | <View>{getTypeLabel(item?.type)}({formatNumber(item?.mileage)}公里)</View> |
| | | <View>{formatMoney(item?.money)}元</View> |
| | | </View> |
| | | ))} |
| | | </View> |
| | | </View> |
| | | } |
| | | </View> |
| | | } |
| | | {Number(fareBreakdown?.redPacketMoney) > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>红包</View> |
| | | <View className="color1">-{formatMoney(fareBreakdown?.redPacketMoney)}元</View> |
| | | </View> |
| | | </View> |
| | | } |
| | | {Number(fareBreakdown?.couponMoney) > 0 && |
| | | <View className="particulars-detail-item"> |
| | | <View className="particulars-detail-item-title"> |
| | | <View>优惠券</View> |
| | | <View className="color1">-{formatMoney(fareBreakdown?.couponMoney)}元</View> |
| | | </View> |
| | | </View> |
| | | } |
| | | </View> |
| | | } |
| | | <View className="car-card-end-cost"> |
| | | <View className="car-card-end-cost-money"> |
| | | 全程{formatNumber(fareBreakdown?.kilometer)}公里|{formatNumber(fareBreakdown?.time)}分钟 |
| | |
| | | tripStatus == 7 && |
| | | <View className="car-card-reach"> |
| | | <View className="car-card-reach-text">点击按钮开车门</View> |
| | | <Image className='car-card-reach-img' src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/fd2b5c95-ba7b-405c-b566-98357ec9c3a6.png' /> |
| | | <Image className='car-card-reach-img' src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/f4a18c3f-a2db-45e6-8967-1510355b5e3b.webp' /> |
| | | <View className="car-card-reach-btn"> |
| | | <View className="car-card-reach-btn-item" onClick={() => handleUnlock('trunk', () => setTrunkStatus((prev) => !prev))}> |
| | | { |
| | |
| | | color: #F58900; |
| | | } |
| | | |
| | | &-desc-red { |
| | | margin-top: 8px; |
| | | font-size: 24px; |
| | | line-height: 34px; |
| | | color: #F53F3F; |
| | | } |
| | | |
| | | .cancel { |
| | | padding: 0 24px; |
| | | border-radius: 3112px; |
| | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | callImmediately: () => void; |
| | | fareEstimate?: any; |
| | | handleOpenPassengerNotice?: () => void; |
| | | walkingTime?: number; |
| | | } |
| | | |
| | | export default function ReleaseRoute({ startSite, endSite, callImmediately, fareEstimate = {}, handleOpenPassengerNotice }: Props) { |
| | | export default function ReleaseRoute({ startSite, endSite, callImmediately, fareEstimate = {}, handleOpenPassengerNotice, walkingTime = 0 }: Props) { |
| | | |
| | | const toSeleteSitePage = (type: string) => { |
| | | Taro.navigateTo({ |
| | |
| | | <Image className="now-site-icon" src={rightIcon} /> |
| | | </View> |
| | | </View> |
| | | <View className="now-site-walk">步行6分钟</View> |
| | | { |
| | | walkingTime > 0 && <View className="now-site-walk">步行{walkingTime}分钟</View> |
| | | } |
| | | </View> |
| | | {/* 终点 */} |
| | | <View onClick={() => toSeleteSitePage('end')} className="to-site"> |
| | |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | justify-content: center; |
| | | justify-content: flex-start; |
| | | overflow: visible; // 确保辐射动画不被裁剪 |
| | | min-width: 220px; |
| | | height: 270px; |
| | | } |
| | | |
| | | .waiting-radiation-box { |
| | | .waiting-radar { |
| | | position: absolute; |
| | | top: 100%; // 移到气泡下方 |
| | | left: 50%; |
| | | margin-top: 30px; // 向下偏移以对齐Marker锚点(预估值) |
| | | width: 0; |
| | | height: 0; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | top: auto; |
| | | bottom: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 220px; |
| | | pointer-events: none; |
| | | overflow: visible; |
| | | } |
| | | |
| | | @keyframes ripple { |
| | | 0% { |
| | | opacity: 0.6; |
| | | transform: translate(-50%, -50%) scale(0.2); |
| | | } |
| | | |
| | | 100% { |
| | | opacity: 0; |
| | | transform: translate(-50%, -50%) scale(2.5); |
| | | } |
| | | } |
| | | |
| | | .waiting-radiation-ring { |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 50%; |
| | | transform: translate(-50%, -50%); |
| | | width: 200px; |
| | | height: 200px; |
| | | border-radius: 50%; |
| | | background: rgba(26, 79, 255, 0.05); |
| | | border: 1px solid rgba(26, 79, 255, 0.2); |
| | | opacity: 0; // 初始不可见,由动画控制 |
| | | animation: ripple 3.5s infinite linear; |
| | | |
| | | &:nth-child(1) { |
| | | animation-delay: 0s; |
| | | } |
| | | |
| | | &:nth-child(2) { |
| | | animation-delay: 1s; |
| | | } |
| | | } |
| | | |
| | | |
| | | .waiting-callout-bg { |
| | | position: relative; |
| | |
| | | import suggest from '../../../robotaxi/assets/suggest.png' |
| | | import originIcon from '../../../robotaxi/assets/origin-icon.png' |
| | | import wsManager from '../../../utils/websocket'; |
| | | // 2-13修改的备注 start: 引入步行路径方法与箭头资源 |
| | | import { getWalkingRoute } from '../../../utils/Robotaxi-public-method'; |
| | | import lineRight from '../../../robotaxi/assets/line-right.png'; |
| | | // 2-13修改的备注 end |
| | | import { |
| | | saveOrder, |
| | | queryRouteAndFare, |
| | |
| | | const [cancelType, setCancelType] = useState(1); // 取消类型 |
| | | const [showEvaluatePopup, setShowEvaluatePopup] = useState(false); // 评价弹窗是否显示 |
| | | const [routePoints, setRoutePoints] = useState<any[]>([]); // 路线点集合 |
| | | // 2-13修改的备注 start: 新增步行路径点 |
| | | // 新增步行路径点 |
| | | const [walkingPoints, setWalkingPoints] = useState<any[]>([]); |
| | | // 2-13修改的备注 end |
| | | const [walkingTime, setWalkingTime] = useState<number>(0); |
| | | const [fareEstimate, setFareEstimate] = useState<any>({}); // 预估费用信息 |
| | | const [vehicleLocation, setVehicleLocation] = useState<{ latitude: number, longitude: number } | null>(null); |
| | | const [routeDistance, setRouteDistance] = useState<number | null>(null); // 路线距离 |
| | |
| | | |
| | | const tripStatusRef = useRef(tripStatus); // 行程状态引用 |
| | | const routeRequestKeyRef = useRef<string>(''); // 路线请求键 |
| | | // 2-13修改的备注 start: 在发布行程状态也允许展示路线 |
| | | // 在发布行程状态也允许展示路线 |
| | | const shouldShowRoute = [1, 4, 5, 6, 8, 9].includes(tripStatus); |
| | | // 2-13修改的备注 end |
| | | const authSuccessBound = useRef(false); // 认证成功是否绑定 |
| | | const messageBound = useRef(false); // 消息是否绑定 |
| | | const orderStatusBound = useRef(false); // 订单状态是否绑定 |
| | |
| | | const orderStatusHandlerRef = useRef<(data: any) => void>(); // 订单状态处理函数 |
| | | const orderRouteHandlerRef = useRef<(data: any) => void>(); // 订单路线处理函数 |
| | | const vehicleLocationHandlerRef = useRef<(data: any) => void>(); // 车辆位置处理函数 |
| | | // 2-13修改的备注 start: 记录已为某订单拉取过车辆信息,避免重复 |
| | | // 记录已为某订单拉取过车辆信息,避免重复 |
| | | const lastFetchedVehicleOrderIdRef = useRef<string | null>(null); |
| | | // 2-13修改的备注 end |
| | | |
| | | // 应用路线点 |
| | | const applyRoutePoints = (points: any[]) => { |
| | |
| | | }; |
| | | |
| | | const requestRouteAndFare = (originLat: number, originLng: number, destLat: number, destLng: number) => { |
| | | const requestKey = `${originLng},${originLat}-${destLng},${destLat}`; |
| | | if (requestKey === routeRequestKeyRef.current) { |
| | | return; |
| | | } |
| | | routeRequestKeyRef.current = requestKey; |
| | | // const requestKey = `${originLng},${originLat}-${destLng},${destLat}`; |
| | | // if (requestKey === routeRequestKeyRef.current) { |
| | | // return; |
| | | // } |
| | | // routeRequestKeyRef.current = requestKey; |
| | | const selectedCity = Taro.getStorageSync('selectedCity'); |
| | | const openCityId = selectedCity ? selectedCity.id : 0; |
| | | const params = { |
| | |
| | | if (res?.fareEstimate) { |
| | | setFareEstimate(res.fareEstimate); |
| | | } |
| | | if(res?.xpPricingCard){ |
| | | if (res?.xpPricingCard) { |
| | | Taro.setStorageSync('xpPricingCard', res.xpPricingCard); |
| | | } |
| | | // 2-13修改的备注 start: 解析预估阶段返回的路线并展示 |
| | | // 解析预估阶段返回的路线并展示 |
| | | const route = res?.route; |
| | | const distance = Number(route?.distance); |
| | | const duration = Number(route?.duration); |
| | |
| | | } else { |
| | | applyRoutePoints([]); |
| | | } |
| | | // 2-13修改的备注 end |
| | | |
| | | }).catch(() => { |
| | | }); |
| | | }; |
| | | |
| | | // 2-13修改的备注 start: 计算“当前位置→上车点”的步行路径 |
| | | // 计算“当前位置→上车点”的步行路径 |
| | | const updateWalkingRouteForStart = async () => { |
| | | try { |
| | | const loc = await Taro.getLocation({ type: 'gcj02' }); |
| | |
| | | } |
| | | const origin = `${originLng},${originLat}`; |
| | | const destination = `${targetLng},${targetLat}`; |
| | | const wPoints = await getWalkingRoute(origin, destination); |
| | | const { points: wPoints, duration } = await getWalkingRoute(origin, destination); |
| | | if (Array.isArray(wPoints) && wPoints.length > 0) { |
| | | setWalkingPoints(wPoints); |
| | | setWalkingTime(Math.ceil(duration / 60)); |
| | | } else { |
| | | setWalkingPoints([]); |
| | | setWalkingTime(0); |
| | | } |
| | | } catch (e) { |
| | | setWalkingPoints([]); |
| | | setWalkingTime(0); |
| | | } |
| | | }; |
| | | // 2-13修改的备注 end |
| | | |
| | | // 认证成功处理函数 |
| | | useEffect(() => { |
| | |
| | | messageHandlerRef.current = (data) => { |
| | | console.log('ws message:', data); |
| | | }; |
| | | // 2-13修改的备注 start: 优化订单状态回调中的车辆信息拉取逻辑 |
| | | // 优化订单状态回调中的车辆信息拉取逻辑 |
| | | orderStatusHandlerRef.current = (payload) => { |
| | | const mappedTripStatus = payload?.tripStatus; |
| | | console.log('payload?.tripStatus',payload?.tripStatus) |
| | | console.log('payload?.tripStatus', payload?.tripStatus) |
| | | if (typeof mappedTripStatus === 'number') { |
| | | setTripStatus(mappedTripStatus); |
| | | } |
| | |
| | | } |
| | | setShowCancelPopup(true); |
| | | } |
| | | if (payload?.showAnnouncementsPopup) { |
| | | setShowAnnouncementsPopup(true); |
| | | } |
| | | if (payload?.showPassengerNotice) { |
| | | if (Taro.getStorageSync('passengerNoticeShowed') !== 'true') { |
| | | setShowPassengerNotice(true); |
| | |
| | | } |
| | | } |
| | | }; |
| | | // 2-13修改的备注 end |
| | | |
| | | orderRouteHandlerRef.current = (payload) => { |
| | | const points = payload?.points || []; |
| | | applyRoutePoints(points); |
| | |
| | | applyRoutePoints(storedPoints); |
| | | }, [tripStatus]); |
| | | |
| | | // 2-13修改的备注 start: 在tripStatus=1时也绘制预估路线与步行路线 |
| | | // 在tripStatus=1时也绘制预估路线与步行路线 |
| | | useEffect(() => { |
| | | if ((shouldShowRoute) && (routePoints.length > 0 || walkingPoints.length > 0)) { |
| | | const lines: any[] = []; |
| | |
| | | setPolyline([]); |
| | | } |
| | | }, [tripStatus, routePoints, walkingPoints]); |
| | | // 2-13修改的备注 end |
| | | |
| | | // 页面初始化 |
| | | useDidShow(() => { |
| | |
| | | if (originLng && originLat && e?.longitude && e?.latitude) { |
| | | requestRouteAndFare(originLat, originLng, e.latitude, e.longitude); |
| | | } |
| | | // 2-13修改的备注 start: 初始阶段尝试计算步行路线 |
| | | // 初始阶段尝试计算步行路线 |
| | | if (tripStatus === 1 && s?.latitude && s?.longitude) { |
| | | updateWalkingRouteForStart(); |
| | | } |
| | | // 2-13修改的备注 end |
| | | |
| | | }); |
| | | |
| | | useEffect(() => { |
| | |
| | | if (originLng && originLat && endSite?.longitude && endSite?.latitude) { |
| | | requestRouteAndFare(originLat, originLng, endSite.latitude, endSite.longitude); |
| | | } |
| | | // 2-13修改的备注 start: 选择或更新上车点后重新计算步行路线 |
| | | // 选择或更新上车点后重新计算步行路线 |
| | | if (tripStatus === 1 && startSite?.latitude && startSite?.longitude) { |
| | | updateWalkingRouteForStart(); |
| | | } |
| | | // 2-13修改的备注 end |
| | | |
| | | }, [startSite?.longitude, startSite?.latitude, endSite?.longitude, endSite?.latitude, longitude, latitude]); |
| | | |
| | | // 生成标记点 |
| | |
| | | zIndex: 2, |
| | | name: s?.name || '上车点', |
| | | customCallout: { |
| | | anchorY: 8, |
| | | anchorY: [2, 3].includes(tripStatus) ? 80 : 8, |
| | | anchorX: 0, |
| | | display: 'ALWAYS' |
| | | } |
| | |
| | | setMarkers(nextMarkers); |
| | | }, [tripStatus, startSite?.name, startSite?.latitude, startSite?.longitude, endSite.name, endSite?.latitude, endSite?.longitude, vehicleLocation]) |
| | | |
| | | // 2-13修改的备注 start: 调整视图以包含所有路线点(含步行) |
| | | // 调整视图以包含所有路线点(含步行) |
| | | useEffect(() => { |
| | | if (shouldShowRoute && (routePoints.length > 0 || walkingPoints.length > 0)) { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | |
| | | }); |
| | | } |
| | | }, [tripStatus, routePoints, walkingPoints]); |
| | | // 2-13修改的备注 end |
| | | |
| | | const formatTime = (sec: number) => { |
| | | const m = Math.floor(sec / 60); |
| | |
| | | const waitingTime = Number(fareEstimate?.waitingTime || 0); |
| | | const estimatedTime = formatDateTime(new Date(Date.now() + waitingTime * 60 * 1000)); |
| | | const payload = { |
| | | endPointId: endSite.id ?? endSite.pointId ?? endSite.point_id ?? 0, |
| | | endPointId: endSite.id, |
| | | openCityId: openCityId, |
| | | placementAddress: s?.address || s?.name || '', |
| | | placementAddress: s?.name, |
| | | placementLat: s?.latitude, |
| | | placementLon: s?.longitude, |
| | | startPointId: s?.id ?? s?.pointId ?? s?.point_id ?? 0, |
| | | startPointId: s?.id, |
| | | xpElectronicFenceAreaId: s?.electronicAreaId || endSite?.electronicAreaId || '', |
| | | estimatedTime: estimatedTime, |
| | | }; |
| | | try { |
| | | await saveOrder(payload); |
| | | const { orderId } = await saveOrder(payload); |
| | | Taro.setStorageSync('orderId', orderId); |
| | | } catch (error) { |
| | | Taro.showToast({ |
| | | title: '下单失败,请重试', |
| | |
| | | Taro.removeStorageSync('orderRouteLatestType'); |
| | | Taro.removeStorageSync('orderId'); |
| | | Taro.removeStorageSync('tripStatus'); |
| | | updateWalkingRouteForStart(); |
| | | const s = Taro.getStorageSync('startSite'); |
| | | const e = Taro.getStorageSync('endSite'); |
| | | if (s?.latitude && s?.longitude && e?.longitude && e?.latitude) { |
| | | requestRouteAndFare(s.latitude, s.longitude, e.latitude, e.longitude); |
| | | } |
| | | } |
| | | |
| | | // 弹窗操作 |
| | |
| | | <CoverView className="waiting-callout-txt"> |
| | | <CoverView className="waiting-callout-txt-content">{item.name}</CoverView> |
| | | </CoverView> |
| | | <CoverImage className="waiting-radar" src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/63ff36fc-e84c-4078-a53b-467870645105.webp" /> |
| | | </CoverView> |
| | | ) |
| | | } |
| | |
| | | callImmediately={callImmediately} |
| | | fareEstimate={fareEstimate} |
| | | handleOpenPassengerNotice={handleOpenPassengerNotice} |
| | | walkingTime={walkingTime} |
| | | /> |
| | | } |
| | | {/* 发布中弹窗 */} |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data-box { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 16px; |
| | | margin: 176px 135px 0 135px; |
| | | font-size: 32px; |
| | | letter-spacing: normal; |
| | | color: rgba(0, 0, 0, 0.6); |
| | | |
| | | .no-data-image { |
| | | width: 480px; |
| | | height: 400px; |
| | | } |
| | | } |
| | |
| | | return <View className={`tab-item ${activeStatus === item.value ? 'active-status' : ''}`} key={item.value || index} onClick={() => changeStatus(item.value)}>{item.label}</View> |
| | | })} |
| | | </View> |
| | | <View className='coupon-card'> |
| | | {couponList.map((item, index) => { |
| | | return <View className='coupon-item' key={item.couponId || index}> |
| | | { |
| | | couponList.length > 0 && |
| | | <View className='coupon-card'> |
| | | {couponList.map((item, index) => { |
| | | return <View className='coupon-item' key={item.couponId || index}> |
| | | |
| | | <View className='coupon-price'> |
| | | <Text className='price'>{item.discountAmount}</Text> |
| | | <Text className='unit'>元</Text> |
| | | </View> |
| | | <View className='coupon-info'> |
| | | <Text className='name'>{item.title}</Text> |
| | | <Text className='time'>{item.expireTime}到期</Text> |
| | | <View className='more' onClick={() => openPopup(item)}> |
| | | <Text className='more-detail'>查看详情</Text> |
| | | <Image className='more-img' src={icon}></Image> |
| | | <View className='coupon-price'> |
| | | <Text className='price'>{item.discountAmount}</Text> |
| | | <Text className='unit'>元</Text> |
| | | </View> |
| | | </View> |
| | | {/* 状态 1-未使用 2-已使用 3-已过期 0待领取 */} |
| | | {item.status === 0 ? ( |
| | | <View className='coupon-info'> |
| | | <Text className='name'>{item.title}</Text> |
| | | <Text className='time'>{item.expireTime}到期</Text> |
| | | <View className='more' onClick={() => openPopup(item)}> |
| | | <Text className='more-detail'>查看详情</Text> |
| | | <Image className='more-img' src={icon}></Image> |
| | | </View> |
| | | </View> |
| | | {/* 状态 1-未使用 2-已使用 3-已过期 0待领取 */} |
| | | {item.status === 0 ? ( |
| | | <View className='receive' onClick={(e) => handleReceive(e, item)}>领取</View> |
| | | ) : item.status === 1 ? ( |
| | | <View className='receive' style={{backgroundColor: '#ccc'}}>未使用</View> |
| | | ) : item.status === 2 ? ( |
| | | <View className='receive' style={{backgroundColor: '#f5f5f5', color: '#999'}}>已使用</View> |
| | | ) : ( |
| | | <View className='receive' style={{backgroundColor: '#f5f5f5', color: '#999'}}>已过期</View> |
| | | )} |
| | | </View> |
| | | })} |
| | | </View> |
| | | ) : item.status === 1 ? ( |
| | | <View className='receive' style={{ backgroundColor: '#ccc' }}>未使用</View> |
| | | ) : item.status === 2 ? ( |
| | | <View className='receive' style={{ backgroundColor: '#f5f5f5', color: '#999' }}>已使用</View> |
| | | ) : ( |
| | | <View className='receive' style={{ backgroundColor: '#f5f5f5', color: '#999' }}>已过期</View> |
| | | )} |
| | | </View> |
| | | })} |
| | | </View> |
| | | } |
| | | { |
| | | !couponList.length && <View className='no-data-box'> |
| | | <Image className='no-data-image' src={require('../../assets/no-data-coupon.png')} /> |
| | | <View>暂无优惠券</View> |
| | | </View> |
| | | } |
| | | |
| | | {/* 优惠券说明 */} |
| | | {visible && currentCoupon && <View className='privacy-popup' catchMove> |
| | |
| | | background: #FFFFFF; |
| | | margin-bottom: 24px; |
| | | padding: 28px 40px 24px 32px; |
| | | &:last-child{ |
| | | |
| | | &:last-child { |
| | | margin-bottom: 32px; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | } |
| | | |
| | | .no-data-box { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 16px; |
| | | margin: 176px 135px 0 135px; |
| | | font-size: 32px; |
| | | letter-spacing: normal; |
| | | color: rgba(0, 0, 0, 0.6); |
| | | |
| | | .no-data-image { |
| | | width: 480px; |
| | | height: 400px; |
| | | } |
| | | } |
| | |
| | | |
| | | return ( |
| | | <View className='order-page'> |
| | | {groups.map((group, gi) => { |
| | | {groups.length > 0 && groups.map((group, gi) => { |
| | | return <View key={group.dateLabel || gi}> |
| | | {(group.orders || []).map((item, index) => { |
| | | return <View className='order-item' key={item.orderId || index} onClick={() => toOrderDetail(item)}> |
| | |
| | | })} |
| | | </View> |
| | | })} |
| | | |
| | | { |
| | | !groups.length && <View className='no-data-box'> |
| | | <Image className='no-data-image' src={require('../../assets/no-data-city.png')} /> |
| | | <View>暂无订单</View> |
| | | </View> |
| | | } |
| | | </View> |
| | | ) |
| | | } |
| | |
| | | } |
| | | const origin = `${originLng},${originLat}`; |
| | | const destination = `${selectedSite.longitude},${selectedSite.latitude}`; |
| | | getWalkingRoute(origin, destination).then((points) => { |
| | | getWalkingRoute(origin, destination).then(({ points }) => { |
| | | if (!Array.isArray(points) || points.length === 0) { |
| | | setPolyline([]); |
| | | return; |
| | |
| | | import arrow from '../../assets/arrow.png' |
| | | import { getStartPoints, getEndPoints, queryPointByName } from '../../api/site-search'; |
| | | import { formatDistance, debounce } from '../../../utils/Robotaxi-public-method'; |
| | | import { AMAP_KEY } from '../../../constants/api'; |
| | | import amapFile from '../../../libs/amap-wx.130.js'; |
| | | |
| | | |
| | | |
| | |
| | | |
| | | try { |
| | | const res = await queryPointByName(payload); |
| | | const { availablePoints, unavailablePoints } = res; |
| | | const { availablePoints } = res; |
| | | |
| | | if (Array.isArray(availablePoints)) { |
| | | const list = availablePoints.map(item => ({ |
| | |
| | | setSiteList([]); |
| | | } |
| | | |
| | | if (Array.isArray(unavailablePoints)) { |
| | | const list = unavailablePoints.map(item => ({ |
| | | ...item, |
| | | distance: formatDistance(item.distance) |
| | | })); |
| | | setNonOperationalSite(list); |
| | | } else { |
| | | setNonOperationalSite([]); |
| | | } |
| | | const myAmapFun = new amapFile.AMapWX({ key: AMAP_KEY }); |
| | | myAmapFun.getPoiAround({ |
| | | querykeywords: queryVal, |
| | | location: `${longitude},${latitude}`, |
| | | success: (data) => { |
| | | if (data && data.poisData) { |
| | | const list = data.poisData.map(item => { |
| | | const locationArr = item.location.split(','); |
| | | return { |
| | | ...item, |
| | | longitude: Number(locationArr[0]), |
| | | latitude: Number(locationArr[1]), |
| | | distance: formatDistance(item.distance) |
| | | } |
| | | }); |
| | | setNonOperationalSite(list); |
| | | } |
| | | }, |
| | | fail: (info) => { |
| | | console.log(info); |
| | | } |
| | | }); |
| | | |
| | | } catch (error) { |
| | | console.error('search error', error); |
| | | setSiteList([]); |
| | |
| | | }) |
| | | } |
| | | |
| | | const onChangeSelectSite = (item) => { |
| | | const onChangeSelectSite = (item, isNonOperational = true) => { |
| | | if (!isNonOperational) { |
| | | Taro.showToast({ |
| | | title: '当前站点不在运营区域内', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | if (pageType == 'start') { |
| | | const endSiteCache = Taro.getStorageSync('endSite'); |
| | | // if (endSiteCache?.electronicAreaId && item?.electronicAreaId && endSiteCache.electronicAreaId !== item.electronicAreaId) { |
| | | // Taro.showToast({ |
| | | // title: '起终点需在同一运营区域', |
| | | // icon: 'none', |
| | | // }) |
| | | // return |
| | | // } |
| | | if (endSiteCache?.electronicAreaId && item?.electronicAreaId && endSiteCache.electronicAreaId !== item.electronicAreaId) { |
| | | Taro.showToast({ |
| | | title: '起终点需在同一运营区域', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | Taro.setStorageSync('startSite', item); |
| | | Taro.navigateBack() |
| | | } else if (pageType == 'end') { |
| | | const startSiteCache = Taro.getStorageSync('startSite'); |
| | | // if (startSiteCache?.electronicAreaId && item?.electronicAreaId && startSiteCache.electronicAreaId !== item.electronicAreaId) { |
| | | // Taro.showToast({ |
| | | // title: '起终点需在同一运营区域', |
| | | // icon: 'none', |
| | | // }) |
| | | // return |
| | | // } |
| | | if (startSiteCache?.electronicAreaId && item?.electronicAreaId && startSiteCache.electronicAreaId !== item.electronicAreaId) { |
| | | Taro.showToast({ |
| | | title: '起终点需在同一运营区域', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | Taro.setStorageSync('endSite', item) |
| | | Taro.reLaunch({ |
| | | url: '/robotaxi/pages/begin-route/index', |
| | |
| | | <View className='card-content'> |
| | | { |
| | | nonOperationalSite.map((item, index) => { |
| | | return <View onClick={() => onChangeSelectSite(item)} className='history-item' key={item.id || index}> |
| | | return <View onClick={() => onChangeSelectSite(item, false)} className='history-item' key={item.id || index}> |
| | | <Image className='history-icon' src={address}></Image> |
| | | <View className='history-info'> |
| | | <View className='history-name'>{item.name || '_'}</View> |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import request from './request'; |
| | | import { getFenceCheck, getNearestSite } from '../robotaxi/api/home'; |
| | | import { getOpenCity } from '../robotaxi/api/select-city'; |
| | | |
| | | // 辅助函数:计算两点之间的距离(单位:米) |
| | | export const getDistance = (lat1: number, lng1: number, lat2: number, lng2: number) => { |
| | |
| | | } |
| | | |
| | | // 获取步行规划路线 |
| | | export const getWalkingRoute = (origin: string, destination: string): Promise<any[]> => { |
| | | export const getWalkingRoute = (origin: string, destination: string): Promise<{ points: any[], duration: number, distance: number }> => { |
| | | return new Promise((resolve, reject) => { |
| | | const myAmapFun = new amapFile.AMapWX({ key: AMAP_KEY }); |
| | | myAmapFun.getWalkingRoute({ |
| | |
| | | destination: destination, |
| | | success: (data) => { |
| | | const points = parseRouteToPoints(data); |
| | | resolve(points); |
| | | let duration = 0; |
| | | let distance = 0; |
| | | if (data.paths && data.paths[0]) { |
| | | duration = Number(data.paths[0].duration); |
| | | distance = Number(data.paths[0].distance); |
| | | } |
| | | resolve({ points, duration, distance }); |
| | | }, |
| | | fail: (info) => { |
| | | reject(info); |
| | |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // 获取城市中心经纬度 |
| | | export const getCityLocation = (cityName: string): Promise<{ latitude: number, longitude: number }> => { |
| | | return new Promise((resolve, reject) => { |
| | | const myAmapFun = new amapFile.AMapWX({ key: AMAP_KEY }); |
| | | myAmapFun.getInputtips({ |
| | | keywords: cityName, |
| | | city: cityName, |
| | | success: (data) => { |
| | | if (data && data.tips && data.tips.length > 0) { |
| | | // 优先取第一个有坐标的结果 |
| | | const tip = data.tips.find(t => t.location && t.location.length > 0); |
| | | if (tip) { |
| | | const locationArr = tip.location.split(','); |
| | | if (locationArr.length === 2) { |
| | | resolve({ |
| | | longitude: Number(locationArr[0]), |
| | | latitude: Number(locationArr[1]) |
| | | }); |
| | | return; |
| | | } |
| | | } |
| | | reject('No valid location found'); |
| | | } else { |
| | | reject('No data found'); |
| | | } |
| | | }, |
| | | fail: (info) => { |
| | | reject(info); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 获取包含 ID 的城市信息 |
| | | export const getCityInfoWithId = async (cityCode: string, cityName: string) => { |
| | | try { |
| | | const res: any = await getOpenCity(); |
| | | console.log(res); |
| | | console.log(cityCode); |
| | | |
| | | let cityId = 0; |
| | | if (Array.isArray(res.cities)) { |
| | | const target = res.cities.find(item => item.adcode === cityCode); |
| | | if (target) { |
| | | cityId = target.id; |
| | | } |
| | | } |
| | | return { |
| | | name: cityName, |
| | | code: cityCode, |
| | | id: cityId |
| | | }; |
| | | } catch (error) { |
| | | console.error('getOpenCity error', error); |
| | | return { |
| | | name: cityName, |
| | | code: cityCode, |
| | | id: 0 |
| | | }; |
| | | } |
| | | }; |