src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -78,8 +78,8 @@ <Image onClick={onPersonalCenterEntry} className='map-entry-icon' src={personalCenter} /> <View className='split-line'></View> <Image onClick={onServiceEntry} className='map-entry-icon' src={contactService} /> <View className='split-line'></View> <Image onClick={onCouponEntry} className='map-entry-icon' src={coupon} /> {/* <View className='split-line'></View> <Image onClick={onCouponEntry} className='map-entry-icon' src={coupon} /> */} </View> </> ); src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -248,7 +248,7 @@ const renderControlIcon = (controlType: 'door' | 'trunk', defaultIconSrc: string, className: string) => { const isLoading = loadingControlType === controlType; return ( <> <View style={{ position: 'relative', display: 'flex', justifyContent: 'center', alignItems: 'center' }}> <Image className={className} style={isLoading ? { visibility: 'hidden' } : undefined} src={defaultIconSrc} /> {isLoading && ( <Image @@ -257,7 +257,7 @@ src={SWITCH_LOADING_ICON_URL} /> )} </> </View> ); }; @@ -266,11 +266,11 @@ <View className="car-card-arrive"> <View className={`car-card-arrive-item ${trunkStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={() => handleUnlock('trunk', () => setTrunkStatus(!trunkStatus))}> {renderControlIcon('trunk', trunkStatus ? TRUNK_OPEN_ICON_URL : TRUNK_CLOSE_ICON_URL, 'car-card-arrive-item-img')} <View className="car-card-arrive-item-text" style={loadingControlType === 'trunk' ? { visibility: 'hidden' } : undefined}>{trunkStatus ? '后备箱' : '后备箱'}</View> <View className="car-card-arrive-item-text">后备箱</View> </View> <View className={`car-card-arrive-item ${lockStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={() => handleUnlock('door')}> {renderControlIcon('door', lockStatus ? LOCK_OPEN_ICON_URL : LOCK_CLOSE_ICON_URL, 'car-card-arrive-item-img')} <View className="car-card-arrive-item-text" style={loadingControlType === 'door' ? { visibility: 'hidden' } : undefined}>车锁</View> <View className="car-card-arrive-item-text">车锁</View> </View> </View> ); @@ -284,7 +284,7 @@ <View className="car-card-reach-btn"> <View className={`car-card-reach-btn-item ${trunkStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={() => handleUnlock('trunk', () => setTrunkStatus(!trunkStatus))}> {renderControlIcon('trunk', trunkStatus ? TRUNK_OPEN_ICON_URL : TRUNK_CLOSE_ICON_URL, 'car-card-reach-btn-item-img')} <View className="car-card-reach-btn-item-text" style={loadingControlType === 'trunk' ? { visibility: 'hidden' } : undefined}>{trunkStatus ? '后备箱' : '后备箱'}</View> <View className="car-card-reach-btn-item-text">后备箱</View> </View> <View className="car-card-reach-btn-item" onClick={handleOpenOverRoutePopupWithDebounce}> <Image className="car-card-reach-btn-item-img" src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/over-gps-icon.png" /> src/robotaxi/pages/begin-route/hooks/use-polyline-render.ts
@@ -391,8 +391,8 @@ lines.push({ id: WALKING_POLYLINE_ID_OFFSET + allRoutePoints.length, points: walkingPoints, color: '#7B91FF', width: 8, color: '#45A5FF', width: deps.isAndroid ? 5 : 8, dottedLine: true, arrowLine: false, }); src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts
@@ -31,7 +31,7 @@ /** * 将接驾中的订单回滚为等待接单态。 */ const rollbackOrderToPublishing = (deps: WebSocketDepsRef['current']) => { const rollbackOrderToCallingCar = (deps: WebSocketDepsRef['current']) => { resetRouteRuntimeState(deps); deps.setVehicleInfo({}); deps.setFinishCountdownEnabled(false); @@ -52,9 +52,9 @@ Taro.removeStorageSync('pickupEndTime'); Taro.removeStorageSync('waitingTimerStartAt'); Taro.removeStorageSync('waitingTimerElapsed'); Taro.setStorageSync('tripStatus', TripStatus.Publishing); deps.setTripStatus(TripStatus.Publishing); deps.tripStatusRef.current = TripStatus.Publishing; Taro.setStorageSync('tripStatus', TripStatus.CallingCar); deps.setTripStatus(TripStatus.CallingCar); deps.tripStatusRef.current = TripStatus.CallingCar; }; /** @@ -141,7 +141,9 @@ return; } lastFetchedVehicleOrderIdRef.current = null; rollbackOrderToPublishing(currentDeps); rollbackOrderToCallingCar(currentDeps); currentDeps.setCancelType(6); currentDeps.setShowCancelPopup(true); } }); }; src/robotaxi/pages/pricing-rules/index.tsx
@@ -61,35 +61,74 @@ return val !== null && val !== undefined && val !== ''; } function hasConfiguredDuration(val: any) { if (!hasDisplayValue(val)) return false; const raw = String(val).trim(); if (!raw) return false; const num = Number(raw); return !(Number.isFinite(num) && num === 0); } function buildMileageFeeText(price: any, includedKm: any) { const formattedPrice = formatMoney(price); if (!formattedPrice) return ''; const suffix = hasDisplayValue(includedKm) ? `(超出${includedKm}公里后)` : ''; return `${formattedPrice}元/公里${suffix}`; } function buildDurationFeeText(price: any, includedMin: any) { const formattedPrice = formatMoney(price); if (!formattedPrice) return ''; const suffix = hasConfiguredDuration(includedMin) ? `(超出${includedMin}分钟后)` : ''; return `${formattedPrice}元/分钟${suffix}`; } function resolvePricingType(raw: any) { const num = Number(raw); return Number.isFinite(num) ? num : 0; } function buildFixedPriceDescription(includedKm: any, includedMin: any) { const hasIncludedKm = hasDisplayValue(includedKm); const hasIncludedMin = hasConfiguredDuration(includedMin); const packageParts: string[] = []; if (hasDisplayValue(includedKm)) { if (hasIncludedKm) { packageParts.push(`${includedKm}公里里程`); } if (hasDisplayValue(includedMin)) { if (hasIncludedMin) { packageParts.push(`${includedMin}分钟用车时长`); } const packageText = packageParts.length > 0 ? `套餐内含${packageParts.join('、')}` : '套餐内含基础用车额度'; return `本车型为一口价计费,${packageText};正常行程不改目的地/路线按预估价结算;里程或用车时长任一超出套餐额度、或中途改目的地/大幅变更路线,超出部分单独叠加计费。`; const overageText = hasIncludedKm && hasIncludedMin ? '里程或用车时长任一超出套餐额度、或中途改目的地/大幅变更路线,超出部分单独叠加计费。' : hasIncludedKm ? '超出套餐内里程额度、或中途改目的地/大幅变更路线,超出部分单独叠加计费。' : hasIncludedMin ? '超出套餐内用车时长额度、或中途改目的地/大幅变更路线,超出部分单独叠加计费。' : '超出套餐额度、或中途改目的地/大幅变更路线,超出部分单独叠加计费。'; return `本车型为一口价计费,${packageText};正常行程不改目的地/路线按预估价结算;${overageText}`; } function buildStartDescription(includedKm: any, includedMin: any) { const hasIncludedKm = hasDisplayValue(includedKm); const hasIncludedMin = hasConfiguredDuration(includedMin); const parts: string[] = []; if (hasDisplayValue(includedKm)) { if (hasIncludedKm) { parts.push(`包含里程${includedKm}公里`); } if (hasDisplayValue(includedMin)) { if (hasIncludedMin) { parts.push(`包含时长${includedMin}分钟`); } if (parts.length === 0) { return '起步价内包含一定的基础里程或时长,超出后将在起步价基础上累加计费。'; } return `${parts.join(',')},包含里程或包含时长(低速时长)任何一项超出时,将在起步价基础上累加计费。`; if (hasIncludedKm && hasIncludedMin) { return `${parts.join(',')},包含里程或包含时长(低速时长)任何一项超出时,将在起步价基础上累加计费。`; } if (hasIncludedKm) { return `${parts.join(',')},超出包含里程后,将在起步价基础上累加计费。`; } return `${parts.join(',')},超出包含时长(低速时长)后,将在起步价基础上累加计费。`; } function PricingRulesPage() { @@ -141,7 +180,7 @@ const baseIncludedMinRaw = isFixedPriceMode ? fixedIncludedMinRaw : (startingFareList?.[0]?.num5 ?? basicPricing?.num3); const startTitleMeta = [hasDisplayValue(baseIncludedKmRaw) ? `${baseIncludedKmRaw}公里` : '', hasDisplayValue(baseIncludedMinRaw) ? `${baseIncludedMinRaw}分钟` : ''] const startTitleMeta = [hasDisplayValue(baseIncludedKmRaw) ? `${baseIncludedKmRaw}公里` : '', hasConfiguredDuration(baseIncludedMinRaw) ? `${baseIncludedMinRaw}分钟` : ''] .filter(Boolean) .join(','); @@ -159,7 +198,7 @@ ? [ { label: '一口价', value: fixedPackagePrice ? `${fixedPackagePrice}元` : '' }, { label: '包含里程', value: hasDisplayValue(fixedIncludedKmRaw) ? `${fixedIncludedKmRaw}公里` : '' }, { label: '包含时长', value: hasDisplayValue(fixedIncludedMinRaw) ? `${fixedIncludedMinRaw}分钟` : '' } { label: '包含时长', value: hasConfiguredDuration(fixedIncludedMinRaw) ? `${fixedIncludedMinRaw}分钟` : '' } ].filter(item => item.value) : []; @@ -214,17 +253,11 @@ : (startingFareRows.length > 0 || Boolean(holidayStartFarePrice)); const baseIncludedKm = hasDisplayValue(baseIncludedKmRaw) ? String(baseIncludedKmRaw) : '-'; const baseIncludedMin = hasDisplayValue(baseIncludedMinRaw) ? String(baseIncludedMinRaw) : '-'; const holidayMileageFeePrice = formatMoney(basicPricing?.num30); const holidayMileageFeeText = holidayMileageFeePrice ? `${holidayMileageFeePrice}元/公里(超出${baseIncludedKm}公里后)` : ''; const holidayMileageFeeText = buildMileageFeeText(holidayMileageFeePrice, baseIncludedKmRaw); const holidayDurationFeePrice = formatMoney(basicPricing?.num31); const holidayDurationFeeText = holidayDurationFeePrice ? `${holidayDurationFeePrice}元/分钟(超出${baseIncludedMin}分钟后)` : ''; const holidayDurationFeeText = buildDurationFeeText(basicPricing?.num31, baseIncludedMinRaw); const hasAnyDurationFee = shouldRenderDynamicSections && @@ -241,12 +274,12 @@ const name = index === 0 ? '普通时段' : `普通时段${index + 1}`; const range = item?.num1 && item?.num2 ? `${padTime(item.num1)}-${padTime(item.num2)}` : ''; const includedKm = item?.num4 ?? baseIncludedKm; const price = formatMoney(item?.num6); if (!price) return null; const fee = buildMileageFeeText(item?.num6, includedKm); if (!fee) return null; return { name, time: range, fee: `${price}元/公里(超出${includedKm}公里后)` fee }; }) .filter(Boolean) @@ -256,7 +289,7 @@ { name: '夜间时段', time: nightRange, fee: `${formatMoney(basicPricing.num16)}元/公里(超出${baseIncludedKm}公里后)` fee: buildMileageFeeText(basicPricing.num16, baseIncludedKmRaw) } ] : []), @@ -265,7 +298,7 @@ { name: '早高峰时段', time: peakRange1, fee: `${formatMoney(basicPricing.num24)}元/公里(超出${baseIncludedKm}公里后)` fee: buildMileageFeeText(basicPricing.num24, baseIncludedKmRaw) } ] : []), @@ -274,7 +307,7 @@ { name: '晚高峰时段', time: peakRange2, fee: `${formatMoney(basicPricing.num24)}元/公里(超出${baseIncludedKm}公里后)` fee: buildMileageFeeText(basicPricing.num24, baseIncludedKmRaw) } ] : []), @@ -287,13 +320,13 @@ if (!hasAnyDurationFee) return null; const name = index === 0 ? '普通时段' : `普通时段${index + 1}`; const range = item?.num1 && item?.num2 ? `${padTime(item.num1)}-${padTime(item.num2)}` : ''; const includedMin = item?.num5 ?? baseIncludedMin; const price = hasDisplayValue(item?.num7) ? formatMoney(item?.num7) : '0.00'; if (!price) return null; const includedMin = hasConfiguredDuration(item?.num5) ? item?.num5 : baseIncludedMinRaw; const fee = buildDurationFeeText(item?.num7, includedMin); if (!fee) return null; return { name, time: range, fee: `${price}元/分钟(超出${includedMin}分钟后)` fee }; }) .filter(Boolean) @@ -303,7 +336,7 @@ { name: '夜间时段', time: nightRange, fee: `${formatMoney(basicPricing.num17)}元/分钟(超出${baseIncludedMin}分钟后)` fee: buildDurationFeeText(basicPricing.num17, baseIncludedMinRaw) } ] : []), @@ -312,7 +345,7 @@ { name: '早高峰时段', time: peakRange1, fee: `${formatMoney(basicPricing.num25)}元/分钟(超出${baseIncludedMin}分钟后)` fee: buildDurationFeeText(basicPricing.num25, baseIncludedMinRaw) } ] : []), @@ -321,7 +354,7 @@ { name: '晚高峰时段', time: peakRange2, fee: `${formatMoney(basicPricing.num25)}元/分钟(超出${baseIncludedMin}分钟后)` fee: buildDurationFeeText(basicPricing.num25, baseIncludedMinRaw) } ] : []), src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx
@@ -154,7 +154,7 @@ </View> )} {(deps.nearbySites.length > 0 && deps.pageType === 'end') && ( <View className="limit-tip">点击紫色原点或拖动大头针,可修改{deps.pageType === 'start' ? '上车点' : '目的地'}</View> <View className="limit-tip">点击紫色圆点或拖动大头针,可修改{deps.pageType === 'start' ? '上车点' : '目的地'}</View> )} </View> {shouldShowStationSelection && ( src/robotaxi/pages/select-map-site/hooks/use-select-map-site-walking-route.ts
@@ -52,7 +52,7 @@ deps.setPolyline([{ points: displayPoints, color: '#6D7FD9', width: 6, width: deps.isAndroid ? 4 : 6, dottedLine: true, arrowLine: false, }]); src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
@@ -278,7 +278,9 @@ buildCarMarkerStructureSignature(prevCar) !== buildCarMarkerStructureSignature(nextCar); const latestVehicleLatitude = Number(deps.vehicleLocation?.latitude); const latestVehicleLongitude = Number(deps.vehicleLocation?.longitude); if (!shouldRefreshCarMarkerStructure && !shouldClearCarRotate) { // 车辆在签名中以 'translate' 占位,签名差异只可能来自非车辆 marker(如终点改变)。 // 因此仅当非车辆 marker 结构未变、车辆结构未变且无需清理朝向时才保留旧 marker,否则必须应用最新 marker。 if (!shouldRefreshCarMarkerStructure && !shouldClearCarRotate && prevMarkersSignature === nextMarkersSignature) { return prevMarkers; } mergedMarkers[nextCarIndex] = { src/robotaxi/pages/share-trip/hooks/use-polyline-render.ts
@@ -281,8 +281,8 @@ lines.push({ id: WALKING_POLYLINE_ID_OFFSET, points: walkingPoints, color: '#7B91FF', width: 6, color: '#45A5FF', width: deps.isAndroid ? 4 : 6, dottedLine: true, arrowLine: false }); src/robotaxi/pages/share-trip/hooks/use-share-trip-runtime.ts
@@ -57,6 +57,7 @@ const startSiteRef = useRef<any>(deps.startSite); // 上车点引用 const endSiteRef = useRef<any>(deps.endSite); // 下车点引用 const infoStatusRef = useRef<TripStatus | null>(null); // 最新详情状态引用 const lastRouteIdRef = useRef<string>(''); // 最近一次路线 ID,辅助识别修改目的地后的新路线 const errorToastShownRef = useRef(false); // 错误提示节流标记 const applySmoothRouteSnapshotRef = useRef(deps.applySmoothRouteSnapshot); // 平滑快照消费函数引用 @@ -480,6 +481,7 @@ const applyShareInfo = (info: any) => { const mappedStatus = resolveShareTripStatus(info?.status); const nextStartSite = buildSite(info, 'start'); const nextEndSite = buildSite(info, 'end'); const etaMinutes = Math.max( @@ -572,12 +574,26 @@ const routeStatus = resolveRouteOrderStatus(routeResponse); const routeEndPoint = extractRouteEndPoint(routeResponse); const currentEndSitePoint = normalizePoint(endSiteRef.current); // 行程中修改目的地时,订单状态通常不变,因此需要额外通过路线终点变化补拉详情。 const hasRouteDestinationChanged = ( !!routeEndPoint && !!currentEndSitePoint && !isSameCoordinate(routeEndPoint, currentEndSitePoint) const routeKey = String( getFieldValue(routeResponse?.content?.route, ['routeID', 'routeId']) ?? getFieldValue(routeResponse?.content, ['routeID', 'routeId']) ?? getFieldValue(routeResponse?.data, ['routeID', 'routeId']) ?? getFieldValue(routeResponse, ['routeID', 'routeId']) ?? '' ); const hasRouteIdChanged = ( !!routeKey && !!lastRouteIdRef?.current && routeKey !== lastRouteIdRef.current ); // 行程中修改目的地时,订单状态通常不变,因此需要额外通过路线终点变化补拉详情。 // 只要最新路线终点和当前展示的终点 (endSite) 不一致,或者 routeID 变了,就应该持续去拉详情 const hasRouteDestinationChanged = ( (!!routeEndPoint && !!currentEndSitePoint && !isSameCoordinate(routeEndPoint, currentEndSitePoint)) || hasRouteIdChanged ); const shouldFetchInfo = ( (routeStatus !== null && routeStatus !== infoStatusRef.current) || hasRouteDestinationChanged @@ -609,12 +625,15 @@ if (mappedStatus === TripStatus.CancelOnlyPickup) { applyCanceledTripState(nextStartSite, nextEndSite); lastRouteIdRef.current = ''; stopPolling(); errorToastShownRef.current = false; return false; } syncShareRoute(routeResponse, mappedStatus, nextStartSite, nextEndSite); if (routeKey) lastRouteIdRef.current = routeKey; if (mappedStatus >= TripStatus.ArrivedDestination) { stopPolling(); errorToastShownRef.current = false; @@ -641,6 +660,16 @@ ); if (cancelled) return; syncShareRoute(routeResponse, infoState.mappedStatus, infoState.nextStartSite, infoState.nextEndSite); const routeKey = String( getFieldValue(routeResponse?.content?.route, ['routeID', 'routeId']) ?? getFieldValue(routeResponse?.content, ['routeID', 'routeId']) ?? getFieldValue(routeResponse?.data, ['routeID', 'routeId']) ?? getFieldValue(routeResponse, ['routeID', 'routeId']) ?? '' ); if (routeKey) lastRouteIdRef.current = routeKey; stopPolling(); errorToastShownRef.current = false; return;