par-dongguoqing
2026-07-08 c2f9ea0cce51c55e2fb8e9d6948fb3e7c3afb275
fix:功能新分支合并
10个文件已修改
172 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/components/begin-route-view.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-polyline-render.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/pricing-rules/index.tsx 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/hooks/use-select-map-site-walking-route.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-polyline-render.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-share-trip-runtime.ts 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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;