src/app.config.ts
@@ -33,6 +33,7 @@ 'pages/expense-description/index', 'pages/passenger-notice/index', 'pages/pricing-rules/index', 'pages/ride-guide/index', ], } ]; src/pages/robotaxi/home/index.tsx
@@ -25,6 +25,7 @@ } from '../../../utils/Robotaxi-public-method'; import { _ROBOTAXI_WS_HOST } from '../../../constants/api'; import { getGeofence, getBannerList, getAgreement, getOrderActive } from '../../../robotaxi/api/home'; import { getServicePhone } from '../../../robotaxi/api/customer-service'; import { getOrderDistance } from '../../../robotaxi/api/select-map-site'; import { checkOtp } from '../../../utils/tools'; @@ -51,7 +52,7 @@ const [adsorptionDistance, setAdsorptionDistance] = useState(null); // 吸附距离 const [orderData, setOrderData] = useState<any>({}); // 订单数据 const [boxHeight, setBoxHeight] = useState<number | string>('auto'); // 活动区域高度 const [boxHeight, setBoxHeight] = useState<number>(266); // 活动区域高度 const startY = useRef(0); // 拖动开始时的Y坐标 const startHeight = useRef(0); // 拖动开始时的高度 const fullHeight = useRef(0); // 活动区域最大高度 @@ -60,6 +61,7 @@ const shouldIgnoreRegionChange = useRef(false); // 是否忽略地图区域变化 const resetTimer = useRef<any>(null); // 自动归位定时器 const isReturningToOrigin = useRef(false); // 是否正在自动归位 const skipNextLocationCenterRef = useRef(false); const orderStatusBound = useRef(false); const DEFAULT_LATITUDE = 39.916527; // 北京故宫纬度 @@ -72,8 +74,8 @@ const ORDER_STATUS = { 4: '车辆正在赶来', 5: '正在前往目的地', 6: '车辆已到达上车点,请尽快前往', 5: '车辆已到达上车点,请尽快前往', 6: '正在前往目的地', 7: '车辆已到达目的地,请尽快结束行程', } @@ -93,6 +95,9 @@ // 监听页面显示 useDidShow(() => { getServicePhone().then((res) => { Taro.setStorageSync('customerServicePhone', res?.customerServicePhone || ''); }) // 从本地存储中获取选中的城市 const selectedCity = Taro.getStorageSync('selectedCity'); if (selectedCity) { @@ -192,7 +197,9 @@ }); } } else { getLocationInfo(true); const shouldUpdateCenter = !skipNextLocationCenterRef.current; skipNextLocationCenterRef.current = false; getLocationInfo(shouldUpdateCenter); } } } @@ -293,23 +300,36 @@ if (res && Array.isArray(res) && res.length > 0) { const newPolygons = formatGeofencePolygons(res); setPolygons(newPolygons); if (orderData || orderData?.id) return; if ((orderData || orderData?.id) && JSON.stringify(orderData) != '{}') return; // 定位到第一个运营区域的第一个点 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)); skipNextLocationCenterRef.current = true; shouldIgnoreRegionChange.current = true; const mapCtx = Taro.createMapContext('map'); mapCtx.moveToLocation({ latitude: Number(firstPoint.lat), longitude: Number(firstPoint.lng), }); } } else { setPolygons([]); if (orderData || orderData?.id) return; if ((orderData || orderData?.id) && JSON.stringify(orderData) != '{}') return; // 定位到城市中心 if (selectedCity.name) { 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); }); @@ -323,6 +343,13 @@ 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); }); @@ -338,19 +365,15 @@ query.select('.select-site').boundingClientRect(rect => { if (rect) { const contentHeight = rect.height; fullHeight.current = contentHeight; // 根据有多少banner来,最大显示高度为536 // 如果内容高度小于536,则使用内容高度(自适应) // 如果内容高度大于536,则限制为536(需要拖拽查看更多) minHeight.current = Math.min(contentHeight, 536); setBoxHeight(minHeight.current); const baseHeight = 266; fullHeight.current = Math.max(contentHeight, baseHeight); minHeight.current = baseHeight; setBoxHeight(baseHeight); } else { // 兜底逻辑 minHeight.current = 536; fullHeight.current = 536; setBoxHeight(536); minHeight.current = 266; fullHeight.current = 266; setBoxHeight(266); } }).exec(); }, 500); @@ -592,7 +615,10 @@ }; const openService = () => { setShowCustomerService(true); const phoneNumber = Taro.getStorageSync('customerServicePhone'); Taro.makePhoneCall({ phoneNumber, }) } const handleOpenSetting = () => { @@ -776,7 +802,7 @@ {/* 选择站点 */} <View className="content-box" style={{ height: typeof boxHeight === 'number' ? `${boxHeight}px` : 'auto', transition: isDragging.current ? 'none' : 'height 0.3s ease-out' }} style={{ height: `${boxHeight}px`, transition: isDragging.current ? 'none' : 'height 0.3s ease-out' }} onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} src/robotaxi/api/begin-route.ts
@@ -122,4 +122,13 @@ url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/cancel/${orderId}`, method: 'PUT' }); } // 订单查询实时轨迹路线 export const queryOrderRoute = async (payload) => { return request({ url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/queryOrderRoute`, method: 'POST', payload }); } src/robotaxi/api/home.ts
@@ -75,4 +75,12 @@ url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/active`, method: 'GET' }); } // 乘车指南 export const getRideGuideList = async () => { return request({ url: _ROBOTAXI_HOST + `/operation/base/v1/mini-program/guide/list`, method: 'GET' }); } src/robotaxi/api/order.ts
@@ -1,18 +1,8 @@ import request from '../../utils/request'; import { _ROBOTAXI_HOST } from '../../constants/api'; export interface OrderHistoryOrder { amount: number; destinationAddress: string; orderId: string; orderTime: string; originAddress: string; time?: string; } export interface OrderHistoryGroup { dateLabel: string; orders: OrderHistoryOrder[]; } export interface GetOrderHistoryPayload { src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -28,7 +28,7 @@ handleOpenEvaluatePopup?: () => void; // 打开评价弹窗 handleOpenAnnouncementsPopup?: () => void; // 打开公告弹窗 estimatedWaitMinutes?: number; // 预计等待时间 routeDurationMinutes?: number | null; // 预计行程时间 routeDurationSeconds?: number | null; // 预计行程时间 vehicleInfo?: { // 车辆信息 vehiclePictures?: string[] | string; // 车辆图片 vehicleCode?: string; // 车辆编号 @@ -38,11 +38,11 @@ orderId?: string; // 订单ID } export default function CallingCar({ tripStatus = 3, handleOpenCancelPopup, handleCancelTimeout, handleOpenOverRoutePopup, handlePay, updateStatus, handleOpenEvaluatePopup, handleOpenAnnouncementsPopup, estimatedWaitMinutes, routeDurationMinutes, vehicleInfo, orderId }: Props) { export default function CallingCar({ tripStatus = 3, handleOpenCancelPopup, handleCancelTimeout, handleOpenOverRoutePopup, handlePay, updateStatus, handleOpenEvaluatePopup, handleOpenAnnouncementsPopup, estimatedWaitMinutes, routeDurationSeconds, vehicleInfo, orderId }: Props) { const [trunkStatus, setTrunkStatus] = useState(false); // 后备箱状态 const [lockStatus, setLockStatus] = useState(false); // 锁车状态 const [tempStatus, setTempStatus] = useState(); // 温度状态 const [tempStatus, setTempStatus] = useState<number | null>(); // 温度状态 const [activeIndex, setActiveIndex] = useState(0) // 默认选中"很满意" const [waitMinutes, setWaitMinutes] = useState<number | null>(null); // 预计等待时间 const [routeDuration, setRouteDuration] = useState<number | null>(null); // 预计行程时间 @@ -61,6 +61,9 @@ const finishEndTimeRef = useRef<number | null>(null); // 到达目的地时间 const lastFareOrderIdRef = useRef<string | null>(null); const hasShownAnnouncementsRef = useRef(false); // 是否已展示过公告弹窗 const TEMP_MIN = 22; const TEMP_MAX = 28; const formatCountdown = (seconds: number) => { const safeSeconds = Math.max(0, seconds); @@ -101,13 +104,23 @@ return Date.now() + num * 1000; }; const displayRouteDuration = typeof routeDurationMinutes === 'number' && !Number.isNaN(routeDurationMinutes) ? routeDurationMinutes : routeDuration; const displayRouteDurationSeconds = typeof routeDurationSeconds === 'number' && !Number.isNaN(routeDurationSeconds) ? routeDurationSeconds : routeDuration; const formatMoney = (value: any) => { const num = Number(value); if (Number.isNaN(num)) { return '--'; } return num.toFixed(1); }; const formatDurationDisplay = (value: any) => { const num = Number(value); if (Number.isNaN(num)) { return { value: 0, unit: '分钟' }; } if (num < 60) { return { value: Math.max(0, Math.round(num)), unit: '秒' }; } return { value: Math.ceil(num / 60), unit: '分钟' }; }; const formatNumber = (value: any, digits = 1) => { const num = Number(value); @@ -121,12 +134,13 @@ if (type === 3) return '节假日时段'; return '普通时段'; }; const isArriveSoon = tripStatus === 4 && typeof displayRouteDuration === 'number' && !Number.isNaN(displayRouteDuration) && displayRouteDuration < 1; const durationDisplay = formatDurationDisplay(displayRouteDurationSeconds); const isArriveSoon = tripStatus === 4 && typeof displayRouteDurationSeconds === 'number' && !Number.isNaN(displayRouteDurationSeconds) && displayRouteDurationSeconds < 60; const titleMap = { 3: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>, 4: <>车辆还有<View className="time">{displayRouteDuration}</View>分钟到达</>, 4: <>车辆还有<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}到达</>, 5: <>车辆已到达上车点<View className="time">{pickupCountdownSeconds !== null ? formatCountdown(pickupCountdownSeconds) : '--:--'}</View></>, 6: <>预计<View className="time">{displayRouteDuration}</View>分钟抵达目的地</>, 6: <>预计<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}抵达目的地</>, 7: <>已到达目的地<View className="time">{finishCountdownSeconds !== null ? formatCountdown(finishCountdownSeconds) : '--:--'}</View></>, 8: '行程已结束', 9: '支付成功' @@ -166,9 +180,11 @@ }).catch(() => { }); } else if (tripStatus === 4) { // 即将到达:加载路线时间、限停时间与当前车内温度 const durationUnit = Taro.getStorageSync('orderRouteDurationUnit'); const duration = Number(Taro.getStorageSync('orderRouteDuration')); if (!Number.isNaN(duration)) { setRouteDuration(duration); const durationSeconds = durationUnit === 's' ? duration : duration * 60; if (!Number.isNaN(durationSeconds)) { setRouteDuration(durationSeconds); } const s = Taro.getStorageSync('startSite'); const time = Number(s?.time); @@ -178,8 +194,8 @@ const targetOrderId = orderId || Taro.getStorageSync('orderId'); if (targetOrderId) { // 暂时不调用车辆温度 setTempStatus(19); lastConfirmedTempRef.current = 19; setTempStatus(26); lastConfirmedTempRef.current = 26; // queryTemperatureByOrderId({ orderId: targetOrderId }).then((res: any) => { // const currentTemperature = Number(res?.currentTemperature ?? res?.temperature); // if (!Number.isNaN(currentTemperature)) { @@ -190,9 +206,11 @@ } } else if (tripStatus === 5) { // 已到达上车点:根据限停时间启动倒计时并处理超时 const durationUnit = Taro.getStorageSync('orderRouteDurationUnit'); const duration = Number(Taro.getStorageSync('orderRouteDuration')); if (!Number.isNaN(duration)) { setRouteDuration(duration); const durationSeconds = durationUnit === 's' ? duration : duration * 60; if (!Number.isNaN(durationSeconds)) { setRouteDuration(durationSeconds); } const s = Taro.getStorageSync('startSite'); const time = Number(s?.time); @@ -318,7 +336,18 @@ // 处理温度变化 const handleTempChange = (delta: number) => { setTempStatus((prev) => { if (typeof prev !== 'number') { return prev; } const next = prev + delta; if (next < TEMP_MIN) { Taro.showToast({ title: `温度最低${TEMP_MIN}℃`, icon: 'none' }); return prev; } if (next > TEMP_MAX) { Taro.showToast({ title: `温度最高${TEMP_MAX}℃`, icon: 'none' }); return prev; } scheduleTemperatureUpdate(next); return next; }); @@ -342,8 +371,17 @@ }); return; } onSuccess?.(); if (doorType === 'trunk') { Taro.showToast({ title: '后备箱已解锁', icon: 'none' }); } if (doorType === 'door') { Taro.showToast({ title: '车门已解锁', icon: 'none' }); if (!hasShownAnnouncementsRef.current) { handleOpenAnnouncementsPopup?.(); hasShownAnnouncementsRef.current = true; @@ -435,9 +473,17 @@ <View className="car-card-temp"> <View className="car-card-temp-title">当前温度</View> <View className="car-card-temp-content"> <Image className="car-card-temp-content-img" src={require('../../../assets/T-down.png')} onClick={() => handleTempChange(-1)} /> <Image className="car-card-temp-content-img" src={require('../../../assets/T-down.png')} onClick={typeof tempStatus === 'number' && tempStatus > TEMP_MIN ? () => handleTempChange(-1) : undefined} /> <View className="car-card-temp-content-text">{tempStatus}℃</View> <Image className="car-card-temp-content-img" src={require('../../../assets/T-up.png')} onClick={() => handleTempChange(1)} /> <Image className="car-card-temp-content-img" src={require('../../../assets/T-up.png')} onClick={typeof tempStatus === 'number' && tempStatus < TEMP_MAX ? () => handleTempChange(1) : undefined} /> </View> </View> </View> @@ -473,90 +519,88 @@ <Image style={{ transform: showDetail ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.3s' }} src={require('../../../assets/ccc-icon-right.png')} /> </View> </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 className={`particulars-detail ${showDetail ? 'expand' : ''}`}> {Number(fareBreakdown?.orderMoney) > 0 && <View className="particulars-detail-item"> <View className="particulars-detail-item-title"> <View>订单总额</View> <View>{formatMoney(fareBreakdown?.orderMoney)}元</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> </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> } {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> {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> {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> } {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> } {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.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> {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> } {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> } {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.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> {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> } {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> } {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.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> } {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> } {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> } {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> <View className="car-card-end-cost"> <View className="car-card-end-cost-money"> 全程{formatNumber(fareBreakdown?.kilometer)}公里|{formatNumber(fareBreakdown?.time)}分钟 @@ -581,14 +625,9 @@ <View className="car-card-reach-text">点击按钮开车门</View> <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))}> { trunkStatus ? <Image className="car-card-reach-btn-item-img" src={require('../../../assets/trunk-open.png')} /> : <Image className="car-card-reach-btn-item-img" src={require('../../../assets/trunk-close.png')} /> } <View className="car-card-reach-btn-item-text">{trunkStatus ? '关闭后备箱' : '打开后备箱'}</View> <View className="car-card-reach-btn-item" onClick={() => handleUnlock('trunk', () => setTrunkStatus(true))}> <Image className="car-card-reach-btn-item-img" src={require('../../../assets/trunk-open.png')} /> <View className="car-card-reach-btn-item-text">打开后备箱</View> </View> <View className="car-card-reach-btn-item" onClick={handleOpenOverRoutePopup}> <Image className="car-card-reach-btn-item-img" src={require('../../../assets/over-gps-icon.png')} /> src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
@@ -287,7 +287,7 @@ display: flex; flex-direction: column; gap: 16px; overflow: hidden; overflow:auto; max-height: 0; margin-top: 0; src/robotaxi/pages/begin-route/components/css-folder/release-route.less
@@ -105,7 +105,9 @@ } .route-content { position: relative; margin-top: 46px; z-index: 10; .tip-icon { width: 22px; src/robotaxi/pages/begin-route/components/passenger-notice.tsx
@@ -43,7 +43,7 @@ return; } getPrivacyContent(5).then((res: any) => { setNoticeContent(res); setNoticeContent(res.content); }).catch(() => { }); }, [show]); src/robotaxi/pages/begin-route/components/release-route.tsx
@@ -18,7 +18,7 @@ const toSeleteSitePage = (type: string) => { Taro.navigateTo({ url: '/robotaxi/pages/site-search/index?type=' + type url: '/robotaxi/pages/site-search/index?pageType=' + type }) } const toPriceDescription = () => { @@ -58,8 +58,8 @@ <View className="route-content"> <View className="route-content-top"> <View className="route-content-txt">预估等待{fareEstimate.waitingTime || 0}分钟</View> <View className="route-content-top-money"> <Image className="tip-icon" src={tipDefaultIcon} onClick={() => toPriceDescription()} /> <View className="route-content-top-money" onClick={() => toPriceDescription()} > <Image className="tip-icon" src={tipDefaultIcon} /> 预估 <Text className="money-num">{fareEstimate.finalFare || 0}</Text> 元 src/robotaxi/pages/begin-route/index.tsx
@@ -19,7 +19,7 @@ import suggest from '../../../robotaxi/assets/suggest.png' import originIcon from '../../../robotaxi/assets/origin-icon.png' import wsManager from '../../../utils/websocket'; import { getWalkingRoute } from '../../../utils/Robotaxi-public-method'; import { getWalkingRoute, formatDurationToClock, formatDateTime, getStoredRoutePoints, clearOrderTripStorage } from '../../../utils/Robotaxi-public-method'; import lineRight from '../../../robotaxi/assets/line-right.png'; import { saveOrder, @@ -27,6 +27,7 @@ queryVehicleByOrderId, getOrderPayment, cancelOrder, queryOrderRoute, } from '../../../robotaxi/api/begin-route'; export default function SelectMapSite() { @@ -68,7 +69,7 @@ const tripStatusRef = useRef(tripStatus); // 行程状态引用 const routeRequestKeyRef = useRef<string>(''); // 路线请求键 // 在发布行程状态也允许展示路线 const shouldShowRoute = [1, 4, 5, 6, 8, 9].includes(tripStatus); const shouldShowRoute = [1, 4, 6, 8, 9].includes(tripStatus); const authSuccessBound = useRef(false); // 认证成功是否绑定 const messageBound = useRef(false); // 消息是否绑定 const orderStatusBound = useRef(false); // 订单状态是否绑定 @@ -79,6 +80,7 @@ const orderStatusHandlerRef = useRef<(data: any) => void>(); // 订单状态处理函数 const orderRouteHandlerRef = useRef<(data: any) => void>(); // 订单路线处理函数 const vehicleLocationHandlerRef = useRef<(data: any) => void>(); // 车辆位置处理函数 const socketOpenHandlerRef = useRef<(() => void) | null>(null); // 记录已为某订单拉取过车辆信息,避免重复 const lastFetchedVehicleOrderIdRef = useRef<string | null>(null); const mapRectRef = useRef<any>(null); @@ -92,6 +94,7 @@ }, []); const shouldShowWaitingRadar = isAndroid && [2, 3].includes(tripStatus); // 加载等待弹窗图片 useEffect(() => { if (![2, 3].includes(tripStatus)) return; if (Taro.getEnv?.() !== Taro.ENV_TYPE.WEAPP) { @@ -122,6 +125,7 @@ }; }, [tripStatus]); // 更新等待雷达位置 const updateWaitingRadarPosition = () => { if (!shouldShowWaitingRadar) { return; @@ -170,6 +174,7 @@ .exec(); }; // 更新等待雷达位置 useEffect(() => { updateWaitingRadarPosition(); }, [shouldShowWaitingRadar, startSite?.latitude, startSite?.longitude, longitude, latitude]); @@ -183,31 +188,37 @@ } }; // 获取存储的路线点 const getStoredRoutePoints = (status?: number) => { const cached = Taro.getStorageSync('orderRoutePoints') || {}; const preferredType = typeof status === 'number' && status >= 6 ? 2 : 1; const preferred = cached[String(preferredType)]; if (Array.isArray(preferred) && preferred.length > 0) { return preferred; // 归一化距离(公里) const normalizeDistanceKm = (value: any) => { const num = Number(value); if (Number.isNaN(num)) { return null; } const latestType = Taro.getStorageSync('orderRouteLatestType'); const latest = cached[String(latestType)]; if (Array.isArray(latest) && latest.length > 0) { return latest; } if (Array.isArray(cached.latest) && cached.latest.length > 0) { return cached.latest; } const keys = Object.keys(cached); for (const key of keys) { if (Array.isArray(cached[key]) && cached[key].length > 0) { return cached[key]; } } return []; return Number((num / 1000).toFixed(1)); }; // 归一化时间(秒) const normalizeDurationSeconds = (value: any) => { const num = Number(value); if (Number.isNaN(num)) { return null; } return Math.round(num); }; // 格式化时间显示(分钟或秒) const formatDurationDisplay = (value: any) => { const num = Number(value); if (Number.isNaN(num)) { return { value: 0, unit: '分钟' }; } if (num < 60) { return { value: Math.max(0, Math.round(num)), unit: '秒' }; } return { value: Math.ceil(num / 60), unit: '分钟' }; }; // 请求路线和车费 const requestRouteAndFare = (originLat: number, originLng: number, destLat: number, destLng: number, force = false) => { const requestKey = `${originLng},${originLat}-${destLng},${destLat}`; if (!force && requestKey === routeRequestKeyRef.current) { @@ -239,13 +250,17 @@ const distance = Number(route?.distance); const duration = Number(route?.duration); const points = (Array.isArray(route?.polyline) ? route?.polyline : []); if (!Number.isNaN(distance)) { setRouteDistance(distance); Taro.setStorageSync('orderRouteDistance', distance); const distanceKm = normalizeDistanceKm(distance); if (distanceKm !== null) { setRouteDistance(distanceKm); Taro.setStorageSync('orderRouteDistance', distanceKm); Taro.setStorageSync('orderRouteDistanceUnit', 'km'); } if (!Number.isNaN(duration)) { setRouteDuration(duration); Taro.setStorageSync('orderRouteDuration', duration); const durationSeconds = normalizeDurationSeconds(duration); if (durationSeconds !== null) { setRouteDuration(durationSeconds); Taro.setStorageSync('orderRouteDuration', durationSeconds); Taro.setStorageSync('orderRouteDurationUnit', 's'); } if (Array.isArray(points) && points.length > 0) { applyRoutePoints(points); @@ -324,7 +339,7 @@ }) .catch(() => { }); } if (payload?.clearRoute) { if (mappedTripStatus === 5) { setRoutePoints([]); setPolyline([]); } @@ -353,16 +368,25 @@ orderRouteHandlerRef.current = (payload) => { const points = payload?.points || []; applyRoutePoints(points); if (tripStatusRef.current !== 5) { applyRoutePoints(points); } else { setRoutePoints([]); setPolyline([]); } const distance = Number(payload?.content?.distance); const duration = Number(payload?.content?.duration); if (!Number.isNaN(distance)) { setRouteDistance(distance); Taro.setStorageSync('orderRouteDistance', distance); const distanceKm = normalizeDistanceKm(distance); if (distanceKm !== null) { setRouteDistance(distanceKm); Taro.setStorageSync('orderRouteDistance', distanceKm); Taro.setStorageSync('orderRouteDistanceUnit', 'km'); } if (!Number.isNaN(duration)) { setRouteDuration(duration); Taro.setStorageSync('orderRouteDuration', duration); const durationSeconds = normalizeDurationSeconds(duration); if (durationSeconds !== null) { setRouteDuration(durationSeconds); Taro.setStorageSync('orderRouteDuration', durationSeconds); Taro.setStorageSync('orderRouteDurationUnit', 's'); } }; vehicleLocationHandlerRef.current = (payload) => { @@ -404,12 +428,16 @@ Taro.setStorageSync('tripStatus', tripStatus); tripStatusRef.current = tripStatus; let timer: any = null; if (tripStatus === 2 || tripStatus === 3) { // 状态为2或3时,将地图中心定位到上车点 if (tripStatus == 2 || tripStatus == 3) { const s = Taro.getStorageSync('startSite'); if (s && s.latitude && s.longitude) { setLatitude(s.latitude); setLongitude(s.longitude); const mapCtx = Taro.createMapContext('map'); mapCtx.moveToLocation({ latitude: s.latitude, longitude: s.longitude, }); } const cachedStartAt = Number(Taro.getStorageSync(WAITING_TIMER_START_KEY)); @@ -431,6 +459,34 @@ timer = setInterval(() => { updateElapsed(); }, 1000); } else if (tripStatus == 5) { const s = Taro.getStorageSync('startSite'); if (s && s.latitude && s.longitude) { setLatitude(s.latitude); setLongitude(s.longitude); const mapCtx = Taro.createMapContext('map'); mapCtx.moveToLocation({ latitude: s.latitude, longitude: s.longitude, }); } setElapsedSeconds(0); Taro.removeStorageSync(WAITING_TIMER_START_KEY); Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY); } else if (tripStatus == 7) { const e = Taro.getStorageSync('endSite'); if (e && e.latitude && e.longitude) { setLatitude(e.latitude); setLongitude(e.longitude); const mapCtx = Taro.createMapContext('map'); mapCtx.moveToLocation({ latitude: e.latitude, longitude: e.longitude, }); } setElapsedSeconds(0); Taro.removeStorageSync(WAITING_TIMER_START_KEY); Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY); } else { setElapsedSeconds(0); Taro.removeStorageSync(WAITING_TIMER_START_KEY); @@ -441,6 +497,7 @@ }; }, [tripStatus]); // 页面隐藏时保存等待时间 useDidHide(() => { const currentTripStatus = tripStatusRef.current; if (currentTripStatus === 2 || currentTripStatus === 3) { @@ -455,16 +512,81 @@ } Taro.removeStorageSync(WAITING_TIMER_START_KEY); Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY); if (socketOpenHandlerRef.current) { wsManager.off('open', socketOpenHandlerRef.current); socketOpenHandlerRef.current = null; } }); // 监听tripStatus变化,应用存储的路线点 useEffect(() => { if (tripStatus === 5) { setRoutePoints([]); setPolyline([]); return; } const storedPoints = getStoredRoutePoints(tripStatus); applyRoutePoints(storedPoints); }, [tripStatus]); // 在tripStatus=1时也绘制预估路线与步行路线 useEffect(() => { if ((shouldShowRoute) && (routePoints.length > 0 || walkingPoints.length > 0)) { const s = Taro.getStorageSync('startSite'); const pickupMarker = s?.latitude && s?.longitude ? { id: Date.now() + 1, latitude: s.latitude, longitude: s.longitude, iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/51e7cb75-72af-4994-9d6e-d999efb2fbc7.png', width: 22, height: 40, anchor: { x: 0.5, y: 1 }, zIndex: 2, name: s?.name || '上车点', customCallout: { anchorY: [2, 3].includes(tripStatus) ? 150 : -5, anchorX: 0, display: 'ALWAYS' } } : null; const dropoffMarker = endSite?.latitude && endSite?.longitude ? { id: Date.now() + 2, latitude: endSite.latitude, longitude: endSite.longitude, iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/6a56b1eb-ec37-4839-8733-262942af070d.png', width: 22, height: 40, anchor: { x: 0.5, y: 1 }, zIndex: 2, name: endSite.name || '目的地', customCallout: { anchorY: -5, anchorX: 0, display: 'ALWAYS' } } : null; const carMarker = vehicleLocation ? { id: Date.now() + 3, latitude: vehicleLocation.latitude, longitude: vehicleLocation.longitude, iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e51e92e6-700d-4e83-b3e0-14e6f18e0b9a.png', width: 40, height: 20, anchor: { x: 0.5, y: 1 }, zIndex: 3, name: '车辆位置', customCallout: { anchorY: 50, anchorX: 0, display: 'ALWAYS' } } : null; const includePickup = [1, 2, 3, 4, 5, 8, 9].includes(tripStatus); const includeDropoff = [1, 6, 7, 8, 9].includes(tripStatus); const includeCar = [4, 5, 6, 7].includes(tripStatus); const nextMarkers: any[] = []; if (includePickup && pickupMarker) nextMarkers.push(pickupMarker); if (includeCar && carMarker) nextMarkers.push(carMarker); if (includeDropoff && dropoffMarker) nextMarkers.push(dropoffMarker); setMarkers(nextMarkers); if (shouldShowRoute && (routePoints.length > 0 || walkingPoints.length > 0)) { const lines: any[] = []; if (routePoints.length > 0) { lines.push({ @@ -486,16 +608,60 @@ }); } setPolyline(lines); const mapCtx = Taro.createMapContext('map'); const allPoints = [ ...routePoints, ...walkingPoints, ]; mapCtx.includePoints({ points: allPoints, padding: [50, 50, 500, 50] }); } else { setPolyline([]); } }, [tripStatus, routePoints, walkingPoints]); }, [tripStatus, shouldShowRoute, routePoints, walkingPoints, startSite?.name, startSite?.latitude, startSite?.longitude, endSite.name, endSite?.latitude, endSite?.longitude, vehicleLocation]); const bindSocketListeners = () => { if (!orderStatusBound.current && orderStatusHandlerRef.current) { wsManager.on('order_status', orderStatusHandlerRef.current); orderStatusBound.current = true; } if (!orderRouteBound.current && orderRouteHandlerRef.current) { wsManager.on('order_route', orderRouteHandlerRef.current); orderRouteBound.current = true; } if (!vehicleLocationBound.current && vehicleLocationHandlerRef.current) { wsManager.on('vehicle_location', vehicleLocationHandlerRef.current); vehicleLocationBound.current = true; } }; const ensureSocketListeners = () => { if (wsManager.isConnectedStatus()) { bindSocketListeners(); return; } if (socketOpenHandlerRef.current) { return; } const handleOpen = () => { bindSocketListeners(); if (socketOpenHandlerRef.current) { wsManager.off('open', socketOpenHandlerRef.current); socketOpenHandlerRef.current = null; } }; socketOpenHandlerRef.current = handleOpen; wsManager.on('open', handleOpen); }; // 页面初始化 useDidShow(() => { const s = Taro.getStorageSync('startSite'); const e = Taro.getStorageSync('endSite'); const cachedOrderId = Taro.getStorageSync('orderId'); const cachedTripStatus = Number(Taro.getStorageSync('tripStatus')) || tripStatus; if (s) { setStartSite(s); } @@ -510,6 +676,50 @@ }) .catch(() => { }); } if (cachedOrderId && [4, 5, 6, 7, 8].includes(cachedTripStatus)) { const routeType = cachedTripStatus <= 5 ? 1 : 2; queryOrderRoute({ orderId: cachedOrderId, routeType }) .then((res: any) => { const data = res?.data ?? res?.result ?? res; const rawPoints = Array.isArray(data?.polyline) ? data.polyline : []; const points = rawPoints .map((item: any) => ({ longitude: Number(item?.longitude), latitude: Number(item?.latitude), })) .filter((item: any) => Number.isFinite(item.longitude) && Number.isFinite(item.latitude)); if (points.length > 0) { if (cachedTripStatus !== 5) { applyRoutePoints(points); const first = points[0]; setVehicleLocation({ latitude: first.latitude, longitude: first.longitude }); } else { const last = points[points.length - 1]; setVehicleLocation({ latitude: last.latitude, longitude: last.longitude }); } const cached = Taro.getStorageSync('orderRoutePoints') || {}; cached[String(routeType)] = points; cached.latest = points; Taro.setStorageSync('orderRoutePoints', cached); Taro.setStorageSync('orderRouteLatestType', routeType); } const distance = Number(data?.distance); const distanceKm = normalizeDistanceKm(distance); if (distanceKm !== null) { setRouteDistance(distanceKm); Taro.setStorageSync('orderRouteDistance', distanceKm); Taro.setStorageSync('orderRouteDistanceUnit', 'km'); } const duration = Number(data?.duration); const durationSeconds = normalizeDurationSeconds(duration); if (durationSeconds !== null) { setRouteDuration(durationSeconds); Taro.setStorageSync('orderRouteDuration', durationSeconds); Taro.setStorageSync('orderRouteDurationUnit', 's'); } }) .catch(() => { }); } if (s && s.latitude && s.longitude) { setLatitude(s.latitude); @@ -518,14 +728,24 @@ getLocationInfo(); } const storedPoints = getStoredRoutePoints(tripStatus); applyRoutePoints(storedPoints); if (cachedTripStatus === 5) { applyRoutePoints([]); } else if (cachedTripStatus !== 5) { applyRoutePoints(storedPoints); } else { applyRoutePoints([]); } const storedDistance = Number(Taro.getStorageSync('orderRouteDistance')); if (!Number.isNaN(storedDistance)) { setRouteDistance(storedDistance); const storedDistanceUnit = Taro.getStorageSync('orderRouteDistanceUnit'); const normalizedStoredDistance = storedDistanceUnit === 'km' ? storedDistance : normalizeDistanceKm(storedDistance); if (normalizedStoredDistance !== null) { setRouteDistance(normalizedStoredDistance); } const storedDuration = Number(Taro.getStorageSync('orderRouteDuration')); if (!Number.isNaN(storedDuration)) { setRouteDuration(storedDuration); const storedDurationUnit = Taro.getStorageSync('orderRouteDurationUnit'); const normalizedStoredDuration = storedDurationUnit === 's' ? storedDuration : Math.round(storedDuration * 60); if (!Number.isNaN(normalizedStoredDuration)) { setRouteDuration(normalizedStoredDuration); } const originLng = s?.longitude || longitude; const originLat = s?.latitude || latitude; @@ -537,6 +757,9 @@ updateWalkingRouteForStart(); } if (cachedOrderId) { ensureSocketListeners(); } }); useEffect(() => { @@ -552,94 +775,6 @@ }, [startSite?.longitude, startSite?.latitude, endSite?.longitude, endSite?.latitude, longitude, latitude]); // 生成标记点 useEffect(() => { const s = Taro.getStorageSync('startSite'); const pickupMarker = s?.latitude && s?.longitude ? { id: Date.now() + 1, latitude: s.latitude, longitude: s.longitude, iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/51e7cb75-72af-4994-9d6e-d999efb2fbc7.png', width: 22, height: 40, anchor: { x: 0.5, y: 1 }, zIndex: 2, name: s?.name || '上车点', customCallout: { anchorY: [2, 3].includes(tripStatus) ? 150 : 8, anchorX: 0, display: 'ALWAYS' } } : null; const dropoffMarker = endSite?.latitude && endSite?.longitude ? { id: Date.now() + 2, latitude: endSite.latitude, longitude: endSite.longitude, iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/6a56b1eb-ec37-4839-8733-262942af070d.png', width: 22, height: 40, anchor: { x: 0.5, y: 1 }, zIndex: 2, name: endSite.name || '目的地', customCallout: { anchorY: 8, anchorX: 0, display: 'ALWAYS' } } : null; const carMarker = vehicleLocation ? { id: Date.now() + 3, latitude: vehicleLocation.latitude, longitude: vehicleLocation.longitude, iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e51e92e6-700d-4e83-b3e0-14e6f18e0b9a.png', width: 40, height: 20, anchor: { x: 0.5, y: 1 }, zIndex: 3, name: '车辆位置', customCallout: { anchorY: 0, anchorX: 0, display: 'ALWAYS' } } : null; const includePickup = [1, 2, 3, 4, 5, 8, 9].includes(tripStatus); const includeDropoff = [1, 6, 7, 8, 9].includes(tripStatus); const includeCar = [4, 5, 6, 7].includes(tripStatus); const nextMarkers: any[] = []; if (includePickup && pickupMarker) nextMarkers.push(pickupMarker); if (includeCar && carMarker) nextMarkers.push(carMarker); if (includeDropoff && dropoffMarker) nextMarkers.push(dropoffMarker); setMarkers(nextMarkers); }, [tripStatus, startSite?.name, startSite?.latitude, startSite?.longitude, endSite.name, endSite?.latitude, endSite?.longitude, vehicleLocation]) // 调整视图以包含所有路线点(含步行) useEffect(() => { if (shouldShowRoute && (routePoints.length > 0 || walkingPoints.length > 0)) { const mapCtx = Taro.createMapContext('map'); const allPoints = [ ...routePoints, ...walkingPoints, ]; mapCtx.includePoints({ points: allPoints, padding: [50, 50, 500, 50] }); } }, [tripStatus, routePoints, walkingPoints]); const formatTime = (sec: number) => { const m = Math.floor(sec / 60); const s = sec % 60; return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; } const formatDateTime = (date: Date) => { const y = date.getFullYear(); const mo = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); const h = String(date.getHours()).padStart(2, '0'); const mi = String(date.getMinutes()).padStart(2, '0'); const s = String(date.getSeconds()).padStart(2, '0'); return `${y}-${mo}-${d} ${h}:${mi}:${s}`; }; // 获取用户位置信息 const getLocationInfo = () => { @@ -737,12 +872,7 @@ setShowCancelPopup(false); setTripStatus(1); setOrderId(''); Taro.removeStorageSync('orderRouteDuration'); Taro.removeStorageSync('orderRouteDistance'); Taro.removeStorageSync('orderRoutePoints'); Taro.removeStorageSync('orderRouteLatestType'); Taro.removeStorageSync('orderId'); Taro.removeStorageSync('tripStatus'); clearOrderTripStorage(); updateWalkingRouteForStart(); const s = Taro.getStorageSync('startSite'); const e = Taro.getStorageSync('endSite'); @@ -858,13 +988,8 @@ const toBack = () => { if (tripStatus == 9 || tripStatus == 1) { Taro.removeStorageSync('tripStatus'); clearOrderTripStorage(); Taro.removeStorageSync('endSite'); Taro.removeStorageSync('orderRouteDuration'); Taro.removeStorageSync('orderRouteDistance'); Taro.removeStorageSync('orderRoutePoints'); Taro.removeStorageSync('orderRouteLatestType'); Taro.removeStorageSync('orderId'); } Taro.switchTab({ url: '/pages/robotaxi/home/index' @@ -881,6 +1006,8 @@ updateWaitingRadarPosition(); } }; const durationDisplay = formatDurationDisplay(routeDuration); return ( <View className="home-container"> @@ -907,7 +1034,7 @@ <CoverView className="waiting-callout-content"> {/* <CoverView className="waiting-callout-bg"> <CoverImage className="waiting-callout-bg-img" src={waitingCalloutBgSrc} /> <CoverView className="waiting-callout-bg-text">{formatTime(elapsedSeconds)}</CoverView> <CoverView className="waiting-callout-bg-text">{formatDurationToClock(elapsedSeconds)}</CoverView> </CoverView> */} <CoverView className="waiting-callout-txt"> <CoverView className="waiting-callout-txt-content">{item.name}</CoverView> @@ -924,16 +1051,19 @@ (!['车辆位置', '已到达'].includes(item.name)) && <CoverView className="custom-callout-content"> {item.name} { tripStatus == 1 && <CoverImage className="custom-callout-icon" src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/f9e285fc-4c09-404d-90f8-4bac178565d0.png' /> } </CoverView> } { (item.name === '车辆位置' && [4, 5, 6].includes(tripStatus)) && (item.name === '车辆位置' && [4, 6].includes(tripStatus)) && <CoverView className="custom-callout-content"> <CoverView className="car-time"> <CoverView className="car-time-num">{routeDistance ?? 0}</CoverView> <CoverView>公里</CoverView> <CoverView className="car-time-num">{routeDuration ?? 0}</CoverView> <CoverView>分钟</CoverView> <CoverView className="car-time-num">{durationDisplay.value}</CoverView> <CoverView>{durationDisplay.unit}</CoverView> </CoverView> </CoverView> } @@ -1005,7 +1135,7 @@ updateStatus={() => setTripStatus(prev => prev + 1)} handleOpenEvaluatePopup={handleOpenEvaluatePopup} estimatedWaitMinutes={fareEstimate?.waitingTime} routeDurationMinutes={routeDuration} routeDurationSeconds={routeDuration} /> } </View> src/robotaxi/pages/expense-description/index.tsx
@@ -14,9 +14,11 @@ const [fareData, setFareData] = useState<FareBreakdownResponse | null>(null); const [faqData, setFaqData] = useState<FAQItem[]>([]); const [showEvaluatePopup, setShowEvaluatePopup] = useState(false); const [orderId, setOrderId] = useState(''); useEffect(() => { fetchData(router.params?.orderId); setOrderId(router.params?.orderId || ''); fetchData(router.params?.orderId || ''); fetchFAQ(); }, []); @@ -60,9 +62,7 @@ return; } const firstId = faqData[0]?.id; if (typeof firstId === 'number') { setExpandedItems([firstId]); } setExpandedItems([firstId]); }, [faqData, expandedItems.length]); const getTimeLabel = (type: number) => { @@ -84,6 +84,10 @@ return (amount || 0).toFixed(2); }; const toRulePage = () => { Taro.navigateTo({ url: '/robotaxi/pages/pricing-rules/index' }); }; if (!fareData) return null; return ( @@ -92,7 +96,7 @@ <View className='order-info'> <View className='flex-between-card'> <View className='title'>账单信息</View> <View className='look-rule' onClick={() => Taro.navigateTo({ url: '/robotaxi/pages/expense-rule/index' })}> <View className='look-rule' onClick={() => toRulePage()}> <Text className='look-rule-txt'>查看计价规则</Text> <Image className='arrow-icon' src={Subtract}></Image> </View> @@ -141,17 +145,41 @@ </View> ))} {/* 优惠券 */} {/* 远途费 */} <View className='flex-between-card'> <View className='price-label'>优惠券</View> <View className='price-value color-red'>-{formatMoney(fareData.couponMoney)}元</View> <View className='price-label'>远途费</View> <View className='price-value'>{sumMoney(fareData.longDistance)}元</View> </View> {fareData.longDistance?.map((item, index) => ( <View className='flex-between-card mb-16' key={index}> <View className='price-notice-label'>{getTimeLabel(item.type)}({item.mileage}公里)</View> <View className='price-notice-value'>{formatMoney(item.money)}元</View> </View> ))} {/* 红包 */} { fareData.redPacketMoney > 0 && <View className='flex-between-card mb-16'> <View className='price-label'>红包</View> <View className='price-value color-red'>-{formatMoney(fareData.redPacketMoney)}元</View> </View> } {/* 优惠券 */} { fareData.couponMoney > 0 && <View className='flex-between-card'> <View className='price-label'>优惠券</View> <View className='price-value color-red'>-{formatMoney(fareData.couponMoney)}元</View> </View> } </View> {/* 总价格 */} <View className='flex-card total'> <Text className='total-text'>总费用</Text> <Text className='num'>{formatMoney(fareData.payMoney)}</Text> <Text className='num'>{formatMoney(fareData.orderMoney)}</Text> <Text className='unit'>元</Text> </View> </View> src/robotaxi/pages/order/index.tsx
@@ -2,7 +2,7 @@ import { Component, useState, useEffect } from 'react'; import { View, Text, Image, Input } from '@tarojs/components'; import './index.less'; import { getOrderHistoryList, OrderHistoryGroup, OrderHistoryOrder } from '../../../robotaxi/api/order'; import { getOrderHistoryList, OrderHistoryGroup } from '../../../robotaxi/api/order'; export default function OrderPage() { const [groups, setGroups] = useState<OrderHistoryGroup[]>([]) @@ -49,8 +49,24 @@ }) } const againOrder = (item?: OrderHistoryOrder) => { Taro.navigateTo({ const againOrder = (item: any) => { Taro.setStorageSync('startSite', { id: item.startPointId || '', name: item.startPointName || '', latitude: item.startLat || 0, longitude: item.startLon || 0, electronicAreaId: item.xpElectronicFenceAreaId || '', time: item.startParkingTimeLimit || 0, }) Taro.setStorageSync('endSite', { id: item.endPointId || '', name: item.endPointName || '', latitude: item.endLat || 0, longitude: item.endLon || 0, electronicAreaId: item.xpElectronicFenceAreaId || '', time: item.endParkingTimeLimit || 0, }) Taro.switchTab({ url: '/pages/robotaxi/home/index' }) } @@ -66,7 +82,7 @@ return ( <View className='order-page'> {groups.length > 0 && groups.map((group, gi) => { return <View key={group.dateLabel || 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 className='order-time'>{`${item.time} ${item.orderTime}` || formatTime(item.time)}</View> @@ -77,7 +93,7 @@ </View> </View> })} </View> </View>) })} { !groups.length && <View className='no-data-box'> src/robotaxi/pages/pricing-rules/index.tsx
@@ -1,24 +1,24 @@ import Taro, { useRouter } from '@tarojs/taro'; import Taro from '@tarojs/taro'; import { useState, useEffect } from 'react'; import { View, Text, RichText } from '@tarojs/components'; import { getPricingRules } from '../../api/pricing'; import './index.less'; export default function PricingRulesPage() { const router = useRouter(); const { cityID } = router.params; const [pricingData, setPricingData] = useState<any>(null); const [loading, setLoading] = useState(true); const [pricingHtml, setPricingHtml] = useState<string>(''); const [pricingNodes, setPricingNodes] = useState<any[]>([]); useEffect(() => { if (cityID) { fetchPricingRules(cityID); const cachedCity = Taro.getStorageSync('selectedCity'); const cachedCityId = cachedCity?.id; if (cachedCityId) { fetchPricingRules(String(cachedCityId)); } else { setLoading(false); } }, [cityID]); }, []); const extractHtmlFromData = (data: any): string => { if (!data) return ''; src/robotaxi/pages/ride-guide/index.config.ts
New file @@ -0,0 +1,3 @@ export default { navigationBarTitleText: '乘车指南' }; src/robotaxi/pages/ride-guide/index.less
New file @@ -0,0 +1,5 @@ .ride-guide { height: 100%; width: 100%; overflow-y: auto; } src/robotaxi/pages/ride-guide/index.tsx
New file @@ -0,0 +1,22 @@ import Taro from "@tarojs/taro"; import { View, Image } from "@tarojs/components"; import { useEffect, useState } from 'react'; import './index.less' import { getRideGuideList } from '../../api/home'; export default function PassengerNotice() { const [content, setContent] = useState(''); useEffect(() => { getRideGuideList().then((res: any) => { const { url } = JSON.parse(res?.guides?.[0]?.bannerImage) setContent(url); }).catch(() => { }); }, []); return ( <View className='ride-guide'> <Image src={content} style={{ width: '100%', height: '100%' }} mode="aspectFit" /> </View> ) } src/robotaxi/pages/select-map-site/index.tsx
@@ -192,7 +192,10 @@ }; const openService = () => { setShowCustomerService(true); const phoneNumber = Taro.getStorageSync('customerServicePhone'); Taro.makePhoneCall({ phoneNumber, }) } @@ -311,14 +314,14 @@ }); }, [pageType, userLatitude, userLongitude, latitude, longitude, selectedSite?.latitude, selectedSite?.longitude]); useEffect(() => { if (!polyline.length || !polyline[0]?.points?.length) return; const mapCtx = Taro.createMapContext('map'); mapCtx.includePoints({ points: polyline[0].points, padding: [80, 80, 200, 80] }); }, [polyline]); // useEffect(() => { // if (!polyline.length || !polyline[0]?.points?.length) return; // const mapCtx = Taro.createMapContext('map'); // mapCtx.includePoints({ // points: polyline[0].points, // padding: [80, 80, 200, 80] // }); // }, [polyline]); const toCoupon = () => { Taro.navigateTo({ src/utils/Robotaxi-public-method.ts
@@ -315,3 +315,59 @@ }; } }; // 格式化秒数为时钟时间(分钟:秒) export const formatDurationToClock = (sec: number) => { const safeSeconds = Math.max(0, sec); const m = Math.floor(safeSeconds / 60); const s = safeSeconds % 60; return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; }; // 格式化日期时间 export const formatDateTime = (date: Date) => { const y = date.getFullYear(); const mo = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); const h = String(date.getHours()).padStart(2, '0'); const mi = String(date.getMinutes()).padStart(2, '0'); const s = String(date.getSeconds()).padStart(2, '0'); return `${y}-${mo}-${d} ${h}:${mi}:${s}`; }; // 获取存储的路线点 export const getStoredRoutePoints = (status?: number) => { const cached = Taro.getStorageSync('orderRoutePoints') || {}; const preferredType = typeof status === 'number' && status >= 6 ? 2 : 1; const preferred = cached[String(preferredType)]; if (Array.isArray(preferred) && preferred.length > 0) { return preferred; } const latestType = Taro.getStorageSync('orderRouteLatestType'); const latest = cached[String(latestType)]; if (Array.isArray(latest) && latest.length > 0) { return latest; } if (Array.isArray(cached.latest) && cached.latest.length > 0) { return cached.latest; } const keys = Object.keys(cached); for (const key of keys) { if (Array.isArray(cached[key]) && cached[key].length > 0) { return cached[key]; } } return []; }; // 清除订单行程存储 export const clearOrderTripStorage = () => { Taro.removeStorageSync('orderRouteDuration'); Taro.removeStorageSync('orderRouteDurationUnit'); Taro.removeStorageSync('orderRouteDistance'); Taro.removeStorageSync('orderRouteDistanceUnit'); Taro.removeStorageSync('orderRoutePoints'); Taro.removeStorageSync('orderRouteLatestType'); Taro.removeStorageSync('orderId'); Taro.removeStorageSync('tripStatus'); }; src/utils/request.ts
@@ -151,7 +151,7 @@ // 获取uid const uid = await getStorage(_UID) || '-1'; header['XP-Uid'] = uid.toString(); header['XP-Uid'] = '20260227200715' || uid.toString(); // 获取设备id header['XP-Deviceid'] = Taro.getStorageSync('wxOpenId') || 'default_device_id'; src/utils/tools.ts
@@ -543,21 +543,22 @@ // 查看本地是否已有otp export function checkOtp(autoLogin?, hideBack?) { return new Promise((resolve) => { getStorage('otp') .then(res => { resolve(res); // 无otp,发起登录请求 if (!res && autoLogin) { if (hideBack) { jumpTo({ url: '/pages/user/login?hideBack=1' }); } else { jumpTo({ url: '/pages/user/login' }); } } }) .catch(() => { resolve(''); }); resolve(true) // getStorage('otp') // .then(res => { // resolve(res); // // 无otp,发起登录请求 // if (!res && autoLogin) { // if (hideBack) { // jumpTo({ url: '/pages/user/login?hideBack=1' }); // } else { // jumpTo({ url: '/pages/user/login' }); // } // } // }) // .catch(() => { // resolve(''); // }); }); } src/utils/websocket.ts
@@ -42,7 +42,7 @@ const uid = await getStorage(_UID) || '-1'; const task = Taro.connectSocket({ header: { 'XP-Uid': uid.toString(), 'XP-Uid': '20260227200715' || uid.toString(), }, url: this.url, success: () => { }, @@ -88,7 +88,7 @@ this.reconnectAttempts = 0; this.startHeartbeat(); const uid = await getStorage(_UID) || '-1'; this.send({ type: 'auth', appUserId: uid, timestamp: Date.now() }); this.send({ type: 'auth', appUserId: '20260227200715', timestamp: Date.now() }); this.notifyListeners('open', { status: 'connected' }); }); @@ -136,7 +136,7 @@ this.reconnectAttempts = 0; this.startHeartbeat(); const uid = await getStorage(_UID) || '-1'; this.send({ type: 'auth', appUserId: '12345678', timestamp: Date.now() }); this.send({ type: 'auth', appUserId: '20260227200715', timestamp: Date.now() }); this.notifyListeners('open', { status: 'connected' }); }); @@ -339,10 +339,13 @@ // 2-12 新增 const points = this.normalizeRoutePoints(content); if (!Number.isNaN(duration)) { Taro.setStorageSync('orderRouteDuration', duration); Taro.setStorageSync('orderRouteDuration', Math.round(duration)); Taro.setStorageSync('orderRouteDurationUnit', 's'); } if (!Number.isNaN(distance)) { Taro.setStorageSync('orderRouteDistance', distance); const distanceKm = Number((distance / 1000).toFixed(1)); Taro.setStorageSync('orderRouteDistance', distanceKm); Taro.setStorageSync('orderRouteDistanceUnit', 'km'); } if (points.length > 0) { const cached = Taro.getStorageSync('orderRoutePoints') || {};