From f525b6c3cc105bd1a997d893937689bebcc9570d Mon Sep 17 00:00:00 2001
From: 卢春龙 <lucl@xiaopeng.com>
Date: 星期一, 17 八月 2026 09:33:38 +0800
Subject: [PATCH] Merge branch 'feature_2.6.0' into 'develop'

---
 src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts                                   |   25 
 src/robotaxi/pages/begin-route/hooks/use-order-payment.ts                                      |   18 
 src/robotaxi/pages/select-map-site/hooks/use-select-map-site.ts                                |   19 
 src/robotaxi/pages/begin-route/hooks/use-route-manager.ts                                      |    1 
 src/robotaxi/pages/coupon/index.tsx                                                            |    8 
 src/utils/preload-assets.ts                                                                    |   54 
 src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx           |   43 
 src/robotaxi/api/feedback.ts                                                                   |   14 
 src/robotaxi/components/demo/index.tsx                                                         |    5 
 src/robotaxi/constants/preload-assets.ts                                                       |   46 
 src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts |   12 
 src/pages/robotaxi/home/hooks/use-region-change.ts                                             |   52 
 src/robotaxi/pages/begin-route/hooks/use-defer-until-dispatch-settled.ts                       |   51 
 src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts  |    4 
 src/robotaxi/constants/trip-status.ts                                                          |   20 
 src/robotaxi/pages/begin-route/components/calling-car-modules/fragrance-control/index.tsx      |   10 
 src/robotaxi/pages/begin-route/hooks/use-popup-manager.ts                                      |    3 
 src/robotaxi/pages/begin-route/hooks/use-transition-asset-preload.ts                           |   54 
 src/robotaxi/pages/begin-route/components/begin-route-view.tsx                                 |  175 +
 src/robotaxi/pages/begin-route/components/calling-car-modules/seat-hv-control/index.tsx        |   19 
 src/robotaxi/pages/begin-route/hooks/camera/policies/drive.policy.ts                           |   77 
 src/robotaxi/components/fireworks/index.less                                                   |    9 
 src/robotaxi/pages/begin-route/components/debug-transition-panel.tsx                           |  173 +
 src/robotaxi/pages/begin-route/components/debug-order-simulator-panel.tsx                      |   10 
 src/robotaxi/pages/begin-route/components/modify-destination-review.tsx                        |   30 
 src/robotaxi/pages/payment-setting-detail/index.tsx                                            |    4 
 src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx                         |   10 
 src/pages/robotaxi/home/components/privacy-popup/index.tsx                                     |    7 
 src/robotaxi/pages/begin-route/components/css-folder/fragrance-control.less                    |    4 
 src/robotaxi/pages/begin-route/hooks/camera/collect-viewport-points.ts                         |   73 
 src/robotaxi/pages/begin-route/components/dispatch-transition-layer.tsx                        |  148 +
 src/robotaxi/pages/begin-route/components/css-folder/release-be-being.less                     |    2 
 src/robotaxi/pages/begin-route/components/css-folder/route-selection-panel.less                |   25 
 src/robotaxi/pages/begin-route/components/route-preference-popup/index.less                    |    5 
 src/robotaxi/utils/platform-env.ts                                                             |   67 
 src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts                                       | 1322 ---------
 src/robotaxi/style/ui-variables.less                                                           |    1 
 src/robotaxi/pages/begin-route/components/cancel-popup.tsx                                     |   76 
 src/robotaxi/pages/begin-route/hooks/camera/policies/calling.policy.ts                         |   32 
 src/robotaxi/pages/begin-route/hooks/use-route-interaction.ts                                  |   54 
 src/robotaxi/pages/begin-route/components/css-folder/release-route.less                        |   26 
 src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts                                       |   19 
 src/robotaxi/pages/begin-route/hooks/camera/map-perf-log.ts                                    |   12 
 src/robotaxi/pages/begin-route/hooks/camera/policies/publish-route.policy.ts                   |   54 
 src/robotaxi/api/order.ts                                                                      |    1 
 src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx          |   25 
 src/robotaxi/pages/begin-route/components/evaluate-popup.tsx                                   |   77 
 src/robotaxi/pages/begin-route/hooks/camera/camera-policy.types.ts                             |   61 
 src/robotaxi/pages/begin-route/hooks/camera/policies/order-detail.policy.ts                    |   35 
 src/robotaxi/components/site-route-card/index.less                                             |   86 
 src/robotaxi/pages/begin-route/hooks/camera/map-camera.ts                                      |  455 +++
 src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx       |    9 
 src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts                                   |   13 
 src/pages/robotaxi/home/hooks/use-navigation.ts                                                |   19 
 src/robotaxi/pages/share-trip/hooks/use-map-markers.ts                                         |    2 
 src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx                                    |   96 
 src/robotaxi/components/fireworks/index.tsx                                                    |  436 +++
 src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx           |   24 
 src/pages/robotaxi/home/index.tsx                                                              |    3 
 src/robotaxi/pages/begin-route/hooks/use-route-request.ts                                      |    5 
 src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx              |    8 
 src/robotaxi/constants/assets.ts                                                               |   62 
 src/pages/robotaxi/home/constants.ts                                                           |    4 
 src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.tsx              |   18 
 src/robotaxi/pages/begin-route/components/css-folder/temperature-control.less                  |    3 
 src/pages/robotaxi/home/hooks/use-quick-capsule.ts                                             |    9 
 src/robotaxi/pages/begin-route/components/calling-car-modules/music-control/index.tsx          |   19 
 src/robotaxi/components/fireworks/error-boundary.tsx                                           |   35 
 src/robotaxi/components/site-route-card/index.tsx                                              |   71 
 src/robotaxi/pages/begin-route/hooks/use-dispatch-transition.ts                                |  170 +
 src/robotaxi/pages/begin-route/hooks/use-credit-pay-return.ts                                  |   10 
 src/robotaxi/pages/begin-route/components/trip-finished-fireworks/index.tsx                    |   60 
 src/robotaxi/pages/feedback/index.tsx                                                          |    8 
 src/robotaxi/pages/expense-description/index.less                                              |   68 
 src/robotaxi/pages/begin-route/components/css-folder/evaluate-popup.less                       |   10 
 src/robotaxi/pages/begin-route/components/css-folder/seat-hv-control.less                      |   23 
 src/pages/robotaxi/home/hooks/use-origin-action.ts                                             |    3 
 src/robotaxi/pages/begin-route/components/css-folder/modify-destination-review.less            |   66 
 src/robotaxi/utils/video-preload.ts                                                            |   69 
 src/robotaxi/pages/begin-route/hooks/camera/use-map-camera-controller.ts                       |  258 ++
 src/robotaxi/pages/begin-route/components/calling-car.tsx                                      |  202 +
 src/pages/robotaxi/home/hooks/use-site-state.ts                                                |    7 
 src/pages/robotaxi/home/components/map-pointer/index.tsx                                       |    5 
 src/pages/robotaxi/home/index.less                                                             |   80 
 src/robotaxi/pages/begin-route/hooks/use-modify-destination-flow.ts                            |    8 
 src/robotaxi/pages/begin-route/hooks/camera/policies/pickup.policy.ts                          |   78 
 src/robotaxi/pages/begin-route/hooks/camera/policies/index.ts                                  |   23 
 src/robotaxi/pages/begin-route/hooks/use-order-status.ts                                       |    3 
 .gitignore                                                                                     |    2 
 src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts                               |   12 
 src/robotaxi/pages/begin-route/index.less                                                      |   13 
 src/robotaxi/pages/share-trip/components/share-trip-view.tsx                                   |   45 
 src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.less             |    2 
 src/robotaxi/constants/music-control-feature.ts                                                |   13 
 src/robotaxi/pages/feedback/hooks/use-media-upload.ts                                          |    7 
 src/robotaxi/pages/begin-route/hooks/use-order-submit.ts                                       |   28 
 src/robotaxi/pages/begin-route/components/begin-route-view-context.tsx                         |   18 
 src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx            |   40 
 src/robotaxi/pages/begin-route/components/css-folder/calling-car.less                          |  304 +
 src/robotaxi/pages/begin-route/hooks/use-order-payment-result-query.ts                         |    7 
 src/robotaxi/pages/personal-center/index.tsx                                                   |    2 
 src/robotaxi/pages/share-trip/index.less                                                       |   77 
 src/robotaxi/api/begin-route.ts                                                                |   15 
 src/pages/robotaxi/home/hooks/use-marketing.ts                                                 |    4 
 src/robotaxi/pages/share-trip/hooks/use-view-delegates.tsx                                     |   10 
 src/robotaxi/pages/expense-description/index.tsx                                               |   21 
 src/robotaxi/pages/begin-route/components/css-folder/cancel-popup.less                         |   52 
 src/robotaxi/pages/select-map-site/index.less                                                  |   70 
 src/robotaxi/pages/begin-route/index.tsx                                                       |  434 +-
 src/robotaxi/pages/begin-route/hooks/use-order-cancel.ts                                       |   67 
 /dev/null                                                                                      |  217 -
 src/robotaxi/pages/order/detail/index.tsx                                                      |   12 
 src/robotaxi/pages/begin-route/hooks/use-page-restore.ts                                       |   40 
 src/robotaxi/utils/user-location.ts                                                            |   79 
 src/robotaxi/pages/begin-route/components/css-folder/service-route-change.less                 |   94 
 src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts  |    8 
 src/robotaxi/components/fireworks/礼花效果.md                                                      |  139 +
 src/robotaxi/pages/begin-route/hooks/use-map-markers.ts                                        |   11 
 src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts                                  |    7 
 119 files changed, 4,762 insertions(+), 2,688 deletions(-)

diff --git a/.gitignore b/.gitignore
index 456b0cb..7937225 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,6 @@
 .history
 key/
 .swc/
+.tmp_frames/
+.qoder/
 # project.private.config.json
diff --git a/src/pages/robotaxi/home/components/map-pointer/index.tsx b/src/pages/robotaxi/home/components/map-pointer/index.tsx
index 8985d75..0b658d2 100644
--- a/src/pages/robotaxi/home/components/map-pointer/index.tsx
+++ b/src/pages/robotaxi/home/components/map-pointer/index.tsx
@@ -37,8 +37,9 @@
 
   return (
     <View className="pointer-box">
+      {/* 雷达只在真正命中吸附、指针跳动到具体站点时才展示(由 bounce 驱动),不随真实请求态 nearbyLoading 提前出现 */}
       {
-        props.nearbyLoading && props.isAndroid && (
+        props.bounce && props.isAndroid && (
           <Image className="waiting-radar-img" src={props.waitingRadarSrc} webp />
         )
       }
@@ -64,7 +65,7 @@
         )
       }
       {
-        props.nearbyLoading && !props.isAndroid && (
+        props.bounce && !props.isAndroid && (
           <Image className="waiting-radar" src={props.waitingRadarSrc} />
         )
       }
diff --git a/src/pages/robotaxi/home/components/privacy-popup/index.tsx b/src/pages/robotaxi/home/components/privacy-popup/index.tsx
index a093735..0f4b792 100644
--- a/src/pages/robotaxi/home/components/privacy-popup/index.tsx
+++ b/src/pages/robotaxi/home/components/privacy-popup/index.tsx
@@ -4,6 +4,7 @@
 import './index.less';
 import { privicyShowManager } from './utils'
 import { submitAgreement } from '../../../../../robotaxi/api/home'
+import { showToast } from '../../../../../robotaxi/components/toast'
 import { getNormalizedLoginAgreementList, type LoginAgreementItem } from '../../../../../robotaxi/utils/login-agreement'
 import { reportEvent } from '../../../../../utils/Robotaxi-public-method'
 import { PROTOCOL_CLICK_EVENT_ID } from '../../../../../robotaxi/constants/report-events'
@@ -32,7 +33,7 @@
       }, {});
       setIsAgree(initState);
     }).catch(() => {
-      Taro.showToast({ title: '协议获取失败,请重试', icon: 'none' });
+      showToast({ title: '协议获取失败,请重试', icon: 'none' });
     });
   };
 
@@ -97,7 +98,7 @@
 
   const handleAgree = () => {
     if (!isAllAgree) {
-      Taro.showToast({ title: '请先同意全部协议', icon: 'none' });
+      showToast({ title: '请先同意全部协议', icon: 'none' });
       return
     };
     const selectedType = Object.keys(isAgree)
@@ -113,7 +114,7 @@
       privicyShowManager.setHasShow(true)
       onAgree?.();
     }).catch(() => {
-      Taro.showToast({ title: '提交失败,请重试', icon: 'none' });
+      showToast({ title: '提交失败,请重试', icon: 'none' });
     });
   };
 
diff --git a/src/pages/robotaxi/home/constants.ts b/src/pages/robotaxi/home/constants.ts
index 530c279..0f7e474 100644
--- a/src/pages/robotaxi/home/constants.ts
+++ b/src/pages/robotaxi/home/constants.ts
@@ -33,8 +33,8 @@
 /** 首页订单状态文案映射。 */
 export const HOME_ORDER_STATUS: Partial<Record<TripStatus, string>> = {
   [TripStatus.VehicleApproaching]: '车辆正在赶来',
-  [TripStatus.VehicleArrivedPickup]: '车辆已到达上车点,请尽快前往',
+  [TripStatus.VehicleArrivedPickup]: '车辆已到达上车点',
   [TripStatus.InService]: '正在前往目的地',
-  [TripStatus.ArrivedDestination]: '车辆已到达目的地,请尽快结束行程',
+  [TripStatus.ArrivedDestination]: '车辆已到达目的地',
   [TripStatus.TripFinished]: '行程已结束,请尽快支付'
 };
diff --git a/src/pages/robotaxi/home/hooks/use-marketing.ts b/src/pages/robotaxi/home/hooks/use-marketing.ts
index c5d4859..83eef91 100644
--- a/src/pages/robotaxi/home/hooks/use-marketing.ts
+++ b/src/pages/robotaxi/home/hooks/use-marketing.ts
@@ -1,5 +1,5 @@
-import Taro from '@tarojs/taro';
 import { useState } from 'react';
+import { showToast } from '../../../../robotaxi/components/toast';
 import { FLOAT_ACTIVITY_DEFAULT_IMAGE } from '../constants';
 
 type MarketingData = Record<string, any>;
@@ -163,7 +163,7 @@
    */
   const handleFloatActivityClick = () => {
     if (!floatActivityUrl) {
-      Taro.showToast({ title: '活动页面建设中', icon: 'none' });
+      showToast({ title: '活动页面建设中', icon: 'none' });
       return;
     }
     deps.toH5(floatActivityUrl, true);
diff --git a/src/pages/robotaxi/home/hooks/use-navigation.ts b/src/pages/robotaxi/home/hooks/use-navigation.ts
index 8248310..16913e4 100644
--- a/src/pages/robotaxi/home/hooks/use-navigation.ts
+++ b/src/pages/robotaxi/home/hooks/use-navigation.ts
@@ -24,6 +24,7 @@
   writeConfirmRideSiteSourceType
 } from '../../../../robotaxi/constants/confirm-ride-report';
 import { resolveHomeStartSiteDisplaySite } from '../../../../robotaxi/hooks/use-home-start-site-display';
+import { showToast } from '../../../../robotaxi/components/toast';
 
 type Deps = {
   /** 是否未登录 */
@@ -124,12 +125,12 @@
     try {
       const res = await Taro.getNetworkType();
       if (res?.networkType === 'none') {
-        Taro.showToast({ title: '网络未连接', icon: 'none' });
+        showToast({ title: '网络未连接', icon: 'none' });
         return false;
       }
       return true;
     } catch (e) {
-      Taro.showToast({ title: '网络未连接', icon: 'none' });
+      showToast({ title: '网络未连接', icon: 'none' });
       return false;
     }
   };
@@ -176,11 +177,11 @@
     const ok = await ensureNetwork();
     if (!ok) return;
     if (deps.isLocationAuthDenied) {
-      Taro.showToast({ title: '请先开启定位权限', icon: 'none' });
+      showToast({ title: '请先开启定位权限', icon: 'none' });
       return;
     }
     if (!deps.electronicAreaId) {
-      Taro.showToast({ title: '请先移动到运营区内', icon: 'none' });
+      showToast({ title: '请先移动到运营区内', icon: 'none' });
       return;
     }
     if (isToMapStart) {
@@ -197,7 +198,7 @@
       const preferredStartSite = resolveHomeStartSiteDisplaySite();
       const effectiveStartSite = preferredStartSite || deps.currentSite;
       if (!effectiveStartSite || !effectiveStartSite.id) {
-        Taro.showToast({ title: '请先选择起点站点', icon: 'none' });
+        showToast({ title: '请先选择起点站点', icon: 'none' });
         return;
       }
       Taro.setStorageSync('startSite', effectiveStartSite);
@@ -223,7 +224,7 @@
     const startLongitude = hasValidSiteCoord ? siteLongitude : (hasValidMapCoord ? mapLongitude : null);
     const startLatitude = hasValidSiteCoord ? siteLatitude : (hasValidMapCoord ? mapLatitude : null);
     if (startLongitude === null || startLatitude === null) {
-      Taro.showToast({ title: '请先移动到运营区内', icon: 'none' });
+      showToast({ title: '请先移动到运营区内', icon: 'none' });
       return;
     }
     Taro.navigateTo({
@@ -321,7 +322,7 @@
     if (!ok) return;
     if (deps.isLocationAuthDenied) return;
     if (!deps.selectedCity?.code) {
-      Taro.showToast({ title: '此城市暂未开通运营区域', icon: 'none' });
+      showToast({ title: '此城市暂未开通运营区域', icon: 'none' });
       return;
     }
     let nextPolygons = deps.polygons;
@@ -341,7 +342,7 @@
       deps.setPolygons([]);
     }
     if (!nextPolygons || nextPolygons.length === 0) {
-      Taro.showToast({ title: '此城市暂未开通运营区域', icon: 'none' });
+      showToast({ title: '此城市暂未开通运营区域', icon: 'none' });
       return;
     }
     const points = extractPointsFromPolygons(nextPolygons);
@@ -358,7 +359,7 @@
         });
       }, 600);
     } else {
-      Taro.showToast({ title: '此城市暂未开通运营区域', icon: 'none' });
+      showToast({ title: '此城市暂未开通运营区域', icon: 'none' });
     }
   };
 
diff --git a/src/pages/robotaxi/home/hooks/use-origin-action.ts b/src/pages/robotaxi/home/hooks/use-origin-action.ts
index b80efb2..51b0c0b 100644
--- a/src/pages/robotaxi/home/hooks/use-origin-action.ts
+++ b/src/pages/robotaxi/home/hooks/use-origin-action.ts
@@ -1,4 +1,5 @@
 import Taro from '@tarojs/taro';
+import { showToast } from '../../../../robotaxi/components/toast';
 
 type Deps = {
   /** 是否拒绝定位 */
@@ -37,7 +38,7 @@
   const toOrigin = async () => {
     const systemSetting = await Promise.resolve((Taro as any).getSystemSetting?.());
     if (systemSetting?.locationEnabled === false) {
-      Taro.showToast({ title: '需开启设备定位服务开关', icon: 'none' });
+      showToast({ title: '需开启设备定位服务开关', icon: 'none' });
       return;
     }
     if (deps.isLocationAuthDenied) return;
diff --git a/src/pages/robotaxi/home/hooks/use-quick-capsule.ts b/src/pages/robotaxi/home/hooks/use-quick-capsule.ts
index bdffa11..2340f13 100644
--- a/src/pages/robotaxi/home/hooks/use-quick-capsule.ts
+++ b/src/pages/robotaxi/home/hooks/use-quick-capsule.ts
@@ -14,6 +14,7 @@
   OrderDistanceConfig
 } from '../../../../robotaxi/utils/order-distance';
 import { hasActiveTripOrderInStorage } from '../../../../robotaxi/utils/order';
+import { showToast } from '../../../../robotaxi/components/toast';
 
 type CapsuleSite = {
   id: string | number;
@@ -142,16 +143,16 @@
    */
   const handleQuickCapsuleClick = async () => {
     if (hasActiveTripOrderInStorage()) {
-      Taro.showToast({ title: '您已有进行中的订单', icon: 'none' });
+      showToast({ title: '您已有进行中的订单', icon: 'none' });
       return;
     }
     const targetStartSite = resolveValidStartSite();
     if (!targetStartSite) {
-      Taro.showToast({ title: '请先选择起点站点', icon: 'none' });
+      showToast({ title: '请先选择起点站点', icon: 'none' });
       return;
     }
     if (!capsuleSite) {
-      Taro.showToast({ title: '暂无快捷目的地', icon: 'none' });
+      showToast({ title: '暂无快捷目的地', icon: 'none' });
       return;
     }
     const distanceConfig = await ensureDistanceConfigReady();
@@ -160,7 +161,7 @@
       endSite: capsuleSite,
       minDistance: distanceConfig?.minDistance
     })) {
-      Taro.showToast({ title: ORDER_DISTANCE_MIN_DISTANCE_TOAST, icon: 'none' });
+      showToast({ title: ORDER_DISTANCE_MIN_DISTANCE_TOAST, icon: 'none' });
       return;
     }
     Taro.setStorageSync('endSite', capsuleSite);
diff --git a/src/pages/robotaxi/home/hooks/use-region-change.ts b/src/pages/robotaxi/home/hooks/use-region-change.ts
index 4948306..6ca7136 100644
--- a/src/pages/robotaxi/home/hooks/use-region-change.ts
+++ b/src/pages/robotaxi/home/hooks/use-region-change.ts
@@ -5,6 +5,9 @@
   writeHomeStartSiteDisplayState
 } from '../../../../robotaxi/hooks/use-home-start-site-display';
 
+/** 指针跳动到附近站点的动效总时长:指针跳动(600ms)+ 雷达扫描确认(320ms),需与 index.less 中对应动画时长保持一致。 */
+const POINTER_BOUNCE_SEQUENCE_DURATION_MS = 920;
+
 type Deps = {
   mapState: any;
   siteState: any;
@@ -56,7 +59,6 @@
    */
   const queryPointerByCenter = async (lat: number, lng: number, options?: { allowAdsorb?: boolean }) => {
     const allowAdsorb = options?.allowAdsorb !== false;
-    siteState.setBounce(true);
     siteState.setShowToolTip(false);
     try {
       const { isInside, newMarkers, sites } = await mapState.updateMapData(lat, lng, undefined, { commitMarkers: false });
@@ -68,38 +70,43 @@
         if (allowAdsorb) {
           siteState.setShowToolTip(true); // 运营区外恢复气泡提示
         }
-        siteState.setBounce(false);
         return;
       }
       const nearestCandidates = sites.length > 0 ? sites : newMarkers;
       const { adsorbed, marker } = allowAdsorb
-        ? siteState.tryAdsorb(lat, lng, nearestCandidates)
+        ? siteState.tryAdsorb(lat, lng, nearestCandidates, { deferShowToolTip: true })
         : { adsorbed: false, marker: null }; // 仅浏览运营区域时不吸附最近点位
       commitQueriedMarkers(newMarkers, true, adsorbed);
       const finalLat = adsorbed && marker ? marker.latitude : lat; // 最终纬度
       const finalLng = adsorbed && marker ? marker.longitude : lng; // 最终经度
+      if (allowAdsorb && adsorbed) {
+        // 地图中心点(硬切换)与指针跳动动画在同一时刻触发,避免二者时序脱钩导致的突兀感
+        siteState.setBounce(true);
+      }
       if (allowAdsorb) {
         // 仅浏览运营区域时不回写地图中心,避免 Map 组件按旧 scale 重绘导致刚展开的运营区视野被收回
         mapState.setLatitude(finalLat);
         mapState.setLongitude(finalLng);
       }
       settleRegionResult({ lat: finalLat, lon: finalLng, isInside: true });
-      setTimeout(async () => {
-        siteState.setBounce(false);
-        if (!adsorbed) {
-          await siteState.resolveAddress(lat, lng);
-        }
+      if (adsorbed) {
+        setTimeout(() => {
+          siteState.setBounce(false);
+          // 跳动 + 雷达动效播放完毕后才展示 tooltip,避免与前两段动效叠加显得突兀
+          siteState.setShowToolTip(true);
+        }, POINTER_BOUNCE_SEQUENCE_DURATION_MS);
+      } else {
+        await siteState.resolveAddress(lat, lng);
         if (!allowAdsorb) {
           siteState.setShowToolTip(false); // 查看运营区域期间隐藏气泡,避免遮挡围栏,拖动地图后自动恢复
         }
-      }, 600);
+      }
     } catch (error) {
       siteState.setBounce(false);
     }
   };
 
   const handleRegionEnd = (causedBy: string, wasReturningToOrigin: boolean) => {
-    siteState.setBounce(true);
     const mapCtx = Taro.createMapContext('map');
     mapCtx.getScale({
       success: (res) => {
@@ -148,7 +155,7 @@
             return;
           }
           const nearestCandidates = sites.length > 0 ? sites : newMarkers;
-          const { adsorbed, marker } = siteState.tryAdsorb(lat, lng, nearestCandidates);
+          const { adsorbed, marker } = siteState.tryAdsorb(lat, lng, nearestCandidates, { deferShowToolTip: true });
           commitQueriedMarkers(newMarkers, true, adsorbed);
           const finalLat = adsorbed && marker ? marker.latitude : lat; // 最终纬度
           const finalLng = adsorbed && marker ? marker.longitude : lng; // 最终经度
@@ -156,20 +163,29 @@
             syncManualSelectedStartSite(marker);
           }
           settleRegionResult({ lat: finalLat, lon: finalLng, isInside: true });
-          if ((causedBy === 'gesture' || causedBy === 'drag' || wasReturningToOrigin) && marker) {
+          const isGestureSettle = causedBy === 'gesture' || causedBy === 'drag' || wasReturningToOrigin;
+          const willPlayBounceSequence = isGestureSettle && adsorbed && !!marker;
+          if (willPlayBounceSequence) {
+            // 命中吸附且为手势/拖拽结算:地图中心点(硬切换)与指针跳动动画在同一时刻触发,避免二者时序脱钩导致的突兀感
+            siteState.setBounce(true);
             mapCtx.moveToLocation({
               latitude: marker.latitude,
               longitude: marker.longitude
             });
             mapState.setLatitude(marker.latitude);
             mapState.setLongitude(marker.longitude);
+            setTimeout(() => {
+              siteState.setBounce(false);
+              // 跳动 + 雷达动效播放完毕后才展示 tooltip,避免与前两段动效叠加显得突兀
+              siteState.setShowToolTip(true);
+            }, POINTER_BOUNCE_SEQUENCE_DURATION_MS);
+          } else if (adsorbed) {
+            // 未触发跳动动效(如吸附命中但非手势导致的静默刷新)时,立即展示 tooltip
+            siteState.setShowToolTip(true);
+          } else {
+            // 未命中吸附:不播放跳动/雷达动效,立即回填地址,避免无意义的等待
+            await siteState.resolveAddress(lat, lng);
           }
-          setTimeout(async () => {
-            siteState.setBounce(false);
-            if (!adsorbed) {
-              await siteState.resolveAddress(lat, lng);
-            }
-          }, 600);
         } catch (error) {
           siteState.setBounce(false);
         }
diff --git a/src/pages/robotaxi/home/hooks/use-site-state.ts b/src/pages/robotaxi/home/hooks/use-site-state.ts
index 32a1acb..ba19358 100644
--- a/src/pages/robotaxi/home/hooks/use-site-state.ts
+++ b/src/pages/robotaxi/home/hooks/use-site-state.ts
@@ -28,14 +28,17 @@
 
   /**
    * 统一吸附判断,命中阈值则更新吸附站点并显示提示。
+   * @param options.deferShowToolTip 为 true 时不立即展示 tooltip,由调用方在合适的时机(如指针跳动动效结束后)自行调用 setShowToolTip(true)。
    */
-  const tryAdsorb = (lat: number, lng: number, candidates: any[]) => {
+  const tryAdsorb = (lat: number, lng: number, candidates: any[], options?: { deferShowToolTip?: boolean }) => {
     const threshold = adsorptionDistance || 1000;
     const { targetMarker, minDistance } = findNearestMarker(lat, lng, candidates);
     if (minDistance < threshold && targetMarker) {
       setIsAdsorbed(true);
       setCurrentSite(targetMarker);
-      setShowToolTip(true);
+      if (!options?.deferShowToolTip) {
+        setShowToolTip(true);
+      }
       Taro.setStorageSync('startSite', targetMarker);
       writeConfirmRideSiteSourceType('start', RECOMMENDED_CONFIRM_RIDE_SOURCE_TYPE);
       return { adsorbed: true, marker: targetMarker };
diff --git a/src/pages/robotaxi/home/index.less b/src/pages/robotaxi/home/index.less
index 0fda132..7f6ce3e 100644
--- a/src/pages/robotaxi/home/index.less
+++ b/src/pages/robotaxi/home/index.less
@@ -123,7 +123,7 @@
   .order-status {
     position: relative;
     border-radius: 24px;
-    background-color: #141E27;
+    background-color: @black-text-main;
     background-image: url("https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/order-card-homepage-20260724.png");
     background-size: cover;
     box-sizing: border-box;
@@ -214,9 +214,11 @@
       font-size: 24px;
       font-weight: 500;
       letter-spacing: normal;
-      color: rgba(0, 0, 0, 0.9);
+      color: @black-text-main;
       border-radius: 200px;
       background: #ECF0F5;
+      flex-shrink: 0;
+      flex-grow: 0;
     }
   }
 }
@@ -348,8 +350,8 @@
 
 .coupon-message-toast {
   position: fixed;
-  top: 370px;
-  right: 144px;
+  top: 393px;
+  right: 124px;
   z-index: 1000012;
   max-width: 560px;
   min-height: 64px;
@@ -390,6 +392,8 @@
   .toolTip {
     position: relative;
     z-index: 10;
+    animation: tooltip-fade-in 240ms ease-out;
+    will-change: transform, opacity;
 
     .toolTip-bg {
       background-image: url("https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/tooltip-bg-20260715.png");
@@ -471,6 +475,8 @@
     width: 64px;
     height: 86px;
     margin-top: -54px;
+    opacity: 1;
+    transition: opacity 240ms ease-out;
 
     &.bounce {
       animation: pointer-bounce 600ms ease-in-out;
@@ -480,28 +486,32 @@
       opacity: 0;
     }
   }
-}
 
-.waiting-radar {
-  position: fixed;
-  width: 700px;
-  height: 350px;
-  top: 48%;
-  left: 50%;
-  transform: translate(-50%, -48%);
-  z-index: 1;
-  pointer-events: none;
-}
+  // 雷达以站点位置(.pointer-box 底边,即 translate(-50%,-100%) 后与地图中心点对齐的边)为圆心展开,
+  // 用绝对定位直接锚定该边,不受内部 flex 子元素排列影响
+  .waiting-radar {
+    position: absolute;
+    width: 700px;
+    height: 350px;
+    bottom: 0;
+    left: 50%;
+    transform: translate(-50%, 50%);
+    z-index: 1;
+    pointer-events: none;
+    animation: radar-fade-in 200ms ease-out;
+  }
 
-.waiting-radar-img {
-  position: fixed;
-  width: 700px;
-  height: 350px;
-  top: 48%;
-  left: 50%;
-  transform: translate(-50%, -48%);
-  z-index: 1;
-  pointer-events: none;
+  .waiting-radar-img {
+    position: absolute;
+    width: 700px;
+    height: 350px;
+    bottom: 0;
+    left: 50%;
+    transform: translate(-50%, 50%);
+    z-index: 1;
+    pointer-events: none;
+    animation: radar-fade-in 200ms ease-out;
+  }
 }
 
 @keyframes pointer-bounce {
@@ -522,6 +532,28 @@
   }
 }
 
+@keyframes tooltip-fade-in {
+  0% {
+    opacity: 0;
+    transform: translateY(6px);
+  }
+
+  100% {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+@keyframes radar-fade-in {
+  0% {
+    opacity: 0;
+  }
+
+  100% {
+    opacity: 1;
+  }
+}
+
 .home-container {
   display: flex;
   flex-direction: column;
diff --git a/src/pages/robotaxi/home/index.tsx b/src/pages/robotaxi/home/index.tsx
index 71fc5ab..2826e9b 100644
--- a/src/pages/robotaxi/home/index.tsx
+++ b/src/pages/robotaxi/home/index.tsx
@@ -41,6 +41,7 @@
 import { checkOtp, toH5 } from '../../../utils/tools';
 import { getCityLocation, reportEvent } from '../../../utils/Robotaxi-public-method';
 import { withRobotaxiWhitelist } from '../../../utils/whitelist';
+import { withToast } from '../../../robotaxi/components/toast/with-toast';
 import {
   HOME_MAP_ID,
   HOME_ORDER_STATUS,
@@ -636,4 +637,4 @@
   );
 }
 
-export default withRobotaxiWhitelist(Home);
+export default withRobotaxiWhitelist(withToast(Home));
diff --git a/src/robotaxi/api/begin-route.ts b/src/robotaxi/api/begin-route.ts
index ae54dbb..c8539ec 100644
--- a/src/robotaxi/api/begin-route.ts
+++ b/src/robotaxi/api/begin-route.ts
@@ -43,12 +43,20 @@
     QueryLedgerCreditOrderResultResponse
 } from './types/begin-route';
 
+type QueryRouteAndFareRequestOptions = {
+    showToast?: boolean;
+};
+
 // 导航-获取路线和费用
-export const queryRouteAndFare = async (payload: QueryRouteAndFarePayload): Promise<QueryRouteAndFareResponse> => {
+export const queryRouteAndFare = async (
+    payload: QueryRouteAndFarePayload,
+    options: QueryRouteAndFareRequestOptions = {}
+): Promise<QueryRouteAndFareResponse> => {
     return request({
         url: _ROBOTAXI_HOST + '/itinerary/base/v1/mini-program/navigation/multi-route-and-fare',
         method: 'POST',
-        payload
+        payload,
+        showToast: options.showToast
     }, true);
 }
 
@@ -278,7 +286,8 @@
     return request({
         url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/comment`,
         method: 'POST',
-        payload
+        payload,
+        showToast: false
     }, true);
 }
 
diff --git a/src/robotaxi/api/feedback.ts b/src/robotaxi/api/feedback.ts
index 8ff3d3e..c1017c4 100644
--- a/src/robotaxi/api/feedback.ts
+++ b/src/robotaxi/api/feedback.ts
@@ -1,9 +1,21 @@
 import request from '../../utils/request';
 import { _ROBOTAXI_HOST } from '../../constants/api';
-import type { SubmitComplaintPayload, SubmitFeedbackResponse } from './types/begin-route';
+import type { SubmitComplaintPayload, SubmitFeedbackResponse, SubmitOrderCommentPayload } from './types/begin-route';
 import type { FeedbackTypeItem } from './types/feedback';
 
 // 提交订单投诉
+export const submitOrderComplaint = async (
+    payload: SubmitOrderCommentPayload
+): Promise<SubmitFeedbackResponse> => {
+    return request({
+        url: _ROBOTAXI_HOST + `/operation/base/v1/mini-program/orderComplaint/submit`,
+        method: 'POST',
+        payload,
+        showToast: false
+    }, true);
+}
+
+// 提交独立反馈投诉
 export const submitComplaint = async (
     payload: SubmitComplaintPayload
 ): Promise<SubmitFeedbackResponse> => {
diff --git a/src/robotaxi/api/order.ts b/src/robotaxi/api/order.ts
index 402c6ff..a1ef4a1 100644
--- a/src/robotaxi/api/order.ts
+++ b/src/robotaxi/api/order.ts
@@ -53,6 +53,7 @@
     start: FareItem[];
     time: number;
     payMoney?: number; // 实际支付金额,虽然detail.txt没写,但页面展示需要,可能包含在返回中或需计算
+    hasComplaint: boolean; // 是否已反馈
 }
 
 export type FareBreakdownPayload = {
diff --git a/src/robotaxi/components/demo/index.tsx b/src/robotaxi/components/demo/index.tsx
index 60e52a2..2dec586 100644
--- a/src/robotaxi/components/demo/index.tsx
+++ b/src/robotaxi/components/demo/index.tsx
@@ -1,4 +1,3 @@
-import Taro from '@tarojs/taro'
 import { PureComponent } from 'react'
 import { View, Text } from '@tarojs/components'
 import './index.less'
@@ -164,8 +163,8 @@
             fareEstimate={DEMO_MODIFY_FARE_ESTIMATE}
             routeOptions={DEMO_MODIFY_ROUTE_OPTIONS}
             selectedRouteIndex={0}
-            onEditDestination={() => Taro.showToast({ title: '编辑目的地(Demo)', icon: 'none' })}
-            onConfirm={() => Taro.showToast({ title: '确认修改(Demo)', icon: 'none' })}
+            onEditDestination={() => showToast({ title: '编辑目的地(Demo)', icon: 'none' })}
+            onConfirm={() => showToast({ title: '确认修改(Demo)', icon: 'none' })}
           />
         </View>
       </View>
diff --git a/src/robotaxi/components/fireworks/error-boundary.tsx b/src/robotaxi/components/fireworks/error-boundary.tsx
new file mode 100644
index 0000000..b0ff54d
--- /dev/null
+++ b/src/robotaxi/components/fireworks/error-boundary.tsx
@@ -0,0 +1,35 @@
+import { Component, ReactNode } from 'react';
+
+// 通用错误边界:捕获子树在「渲染 / 生命周期」阶段抛出的错误,出错后静默渲染为空,
+// 避免动效等非核心 UI 的异常向上冒泡拖垮整个页面。
+// 注意:错误边界无法捕获异步回调(requestAnimationFrame、Promise、事件处理器)中的错误,
+// 那部分需在对应异步逻辑内自行 try/catch 兜底。
+type Props = {
+  children: ReactNode;
+  /** 出错时的兜底 UI,默认不显示任何内容 */
+  fallback?: ReactNode;
+};
+
+type State = {
+  hasError: boolean;
+};
+
+export default class ErrorBoundary extends Component<Props, State> {
+  state: State = { hasError: false };
+
+  static getDerivedStateFromError(): State {
+    return { hasError: true };
+  }
+
+  componentDidCatch(error: any) {
+    // eslint-disable-next-line no-console
+    console.warn('[Fireworks] 已忽略渲染错误:', error);
+  }
+
+  render() {
+    if (this.state.hasError) {
+      return this.props.fallback ?? null;
+    }
+    return this.props.children;
+  }
+}
diff --git a/src/robotaxi/components/fireworks/index.less b/src/robotaxi/components/fireworks/index.less
new file mode 100644
index 0000000..dc00905
--- /dev/null
+++ b/src/robotaxi/components/fireworks/index.less
@@ -0,0 +1,9 @@
+.xp-fireworks-canvas {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 999;
+  pointer-events: none;
+}
diff --git a/src/robotaxi/components/fireworks/index.tsx b/src/robotaxi/components/fireworks/index.tsx
new file mode 100644
index 0000000..3627a9e
--- /dev/null
+++ b/src/robotaxi/components/fireworks/index.tsx
@@ -0,0 +1,436 @@
+import Taro from '@tarojs/taro';
+import { Canvas } from '@tarojs/components';
+import {
+  forwardRef,
+  useEffect,
+  useImperativeHandle,
+  useRef,
+} from 'react';
+import ErrorBoundary from './error-boundary';
+import './index.less';
+
+// 粒子礼花动效:小程序原生 Canvas 2D 自绘,复刻 react-canvas-confetti 的 fireworks 预设。
+// 参数命名与 canvas-confetti 对齐,方便与 H5 侧对照调参。
+
+export type FireworkShape = 'square' | 'circle';
+
+export type FireworksOptions = {
+  /** 单簇粒子数量(连发时随剩余时间递减) */
+  particleCount?: number;
+  /** 散布角度(度),360 即向四周炸开 */
+  spread?: number;
+  /** 发射方向角度(度),90 表示向上(spread=360 时无影响) */
+  angle?: number;
+  /** 初始速度 */
+  startVelocity?: number;
+  /** 重力(每帧对 vy 的增量) */
+  gravity?: number;
+  /** 横向漂移 */
+  drift?: number;
+  /** 每帧速度衰减(空气阻力) */
+  decay?: number;
+  /** 粒子大小缩放(方片边长 = 6 * scalar) */
+  scalar?: number;
+  /** 粒子存活帧数 */
+  ticks?: number;
+  /** 颜色数组 */
+  colors?: string[];
+  /** 形状(单个),与 shapes 二选一 */
+  shape?: FireworkShape;
+  /** 形状数组,随机取一,优先级高于 shape */
+  shapes?: FireworkShape[];
+  /** 发射中心点,取值 0~1 的相对坐标(x 向右、y 向下) */
+  origin?: { x?: number; y?: number };
+  /** 连发总时长(ms),仅 play 连发时生效 */
+  duration?: number;
+  /** 两次发射间隔(ms),仅 play 连发时生效 */
+  interval?: number;
+};
+
+export type FireworksRef = {
+  /** 连发礼花(在 duration 内每隔 interval 在随机位置发射一簇) */
+  play: (options?: FireworksOptions) => void;
+  /** 单次发射一簇礼花(等价 canvas-confetti 的 confetti(options)) */
+  shoot: (options?: FireworksOptions) => void;
+  /** 立即停止并清空 */
+  stop: () => void;
+};
+
+type Props = {
+  /** canvas 层级 */
+  zIndex?: number;
+};
+
+const DEFAULTS = {
+  particleCount: 80,
+  spread: 360,
+  angle: 90,
+  startVelocity: 28,
+  gravity: 0.85,
+  drift: 0,
+  decay: 0.9,
+  scalar: 1,
+  ticks: 150,
+  colors: ['#7FE6F2', '#F9A76A', '#95CF29'],
+  shapes: ['square'] as FireworkShape[],
+  origin: { x: 0.5, y: 0.5 },
+  duration: 2500,
+  interval: 250,
+};
+
+type NormalizedOptions = {
+  particleCount: number;
+  spread: number;
+  angle: number;
+  startVelocity: number;
+  gravity: number;
+  drift: number;
+  decay: number;
+  scalar: number;
+  ticks: number;
+  colors: string[];
+  shapes: FireworkShape[];
+  origin: { x: number; y: number };
+  duration: number;
+  interval: number;
+};
+
+// 合并默认值 + 兼容 shape 单值写法,产出归一化配置
+function normalize(options: FireworksOptions = {}): NormalizedOptions {
+  const shapes = options.shapes && options.shapes.length
+    ? options.shapes
+    : options.shape
+      ? [options.shape]
+      : DEFAULTS.shapes;
+  return {
+    particleCount: options.particleCount ?? DEFAULTS.particleCount,
+    spread: options.spread ?? DEFAULTS.spread,
+    angle: options.angle ?? DEFAULTS.angle,
+    startVelocity: options.startVelocity ?? DEFAULTS.startVelocity,
+    gravity: options.gravity ?? DEFAULTS.gravity,
+    drift: options.drift ?? DEFAULTS.drift,
+    decay: options.decay ?? DEFAULTS.decay,
+    scalar: options.scalar ?? DEFAULTS.scalar,
+    ticks: options.ticks ?? DEFAULTS.ticks,
+    colors: options.colors ?? DEFAULTS.colors,
+    shapes,
+    origin: {
+      x: options.origin?.x ?? DEFAULTS.origin.x,
+      y: options.origin?.y ?? DEFAULTS.origin.y,
+    },
+    duration: options.duration ?? DEFAULTS.duration,
+    interval: options.interval ?? DEFAULTS.interval,
+  };
+}
+
+function rand(min: number, max: number) {
+  return Math.random() * (max - min) + min;
+}
+
+// 粒子模型对齐 canvas-confetti:每片纸屑带 wobble(飘动)+ tiltAngle(翻转),
+// 因此绘制成会抖动、翻面的四边形,而非静止方块,视觉上更接近真实纸屑。
+type Particle = {
+  x: number;
+  y: number;
+  velocity: number; // 爆发初速(逐帧按 decay 衰减)
+  angle2D: number; // 发射方向(弧度)
+  wobble: number; // 飘动相位
+  wobbleSpeed: number;
+  wobbleX: number;
+  wobbleY: number;
+  tiltAngle: number; // 翻转角
+  gravity: number; // 每帧恒定下落量(= opt.gravity * 3)
+  decay: number;
+  drift: number;
+  scalar: number;
+  color: string;
+  shape: FireworkShape;
+  tick: number;
+  totalTicks: number;
+};
+
+// 生成一簇粒子。angle 为发射主方向(度,90 向上),spread 为散布角度。
+// angle2D = -angle ± spread/2,屏幕 y 向下,故 angle=90 时 sin(angle2D)=-1 表现为向上。
+// 初速取 sqrt(random)×startVelocity:用平方根让粒子按「面积」均匀分布,
+// 避免大量粒子挤在中心(若用线性 random,低速粒子过多会在中心结团);
+// 同时最小值仍接近 0,保留少量粒子填充中心,整体从中心向四周均匀铺满屏幕。
+function createBurst(cx: number, cy: number, particleCount: number, opt: NormalizedOptions) {
+  const list: Particle[] = [];
+  const radAngle = (opt.angle * Math.PI) / 180;
+  const radSpread = (opt.spread * Math.PI) / 180;
+  for (let i = 0; i < particleCount; i++) {
+    list.push({
+      x: cx,
+      y: cy,
+      velocity: Math.sqrt(Math.random()) * opt.startVelocity,
+      angle2D: -radAngle + (0.5 * radSpread - Math.random() * radSpread),
+      wobble: Math.random() * 10,
+      wobbleSpeed: Math.min(0.11, Math.random() * 0.1 + 0.05),
+      wobbleX: 0,
+      wobbleY: 0,
+      tiltAngle: (Math.random() * 0.5 + 0.25) * Math.PI,
+      gravity: opt.gravity * 3,
+      decay: opt.decay,
+      drift: opt.drift,
+      // 每片纸屑大小在 scalar 基础上随机(0.5×~1.8×),大的一端更大,呈现有大有小的层次感
+      scalar: opt.scalar * rand(0.5, 1.8),
+      color: opt.colors[(Math.random() * opt.colors.length) | 0],
+      shape: opt.shapes[(Math.random() * opt.shapes.length) | 0],
+      tick: 0,
+      totalTicks: opt.ticks,
+    });
+  }
+  return list;
+}
+
+// canvas 节点 id 前缀。真正的 id 会在每个实例内拼接自增序号 + 随机串,保证同页多个
+// <Fireworks> 实例不会因固定 id 冲突而 createSelectorQuery 选错节点(class 仍固定用于样式)。
+const CANVAS_ID_PREFIX = 'xp-fireworks-canvas';
+// 实例自增序号:模块级,配合随机串生成唯一 canvas id
+let fireworksInstanceSeq = 0;
+
+const FireworksInner = forwardRef<FireworksRef, Props>((props, ref) => {
+  const canvasRef = useRef<any>(null);
+  const ctxRef = useRef<any>(null);
+  const sizeRef = useRef<{ w: number; h: number }>({ w: 0, h: 0 });
+  const particlesRef = useRef<Particle[]>([]);
+  const runningRef = useRef(false);
+  // 世代号:stop() 递增,loop 每帧比对——若世代变化则自终止,解决 stop() 停不掉已排队的 rAF
+  const generationRef = useRef(0);
+  // 当前发射器:play 设 fireRef,shoot 设 null;运行中更换 fireRef 即可热替换发射逻辑,解决吞新 play()
+  const fireRef = useRef<((now: number) => boolean) | null>(null);
+  // 本实例唯一的 canvas id(惰性生成一次,跨渲染稳定)
+  const canvasIdRef = useRef<string>('');
+  if (!canvasIdRef.current) {
+    fireworksInstanceSeq += 1;
+    canvasIdRef.current = `${CANVAS_ID_PREFIX}-${fireworksInstanceSeq}-${Math.random().toString(36).slice(2, 8)}`;
+  }
+
+  // 统一的错误吞噬:礼花只是锦上添花的动效,任何异常都不应外溢影响页面,出错即静默不显示
+  const swallow = (err: unknown) => {
+    runningRef.current = false;
+    // eslint-disable-next-line no-console
+    console.warn('[Fireworks] 已忽略动效内部错误:', err);
+  };
+
+  // 包裹异步入口方法:错误边界捕获不到 rAF/Promise 等异步栈里的错误,这里统一兜底吞掉
+  const safe = <A extends any[]>(fn: (...args: A) => void | Promise<void>) => (...args: A) => {
+    try {
+      const ret = fn(...args);
+      if (ret && typeof (ret as Promise<void>).catch === 'function') {
+        (ret as Promise<void>).catch(swallow);
+      }
+    } catch (err) {
+      swallow(err);
+    }
+  };
+
+  // 初始化 canvas 节点(Canvas 2D)。同步异常会 reject 由 safe 兜底;
+  // exec 回调是异步栈,safe 捕获不到,故回调内单独 try/catch。
+  const initCanvas = () =>
+    new Promise<void>((resolve) => {
+      if (canvasRef.current) {
+        resolve();
+        return;
+      }
+      const query = Taro.createSelectorQuery();
+      query
+        .select(`#${canvasIdRef.current}`)
+        .fields({ node: true, size: true })
+        .exec((res) => {
+          try {
+            if (!res || !res[0] || !res[0].node) {
+              resolve();
+              return;
+            }
+            const canvas = res[0].node;
+            const ctx = canvas.getContext('2d');
+            const dpr = (Taro.getSystemInfoSync() || {}).pixelRatio || 1;
+            canvas.width = res[0].width * dpr;
+            canvas.height = res[0].height * dpr;
+            ctx.scale(dpr, dpr);
+            canvasRef.current = canvas;
+            ctxRef.current = ctx;
+            sizeRef.current = { w: res[0].width, h: res[0].height };
+            resolve();
+          } catch (err) {
+            swallow(err);
+            resolve();
+          }
+        });
+    });
+
+  // 逐帧更新并绘制单个粒子,返回 false 表示已消亡。逻辑对齐 canvas-confetti 的 updateFizz。
+  const updateAndDraw = (ctx: any, p: Particle) => {
+    // 位置:沿 angle2D 前进(速度按 decay 衰减)+ 恒定重力下落 + 横向漂移
+    p.x += Math.cos(p.angle2D) * p.velocity + p.drift;
+    p.y += Math.sin(p.angle2D) * p.velocity + p.gravity;
+    p.velocity *= p.decay;
+
+    // wobble:让粒子飘动,绘制成随时间抖动的四边形
+    p.wobble += p.wobbleSpeed;
+    p.wobbleX = p.x + 10 * p.scalar * Math.cos(p.wobble);
+    p.wobbleY = p.y + 10 * p.scalar * Math.sin(p.wobble);
+
+    // tiltAngle:让纸屑翻面(宽度周期性收缩,模拟 3D 翻转)
+    p.tiltAngle += 0.1;
+    const tiltSin = Math.sin(p.tiltAngle);
+    const tiltCos = Math.cos(p.tiltAngle);
+    const random = Math.random() + 2; // 对齐 canvas-confetti,决定纸片另一维的尺寸
+    const x1 = p.x + random * tiltCos;
+    const y1 = p.y + random * tiltSin;
+    const x2 = p.wobbleX + random * tiltCos;
+    const y2 = p.wobbleY + random * tiltSin;
+
+    const progress = p.tick / p.totalTicks;
+    p.tick++;
+    if (p.tick >= p.totalTicks) return false;
+
+    ctx.globalAlpha = 1 - progress;
+    ctx.fillStyle = p.color;
+    ctx.beginPath();
+    if (p.shape === 'circle') {
+      // 椭圆:随 wobble/tilt 改变长短轴,呈现翻转的圆片(ovalScalar=0.6)
+      ctx.ellipse(
+        p.x,
+        p.y,
+        Math.abs(x2 - x1) * 0.6,
+        Math.abs(y2 - y1) * 0.6,
+        (Math.PI / 10) * p.wobble,
+        0,
+        2 * Math.PI,
+      );
+    } else {
+      // 方片:由 4 个随 wobble/tilt 抖动的顶点构成的四边形,会飘动翻面
+      ctx.moveTo(Math.floor(p.x), Math.floor(p.y));
+      ctx.lineTo(Math.floor(p.wobbleX), Math.floor(y1));
+      ctx.lineTo(Math.floor(x2), Math.floor(y2));
+      ctx.lineTo(Math.floor(x1), Math.floor(p.wobbleY));
+    }
+    ctx.closePath();
+    ctx.fill();
+    return true;
+  };
+
+  // 逐帧渲染循环:读取 fireRef 驱动发射、比对 generationRef 自终止,无粒子且无发射时自动停止 rAF
+  const startLoop = () => {
+    const canvas = canvasRef.current;
+    const ctx = ctxRef.current;
+    if (!canvas || !ctx) return;
+    if (runningRef.current) return; // 循环唯一,发射器靠 fireRef 热替换,新 play 无需重启循环
+    runningRef.current = true;
+    const { w, h } = sizeRef.current;
+    const myGen = generationRef.current;
+
+    const loop = () => {
+      // 单帧出错时吞掉并终止循环,清空粒子,避免错误反复外溢
+      try {
+        // 世代号比对:stop() 递增后,已排队的下一帧检测到变化即自终止
+        if (myGen !== generationRef.current) {
+          runningRef.current = false;
+          return;
+        }
+        const now = Date.now();
+        const keepFiring = fireRef.current ? fireRef.current(now) : false;
+
+        ctx.clearRect(0, 0, w, h);
+        particlesRef.current = particlesRef.current.filter((p) => updateAndDraw(ctx, p));
+        ctx.globalAlpha = 1;
+
+        if (particlesRef.current.length || keepFiring) {
+          canvas.requestAnimationFrame(loop);
+        } else {
+          runningRef.current = false;
+          fireRef.current = null;
+        }
+      } catch (err) {
+        particlesRef.current = [];
+        try {
+          ctx.clearRect(0, 0, w, h);
+        } catch (_) {
+          // 清屏也失败则忽略
+        }
+        swallow(err);
+      }
+    };
+
+    canvas.requestAnimationFrame(loop);
+  };
+
+  // 单次发射一簇礼花(safe 包裹,异步异常统一吞掉)
+  const shoot = safe(async (options: FireworksOptions = {}) => {
+    await initCanvas();
+    if (!canvasRef.current || !ctxRef.current) return;
+    const opt = normalize(options);
+    const { w, h } = sizeRef.current;
+    const cx = opt.origin.x * w;
+    const cy = opt.origin.y * h;
+    fireRef.current = null;
+    particlesRef.current.push(...createBurst(cx, cy, opt.particleCount, opt));
+    startLoop();
+  });
+
+  // 连发礼花:在 duration 内每隔 interval 在随机位置发射一簇。
+  // fireRef 先设后调 startLoop——即使循环已在运行也能热替换发射逻辑,不再吞新 play()。
+  const play = safe(async (options: FireworksOptions = {}) => {
+    await initCanvas();
+    if (!canvasRef.current || !ctxRef.current) return;
+    const opt = normalize(options);
+    const { w, h } = sizeRef.current;
+    const end = Date.now() + opt.duration;
+    let lastFire = 0;
+
+    fireRef.current = (now) => {
+      if (now <= end && now - lastFire >= opt.interval) {
+        lastFire = now;
+        const remain = (end - now) / opt.duration;
+        const count = Math.max(1, Math.ceil(opt.particleCount * remain));
+        const cx = rand(0.2, 0.8) * w;
+        const cy = rand(0.35, 0.5) * h;
+        particlesRef.current.push(...createBurst(cx, cy, count, opt));
+      }
+      return now <= end;
+    };
+    startLoop();
+  });
+
+  const stop = safe(() => {
+    generationRef.current += 1;
+    runningRef.current = false;
+    fireRef.current = null;
+    particlesRef.current = [];
+    const ctx = ctxRef.current;
+    const { w, h } = sizeRef.current;
+    if (ctx) ctx.clearRect(0, 0, w, h);
+  });
+
+  useImperativeHandle(ref, () => ({ play, shoot, stop }));
+
+  useEffect(() => {
+    return () => {
+      generationRef.current += 1;
+      runningRef.current = false;
+      fireRef.current = null;
+      particlesRef.current = [];
+    };
+  }, []);
+
+  return (
+    <Canvas
+      type='2d'
+      id={canvasIdRef.current}
+      className='xp-fireworks-canvas'
+      style={props.zIndex != null ? { zIndex: props.zIndex } : undefined}
+    />
+  );
+});
+
+// 用错误边界包裹:捕获渲染/生命周期阶段的错误(异步错误由内部 safe 兜底),出错静默渲染为空。
+// ref 透传给内部组件,外部照常拿到 play / shoot / stop。
+const Fireworks = forwardRef<FireworksRef, Props>((props, ref) => (
+  <ErrorBoundary>
+    <FireworksInner {...props} ref={ref} />
+  </ErrorBoundary>
+));
+
+export default Fireworks;
diff --git "a/src/robotaxi/components/fireworks/\347\244\274\350\212\261\346\225\210\346\236\234.md" "b/src/robotaxi/components/fireworks/\347\244\274\350\212\261\346\225\210\346\236\234.md"
new file mode 100644
index 0000000..cb62906
--- /dev/null
+++ "b/src/robotaxi/components/fireworks/\347\244\274\350\212\261\346\225\210\346\236\234.md"
@@ -0,0 +1,139 @@
+# 礼花(Fireworks)动效组件
+
+> 小程序原生 **Canvas 2D** 自绘的粒子礼花组件,复刻 [`canvas-confetti`](https://github.com/catdad/canvas-confetti) / [`react-canvas-confetti`](https://github.com/ulitcos/react-canvas-confetti) 的 `confetti`/`fireworks` 观感,**不依赖任何第三方库**。
+> 参数命名与 `canvas-confetti` 对齐,方便与 H5 侧对照调参。
+
+组件位置:`src/robotaxi/components/fireworks/`
+
+```
+fireworks/
+├── index.tsx          # 组件主体(forwardRef,暴露 play/shoot/stop)
+├── error-boundary.tsx # 通用错误边界(吞掉渲染/生命周期错误)
+├── index.less         # 全屏、置顶、pointer-events:none 的 canvas 样式
+└── 礼花效果.md         # 本文档
+```
+
+---
+
+## 1. 背景
+
+现有 H5 工程的撒花动效基于 `react-canvas-confetti`(底层 `canvas-confetti`),强依赖浏览器 DOM/BOM(`document`、`window`、DOM canvas)。**小程序没有 DOM,canvas 是专有组件**,无法直接复用,因此在小程序侧用**原生 Canvas 2D 自绘粒子系统**复刻同样的效果。
+
+## 2. 对外 API
+
+组件通过 `forwardRef` + `useImperativeHandle` 暴露 3 个命令式方法(`FireworksRef`):
+
+| 方法 | 说明 |
+|------|------|
+| `shoot(options?)` | **单次发射一簇**礼花,等价 `canvas-confetti` 的 `confetti(options)`。到达结束态即用此方法。 |
+| `play(options?)` | **连发**:在 `duration` 内每隔 `interval` 在随机位置发射一簇,`particleCount` 随剩余时间递减。 |
+| `stop()` | 立即停止并清空画布。 |
+
+Props:
+
+| Prop | 类型 | 说明 |
+|------|------|------|
+| `zIndex` | `number?` | canvas 层级;不传则用样式默认 `999`。 |
+
+### 用法
+
+```tsx
+import Fireworks, { FireworksRef } from '@/robotaxi/components/fireworks';
+
+const ref = useRef<FireworksRef | null>(null);
+
+// 挂载
+<Fireworks ref={(node) => { ref.current = node; }} zIndex={99999} />
+
+// 单簇(到达庆祝)
+ref.current?.shoot({
+  origin: { x: 0.5, y: 0.4 },
+  angle: 90,
+  particleCount: 260,
+  spread: 360,
+  startVelocity: 42,
+  gravity: 0.9,
+  scalar: 0.9,
+  ticks: 180,
+  colors: ['#FFD666', '#F9A76A', '#FFF1B8', '#7FE6F2', '#95CF29'],
+});
+
+// 连发
+ref.current?.play({ duration: 2500, interval: 250 });
+```
+
+### 当前落地场景
+
+- **到达结束态庆祝**:`src/robotaxi/pages/begin-route/index.tsx` 中,检测 `tripStatus` 由非结束态切到 `TripFinished(8)` 的上升沿,延迟约 180ms 后调用 `shoot` 发射一簇(与背景卡片升起同步呈现,只发一次避免重复观感)。
+- **调参 Demo**:`src/robotaxi/pages/fireworks-demo/index.tsx`(仅调试用),点击按钮调用 `shoot` 绽放一簇。
+
+## 3. 参数(`FireworksOptions`)与默认值
+
+| 参数 | 含义 | 默认值 |
+|------|------|--------|
+| `particleCount` | 单簇粒子数量(连发时随剩余时间递减) | `80` |
+| `spread` | 散布角度(度),`360` 即向四周炸开 | `360` |
+| `angle` | 发射主方向(度),`90` 向上(`spread=360` 时无影响) | `90` |
+| `startVelocity` | 初始速度(实际按 `sqrt(random)` 分布,见下) | `28` |
+| `gravity` | 重力(每帧恒定下落量 = `gravity * 3`) | `0.85` |
+| `drift` | 横向漂移 | `0` |
+| `decay` | 每帧速度衰减(空气阻力) | `0.9` |
+| `scalar` | 粒子大小缩放(每片再随机 `0.5×~1.8×`) | `1` |
+| `ticks` | 粒子存活帧数 | `150` |
+| `colors` | 颜色数组 | `['#7FE6F2', '#F9A76A', '#95CF29']` |
+| `shape` / `shapes` | 形状:`'square'`(方片)/ `'circle'`(椭圆片),`shapes` 优先 | `['square']` |
+| `origin` | 发射中心,相对坐标 `0~1`(x 向右、y 向下) | `{ x: 0.5, y: 0.5 }` |
+| `duration` | 连发总时长(ms),仅 `play` 生效 | `2500` |
+| `interval` | 两次发射间隔(ms),仅 `play` 生效 | `250` |
+
+## 4. 粒子模型与算法
+
+粒子模型对齐 `canvas-confetti`:每片纸屑带 `wobble`(飘动)与 `tiltAngle`(翻转),绘制成**会抖动、翻面的四边形/椭圆**,而非静止方块,视觉上更接近真实纸屑。
+
+**发射(`createBurst`)**
+
+- 主方向 `angle2D = -radAngle + (0.5*radSpread - random*radSpread)`;屏幕 y 向下,故 `angle=90` 时向上。
+- 初速 `velocity = sqrt(random) * startVelocity`:用平方根让粒子按「面积」均匀分布,避免大量粒子挤在中心结团;同时保留少量低速粒子填充中心,整体从中心向四周均匀铺满。
+- 每片大小 `scalar * rand(0.5, 1.8)`:有大有小的层次感。
+
+**每帧更新(`updateAndDraw`)**
+
+```
+x += cos(angle2D) * velocity + drift
+y += sin(angle2D) * velocity + gravity   // gravity 为每帧恒定下落量
+velocity *= decay                         // 速度衰减
+wobble   += wobbleSpeed                    // 飘动相位,决定四边形顶点抖动
+tiltAngle += 0.1                           // 翻面(宽度周期性收缩,模拟 3D 翻转)
+alpha = 1 - tick / totalTicks              // 线性淡出
+tick++ ; if (tick >= totalTicks) → 移除
+```
+
+- 方片:由 4 个随 wobble/tilt 抖动的顶点构成的四边形。
+- 椭圆:随 wobble/tilt 改变长短轴(ovalScalar=0.6),呈现翻转的圆片。
+
+**渲染循环(`startLoop`)**:单实例只跑一个 `requestAnimationFrame` 循环,新发射的粒子追加进同一循环;无存活粒子且无待发射时自动停止 rAF,避免空转耗电。
+
+## 5. 健壮性设计(三层容错)
+
+礼花只是锦上添花的动效,任何异常都不应拖垮页面,出错即静默不显示:
+
+1. **`ErrorBoundary`**:捕获子树在渲染/生命周期阶段抛出的错误,出错渲染为空。
+2. **`safe()` 包裹**:包裹 `shoot`/`play`/`stop` 等异步入口,兜底 rAF/Promise 等异步栈里错误边界捕获不到的错误。
+3. **逐帧 `try/catch`**:单帧出错即清空粒子并终止循环,避免错误反复外溢。
+
+## 6. 关键实现细节
+
+- **Canvas 2D(`type="2d"`)**:新版 canvas,性能优于旧版 `createCanvasContext`。
+- **DPR 适配**:`canvas.width = 逻辑宽 * pixelRatio` 并 `ctx.scale(dpr, dpr)`,否则高清屏发虚。
+- **唯一 canvas id**:每个组件实例惰性生成 `xp-fireworks-canvas-{seq}-{random}` 作为 canvas id(`className` 仍固定 `xp-fireworks-canvas` 用于样式),避免同页多个 `<Fireworks>` 实例因固定 id 导致 `createSelectorQuery` 选错节点。
+- **点击穿透**:canvas `position:fixed` 全屏置顶 + `pointer-events:none`,不阻挡按钮/支付等交互。
+- **卸载清理**:`useEffect` 卸载时停循环、清粒子;到达页对 `<Fireworks>` 做条件渲染,离开结束态即卸载。
+
+## 7. 验收标准
+
+- [ ] 到达结束态(6→8)时,随背景卡片升起绽放一簇礼花,粒子四散、受重力下落并淡出,仅播一次。
+- [ ] 配色/形状/密度可通过 `FireworksOptions` 配置。
+- [ ] 动画期间不阻塞页面交互(点击穿透正常)。
+- [ ] 动画结束后 rAF 停止、canvas 无残留、CPU 回落。
+- [ ] 组件内部任何异常都被静默吞掉,不影响页面主流程。
+- [ ] 真机(iOS + Android)低端机型帧率可接受,无明显卡顿。
diff --git a/src/robotaxi/components/site-route-card/index.less b/src/robotaxi/components/site-route-card/index.less
new file mode 100644
index 0000000..302a2d1
--- /dev/null
+++ b/src/robotaxi/components/site-route-card/index.less
@@ -0,0 +1,86 @@
+// SiteRouteCard 公共样式:抽离自 cancel-site-box / calling-car-site-box / share-site-box /
+// modify-destination-review-select-site,与 release-be-being 的 route-card 结构对齐。
+// 各业务方通过外层 className(如 cancel-site-box)叠加背景/内边距等容器级样式,
+// 组件自身只负责 item/marker/dot/check/line/divider/text 的结构样式。
+@import '../../style/ui-variables.less';
+
+.site-route-card {
+  .site-route-card-item {
+    position: relative;
+
+    display: flex;
+    align-items: flex-start;
+    gap: 16px;
+
+    &.site-route-card-item-first {
+      padding-bottom: 80px;
+    }
+
+    // 虚线连接线:以 item 盒子为基准,用 top/bottom 锚定首尾 marker 中心,
+    // 文字换行导致 item 变高时自动拉伸,无需固定高度。
+    .site-route-card-item-line {
+      position: absolute;
+      left: 14px;
+      top: 40px;
+      bottom: -8px;
+      transform: translateX(-50%);
+      width: 0;
+      border-left: 2px dashed #ccd0d7;
+    }
+
+    // 两项之间的水平分割线:与文字列左对齐,居中于行间距内。
+    .site-route-card-item-divider {
+      position: absolute;
+      left: 48px;
+      right: 0;
+      top: 88px;
+      height: 0;
+      border-top: 1px solid rgba(204, 208, 215, 0.25);
+    }
+
+    .site-route-card-item-marker {
+      position: relative;
+      flex-shrink: 0;
+      width: 28px;
+      height: 44px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      .site-route-card-item-dot {
+        width: 16px;
+        height: 16px;
+        border-radius: 50%;
+        background: @black-text-main;
+
+        &.site-route-card-item-dot-first {
+          background: @gold-light;
+        }
+      }
+
+      .site-route-card-item-check {
+        width: 28px;
+        height: 28px;
+      }
+    }
+
+    .site-route-card-item-row {
+      flex: 1;
+      min-width: 0;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+    }
+
+    .site-route-card-item-text {
+      flex: 1;
+      min-width: 0;
+      font-size: 32px;
+      font-weight: 500;
+      line-height: 48px;
+      letter-spacing: normal;
+      color: @black-text-main;
+      word-break: break-all;
+    }
+  }
+}
diff --git a/src/robotaxi/components/site-route-card/index.tsx b/src/robotaxi/components/site-route-card/index.tsx
new file mode 100644
index 0000000..0b538c2
--- /dev/null
+++ b/src/robotaxi/components/site-route-card/index.tsx
@@ -0,0 +1,71 @@
+import { View, Image } from '@tarojs/components';
+import './index.less';
+
+export type SiteRouteCardItem = {
+  /** 站点名称 */
+  name: React.ReactNode;
+  /** 名称文案的额外 className,用于置灰等场景 */
+  nameClassName?: string;
+  /** 右侧扩展内容(如时间文案),仅在需要时传入 */
+  extra?: React.ReactNode;
+  /** 该项点击事件(如终点可点击编辑) */
+  onClick?: () => void;
+};
+
+type Props = {
+  /** 站点列表,通常为 [起点, 终点] 两项 */
+  sites: SiteRouteCardItem[];
+  /** marker 类型:dot 为纯圆点,check 为图标(需配合 checkIconUrls 传入图标地址) */
+  markerType?: 'dot' | 'check';
+  /** markerType 为 check 时,按 sites 顺序传入对应图标地址 */
+  checkIconUrls?: string[];
+  /** 最外层 className,供各业务方沿用自身既有命名做样式覆盖 */
+  className?: string;
+};
+
+/**
+ * 站点路线卡片:统一的起终点两项展示结构(marker + 虚线连接线 + 分割线 + 文本)。
+ * 抽离自 cancel-site-box / calling-car-site-box / share-site-box / modify-destination-review-select-site
+ * 与 release-be-being 的 route-card 结构对齐。
+ */
+export default function SiteRouteCard({
+  sites,
+  markerType = 'dot',
+  checkIconUrls = [],
+  className = ''
+}: Props) {
+  return (
+    <View className={`site-route-card ${className}`.trim()}>
+      {sites.map((site, index) => {
+        const isFirst = index === 0;
+        return (
+          <View
+            key={index}
+            className={`site-route-card-item${isFirst ? ' site-route-card-item-first' : ''}`}
+            onClick={site.onClick}
+          >
+            {isFirst && <View className='site-route-card-item-line'></View>}
+            {isFirst && <View className='site-route-card-item-divider'></View>}
+            <View className='site-route-card-item-marker'>
+              {markerType === 'check' ? (
+                <Image className='site-route-card-item-check' src={checkIconUrls[index]} />
+              ) : (
+                <View
+                  className={`site-route-card-item-dot${
+                    isFirst ? ' site-route-card-item-dot-first' : ''
+                  }`}
+                ></View>
+              )}
+            </View>
+            <View className='site-route-card-item-row'>
+              <View className={`site-route-card-item-text ${site.nameClassName || ''}`.trim()}>
+                {site.name}
+              </View>
+              {site.extra}
+            </View>
+          </View>
+        );
+      })}
+    </View>
+  );
+}
diff --git a/src/robotaxi/constants/assets.ts b/src/robotaxi/constants/assets.ts
new file mode 100644
index 0000000..cfbe1b3
--- /dev/null
+++ b/src/robotaxi/constants/assets.ts
@@ -0,0 +1,62 @@
+/**
+ * 素材链接单一数据源(Single Source of Truth)。
+ *
+ * 所有跨组件复用、且被预加载逻辑(constants/preload-assets.ts)引用的素材 URL 统一在此声明,
+ * 组件与预加载清单都从这里 import,杜绝「组件改了链接、预加载还指向旧链接」导致的静默失效。
+ *
+ * 依赖方向:组件 → assets.ts ← 预加载清单(assets.ts 处于最底层,不反向依赖任何页面模块)。
+ */
+
+/** 派单成功转场主视频(mp4)。 */
+export const VEHICLE_APPROACHING_VIDEO = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/video/vehicle_approaching_v1_file_w_1128@h_1268.mp4';
+
+/** 派单成功转场视频封面(jpg)。 */
+export const VEHICLE_APPROACHING_POSTER = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/video/vehicle_approaching_poster_v1_file_w_1128@h_1268.jpg';
+
+/** 派单成功转场视频尾帧(jpeg):结尾遮挡层用,挡住原生 <video> 卸载黑帧。 */
+export const VEHICLE_APPROACHING_END_POSTER = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/video/vehicle_approaching_end_poster_v1_file_w_1128@h_1268.jpeg';
+
+/** 常态行程卡片背景图(派单成功落定 / 接驾 / 行程中)。 */
+export const CARD_BG_IMG = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/card-bg-20260714.png';
+
+/** 默认车辆图(无实拍车图时的兜底)。 */
+export const DEFAULT_VEHICLE_IMG = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/default-vehicle-img-20260724-v2.png';
+
+/**
+ * 是否使用接口(queryVehicleByOrderId)动态返回的车图。
+ *
+ * 背景:当前线上只有一种车型,产品要求统一用默认车图 DEFAULT_VEHICLE_IMG,
+ *      暂不使用接口返回的动态车图(vehicleInfo.vehiclePictures.url)。
+ *
+ * 后续有新车型、需要按车型展示实拍车图时,把此开关改回 true 即可完全恢复动态车图逻辑,
+ * 无需改动其余代码(车图渲染处 calling-car.tsx、dispatch-transition-layer.tsx 均已按此开关分流;
+ * 默认车图 DEFAULT_VEHICLE_IMG 已在 constants/preload-assets.ts 静态预加载,展示无闪烁)。
+ */
+export const USE_DYNAMIC_VEHICLE_IMG = false;
+
+/** 到达终点 / 行程结束卡片背景图。 */
+export const TRIP_FINISHED_BG_IMG = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/trip-finished-20260703.png';
+
+/** 发布中/呼叫车中顶部卡片背景图(release-be-being、calling-car 呼叫态共用)。 */
+export const TOP_CARD_BG_IMG = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/top-card-bg-20260722.png';
+
+/** 站点完成态图标(金色:起点,黑色:其余站点)。 */
+export const ROUTE_STATE_ICON_GOLDEN = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-golden-20260702.png';
+export const ROUTE_STATE_ICON_BLACK = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-black-20260702.png';
+
+/** 站点连接虚线。 */
+export const DOTTED_LINE_IMG = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png';
+
+/** 接驾中/行程中状态栏「车正在赶来」图标。 */
+export const COME_ICON_IMG = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/come-icon-20260728.webp';
+
+/** 发布中进度图标。 */
+export const PUBLISH_PROGRESS_IMG = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/publish-progress-20260715.webp';
+
+/**
+ * 全局字体链接。
+ * ⚠️ 必须与 robotaxi/style/ui-variables.less 中的 @font-face src 保持一致;
+ *    less 无法 import JS 常量,两处为约定同步(改一处需同步另一处)。
+ */
+export const FONT_XPSANS_BOLD = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/font/XPSANS-Bold.otf';
+export const FONT_XPSANS_REGULAR = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/font/XPSANS-Regular.otf';
diff --git a/src/robotaxi/constants/music-control-feature.ts b/src/robotaxi/constants/music-control-feature.ts
new file mode 100644
index 0000000..6afb07b
--- /dev/null
+++ b/src/robotaxi/constants/music-control-feature.ts
@@ -0,0 +1,13 @@
+/**
+ * 曲风(车载音乐)卡片展示开关。
+ *
+ * true  → 正常展示曲风卡片与控制面板,并按原逻辑发起查询/下发请求;
+ * false → 曲风卡片与面板完全不展示,且不发起任何曲风相关接口请求
+ *         (非接驾态、连环单等原有守卫逻辑不变)。
+ *
+ * 当前:暂时关闭曲风卡片。
+ */
+export const MUSIC_CONTROL_ENABLED = false;
+
+/** 曲风卡片是否展示 */
+export const isMusicControlEnabled = () => MUSIC_CONTROL_ENABLED;
diff --git a/src/robotaxi/constants/preload-assets.ts b/src/robotaxi/constants/preload-assets.ts
new file mode 100644
index 0000000..489400d
--- /dev/null
+++ b/src/robotaxi/constants/preload-assets.ts
@@ -0,0 +1,46 @@
+import { PreloadAsset } from '../../utils/preload-assets';
+import {
+  VEHICLE_APPROACHING_POSTER,
+  VEHICLE_APPROACHING_END_POSTER,
+  CARD_BG_IMG,
+  DEFAULT_VEHICLE_IMG,
+  TRIP_FINISHED_BG_IMG,
+  TOP_CARD_BG_IMG,
+  ROUTE_STATE_ICON_GOLDEN,
+  ROUTE_STATE_ICON_BLACK,
+  DOTTED_LINE_IMG,
+  COME_ICON_IMG,
+  PUBLISH_PROGRESS_IMG,
+} from './assets';
+
+/**
+ * 转场动效预加载素材清单,按行程状态分组。
+ *
+ * 所有 URL 均来自 constants/assets.ts 单一数据源,组件与本清单引用同一常量,
+ * 组件改链接时预加载自动跟随,不会失效。
+ *
+ * ⚠️ wx.preloadAssets 仅支持 image / font,**不支持视频**:
+ *    派单转场视频(mp4)无法预热,此处仅预加载其封面图(poster)。
+ */
+
+/**
+ * 派单成功转场素材(发布行程 → 发布中 → 呼叫中 → 派单成功/接驾)。
+ * 覆盖从发布行程到接驾整段流程中会用到的图片素材,尽量提前预热,减少首次打车动效卡顿。
+ */
+export const DISPATCH_SUCCESS_ASSETS: PreloadAsset[] = [
+  { type: 'image', src: VEHICLE_APPROACHING_POSTER }, // 转场视频封面(视频本身不支持预热)
+  { type: 'image', src: VEHICLE_APPROACHING_END_POSTER }, // 转场视频尾帧(结尾遮挡层,挡视频卸载黑帧)
+  { type: 'image', src: CARD_BG_IMG }, // 转场落定后的常态卡片背景
+  { type: 'image', src: DEFAULT_VEHICLE_IMG }, // 车模兜底图
+  { type: 'image', src: TOP_CARD_BG_IMG }, // 发布中/呼叫中顶部卡片背景
+  { type: 'image', src: ROUTE_STATE_ICON_GOLDEN }, // 起点完成态图标
+  { type: 'image', src: ROUTE_STATE_ICON_BLACK }, // 其余站点完成态图标
+  { type: 'image', src: DOTTED_LINE_IMG }, // 站点连接虚线
+  { type: 'image', src: COME_ICON_IMG }, // 接驾中/行程中「车正在赶来」图标
+  { type: 'image', src: PUBLISH_PROGRESS_IMG }, // 发布中进度图标
+];
+
+/** 行程结束转场素材(送驾中 → 行程结束)。礼花为纯 canvas 绘制,无素材。 */
+export const TRIP_FINISHED_ASSETS: PreloadAsset[] = [
+  { type: 'image', src: TRIP_FINISHED_BG_IMG }, // 到达终点卡片背景
+];
diff --git a/src/robotaxi/constants/trip-status.ts b/src/robotaxi/constants/trip-status.ts
index 1766b9e..4d6c0d6 100644
--- a/src/robotaxi/constants/trip-status.ts
+++ b/src/robotaxi/constants/trip-status.ts
@@ -52,6 +52,26 @@
   Completed = 10, 
 }
 
+/**
+ * 取消订单弹窗类型枚举:与历史 number 值保持 1:1 对齐,避免行为变化。
+ */
+export enum CancelType {
+  /** 呼叫车中/发布中取消:预计 N 分钟内有车应答 */
+  CallingCar = 1,
+  /** 接驾中取消:车辆还有 N 分钟/时长到达上车点 */
+  VehicleApproaching = 2,
+  /** 车辆已到达上车点后取消 */
+  VehicleArrivedPickup = 3,
+  /** 订单已取消(终态提示,可再来一单) */
+  OrderCanceled = 4,
+  /** 附近没有空余车辆,已自动取消订单 */
+  NoAvailableVehicle = 5,
+  /** 车辆无法继续服务,正在为您重新派车中 */
+  Redispatching = 6,
+  /** 行程中取消:预计 N 分钟到达目的地 */
+  InService = 7,
+}
+
 const TRIP_STATUS_SET = new Set<number>(Object.values(TripStatus).filter((value) => typeof value === 'number') as number[]); // 行程状态值集合
 const SOCKET_STATUS_SET = new Set<number>(Object.values(SocketOrderStatus).filter((value) => typeof value === 'number') as number[]); // socket 状态值集合
 
diff --git a/src/robotaxi/pages/begin-route/components/begin-route-view-context.tsx b/src/robotaxi/pages/begin-route/components/begin-route-view-context.tsx
index ca80d44..3926ce7 100644
--- a/src/robotaxi/pages/begin-route/components/begin-route-view-context.tsx
+++ b/src/robotaxi/pages/begin-route/components/begin-route-view-context.tsx
@@ -1,5 +1,5 @@
 import { createContext, useContext } from 'react';
-import { TripStatus } from '../../../constants/trip-status';
+import { TripStatus, CancelType } from '../../../constants/trip-status';
 
 /** BeginRoute 视图上下文。 */
 export type BeginRouteViewContextValue = {
@@ -7,14 +7,14 @@
   mapLongitude: number;
   /** 地图纬度。 */
   mapLatitude: number;
-  /** 地图缩放等级。 */
-  mapScale: number;
   /** 地图标记点。 */
   markers: any[];
   /** 地图折线。 */
   polyline: any[];
   /** 是否展示定位按钮。 */
   showLocation: boolean;
+  /** 进入页面地图 loading 占位(网格图)是否显示。 */
+  mapLoading: boolean;
   /** marker 气泡点击。 */
   handleCalloutTap: (e: any) => void;
   /** 地图区域变化。 */
@@ -71,6 +71,16 @@
   walkIcon: string;
   /** 渲染底部卡片。 */
   renderTripCard: () => JSX.Element | null;
+  /** 是否展示优惠券选择弹窗。 */
+  showCouponSelectPopup: boolean;
+  /** 优惠券可用性计算订单金额。 */
+  couponOrderMoney: number;
+  /** 当前生效优惠券 ID。 */
+  activeCouponId: string;
+  /** 关闭优惠券选择弹窗。 */
+  handleCloseCouponSelect: () => void;
+  /** 选择优惠券。 */
+  handleSelectCoupon: (item: any) => void;
   /** 是否展示客服弹窗。 */
   showCustomerService: boolean;
   /** 关闭客服弹窗。 */
@@ -78,7 +88,7 @@
   /** 是否展示取消弹窗。 */
   showCancelPopup: boolean;
   /** 取消类型。 */
-  cancelType: number;
+  cancelType: CancelType;
   /** 取消弹窗等待分钟数。 */
   cancelPopupWaitMinutes: number | null;
   /** 起点。 */
diff --git a/src/robotaxi/pages/begin-route/components/begin-route-view.tsx b/src/robotaxi/pages/begin-route/components/begin-route-view.tsx
index 95c7897..cf872a0 100644
--- a/src/robotaxi/pages/begin-route/components/begin-route-view.tsx
+++ b/src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -1,5 +1,8 @@
 import { View, Map, Image } from '@tarojs/components';
+import { memo, useCallback, useRef } from 'react';
 import CustomerServicePopup from '../../../components/customer-service';
+import CouponSelectPopup from '../../../components/coupon-select-popup';
+import Toast from '../../../components/toast';
 import CancelPopup from './cancel-popup';
 import AnnouncementsPopup from './announcements-popup';
 import OverRoute from './over-route';
@@ -10,46 +13,139 @@
 import PrivacyPopup from '../../../../pages/robotaxi/home/components/privacy-popup';
 import NoRoutePopup from './no-route-popup';
 import { useBeginRouteViewContext } from './begin-route-view-context';
+import { mapPerfLog } from '../hooks/camera/map-perf-log';
+
+// <Map> 的 style 必须是模块级常量:内联对象字面量每次渲染都是新引用,
+// 会让 React 对 map 节点 diff 出变化 → Taro setData → 微信 map 全量重应用
+// latitude/longitude/scale(scale 未显式传则被打回默认值),产生 causedBy:'update' 的视口跳变。
+const MAP_STYLE = { width: '100%', height: '87vh' } as const;
+
+type StableNativeMapProps = {
+  mapLongitude: number;
+  mapLatitude: number;
+  markers: any[];
+  polyline: any[];
+  showLocation: boolean;
+  mapLoading: boolean;
+  renderMapOverlays: () => JSX.Element;
+  onCalloutTap: (e: any) => void;
+  onRegionChange: (e: any) => void;
+  onPolylineTap: (e: any) => void;
+};
+
+const STABLE_NATIVE_MAP_PROP_KEYS: Array<keyof StableNativeMapProps> = [
+  'mapLongitude',
+  'mapLatitude',
+  'markers',
+  'polyline',
+  'showLocation',
+  'mapLoading',
+  'renderMapOverlays',
+  'onCalloutTap',
+  'onRegionChange',
+  'onPolylineTap'
+];
+
+/**
+ * 显式记录 memo 比较结果:关闭业务弹窗时应为 changedProps: [];
+ * 若地图确有业务数据变化,则保留正常重渲染能力并直接打印变化字段。
+ */
+const areStableNativeMapPropsEqual = (
+  previous: StableNativeMapProps,
+  next: StableNativeMapProps
+) => {
+  const changedProps = STABLE_NATIVE_MAP_PROP_KEYS.filter(
+    (key) => previous[key] !== next[key]
+  );
+  // mapPerfLog('native-map:compare', { changedProps });
+  return changedProps.length === 0;
+};
+
+/**
+ * 原生地图渲染边界。
+ *
+ * React.memo 负责跳过业务 props 未变化的 React 渲染;主 Map 保持在页面组件作用域,
+ * 确保页面级 Taro.createMapContext('map') 可以继续执行回中、fit 和轨迹操作。
+ */
+const StableNativeMap = memo(({
+  mapLongitude,
+  mapLatitude,
+  markers,
+  polyline,
+  showLocation,
+  mapLoading,
+  renderMapOverlays,
+  onCalloutTap,
+  onRegionChange,
+  onPolylineTap,
+}: StableNativeMapProps) => {
+  // mapPerfLog('native-map:render', {
+  //   markersCount: markers.length,
+  //   polylineCount: polyline.length,
+  //   mapLoading
+  // });
+  return (
+    <Map
+      id='map'
+      longitude={mapLongitude}
+      latitude={mapLatitude}
+      markers={markers}
+      polyline={polyline}
+      showLocation={showLocation}
+      onCalloutTap={onCalloutTap}
+      onRegionChange={onRegionChange}
+      onPolylineTap={onPolylineTap}
+      style={MAP_STYLE}
+    >
+      {renderMapOverlays()}
+      {mapLoading && <View className='map-placeholder-grid' />}
+    </Map>
+  );
+}, areStableNativeMapPropsEqual);
 
 /**
  * 地图区块。
  */
 function BeginRouteMapArea() {
+  const context = useBeginRouteViewContext();
   const {
     mapLongitude,
     mapLatitude,
-    mapScale,
     markers,
     polyline,
     showLocation,
-    handleCalloutTap,
-    handleRegionChange,
-    onPolylineTap,
-    onMapTap,
+    mapLoading,
     renderMapOverlays,
     renderCarCalloutOverlay,
-  } = useBeginRouteViewContext();
+  } = context;
+
+  // 事件回调稳定化:context 里的回调每次渲染都是新引用,直接透传会让 map 节点
+  // 每次重渲染(弹窗开关等)都 diff 出 updatePayload → setData → 原生全量属性重应用。
+  // 用 ref 持有最新回调、向 <Map> 传恒定引用的包装函数,保证无关重渲染时 diff 为空。
+  const latestHandlersRef = useRef(context);
+  latestHandlersRef.current = context;
+  const stableCalloutTap = useCallback((e: any) => latestHandlersRef.current.handleCalloutTap(e), []);
+  const stableRegionChange = useCallback((e: any) => latestHandlersRef.current.handleRegionChange(e), []);
+  const stablePolylineTap = useCallback((e: any) => latestHandlersRef.current.onPolylineTap?.(e), []);
 
   return (
     <>
-      <Map
-        id='map'
-        longitude={mapLongitude}
-        latitude={mapLatitude}
-        scale={mapScale}
+      <StableNativeMap
+        mapLongitude={mapLongitude}
+        mapLatitude={mapLatitude}
         markers={markers}
         polyline={polyline}
         showLocation={showLocation}
-        onCalloutTap={handleCalloutTap}
-        onRegionChange={handleRegionChange}
-        onPolylineTap={onPolylineTap}
-        // onTap={onMapTap}
-        style={{ width: '100%', height: '87vh' }}
-      >
-        {renderMapOverlays()}
-      </Map>
-      {/* iOS 里程气泡:作为 Map 的兄弟级普通 View 浮层,靠同层渲染浮在地图上,z-index 低于底部面板。 */}
-      {renderCarCalloutOverlay()}
+        mapLoading={mapLoading}
+        renderMapOverlays={renderMapOverlays}
+        onCalloutTap={stableCalloutTap}
+        onRegionChange={stableRegionChange}
+        onPolylineTap={stablePolylineTap}
+      />
+      {/* iOS 里程气泡:作为 Map 的兄弟级普通 View 浮层,靠同层渲染浮在地图上,z-index 低于底部面板。
+          包一层常驻 View:气泡内容 null↔有的切换只重写该容器的 children,
+          避免增删 home-container 下与 <Map> 同级的节点导致 map 属性被全量重应用(scale 重置)。 */}
+      <View>{renderCarCalloutOverlay()}</View>
     </>
   );
 }
@@ -166,6 +262,11 @@
  */
 function BeginRoutePopupArea() {
   const {
+    showCouponSelectPopup,
+    couponOrderMoney,
+    activeCouponId,
+    handleCloseCouponSelect,
+    handleSelectCoupon,
     showCustomerService,
     handleCloseCustomerService,
     showCancelPopup,
@@ -206,7 +307,11 @@
   } = useBeginRouteViewContext();
 
   return (
-    <>
+    <View>
+      {/* 弹窗区常驻 View 容器:弹窗组件(如 CancelPopup)隐藏时 return null,节点会整个挂载/卸载。
+          若直接作为 home-container 的 children,增删节点会让 Taro 整组重发兄弟数组(含 <Map> 节点),
+          微信 map 全量重应用 latitude/longitude/scale(scale 被打回默认)。
+          包住后增删只发生在本容器内部,不再连坐 map。 */}
       <CustomerServicePopup
         show={showCustomerService}
         onClose={handleCloseCustomerService}
@@ -243,13 +348,15 @@
         onClose={handleClosePrivacyPopup}
         onAgree={handleClosePrivacyPopup}
       />
-      {!showCancelPopup && (
-        <PassengerNotice
-          show={showPassengerNotice}
-          onClose={handleClosePassengerNotice}
-          onCheckedChange={changeCheckedEnter}
-        />
-      )}
+      {/* 不能写成 {!showCancelPopup && <PassengerNotice/>}:条件挂载会增删兄弟节点,
+          Taro 对长度变化的 children 数组整组 setData,同级的 <map> 节点数据被捎带重发,
+          原生 map 全量重应用 latitude/longitude/scale(scale 被打回默认)。
+          节点常驻、用 show 控制显隐,保证取消弹窗开关不改变节点结构。 */}
+      <PassengerNotice
+        show={showPassengerNotice && !showCancelPopup}
+        onClose={handleClosePassengerNotice}
+        onCheckedChange={changeCheckedEnter}
+      />
       <EvaluatePopup
         show={showEvaluatePopup}
         orderId={orderId}
@@ -267,7 +374,15 @@
         onClose={handleCloseNoRoutePopup!}
         onConfirm={handleConfirmNoRoutePopup!}
       />
-    </>
+      <CouponSelectPopup
+        show={showCouponSelectPopup}
+        orderMoney={couponOrderMoney}
+        initialSelectedCouponId={activeCouponId}
+        onClose={handleCloseCouponSelect}
+        onSelect={handleSelectCoupon}
+      />
+      <Toast />
+    </View>
   );
 }
 
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.less b/src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.less
index 29854ff..bd12086 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.less
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.less
@@ -12,6 +12,8 @@
   flex-shrink: 0;
   width: 104px;
   height: 64px;
+  /* 图片右侧自带 14px 透明留白,负 margin 抵消,使开关视觉右边界与容器对齐 */
+  margin-right: -14px;
 }
 
 .ac-switch-loading {
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.tsx
index 242e92f..8638302 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/ac-switch/index.tsx
@@ -2,8 +2,11 @@
 import './index.less';
 
 // 图片开关三态图标(复用入口卡片同款)
-const SWITCH_OPEN_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/T-switch-open-20260709.png';
-const SWITCH_CLOSE_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/T-switch-close-20260709.png';
+const SWITCH_CLOSE_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/T-switch-close-20260812.png';
+// 开启态:金色轨道 + 白色滑块(浅色背景)
+const SWITCH_OPEN_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/T-switch-open-20260812.png';
+// 开启态:白色轨道 + 金色滑块(彩色/深色卡片背景)
+const SWITCH_OPEN_WHITE_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/T-switch-open-white-20260812.png';
 const SWITCH_LOADING_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/T-switch-loading-20260715.png';
 
 type Props = {
@@ -15,16 +18,23 @@
     onChange?: () => void;
     /** 自定义 class */
     className?: string;
+    /**
+     * 开启态样式(关闭态两者一致):
+     * - gold(默认):金色轨道 + 白色滑块,用于浅色背景(香氛卡片、弹窗内开关);
+     * - white:白色轨道 + 金色滑块,用于彩色/深色卡片背景(空调入口卡片)。
+     */
+    theme?: 'gold' | 'white';
 };
 
 /**
  * 空调风格图片开关(开/关/loading 三态),供卡片与面板统一复用。
  */
-export default function AcSwitch({ checked, loading = false, onChange, className = '' }: Props) {
+export default function AcSwitch({ checked, loading = false, onChange, className = '', theme = 'gold' }: Props) {
+    const openIcon = theme === 'white' ? SWITCH_OPEN_WHITE_ICON : SWITCH_OPEN_ICON;
     return (
         <Image
             className={`ac-switch${loading ? ' ac-switch-loading' : ''} ${className}`.trim()}
-            src={loading ? SWITCH_LOADING_ICON : (checked ? SWITCH_OPEN_ICON : SWITCH_CLOSE_ICON)}
+            src={loading ? SWITCH_LOADING_ICON : (checked ? openIcon : SWITCH_CLOSE_ICON)}
             onClick={(e: any) => { e?.stopPropagation?.(); onChange && onChange(); }}
         />
     );
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx
index 7359c5a..b56260a 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx
@@ -10,9 +10,11 @@
     descriptionColor?: string;
     /** 是否允许点击(默认 true)。false 时不绑定 onClick */
     clickable?: boolean;
+    /** 蓝牙状态首轮判定是否完成(默认 true)。false 时占位保留高度、内容淡出且不可点,避免异步就绪撑高卡片产生跳变 */
+    ready?: boolean;
 };
 
-export default function BluetoothOperateCard({ description, actionText = '', onClick, iconSrc, descriptionColor = '', clickable = true }: Props) {
+export default function BluetoothOperateCard({ description, actionText = '', onClick, iconSrc, descriptionColor = '', clickable = true, ready = true }: Props) {
     const descriptionStyle = descriptionColor ? { color: descriptionColor } : undefined;
     // 右侧区域渲染规则:
     // - 不可点击(如车端钥匙下发失败)→ 不渲染
@@ -20,7 +22,10 @@
     // - 其他可点击场景("去授权" / 已授权)→ 渲染容器;文字仅在有 actionText 时显示,箭头始终显示
     const shouldRenderRight = clickable && actionText !== '去开启';
     return (
-        <View className="bluetooth-operate-card" onClick={clickable ? onClick : undefined}>
+        <View
+            className={`bluetooth-operate-card${ready ? '' : ' bluetooth-operate-card--pending'}`}
+            onClick={ready && clickable ? onClick : undefined}
+        >
             <View className="bluetooth-operate-card-left">
                 <Image src={iconSrc} />
                 <View style={descriptionStyle}>{description}</View>
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
index 6ddad51..3ad150a 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -4,7 +4,7 @@
 import { unlockVehicle, queryVehicleTrunkOpenStatus } from '../../../../api/begin-route';
 import { getOrderId } from '../../../../utils/order';
 import { TripStatus } from '../../../../constants/trip-status';
-import { showToast } from '../../../../components/toast';
+import { showToast, hideToast } from '../../../../components/toast';
 import useMinimumSpinLoading from './use-minimum-spin-loading';
 import TrunkConfirmPopup from './trunk-confirm-popup';
 
@@ -93,13 +93,13 @@
 
     // 获取解锁请求所需定位参数,优先高精定位,失败后降级普通定位
     const getUnlockLocationPayload = async () => {
-        Taro.showLoading({ title: '正在获取位置...', mask: true });
+        showToast({ title: '正在获取位置...', icon: 'loading', mask: true });
         try {
             const location = await Taro.getLocation({ type: 'gcj02', isHighAccuracy: true });
             const longitude = Number(location?.longitude);
             const latitude = Number(location?.latitude);
             if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
-                Taro.hideLoading();
+                hideToast();
                 return { longitude, latitude };
             }
         } catch (error) {
@@ -111,14 +111,14 @@
             const longitude = Number(location?.longitude);
             const latitude = Number(location?.latitude);
             if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
-                Taro.hideLoading();
+                hideToast();
                 return { longitude, latitude };
             }
         } catch (error) {
             console.error('普通定位获取失败:', error);
         }
 
-        Taro.hideLoading();
+        hideToast();
         return null;
     };
 
@@ -144,7 +144,7 @@
         const actionLoadingText = doorType === 'trunk'
             ? (isCloseAction ? '正在关闭后备箱' : '正在开启后备箱')
             : '正在解锁车门';
-        // Taro.showLoading({ title: actionLoadingText, mask: true });
+        // showToast({ title: actionLoadingText, icon: 'loading', mask: true });
         unlockVehicle({
             orderId: targetOrderId,
             doorType,
@@ -157,7 +157,7 @@
                 ? Number(res?.data) == 1
                 : !!res?.data;
             if (!unlockSucceeded) {
-                // Taro.hideLoading();
+                // hideToast();
                 const failMsg = res?.msg || '解锁失败,请重试或联系客服';
                 stopLoading(doorType, () => {
                     showToast({ title: failMsg, icon: 'fail' });
@@ -187,7 +187,7 @@
                         trunkPollTimerRef.current = setTimeout(doPoll, pollInterval);
                     } else {
                         stopPolling();
-                        // Taro.hideLoading();
+                        // hideToast();
                         stopLoading(doorType, () => {
                             showToast({
                                 title: failToastText,
@@ -222,7 +222,7 @@
                             const isOpen = !isCloseAction;
                             setTrunkStatus(isOpen);
                             onSuccess?.();
-                            // Taro.hideLoading();
+                            // hideToast();
                             stopLoading(doorType, () => {
                                 showToast({
                                     title: successToastText,
@@ -240,7 +240,7 @@
             }
 
             if (doorType === 'door') {
-                // Taro.hideLoading();
+                // hideToast();
                 syncDoorUnlockButtonUI();
                 onSuccess?.();
                 stopLoading(doorType, () => {
@@ -248,7 +248,7 @@
                 });
             }
         }).catch(() => {
-            // Taro.hideLoading();
+            // hideToast();
             stopLoading(doorType, () => {
                 showToast({ title: '解锁失败,请重试或联系客服', icon: 'fail' });
             });
@@ -306,7 +306,7 @@
                 </View>
                 <View className={`car-card-arrive-item ${lockStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={() => handleUnlock('door', onDoorUnlockSuccess)}>
                     <View className="car-card-arrive-item-text">车锁</View>
-                    <View className="car-card-arrive-item-status">{lockStatus ? '车门已解锁' : '点击打开'}</View>
+                    <View className="car-card-arrive-item-status">{lockStatus ? '车门已解锁' : '点击解锁'}</View>
                     {renderControlIcon('door', lockStatus ? LOCK_OPEN_ICON_URL : LOCK_CLOSE_ICON_URL, 'car-card-arrive-item-img')}
                 </View>
             </View>
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx
index 824e7ac..6c7c32b 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx
@@ -33,6 +33,14 @@
     const [isEvaluated, setIsEvaluated] = useState(false); // 是否已完成评价
     const skipEvaluateResetRef = useRef(false); // 跳过关闭重置的一次性标记
 
+    // 订单切换时重置评价态:去掉 CallingCar 的 key 后本组件不再随状态重挂载,
+    // 需显式按 orderId 复位,避免上一单「已评价」状态串到下一单。
+    useEffect(() => {
+        setActiveIndex(0);
+        setIsEvaluated(false);
+        skipEvaluateResetRef.current = false;
+    }, [orderId]);
+
     // 外部提交成功后同步评价状态
     useEffect(() => {
         if (typeof evaluateSubmitVersion === 'number' && evaluateSubmitVersion > 0) {
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/fragrance-control/index.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/fragrance-control/index.tsx
index 32423e4..a2f3892 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/fragrance-control/index.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/fragrance-control/index.tsx
@@ -47,6 +47,8 @@
     pageDidShowVersion?: number;
     /** 后端推送:当前被指派车辆是否仍在执行上一单(连环单) */
     hasPreviousOrder?: boolean;
+    /** 门槛:false 时暂不执行查询/下发逻辑(如派单成功转场动效未完成),卡片仍渲染占位 */
+    active?: boolean;
 };
 
 /**
@@ -85,7 +87,7 @@
  * 只要弹窗位于 scroll-view 子树内,iOS 会把它降级为相对滚动容器定位并裁切(Android 表现正常)。
  * 因此调用方必须把 card 放进 ScrollView、把 popup 放在 ScrollView 之外。
  */
-export function useFragranceControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false }: Props): ControlNodes {
+export function useFragranceControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false, active = true }: Props): ControlNodes {
     // 车端真实状态
     const [switchOn, setSwitchOn] = useState(false);
     const [scents, setScents] = useState<FragranceScent[]>([]);
@@ -111,6 +113,10 @@
 
     // 进入接驾态查询香氛状态;连环单不查询、不展示
     useEffect(() => {
+        // 门槛未就绪(如派单成功转场动效未完成):暂不查询,active 就绪后重跑再查询一次。
+        if (!active) {
+            return;
+        }
         if (tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
             // 离开接驾态:重置去重标记,允许下次进入接驾态时重新查询一次。
             queriedOrderIdRef.current = '';
@@ -145,7 +151,7 @@
             setScents([]);
             setSelectedChannelId('');
         });
-    }, [tripStatus, orderId, pageDidShowVersion, hasPreviousOrder]);
+    }, [tripStatus, orderId, pageDidShowVersion, hasPreviousOrder, active]);
 
     // 点击卡片:切换面板开合
     const togglePanel = () => {
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/music-control/index.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/music-control/index.tsx
index b5670bb..144436d 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/music-control/index.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/music-control/index.tsx
@@ -4,6 +4,7 @@
 import type { GenreItem } from '../../../../../api/types/genre';
 import { getOrderId } from '../../../../../utils/order';
 import { TripStatus } from '../../../../../constants/trip-status';
+import { isMusicControlEnabled } from '../../../../../constants/music-control-feature';
 import useBottomPopupAnimation from '../../hooks/use-bottom-popup-animation';
 import type { ControlNodes } from '../types';
 import AcSwitch from '../ac-switch';
@@ -40,6 +41,8 @@
     pageDidShowVersion?: number;
     /** 后端推送:当前被指派车辆是否仍在执行上一单(连环单) */
     hasPreviousOrder?: boolean;
+    /** 门槛:false 时暂不执行查询/下发逻辑(如派单成功转场动效未完成),卡片仍渲染占位 */
+    active?: boolean;
 };
 
 /**
@@ -65,7 +68,7 @@
  * 只要弹窗位于 scroll-view 子树内,iOS 会把它降级为相对滚动容器定位并裁切(Android 表现正常)。
  * 因此调用方必须把 card 放进 ScrollView、把 popup 放在 ScrollView 之外。
  */
-export function useMusicControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false }: Props): ControlNodes {
+export function useMusicControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false, active = true }: Props): ControlNodes {
     // 车端真实开关状态
     const [switchOn, setSwitchOn] = useState(false);
     // 后台配置曲风列表
@@ -90,9 +93,13 @@
     // 开关 loading 仅在由开关(而非磁贴)触发下发时展示
     const switchLoading = applying && applyingGenreId === '';
 
-    // 进入接驾态查询曲风状态;连环单不查询、不展示
+    // 进入接驾态查询曲风状态;功能开关关闭、连环单不查询、不展示
     useEffect(() => {
-        if (tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
+        // 门槛未就绪(如派单成功转场动效未完成):暂不查询,active 就绪后重跑再查询一次。
+        if (!active) {
+            return;
+        }
+        if (!isMusicControlEnabled() || tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
             // 离开接驾态:重置去重标记,允许下次进入接驾态时重新查询一次。
             queriedOrderIdRef.current = '';
             return;
@@ -126,7 +133,7 @@
             setGenres([]);
             setSelectedGenreId('');
         });
-    }, [tripStatus, orderId, pageDidShowVersion, hasPreviousOrder]);
+    }, [tripStatus, orderId, pageDidShowVersion, hasPreviousOrder, active]);
 
     // 点击卡片:切换面板开合
     const togglePanel = () => {
@@ -202,8 +209,8 @@
         applyGenre(nextSwitchOn, targetGenreId, '');
     };
 
-    // 连环单或非接驾态不展示音乐卡片
-    if (tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
+    // 功能开关关闭、连环单或非接驾态不展示音乐卡片
+    if (!isMusicControlEnabled() || tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
         return { card: null, popup: null };
     }
 
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/seat-hv-control/index.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/seat-hv-control/index.tsx
index 71c8ec2..46f0289 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/seat-hv-control/index.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/seat-hv-control/index.tsx
@@ -50,6 +50,10 @@
  */
 export default function SeatHvArea({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false, active = true }: Props) {
     const [seats, setSeats] = useState<SeatHvItem[]>([]);
+    // 座椅状态查询是否已出结果(成功或失败)。未出结果前整块透明,避免先闪「不可用」再切成可用
+    const [queried, setQueried] = useState(false);
+    // 座舱背景图是否加载完成(用于背景渐现;一旦加载过就不再重置,重开面板直接显示)
+    const [bgLoaded, setBgLoaded] = useState(false);
     // 正在下发的按钮标识 `${position}:${kind}`(该按钮显示 loading,其余按钮置灰防重复),空串表示无
     const [applyingKey, setApplyingKey] = useState('');
     const querySeqRef = useRef(0);
@@ -82,11 +86,13 @@
             }
             const list: SeatHvItem[] = Array.isArray(res?.seats) ? res.seats : [];
             setSeats(list);
+            setQueried(true);
         }).catch(() => {
             if (querySeqRef.current !== snapshotSeq) {
                 return;
             }
             setSeats([]);
+            setQueried(true);
         });
     }, [active, tripStatus, orderId, pageDidShowVersion, hasPreviousOrder]);
 
@@ -141,13 +147,24 @@
         }
     };
 
+    // 查询未出结果:整块透明占位(只占据区域高度、不画任何内容),避免先闪一下「不可用」
+    if (!queried) {
+        return <View className='seat-hv-area'></View>;
+    }
+
     if (!hasSeats) {
         return <View className='seat-hv-empty'>{NO_SEAT_TIP}</View>;
     }
 
     return (
         <View className='seat-hv-area'>
-            <Image className='seat-hv-bg' src={SEAT_BG_URL} mode='widthFix' />
+            <Image
+                className={`seat-hv-bg${bgLoaded ? ' is-loaded' : ''}`}
+                src={SEAT_BG_URL}
+                mode='widthFix'
+                onLoad={() => setBgLoaded(true)}
+                onError={() => setBgLoaded(true)}
+            />
             <View className='seat-hv-list'>
                 {seats.map((seat) => {
                     const showVentilation = seat.ventilationLevels > 0;
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
index c12ed3e..566c04e 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
@@ -24,7 +24,10 @@
 
 // 入口卡片图标(复用 temperature-fragrance-control 的 v2 资源)
 const AC_HEADER_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/air-conditioner-20260716.png';
+// 开启态入口卡片图标与箭头:白色版本(配合白色文字与开启态背景)
+const AC_HEADER_ICON_WHITE = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/air-conditioner-white-20260812.png';
 const AC_ARROW_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/temperature-fragrance-arrow-20260706.png';
+const AC_ARROW_ICON_WHITE = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/arrow-white-20260812.png';
 const PANEL_CLOSE_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/close-icon.png';
 const AC_TOGGLE_FAIL_TEXT = '空调开关操作失败,请重试';
 const AC_TEMP_FAIL_TEXT = '温度设置失败,请重试';
@@ -32,7 +35,7 @@
 const AC_TEMP_MAX_TIP_TEXT = '已到空调最高设定温度';
 const TEMP_ITEM_WIDTH = 130; // 温度滑条单项宽度(所有项等宽)
 const TEMP_SCROLL_SETTLE_MS = 120; // 滑动停止判定延时
-const TEMP_LEFT = 10; // 滚动位移误差(适配中间放大效果)
+const TEMP_LEFT = 20; // 滚动位移误差(适配中间放大效果)
 
 type Props = {
     tripStatus: TripStatus;
@@ -40,6 +43,8 @@
     pageDidShowVersion?: number;
     /** 后端推送:当前被指派车辆是否仍在执行上一单(连环单) */
     hasPreviousOrder?: boolean;
+    /** 门槛:false 时暂不执行查询/下发逻辑(如派单成功转场动效未完成),卡片仍渲染占位 */
+    active?: boolean;
 };
 
 /**
@@ -48,7 +53,7 @@
  * 只要弹窗位于 scroll-view 子树内,iOS 会把它降级为相对滚动容器定位并裁切(Android 表现正常)。
  * 因此调用方必须把 card 放进 ScrollView、把 popup 放在 ScrollView 之外。
  */
-export function useAcControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false }: Props): ControlNodes {
+export function useAcControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false, active = true }: Props): ControlNodes {
     const [tempStatus, setTempStatus] = useState<number | null>(null);
     const [acSwitchOn, setAcSwitchOn] = useState(false);
     const [acStatusLoaded, setAcStatusLoaded] = useState(false);
@@ -290,6 +295,11 @@
     };
 
     useEffect(() => {
+        // 门槛未就绪(如派单成功转场动效未完成):暂不查询/下发,避免转场中提前开空调、下发指令。
+        // 不重置去重标记,active 就绪后本 effect 重跑再正常查询一次。
+        if (!active) {
+            return;
+        }
         if (tripStatus !== TripStatus.VehicleApproaching) {
             // 离开接驾态:重置去重标记,允许下次进入接驾态时重新查询一次。
             queriedOrderIdRef.current = '';
@@ -323,7 +333,7 @@
         }
 
         handleFirstTimeInit(targetOrderId);
-    }, [tripStatus, orderId, pageDidShowVersion, hasPreviousOrder]);
+    }, [tripStatus, orderId, pageDidShowVersion, hasPreviousOrder, active]);
 
     useEffect(() => {
         lastConfirmedTempRef.current = tempStatus;
@@ -470,10 +480,11 @@
             >
                 <View className='tf-card-header'>
                     <View className='tf-card-header-left'>
-                        <Image className='tf-card-header-icon' src={AC_HEADER_ICON} />
+                        <Image className='tf-card-header-icon' src={acSwitchOn ? AC_HEADER_ICON_WHITE : AC_HEADER_ICON} />
                         <Text className='tf-card-header-title'>空调</Text>
                     </View>
-                    <AcSwitch checked={acSwitchOn} loading={isTogglingAc} onChange={handleToggleAcSwitch} />
+                    {/* 空调卡片为彩色背景,开启态用白色轨道 + 金色滑块 */}
+                    <AcSwitch checked={acSwitchOn} loading={isTogglingAc} onChange={handleToggleAcSwitch} theme='white' />
                 </View>
                 <View className='tf-card-bottom'>
                     {acStatusLoaded && (
@@ -484,7 +495,7 @@
                     )}
                     <View className='tf-card-footer'>
                         <Text className='tf-card-footer-text'>调节温度|通风加热</Text>
-                        <Image className='tf-card-footer-arrow' src={AC_ARROW_ICON} />
+                        <Image className='tf-card-footer-arrow' src={acSwitchOn ? AC_ARROW_ICON_WHITE : AC_ARROW_ICON} />
                     </View>
                 </View>
             </View>
@@ -532,7 +543,7 @@
                                                     onClick={() => handleTempItemClick(index, value)}
                                                 >
                                                     <Text className='popup-temp-item-value'>{value.toFixed(1)}</Text>
-                                                    {selected && <Text className='popup-temp-item-unit'>℃</Text>}
+                                                    {selected && <Text className='popup-temp-item-unit'>°C</Text>}
                                                 </View>
                                             );
                                         })}
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
index d4c5c37..ef1da4d 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
@@ -3,6 +3,7 @@
 import { useEffect, useState } from 'react';
 import { queryOrderQueueStatus } from '../../../../api/begin-route';
 import { TripStatus } from '../../../../constants/trip-status';
+import SiteRouteCard from '../../../../components/site-route-card';
 
 type QueueStatus = {
   currentPosition: number;
@@ -141,9 +142,9 @@
   const endSiteName = cachedEndSite?.name || cachedEndSite?.address || '-';
 
   // 状态栏右侧的距离/时间文案
-  const distanceText = routeDistance ? `${routeDistance.value}${routeDistance.unit === 'm' ? 'M' : 'KM'}` : '';
-  const approachingTimeText = `${normalizedDurationDisplay.value}MIN`;
-  const inServiceTimeText = `${normalizedDurationDisplay.value}MIN`;
+  const distanceText = routeDistance ? `${routeDistance.value}${routeDistance.unit === 'm' ? '米' : '公里'}` : '';
+  const approachingTimeText = `${normalizedDurationDisplay.value}分钟`;
+  const inServiceTimeText = `${normalizedDurationDisplay.value}分钟`;
 
   return (
     <>
@@ -153,8 +154,8 @@
           <View className='calling-car-content'>
             <View className='calling-car-content-main'>
               <View className='calling-car-content-title calling-car-content-title--strong'>
-                {/* 预计{displayedWaitMinutes ?? '-'}分钟内有车应答 */}
-                预计{waitMinutes}分钟内有车应答
+                预计{displayedWaitMinutes ?? '-'}分钟内有车应答
+                {/* 预计{waitMinutes}分钟内有车应答 */}
               </View>
               <View className='calling-car-content-desc'>
                 正全力为您叫车中...
@@ -168,24 +169,24 @@
             </View>
             <View
               className='calling-car-cancel-btn'
-              // onClick={() => handleOpenCancelPopup?.({ durationDisplay, waitMinutes: displayedWaitMinutes })}
-              onClick={() => handleOpenCancelPopup?.({ durationDisplay, waitMinutes })}
+              onClick={() => handleOpenCancelPopup?.({ durationDisplay, waitMinutes: displayedWaitMinutes })}
+              // onClick={() => handleOpenCancelPopup?.({ durationDisplay, waitMinutes })}
             >
               取消行程
             </View>
           </View>
-          <View className='calling-car-site-box'>
-            <View className='calling-car-site-box-icon'>
-              <Image className='calling-car-site-check'src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-golden-20260702.png' ></Image>
-              <Image className='dotted-line' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png' />
-              <Image className='calling-car-site-check' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-black-20260702.png'></Image>
-            </View>
-            <View className='calling-car-site-box-content'>
-              <View className='calling-car-site-box-content-name'>{startSiteName}</View>
-              <View className='calling-car-site-box-divider'></View>
-              <View className='calling-car-site-box-content-name'>{endSiteName}</View>
-            </View>
-          </View>
+          <SiteRouteCard
+            className='calling-car-site-box'
+            markerType='check'
+            checkIconUrls={[
+              'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-golden-20260702.png',
+              'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-black-20260702.png'
+            ]}
+            sites={[
+              { name: startSiteName },
+              { name: endSiteName }
+            ]}
+          />
         </>
       )}
 
@@ -204,7 +205,9 @@
       {/* 车辆到达上车点:直接展示图片 */}
       {tripStatus === TripStatus.VehicleArrivedPickup && (
         <View className='calling-car-status-box calling-car-arrived-img'>
-          <View className='calling-car-status-box-left'>车辆已到达上车点,倒计时结束将自动取消订单</View>
+          <View className='calling-car-status-box-left'>
+            {doorUnlockedInCurrentPage ? '车门已开启,请尽快上车开启行程' : '车辆已到达上车点,倒计时结束将自动取消订单'}
+          </View>
         </View>
       )}
 
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx
index 65615e1..437cc67 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx
@@ -1,6 +1,9 @@
-import { View } from "@tarojs/components";
+import Taro from "@tarojs/taro";
+import { View, Image } from "@tarojs/components";
+import { TripStatus } from "../../../../constants/trip-status";
 
 type Props = {
+    tripStatus?: TripStatus;
     vehicleInfo?: {
         vehiclePictures?: string;
         vehicleCode?: string;
@@ -9,20 +12,33 @@
     };
 };
 
-export default function VehicleInfoCard({ vehicleInfo }: Props) {
+export default function VehicleInfoCard({ tripStatus, vehicleInfo }: Props) {
+    // 行程中(前往目的地):车牌信息下方展示目的地图标 + 站点名称
+    const showDestination = tripStatus === TripStatus.InService;
+    const cachedEndSite = showDestination ? (Taro.getStorageSync('endSite') || {}) : {};
+    const endSiteName = cachedEndSite?.name || cachedEndSite?.address || '-';
+
     // 展示车辆基础信息:左侧车牌·车身颜色,右侧车型
     return (
-        <View className="car-card-detail">
-            <View className="car-card-detail-plate">
-                <View className="car-card-detail-num">{vehicleInfo?.plateNumber}</View>
-                {vehicleInfo?.vehicleColor && (
-                    <>
-                        <View className="car-card-detail-dot"></View>
-                        <View className="car-card-detail-color-text">{vehicleInfo?.vehicleColor}</View>
-                    </>
-                )}
+        <View className="car-card-detail-wrap">
+            <View className="car-card-detail">
+                <View className="car-card-detail-plate">
+                    <View className="car-card-detail-num">{vehicleInfo?.plateNumber}</View>
+                    {vehicleInfo?.vehicleColor && (
+                        <>
+                            <View className="car-card-detail-dot"></View>
+                            <View className="car-card-detail-color-text">{vehicleInfo?.vehicleColor}</View>
+                        </>
+                    )}
+                </View>
+                <View className="car-card-detail-model">小鹏GX</View>
             </View>
-            <View className="car-card-detail-model">小鹏GX</View>
+            {showDestination && (
+                <View className='car-card-detail-destination'>
+                    <Image className='car-card-detail-destination-icon' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/destination-icon-20260812.png' />
+                    <View className='car-card-detail-destination-name'>{endSiteName}</View>
+                </View>
+            )}
         </View>
     );
 }
diff --git a/src/robotaxi/pages/begin-route/components/calling-car.tsx b/src/robotaxi/pages/begin-route/components/calling-car.tsx
index 8de85be..eb8d4bc 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -1,9 +1,12 @@
-import { View, Image, ScrollView } from "@tarojs/components";
+import { View, Image } from "@tarojs/components";
 import { useState, useEffect, useRef } from 'react';
 import './css-folder/calling-car.less'
 import { formatRemainingDurationDisplay } from '../../../utils/format';
 import { safeParse, formatTripDistance } from '../../../../utils/Robotaxi-public-method';
+import { useDispatchTransition } from '../hooks/use-dispatch-transition';
+import DispatchTransitionLayer from './dispatch-transition-layer';
 import { TripStatus } from '../../../constants/trip-status';
+import { CARD_BG_IMG, DEFAULT_VEHICLE_IMG, TRIP_FINISHED_BG_IMG, USE_DYNAMIC_VEHICLE_IMG } from '../../../constants/assets';
 import { BLUETOOTH_ICON_OFF } from '../hooks/use-bluetooth-access'; // 蓝牙未开/未授权图标
 import TripStatusHeader from './calling-car-modules/trip-status-header'; // 行程状态头
 import VehicleInfoCard from './calling-car-modules/vehicle-info-card'; // 车辆信息卡片
@@ -21,6 +24,14 @@
 type Props = {
     /** 行程状态:3呼叫中、4即将到达、5已到达上车点、6行程中、7到达终点、8待支付、9已支付 */
     tripStatus?: TripStatus;
+    /** 是否在本次挂载时播放"派单成功"转场(由父层捕捉首次进入派单成功(4)边沿传入,因本组件被 key 重挂载无法自判) */
+    playDispatchTransition?: boolean;
+    /** 是否在本次挂载时播放"直达待评价"入场(由父层捕捉 送驾中(6)→待评价(9) 边沿传入,内部免费订单跳过待支付时使用) */
+    playEvaluateEnter?: boolean;
+    /** 是否在本次挂载时播放"行程结束"入场(背景卡升起 + 里程渐显);由父层捕捉进入行程结束(8)边沿传入,恢复态不播放 */
+    playFinishedEnter?: boolean;
+    /** 派单成功转场动效完成(或本次不播放转场)时回调,用于放行页面级蓝牙初始化 */
+    onDispatchTransitionSettled?: () => void;
     /** 打开取消弹窗,入参为预计到达展示文案 */
     handleOpenCancelPopup?: (durationDisplay: any) => void;
     /** 上车点超时后的回调 */
@@ -88,6 +99,8 @@
     orderTrunkStatus?: boolean;
     /** 当前页面内是否已经解锁过车门 */
     doorUnlockedInCurrentPage?: boolean;
+    /** 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮 */
+    forceShowCancelTrip?: boolean;
     /** 手动开门成功后同步页面级开门状态 */
     onDoorUnlockSuccess?: () => void;
     bluetoothStatusReady?: boolean;
@@ -111,6 +124,10 @@
 
 export default function CallingCar({
     tripStatus = TripStatus.CallingCar,
+    playDispatchTransition = false,
+    playEvaluateEnter = false,
+    playFinishedEnter = false,
+    onDispatchTransitionSettled,
     handleOpenCancelPopup,
     handleCancelTimeout,
     handleFinishTimeout,
@@ -143,6 +160,7 @@
     hasPreviousOrder = false,
     orderTrunkStatus,
     doorUnlockedInCurrentPage = false,
+    forceShowCancelTrip = false,
     onDoorUnlockSuccess,
     bluetoothStatusReady = false,
     bluetoothCardDescription = '授权并打开蓝牙链接,享受无感出行',
@@ -168,31 +186,83 @@
     const isArriveSoon = tripStatus === TripStatus.VehicleApproaching && (typeof routeDurationSeconds === 'number' && routeDurationSeconds <= 60);
     /** 上车倒计时即将结束(<60秒) */
     const isPickupCountdownSoon = tripStatus === TripStatus.VehicleArrivedPickup && (typeof pickupCountdownSeconds === 'number' && pickupCountdownSeconds < 60);
-    /** 开门后,接驾态不再展示取消行程。 */
-    const showCancelTrip = [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) && !doorUnlockedInCurrentPage;
+    /** 开门后,接驾态不再展示取消行程;但 ORDER_ROUTE 推送 oddOut === 1 时强制展示。 */
+    const showCancelTrip = forceShowCancelTrip
+        || ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) && !doorUnlockedInCurrentPage);
+
+    // 派单成功转场状态机:phase 状态、定时器、视频事件均由 useDispatchTransition 管理
+    const {
+        dispatchPhase,
+        transitionDone,
+        shouldPlayEnter,
+        endCoverVisible,
+        handleDispatchVideoTimeUpdate,
+        enterSettle,
+    } = useDispatchTransition(playDispatchTransition);
+
+    // 派单成功转场门槛:未播转场时立即就绪;播转场时等动效完成(transitionDone)。
+    // 用于延迟车控/蓝牙卡/操作栏的「展示」与「逻辑执行」,避免转场中触发弹窗与提前下发指令。
+    const contentReady = !shouldPlayEnter || transitionDone;
 
     /** 是否为“到达终点/待支付”状态(图一、图二) */
     const isTripFinished = tripStatus === TripStatus.TripFinished;
     const isCallingCar = tripStatus === TripStatus.CallingCar;
     /**
+     * 「直达待评价」入场:与 useDispatchTransition 里的 shouldPlayEnter 同理,
+     * 父层的 playEvaluateEnter 在下一次渲染即翻 false,直接绑定会把动画类中途卸掉、动效被扼杀。
+     * 过去依赖 key={tripStatus} 重挂载做挂载快照;去 key 后组件不再重挂载,
+     * 改为在渲染期同步捕捉 playEvaluateEnter 上升沿置类(避免先渲染一帧静止态再跳到位移起点的闪跳),
+     * 播放一次后按动画时长(0.7s)清除类名——最终态与静止态一致无跳变,且使下一次 6→9 能重新触发。
+     */
+    const [shouldPlayEvaluateEnter, setShouldPlayEvaluateEnter] = useState(false);
+    const prevPlayEvaluateRef = useRef(false);
+    const evaluateEnterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+    if (playEvaluateEnter && !prevPlayEvaluateRef.current) {
+        prevPlayEvaluateRef.current = true;
+        if (!shouldPlayEvaluateEnter) setShouldPlayEvaluateEnter(true);
+    } else if (!playEvaluateEnter && prevPlayEvaluateRef.current) {
+        prevPlayEvaluateRef.current = false;
+    }
+    useEffect(() => {
+        if (!shouldPlayEvaluateEnter) return;
+        if (evaluateEnterTimerRef.current) clearTimeout(evaluateEnterTimerRef.current);
+        evaluateEnterTimerRef.current = setTimeout(() => setShouldPlayEvaluateEnter(false), 750);
+        return () => {
+            if (evaluateEnterTimerRef.current) clearTimeout(evaluateEnterTimerRef.current);
+        };
+    }, [shouldPlayEvaluateEnter]);
+    /**
+     * 「行程结束」入场:与上面的 shouldPlayEvaluateEnter 完全同理(渲染期捕捉上升沿 → 置类 → 按动画时长清类)。
+     * 父层 playFinishedEnter 仅在真实 live 跃迁进入行程结束(8)时为真;恢复态(从首页进入即已是结束态)恒为 false,
+     * 此时不加 calling-car--finished-enter 类,结束态背景卡与里程直接显示最终态、不再弹起。
+     * 背景卡与里程升起:延迟 700ms + 时长 1s = 1700ms 才结束,故清类须 ≥1700ms,否则会中途移除类
+     * 导致动画被截断、瞬间 snap 到最终态(观感为「快结束时卡顿」)。取 1850ms 留缓冲。
+     */
+    const [shouldPlayFinishedEnter, setShouldPlayFinishedEnter] = useState(false);
+    const prevPlayFinishedRef = useRef(false);
+    const finishedEnterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+    if (playFinishedEnter && !prevPlayFinishedRef.current) {
+        prevPlayFinishedRef.current = true;
+        if (!shouldPlayFinishedEnter) setShouldPlayFinishedEnter(true);
+    } else if (!playFinishedEnter && prevPlayFinishedRef.current) {
+        prevPlayFinishedRef.current = false;
+    }
+    useEffect(() => {
+        if (!shouldPlayFinishedEnter) return;
+        if (finishedEnterTimerRef.current) clearTimeout(finishedEnterTimerRef.current);
+        finishedEnterTimerRef.current = setTimeout(() => setShouldPlayFinishedEnter(false), 1850);
+        return () => {
+            if (finishedEnterTimerRef.current) clearTimeout(finishedEnterTimerRef.current);
+        };
+    }, [shouldPlayFinishedEnter]);
+    /**
      * 车控(空调 / 音乐 / 香氛):卡片与弹窗分开挂载。
-     * 卡片放进横向滑动的 ScrollView;弹窗是 fixed 全屏浮层,必须挂在 ScrollView 之外,
-     * 否则 iOS(WKWebView)下滚动容器会成为 fixed 的包含块,弹窗被限制在卡片滑动区内。
+     * 卡片放进 2 列网格容器;弹窗是 fixed 全屏浮层,单独挂在网格容器之外,避免受父容器定位影响。
      * 三个 hook 内部均按 tripStatus / hasPreviousOrder 守卫,非接驾态不发请求、返回空节点。
      */
-    const acControl = useAcControl({ tripStatus, orderId, pageDidShowVersion, hasPreviousOrder });
-    const musicControl = useMusicControl({ tripStatus, orderId, pageDidShowVersion, hasPreviousOrder });
-    const fragranceControl = useFragranceControl({ tripStatus, orderId, pageDidShowVersion, hasPreviousOrder });
-    /**
-     * 车控卡片当前横向滚动位置。
-     * 弹窗开合与其弹出动画会驱动本组件重渲染,weapp 的 scroll-view 在重渲染 / 重建后会回到
-     * 初始位置(最左),表现为「弹窗一出现卡片就滑回去了」。这里用 ref 记录实时位置并回传
-     * scroll-left,使重渲染后停留在原处;用 ref 而非 state 是为了滚动过程中不触发额外渲染。
-     */
-    const cardScrollLeftRef = useRef(0);
-    const handleCardScroll = (e: any) => {
-        cardScrollLeftRef.current = e?.detail?.scrollLeft ?? 0;
-    };
+    const acControl = useAcControl({ tripStatus, orderId, pageDidShowVersion, hasPreviousOrder, active: contentReady });
+    const musicControl = useMusicControl({ tripStatus, orderId, pageDidShowVersion, hasPreviousOrder, active: contentReady });
+    const fragranceControl = useFragranceControl({ tripStatus, orderId, pageDidShowVersion, hasPreviousOrder, active: contentReady });
     /** 费用明细是否展开(拦截 FareDetailCard 的回调本地记录,用于切换背景与卡片布局) */
     const [detailExpanded, setDetailExpanded] = useState(false);
     /** 大号里程文案:取全程里程,单位参考 formatTripDistance(米→M,公里→KM) */
@@ -202,6 +272,13 @@
     useEffect(() => {
         setDetailExpanded(false);
     }, [resetDetailVersion, tripStatus]);
+
+    // 转场动效完成(或本次未播放转场)后通知父层:与车控卡片同步放行页面级蓝牙初始化。
+    useEffect(() => {
+        if (contentReady) {
+            onDispatchTransitionSettled?.();
+        }
+    }, [contentReady, onDispatchTransitionSettled]);
 
     // 拦截费用明细展开/收起回调:本地记录展开态后再透传给外部
     const handleFareToggle = (expanded: boolean, hasFare: boolean) => {
@@ -216,22 +293,40 @@
         return <Image className='calling-car-bg carlling-bg'  src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/top-card-bg-20260722.png' />
       }
       if(isTripFinished) {
-        return <Image className='calling-car-bg finished-bg'  src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/trip-finished-20260703.png' />
+        return <Image className='calling-car-bg finished-bg'  src={TRIP_FINISHED_BG_IMG} />
       }
-      return  <Image className='calling-car-bg' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/card-bg-20260714.png' />
+      // 派单转场中(挂载首帧 ~ 视频播放完成前):不返回任何背景图,杜绝转场期间出现静态背景图。
+      // 常态背景图改在 DispatchTransitionLayer 中不可见预载。settle 起在此返回背景图(视频层仍盖在其上,
+      // 600ms 后随 transitionDone 卸载、直切露出背景图,此时图片已预载、无加载间隙)。
+      if (shouldPlayEnter && !transitionDone && dispatchPhase !== 'settle') {
+        return null;
+      }
+      return  <Image className='calling-car-bg' src={CARD_BG_IMG} />
     }
-    // 根节点状态类:待支付态 + 明细展开态
-    const rootClassName = `calling-car${isTripFinished ? ' calling-car--finished' : ''}${isTripFinished && detailExpanded ? ' is-detail-expanded' : ''} ${isCallingCar ? 'calling-status-car' : ''}`;
+    // 根节点状态类:呼叫中覆盖 + 待支付态 + 明细展开态 + 派单转场阶段 + 整卡升起入场 + 直达待评价入场
+    const rootClassName = `calling-car${isCallingCar ? ' calling-status-car' : ''}${isTripFinished ? ' calling-car--finished' : ''}${isTripFinished && detailExpanded ? ' is-detail-expanded' : ''}${dispatchPhase !== 'idle' ? ` dispatch-${dispatchPhase}` : ''}${shouldPlayEnter ? ' calling-car--enter' : ''}${shouldPlayEvaluateEnter ? ' calling-car--evaluate-enter' : ''}${shouldPlayFinishedEnter ? ' calling-car--finished-enter' : ''}${!contentReady ? ' dispatch-defer-content' : ''}`;
 
     // 按状态组合渲染行程卡片模块
     return (
         <View className={rootClassName}>
           {getCardBgImg()}
+          <DispatchTransitionLayer
+            shouldPlayEnter={shouldPlayEnter}
+            transitionDone={transitionDone}
+            dispatchPhase={dispatchPhase}
+            endCoverVisible={endCoverVisible}
+            vehicleInfo={vehicleInfo}
+            onTimeUpdate={handleDispatchVideoTimeUpdate}
+            onEnded={enterSettle}
+            onError={enterSettle}
+          />
           {
-            [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.PaymentSuccess].includes(tripStatus) && vehicleInfo?.plateNumber &&
-            <Image
+            // 常驻车图:接驾/行程中/支付成功且有车牌时展示。转场期间车模兜底由 DispatchTransitionLayer 渲染。
+            ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.PaymentSuccess].includes(tripStatus) && vehicleInfo?.plateNumber) && <Image
               className='calling-car-vehicle-pic'
-              src={safeParse(vehicleInfo?.vehiclePictures, {})?.url || 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/default-vehicle-img-20260724-v2.png'}
+              // 当前只有一种车型,产品要求统一用默认车图(USE_DYNAMIC_VEHICLE_IMG=false);
+              // 后续有新车型时把该开关改回 true 即恢复接口动态车图。详见 constants/assets.ts。
+              src={USE_DYNAMIC_VEHICLE_IMG ? (safeParse(vehicleInfo?.vehiclePictures, {})?.url || DEFAULT_VEHICLE_IMG) : DEFAULT_VEHICLE_IMG}
             />
           }
           <View className='calling-car-content-box'>
@@ -244,7 +339,7 @@
             }
             {
               [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.PaymentSuccess].includes(tripStatus) &&
-              <VehicleInfoCard vehicleInfo={vehicleInfo} />
+              <VehicleInfoCard tripStatus={tripStatus} vehicleInfo={vehicleInfo} />
             }
             <TripStatusHeader
               orderId={orderId}
@@ -264,8 +359,9 @@
             />
 
             {
-                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) && bluetoothStatusReady &&
+                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) &&
                 <BluetoothOperateCard
+                  ready={bluetoothStatusReady}
                   description={bluetoothCardDescription}
                   actionText={bluetoothCardActionText}
                   onClick={handleBluetoothCardClick || (() => { })}
@@ -295,23 +391,15 @@
             {
                 [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(tripStatus) &&
                 <View className='car-card'>
-                    {/* 车控卡片横向滑动(仅接驾态展示):weapp 中普通 View 的 overflow 滚动无法响应拖动,必须用 ScrollView。
-                        注意:这里只放卡片,三个控制弹窗在下方 ScrollView 之外单独挂载(iOS fixed 会被滚动容器裁切) */}
+                    {/* 车控卡片(仅接驾态展示):2 列网格布局,列间隔 32px,超过 2 个自动换行。
+                        注意:这里只放卡片,三个控制弹窗在下方单独挂载(弹窗为 fixed 全屏浮层) */}
                     {
                         TripStatus.VehicleApproaching === tripStatus &&
-                        <ScrollView
-                          className='temp-fragrance-scroll'
-                          scrollX
-                          showScrollbar={false}
-                          scrollLeft={cardScrollLeftRef.current}
-                          onScroll={handleCardScroll}
-                        >
-                          <View className='temp-fragrance-control'>
-                            {acControl.card}
-                            {musicControl.card}
-                            {fragranceControl.card}
-                          </View>
-                        </ScrollView>
+                        <View className='temp-fragrance-control'>
+                          {acControl.card}
+                          {musicControl.card}
+                          {fragranceControl.card}
+                        </View>
                     }
 
                     <DoorTrunkControl
@@ -337,6 +425,22 @@
                 </View>
             }
 
+            {
+                /* 行程操作栏:位于车控卡片之后、门箱控制之前(对齐设计稿)。
+                   到达终点/已支付状态下 TripActionBar 内部按钮集合为空、自动返回 null,不占位。 */
+                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus) &&
+                <TripActionBar
+                  tripStatus={tripStatus}
+                  handleCancelTrip={handleCancelTrip}
+                  showCancelTrip={showCancelTrip}
+                  showShareTrip={showShareTrip}
+                  handleModifyDestination={handleModifyDestination}
+                  handleModifyDestinationDisabledClick={handleModifyDestinationDisabledClick}
+                  showModifyDestination={showModifyDestination}
+                  handleShareTrip={handleShareTrip}
+                />
+            }
+
             {/* 车控弹窗(空调 / 音乐 / 香氛):必须挂在横向滑动 ScrollView 之外。
                 iOS(WKWebView)下 scroll-view 的滚动容器会成为 fixed 子元素的包含块,
                 弹窗若留在 ScrollView 内会被限制、裁切在卡片滑动区里(Android 无此问题)。
@@ -359,20 +463,6 @@
                       onOpenCouponSelect={onOpenCouponSelect}
                     />
                 </View>
-            }
-            {
-                /* 行程操作栏:位于费用明细卡之后、门箱控制之前(对齐设计稿) */
-                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus) &&
-                <TripActionBar
-                  tripStatus={tripStatus}
-                  handleCancelTrip={handleCancelTrip}
-                  showCancelTrip={showCancelTrip}
-                  showShareTrip={showShareTrip}
-                  handleModifyDestination={handleModifyDestination}
-                  handleModifyDestinationDisabledClick={handleModifyDestinationDisabledClick}
-                  showModifyDestination={showModifyDestination}
-                  handleShareTrip={handleShareTrip}
-                />
             }
             <DoorTrunkControl
                 tripStatus={tripStatus === TripStatus.ArrivedDestination ? TripStatus.ArrivedDestination : -1}
diff --git a/src/robotaxi/pages/begin-route/components/cancel-popup.tsx b/src/robotaxi/pages/begin-route/components/cancel-popup.tsx
index 4452754..6559690 100644
--- a/src/robotaxi/pages/begin-route/components/cancel-popup.tsx
+++ b/src/robotaxi/pages/begin-route/components/cancel-popup.tsx
@@ -2,17 +2,18 @@
 import { View, Text, Image } from '@tarojs/components';
 import { useEffect, useState } from 'react';
 import './css-folder/cancel-popup.less'
+import SiteRouteCard from '../../../components/site-route-card';
 import { formatRemainingDurationDisplay } from '../../../utils/format';
 import {
     clearOrderTripStorage
 } from '../../../../utils/Robotaxi-public-method'
-import { TripStatus } from '../../../constants/trip-status';
+import { TripStatus, CancelType } from '../../../constants/trip-status';
 import useBottomPopupAnimation from './hooks/use-bottom-popup-animation';
 
 
 type Props = {
     show?: boolean;
-    cancelType?: number;
+    cancelType?: CancelType;
     startSite?: any;
     endSite?: any;
     waitMinutes?: number;
@@ -27,7 +28,7 @@
 const CANCEL_ANIMATION_WEBP = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/cancel-img20260731.png?x-oss-process=image/format,webp';
 const CANCEL_ANIMATION_PNG = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/cancel-img20260731.png';
 
-export default function CancelPopup({ show = false, cancelType = 1, startSite, endSite, waitMinutes, tripStatus, routeDurationSeconds, discountAmount, onClose, onConfirm }: Props) {
+export default function CancelPopup({ show = false, cancelType = CancelType.CallingCar, startSite, endSite, waitMinutes, tripStatus, routeDurationSeconds, discountAmount, onClose, onConfirm }: Props) {
     const { visible, animationData } = useBottomPopupAnimation(show);
     // 占位动画当前使用的图片地址,默认 webp,加载失败时回退到 png
     const [animationSrc, setAnimationSrc] = useState(CANCEL_ANIMATION_WEBP);
@@ -51,42 +52,46 @@
 
     // 不同取消阶段对应的文案配置
     const STATUS_MAP = {
-        1: {
+        [CancelType.CallingCar]: {
             title: `${waitMinutesValue}分钟内预计有车,确定取消吗?`,
             tip: discountAmountValue > 0 ? `本单限时优惠${discountAmountValue}元,重新叫车可能等待更久` : '',
         },
-        2: {
+        [CancelType.VehicleApproaching]: {
             title: tripStatus === TripStatus.VehicleApproaching
                 ? `车辆还有${approachingDurationDisplay.value}${approachingDurationDisplay.unit}到达,确定取消吗`
                 : `车辆还有${waitMinutesValue}分钟到达,确定取消吗`,
             tip: discountAmountValue > 0 ? `本单限时优惠${discountAmountValue}元,重新叫车可能等待更久` : '',
         },
-        3: {
+        [CancelType.VehicleArrivedPickup]: {
             title: '车辆已到达,确定取消吗?',
             tip: '车辆到达情况下取消可能会被限制发单',
         },
-        4: {
+        [CancelType.OrderCanceled]: {
             title: '订单已取消',
             tip: '已为您取消订单,期待您下次体验',
         },
-        5: {
+        [CancelType.NoAvailableVehicle]: {
             title: '附近没有空余车辆,已为您取消订单',
             tip: '当前运力紧张,建议您稍后重新发起订单',
         },
-        6: {
+        [CancelType.Redispatching]: {
             title: '正在为您重新派车中...',
             tip: '当前车辆无法继续服务',
+        },
+        [CancelType.InService]: {
+            title: `预计${approachingDurationDisplay.value}${approachingDurationDisplay.unit}到达目的地,确认取消吗?`,
+            tip: '',
         },
     }
 
     // 关闭弹窗时按类型执行对应流程
     const closePopup = () => {
-        if (cancelType == 5) {
+        if (cancelType == CancelType.NoAvailableVehicle) {
             onConfirm?.();
             onClose?.();
             return
         }
-        if (cancelType == 4) {
+        if (cancelType == CancelType.OrderCanceled) {
             Taro.reLaunch({
                 url: '/pages/robotaxi/home/index',
             })
@@ -109,37 +114,32 @@
                 <View className='cancel-tip-box'>
                     <View className='cancel-tip-box-title'>{STATUS_MAP[cancelType]?.title}</View>
                     {
-                        STATUS_MAP[cancelType]?.tip && <View className={`cancel-tip-box-desc ${cancelType === 4 && 'color1'}`}>{STATUS_MAP[cancelType].tip}</View>
+                        STATUS_MAP[cancelType]?.tip && <View className={`cancel-tip-box-desc ${cancelType === CancelType.OrderCanceled && 'color1'}`}>{STATUS_MAP[cancelType].tip}</View>
                     }
                 </View>
                 {
-                    [4, 5].includes(cancelType) ?
-                        <View>
-                            <View className='cancel-site-box'>
-                                <View className='cancel-site-box-icon'>
-                                    <View className='cancel-site-box-icon-start'></View>
-                                    <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png" />
-                                    <View className='cancel-site-box-icon-end'></View>
-                                </View>
-                                <View className='cancel-site-box-content'>
-                                    <View className='cancel-site-box-content-name'>{startSiteName}</View>
-                                    <View className='cancel-site-box-divider'></View>
-                                    <View className='cancel-site-box-content-name'>{endSiteName}</View>
-                                </View>
-                            </View>
-                            <View className='cancel-btn-box'>
-                                <View className='cancel-btn-again' onClick={onConfirm}>再来一单</View>
-                            </View>
+                  [CancelType.OrderCanceled, CancelType.NoAvailableVehicle].includes(cancelType) ?
+                    <View>
+                      <SiteRouteCard
+                        className='cancel-site-box'
+                        sites={[
+                          { name: startSiteName },
+                          { name: endSiteName }
+                        ]}
+                      />
+                      <View className='cancel-btn-box'>
+                          <View className='cancel-btn-again' onClick={onConfirm}>再来一单</View>
+                      </View>
+                    </View>
+                    :
+                    <>
+                        {/* 占位动画:优先 webp,加载失败回退 png */}
+                        <Image className='cancel-animation' src={animationSrc} onError={handleAnimationError} />
+                        <View className='cancel-btn-box'>
+                            <View  className=' cancel-btn-cancel' onClick={onConfirm}>确认取消</View>
+                            <View className='cancel-btn-again wait-btn' onClick={onClose}>再等等</View>
                         </View>
-                        :
-                        <>
-                            {/* 占位动画:优先 webp,加载失败回退 png */}
-                            <Image className='cancel-animation' src={animationSrc} onError={handleAnimationError} />
-                            <View className='cancel-btn-box'>
-                                <View  className=' cancel-btn-cancel' onClick={onConfirm}>确认取消</View>
-                                <View className='cancel-btn-again wait-btn' onClick={onClose}>再等等</View>
-                            </View>
-                        </>
+                    </>
                 }
             </View>
         </View>
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/calling-car.less b/src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
index a71b574..ba3538e 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
@@ -10,6 +10,19 @@
   }
 }
 
+// 结束态背景图(calling-car-bg)「缓缓升起」进场:
+// height 恒为 844px(不改变高度、不拉伸图片),仅用 translateY 从「相当于 282px 露出」的位置升到最终位置(translateY 0)。
+// 起始位移 = 844 - 282 = 562px,随组件挂载触发,结束停在 translateY(0) 的正确位置。
+@keyframes finished-bg-rise {
+  from {
+    transform: translateY(562px);
+  }
+
+  to {
+    transform: translateY(0);
+  }
+}
+
 .calling-car {
   position: relative;
   padding: 148px 0px 0px 0px;
@@ -21,7 +34,7 @@
   }
   .calling-car-content-box {
     position: relative;
-    padding: 48px 48px 82px 48px;
+    padding: 48px 48px 66px 48px;
     background: @gary-theme;
     border-radius: 60px 60px 0 0;
   }
@@ -31,25 +44,179 @@
     left: 0;
     width: 100%;
     height: 282px;
+    // 转场期间的常态背景图预载副本:仅提前触发加载、全程不可见。
+    // 必须用类隐藏——内联 style 的 opacity:0 会被 Taro 当 falsy 值丢弃,导致副本可见盖住封面图。
+    &.bg-preload { opacity: 0; }
     &.carlling-bg {
       height: 276px;
     }
     &.finished-bg {
       height: 844px;
+      // 背景卡升起动画仅在真实 live 跃迁进入结束态时播放(见 .calling-car--finished-enter);
+      // 恢复态(从首页进入即已是结束态)不加该类,此处默认静态停在最终位(translateY(0)),不再弹起。
     }
   }
   .calling-car-vehicle-pic {
     position: absolute;
     top: -4px;
-    right: 116px;
+    right: 80px;
     width: 342px;
     height: 294px;
     z-index: 1;
+  }
+  // 派单成功转场:视频层(覆盖背景卡位置)
+  .calling-car-video {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: auto;
+    aspect-ratio: 1128 / 1268;
+    // 修复:原生 video 方角会露出黑色,给视频自身加与卡片一致的顶部圆角并裁剪
+    border-radius: 60px 60px 0 0;
+    overflow: hidden;
+    z-index: 0;
+    // 注:不做淡入淡出——模拟器上原生 video 不受 CSS opacity 控制,挂了也无效;挂载即显示 poster、自然过渡到播放。
+  }
+  // 派单成功转场:poster 覆盖层(起始)+ 结尾遮挡层(结束)。均盖在视频之上用于消除黑帧:
+  // - poster:视频真正播放前盖住,消除「加载完成→首帧绘制」间的起始黑帧;
+  // - end-cover:settle 阶段盖住,先于视频上屏、视频卸载后仍在,挡住原生 <video> 卸载
+  //   (原生合成层销毁 → webview 重绘间隙)产生的结尾黑帧。
+  // 尺寸/位置/圆角与 .calling-car-video 完全一致;z-index 与视频同为 0、低于车模(z-index:1),
+  // 保证 settle 期间车模归位动画照常可见。
+  .calling-car-poster,
+  .calling-car-end-cover {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: auto;
+    aspect-ratio: 1128 / 1268;
+    border-radius: 60px 60px 0 0;
+    overflow: hidden;
+    z-index: 0;
+  }
+  // 阶段一/二:视频展示 + 双卡反向位移。视频原地展示、不淡入(模拟器上原生 video 不受 opacity 控制)。
+  &.dispatch-video {
+    // 功能面板缓动下移,直到蓝牙/空调/香氛卡片移出可视区
+    .calling-car-content-box { transform: translateY(360px); transition: transform 0.8s ease; }
+    // 车模在视频区域内放大展示并居中。全程仅 transform 参与过渡,不切换 left/right 避免瞬跳。
+    .calling-car-vehicle-pic {
+      transform: translateY(70%) translateX(-88px) scale(1.5);
+      opacity: 1;
+      transition: transform 0.8s ease, opacity 0.5s ease;
+      z-index: 0;
+    }
+  }
+  // 阶段三:视频结束 → 功能卡回升归位、车模缩小回落到最终停靠位、视频切回图片背景
+  // --dispatch-settle-duration 与 use-dispatch-transition 中 BG_SHRINK_LEAD_SEC 保持一致
+  &.dispatch-settle {
+    --dispatch-settle-duration: 1.055s;
+    .calling-car-content-box { transform: translateY(0); transition: transform var(--dispatch-settle-duration) ease; }
+    // 车模从居中态回落到常驻停靠位(translateX 回 0 即基础态 right:116px 位置),并缩小归位。
+    // transform 函数列表与 video 阶段对齐(translateY/translateX/scale),保证平滑插值、无瞬跳。
+    .calling-car-vehicle-pic {
+      transform: translateY(0) translateX(0) scale(1);
+      opacity: 1;
+      transition: transform var(--dispatch-settle-duration) cubic-bezier(0.22, 1, 0.36, 1), opacity var(--dispatch-settle-duration) ease;
+    }
+  }
+}
+// 派单成功整卡入场:整卡从「呼叫中」卡片位置缓缓升起到位。
+// 用 keyframes 挂载自动播放(weapp 实测可靠);弃用 JS 两帧 + transition:起始态类依赖首帧提交才可能触发过渡,
+// 且会被父层重渲染中途卸掉导致整卡直接切换、无升起。
+.calling-car--enter {
+  // 缓动必须慢起步:入场升起与面板下移是同时进行的父子叠加运动,
+  // 慢起步让面板下移速度全程大于升起速度,面板绝对位置单调下降、无方向反转。
+  animation: calling-car-rise 0.8s cubic-bezier(0.45, 0, 0.2, 1);
+  will-change: transform;
+}
+// 起升距离 = 呼叫中与派单成功两态卡片顶部高度差。不能用 translateY(100%)(卡高 1500+px 会从屏外开始升)。
+@keyframes calling-car-rise {
+  from {
+    transform: translateY(170px);
+  }
+
+  to {
+    transform: translateY(0);
+  }
+}
+// 「送驾中(6) 直达待评价(9)」整卡入场:内部免费订单跳过待支付(8),原本是无动效硬切。
+// 卡片容器为底部锚定(.container position:fixed bottom:0),待评价态内容更高会瞬间向上顶,
+// 故从「相当于送驾态卡顶」的位置升起,用运动吃掉高度差;同时做轻微透明度渐变,柔化内容替换。
+//
+// 起升距离 = 待评价态与送驾态的卡片高度差。
+// 实测依据(同机型两态截图,designWidth 750 下 px 即设计稿单位):以两态共有的首个内容块
+// VehicleInfoCard(“小鹏GX”行)到屏幕底部的距离比对,送驾态约 288px、待评价态约 659px,
+// 即待评价态多出约 370px(新增费用明细两行 + 评价卡,减去状态条 + 操作栏)。
+// 用 LESS 变量而非 CSS 自定义属性:keyframes 内的 var() 在旧版 WKWebView 上存在不生效的风险,
+// LESS 变量在编译期就展开为字面量,按真机观感调这一处即可。
+@evaluate-enter-rise: 380px;
+
+@keyframes calling-car-evaluate-rise {
+  from {
+    transform: translateY(@evaluate-enter-rise);
+    opacity: 0.4;
+  }
+
+  // 透明度单独提前收口:0.4 → 1 用 0.2s 走完(0.7s 总时长的 28.57% 处),
+  // 之后 transform 继续独立完成剩余位移,不再受 opacity 影响。
+  28.57% {
+    opacity: 1;
+  }
+
+  to {
+    transform: translateY(0);
+    opacity: 1;
+  }
+}
+.calling-car--evaluate-enter {
+  // 位移行程约 380px(大于派单入场的 170px),时长相应放到 0.7s,保持速度感与派单入场一致。
+  animation: calling-car-evaluate-rise 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) both;
+  will-change: transform;
+}
+// 「行程结束」入场:仅在真实 live 跃迁进入结束态时(父层 playFinishedEnter 上升沿)加此类,
+// 承载背景卡与里程「一起升起」动画;恢复态不加此类,直接显示最终态、不弹起。
+//
+// 入场时序:礼花先播放(见 trip-finished-fireworks),延迟 @finished-enter-delay 后卡片与里程
+// 才开始升起——让礼花播到中段时卡片才登场,避免二者同时抢镜(延迟解耦,不与礼花播放硬关联)。
+@finished-enter-delay: 700ms;
+.calling-car--finished-enter {
+  .calling-car-bg.finished-bg {
+    // 背景卡(背景图)缓缓升起:延迟至礼花中段后触发,时长 1s
+    animation: finished-bg-rise 1s cubic-bezier(0.22, 0.61, 0.36, 1) @finished-enter-delay both;
+    will-change: transform;
+  }
+  // 里程简化:与背景卡「同延迟、同时长、同曲线」纯位移升起(复用 finished-bg-rise),
+  // 与背景卡锁步移动,视觉上如同印在卡面上一起升起;不再做淡入淡出。
+  // 整块头部(到达目的地 + 里程)一起动,避免只动里程导致标题脱节。
+  .trip-finished-header {
+    animation: finished-bg-rise 1s cubic-bezier(0.22, 0.61, 0.36, 1) @finished-enter-delay both;
+    will-change: transform;
+  }
+}
+// 派单成功转场期间:蓝牙卡 / 车控卡 / 底部操作栏隐藏但保留占位(opacity),逻辑另在各组件延迟执行;
+// 动效完成后移除 .dispatch-defer-content,随 opacity 过渡淡入。用 opacity 而非 visibility,保证淡入过渡生效。
+.calling-car {
+  .bluetooth-operate-card,
+  .temp-fragrance-control,
+  .trip-action-bar {
+    transition: opacity 0.3s ease;
+  }
+  &.dispatch-defer-content {
+    .bluetooth-operate-card,
+    .temp-fragrance-control,
+    .trip-action-bar {
+      opacity: 0;
+      pointer-events: none;
+    }
   }
 }
 // 到达终点/待支付态(图一、图二):背景透明化 + 到达目的地头部 + 白色费用卡
 .calling-car--finished {
   padding-top: 0;
+  // 结束态整卡背景透明:避免背景图升起时顶部露出 .calling-car 的 @gary-theme 底色
+  background: transparent;
   .calling-car-content-box {
     background: transparent;
     padding: 0;
@@ -74,6 +241,8 @@
     font-family: @font-xpsans-bold;
     letter-spacing: normal;
     color: #FFFFFF;
+    // 里程渐显动画仅在真实 live 跃迁进入结束态时播放(见 .calling-car--finished-enter);
+    // 恢复态默认静态显示最终态(opacity:1、translateY(0))。
   }
   // 白色费用卡(横向内边距由卡片自身承担,因 content-box 已透明化)
   // 提升特异性:与 .calling-car .fare-box-card { padding-bottom: 0 } 同特异性且在其之前,
@@ -154,11 +323,30 @@
   .fare-box-card {
     padding-bottom: 0;
   }
+  .car-card-detail-wrap {
+    margin-bottom: 32px;
+  }
   .car-card-detail {
     display: flex;
     align-items: center;
     justify-content: space-between;
-    margin-bottom: 32px;
+  }
+  .car-card-detail-destination {
+    display: flex;
+    align-items: center;
+    margin-top: 12px;
+    .car-card-detail-destination-icon {
+      width: 28px;
+      height: 28px;
+      flex-shrink: 0;
+    }
+    .car-card-detail-destination-name {
+      margin-left: 8px;
+      font-size: 24px;
+      color: @black-text-main;
+      max-width: 620px;
+      word-break: break-all;
+    }
   }
   .car-card-detail-plate {
     display: flex;
@@ -583,8 +771,7 @@
     display: flex;
     align-items: center;
     justify-content: space-between;
-    margin-top: 8px;
-    padding: 32px 8px;
+    padding: 22px 8px 0 8px;
   }
   .trip-action-bar-item {
     display: flex;
@@ -600,6 +787,7 @@
     width: 28px;
     height: 28px;
     margin-right: 8px;
+    margin-top: 2px;
   }
   .trip-action-bar-item-destination {
     .trip-action-bar-item-icon {
@@ -610,7 +798,7 @@
   .trip-action-bar-item-text {
     flex-shrink: 0;
     font-size: 24px;
-    line-height: 24px;
+    line-height: 56px;
     color: @black-text-main;
   }
   .trip-action-bar-share-btn {
@@ -834,9 +1022,17 @@
   padding: 24px 0px;
   border-radius: 16px;
   background: @gary-theme;
+  // 就绪后内容淡入;未就绪时占位保留高度、内容淡出,避免蓝牙状态异步就绪撑高卡片产生跳变
+  transition: opacity 0.3s ease;
+  &--pending {
+    opacity: 0;
+    pointer-events: none;
+  }
   .bluetooth-operate-card-left {
     display: flex;
     align-items: center;
+    // 与右侧箭头容器 line-height:40px 对齐,保证「未就绪(无箭头)」与「就绪(有箭头)」两态行高一致、零跳变
+    min-height: 40px;
     gap: 4px;
     font-size: 24px;
     font-weight: normal;
@@ -887,9 +1083,6 @@
       margin: 0 12px;
       color: rgba(20, 30, 39, 0.2);
     }
-    .calling-car-site-box-content-name {
-      font-weight: 500;
-    }
   }
   .calling-car-cancel-btn {
     flex-shrink: 0;
@@ -909,36 +1102,6 @@
     border-radius: 40px;
     background: rgba(204, 208, 215, 0.25);
     padding: 32px;
-    display: flex;
-    gap: 16px;
-    height: fit-content;
-    .calling-car-site-box-icon {
-      flex-shrink: 0;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      .calling-car-site-check {
-        width: 32px;
-        height: 32px;
-      }
-      .dotted-line {
-        flex: 1;
-        width: 2px;
-        margin: 6px 0;
-        height: fit-content;
-      }
-    }
-    .calling-car-site-box-content {
-      flex: 1;
-      font-size: 28px;
-      line-height: 44px;
-      letter-spacing: normal;
-      color: @black-text-main;
-      .calling-car-site-box-divider {
-        margin: 40px 24px 40px 0px;
-        border-top: 1px solid rgba(20, 30, 39, 0.08);
-      }
-    }
   }
   .calling-car-status-box {
     position: relative;
@@ -996,45 +1159,29 @@
   }
 }
 
-/* 车控入口卡片横向滑动容器(空调 / 音乐 / 香氛,卡片固定宽度,超出宽度可横向滑动)
-   用 ScrollView 而非 View + overflow:weapp WebView 渲染器会拦截 View 上的触摸手势,
-   CSS overflow 滚动在真机与模拟器均无法拖动。 */
-.temp-fragrance-scroll {
+/* 车控入口卡片 2 列网格容器(空调 / 音乐 / 香氛):不再横向滑动,
+   每行 2 个卡片、列间隔 32px,卡片宽度按内容区两等分,超过 2 个自动换行(行间隔同为 32px)。 */
+.temp-fragrance-control {
   position: relative;
   z-index: 2;
-  width: 100vw; /* 滑动区铺满整屏宽 */
-  height: 240px; /* 与卡片等高,避免 scroll-view 高度塌陷 */
   margin-top: 8px;
-  /* 左移 48px 抵消 .calling-car-content-box 的左内边距,使滑动区左边界对齐屏幕左边缘,
-     卡片可一直滑到屏幕两侧边缘(首/末卡片的 48px 视觉留白由内部轨道的 padding 承担)。
-     已显式指定 width: 100vw,故无需再写 margin-right(会因过约束被忽略)。 */
-  margin-left: -48px;
-  white-space: nowrap;
-
-  /* 隐藏横向滚动条(show-scrollbar 仅 enhanced 模式生效,这里用 CSS 兜底) */
-  &::-webkit-scrollbar,
-  ::-webkit-scrollbar {
-    display: none;
-    width: 0;
-    height: 0;
-  }
-}
-
-/* 滑动轨道:宽度由卡片内容决定 */
-.temp-fragrance-control {
-  display: inline-flex;
-  flex-wrap: nowrap;
+  display: flex;
+  flex-wrap: wrap;
   align-items: flex-start;
-  gap: 24px;
-  /* 静止时首卡对齐 48px 内容左边界;滑到底时末卡右侧同样保留 48px 留白 */
-  padding-left: 48px;
-  padding-right: 48px;
+  gap: 32px;
 
-  /* 卡片不参与压缩,保证各卡片保持设计稿宽度并整体溢出滚动 */
+  /* 卡片外层容器:两等分内容宽度(减去一个 32px 列间隔) */
   > .ac-control,
   > .music-control,
   > .fragrance-control {
-    flex-shrink: 0;
+    flex: 0 0 calc((100% - 32px) / 2);
+    width: calc((100% - 32px) / 2);
+    min-width: 0;
+  }
+
+  /* 卡片撑满所在列(全局 .tf-card 为横滑布局下的固定 304px 宽) */
+  .tf-card {
+    width: 100%;
   }
 }
 
@@ -1128,6 +1275,7 @@
   .tf-card-ac-temp-unit {
     margin-top: 8px;
     margin-left: 6px;
+    font-family: @font-xpsans-bold;
     font-size: 28px;
     font-weight: 500;
     line-height: 32px;
@@ -1139,6 +1287,7 @@
     display: flex;
     align-items: center;
     justify-content: space-between;
+    margin-top: 32px;
   }
 
   .tf-card-footer-text {
@@ -1160,14 +1309,21 @@
   padding-top: 8px;
 
   &.is-open {
-    background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/aircondition-open-20260703.png');
+    background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/aircondition-open-20260812.png');
+
+    // 开启态卡片文字统一为白色
+    .tf-card-header-title,
+    .tf-card-ac-temp-value,
+    .tf-card-ac-temp-unit,
+    .tf-card-footer-text {
+      color: #FFFFFF;
+    }
   }
 
   &.is-close {
-    background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/aircondition-close-20260703.png');
+    background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/aircondition-close-20260812.png');
 
-    // 关闭态卡片文字统一弱化
-    .tf-card-header-title,
+    // 关闭态卡片文字统一弱化(标题保持主题色,与香氛卡片一致)
     .tf-card-ac-temp-value,
     .tf-card-ac-temp-unit,
     .tf-card-footer-text {
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/cancel-popup.less b/src/robotaxi/pages/begin-route/components/css-folder/cancel-popup.less
index 4bb2550..1963052 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/cancel-popup.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/cancel-popup.less
@@ -63,57 +63,9 @@
 
         .cancel-site-box {
             margin-top: 48px;
-            border-radius: 40px;
+            padding: 48px 40px;
             background: rgba(204, 208, 215, 0.25);
-            padding: 48px 32px 42px 40px;
-            display: flex;
-            gap: 16px;
-            height: fit-content;
-
-            .cancel-site-box-icon {
-                padding: 16px 0;
-                flex-shrink: 0;
-                display: flex;
-                flex-direction: column;
-                align-items: center;
-
-                .cancel-site-box-icon-start {
-                    width: 16px;
-                    height: 16px;
-                    background: #DFB369;
-                    border-radius: 50%;
-                }
-
-                .cancel-site-box-icon-end {
-                    width: 16px;
-                    height: 16px;
-                    background: #141E27;
-                    border-radius: 50%;
-                }
-
-                image {
-                    flex: 1;
-                    width: 2px;
-                    margin: 7px 0;
-                    height: fit-content;
-                }
-            }
-
-            .cancel-site-box-content {
-                flex: 1;
-                font-size: 32px;
-                line-height: 48px;
-                letter-spacing: normal;
-                color: #141E27;
-                font-weight: 500;
-                .cancel-site-box-divider {
-                  margin: 34px 32px 40px 0px;
-                  border-top: 1px solid rgba(204, 208, 215, 0.25);
-                }
-                .cancel-site-box-content-name {
-                  font-weight: 500;
-                }
-            }
+            border-radius: 40px;
         }
 
         .cancel-animation {
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/evaluate-popup.less b/src/robotaxi/pages/begin-route/components/css-folder/evaluate-popup.less
index 7e80193..fb0131b 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/evaluate-popup.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/evaluate-popup.less
@@ -25,6 +25,11 @@
         border-radius: 60px 60px 0 0;
         padding: 64px 0 0 0;
 
+        &.is-fare-feedback {
+            height: 812px;
+            padding-top: 48px;
+        }
+
         .evaluate-rating-switcher,
         .content-box {
             transition: opacity 180ms ease, transform 180ms ease;
@@ -84,6 +89,11 @@
                 align-self: stretch;
                 padding-left: 30px;
                 box-sizing: border-box;
+
+                &.is-centered {
+                    text-align: center;
+                    padding-left: 0;
+                }
             }
 
             &-content {
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/fragrance-control.less b/src/robotaxi/pages/begin-route/components/css-folder/fragrance-control.less
index 7e37fc8..f68b3d6 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/fragrance-control.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/fragrance-control.less
@@ -28,11 +28,11 @@
   /* 香氛入口卡片:背景随开关状态切换 */
   .tf-card-fragrance {
     &.is-open {
-      background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/fragrance-open-20260703.png');
+      background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/fragrance-open-20260812.png');
     }
 
     &.is-close {
-      background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/fragrance-close-20260703.png');
+      background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/fragrance-close-20260812.png');
 
       // 关闭态卡片文字统一弱化
       .tf-card-fragrance-name,
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/modify-destination-review.less b/src/robotaxi/pages/begin-route/components/css-folder/modify-destination-review.less
index bd072bd..ba328cc 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/modify-destination-review.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/modify-destination-review.less
@@ -59,17 +59,22 @@
     margin-bottom: 32px;
   }
 
-  // 站点样式参考 cancel-popup 的 cancel-site-box:灰底盒子 + 左侧起终点圆点虚线列
+  // 站点样式参考 cancel-popup 的 cancel-site-box(route-card 风格),已抽离为 SiteRouteCard 公共组件,
+  // 此处只保留容器级样式(背景/圆角/内边距)
   .modify-destination-review-select-site {
     position: relative;
     z-index: 1;
-    display: flex;
-    gap: 16px;
     padding: 40px 32px 40px 48px;
     border-radius: 40px;
     background: rgba(204, 208, 215, 0.25);
     box-sizing: border-box;
-    height: fit-content;
+    .site-route-card-item-first {
+      .site-route-card-item-row {
+        .site-route-card-item-text {
+          color: @black-text-04;
+        }
+      }
+    }
   }
 
   &-trip-card {
@@ -80,59 +85,6 @@
     box-sizing: border-box;
     border: 2px solid #ffffff;
     box-shadow: 0 20px 40px 0 rgba(87, 99, 147, 0.08);
-  }
-
-  .modify-destination-review-select-site-start {
-    color: @black-text-04;
-  }
-
-  .modify-destination-review-select-site-icon {
-    padding: 16px 0;
-    flex-shrink: 0;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-  }
-
-  .modify-destination-review-select-site-icon-start {
-    width: 16px;
-    height: 16px;
-    background: #DFB369;
-    border-radius: 50%;
-  }
-
-  .modify-destination-review-select-site-icon-end {
-    width: 16px;
-    height: 16px;
-    background: @black-text-main;
-    border-radius: 50%;
-  }
-
-  .modify-destination-review-select-site-icon image {
-    flex: 1;
-    width: 2px;
-    margin: 7px 0;
-    height: fit-content;
-  }
-
-  .modify-destination-review-select-site-content {
-    flex: 1;
-    min-width: 0;
-    font-size: 32px;
-    line-height: 48px;
-    letter-spacing: normal;
-    color: @black-text-main;
-  }
-
-  .modify-destination-review-select-site-start,
-  .modify-destination-review-select-site-end {
-    font-weight: 500;
-    word-break: break-all;
-  }
-
-  .modify-destination-review-select-site-divider {
-    margin: 34px 32px 40px 0px;
-    border-top: 1px solid rgba(204, 208, 215, 0.25);
   }
 
   &-footer {
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/release-be-being.less b/src/robotaxi/pages/begin-route/components/css-folder/release-be-being.less
index 1307acf..ef0295f 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/release-be-being.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/release-be-being.less
@@ -5,7 +5,7 @@
   background: @gary-theme;
   border-radius: 60px 60px 0px 0px;
   overflow: hidden;
-  padding-top: 84px;
+  padding-top: 94px;
   .release-be-being-content {
     position: relative;
     z-index: 1;
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/release-route.less b/src/robotaxi/pages/begin-route/components/css-folder/release-route.less
index 2593e95..9c67f08 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/release-route.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/release-route.less
@@ -70,9 +70,9 @@
             .route-select-content-item {
                 flex: 1;
                 padding: 16px 0 24px 24px;
-                border-radius: 16px;
-                background: #F5F6FA;
-                border: 1px solid #F2F3F7;
+                border-radius: 20px;
+                background: @gary-theme;
+
 
                 .route-select-content-item-title {
                     font-size: 20px;
@@ -115,28 +115,11 @@
             }
 
             .active-item {
-                background: linear-gradient(97deg, rgba(255, 209, 129, 0.2) 0%, rgba(223, 179, 105, 0.2) 103%) !important;
-                box-sizing: border-box;
+                background: rgba(223, 179, 105, 0.2);
                 position: relative;
 
                 .route-select-content-item-title {
                     color: @gold-text;
-                }
-
-                &::after {
-                    content: "";
-                    position: absolute;
-                    top: 0;
-                    left: 0;
-                    right: 0;
-                    bottom: 0;
-                    border-radius: 16px;
-                    padding: 1px;
-                    background: linear-gradient(180deg, @gold-light -10%, rgba(223, 179, 105, 0.0469) 108%, rgba(0, 0, 0, 0) 108%);
-                    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-                    -webkit-mask-composite: xor;
-                    mask-composite: exclude;
-                    pointer-events: none;
                 }
             }
         }
@@ -362,6 +345,7 @@
             .discount-amount-desc {
                 margin-left: 8px;
                 font-size: 24px;
+                font-weight: 500;
                 line-height: 40px;
                 letter-spacing: normal;
                 color: @gold-text;
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/route-selection-panel.less b/src/robotaxi/pages/begin-route/components/css-folder/route-selection-panel.less
index 41992c8..3fdd5b5 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/route-selection-panel.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/route-selection-panel.less
@@ -48,14 +48,13 @@
     .route-select-content-item {
       flex: 1;
       padding: 16px 0 24px 24px;
-      border-radius: 16px;
+      border-radius: 20px;
       background: #F5F6FA;
-      border: 1px solid #F2F3F7;
 
       .route-select-content-item-title {
         font-size: 20px;
         line-height: 20px;
-        color: rgba(0, 0, 0, 0.9);
+        color: @black-text-main;
       }
 
       .route-select-content-item-time {
@@ -63,7 +62,7 @@
         font-size: 28px;
         font-weight: 500;
         line-height: 40px;
-        color: rgba(0, 0, 0, 0.9);
+        color: @black-text-main;
       }
 
       .route-select-content-item-distance {
@@ -90,27 +89,11 @@
     }
 
     .active-item {
-      background: linear-gradient(97deg, rgba(255, 209, 129, 0.2) 0%, rgba(223, 179, 105, 0.2) 103%) !important;
+      background: rgba(223, 179, 105, 0.2);
       box-sizing: border-box;
       position: relative;
       .route-select-content-item-title {
         color: @gold-text;
-      }
-
-      &::after {
-        content: "";
-        position: absolute;
-        top: 0;
-        left: 0;
-        right: 0;
-        bottom: 0;
-        border-radius: 16px;
-        padding: 1px;
-        background: linear-gradient(180deg, @gold-light -10%, rgba(223, 179, 105, 0.0469) 108%, rgba(0, 0, 0, 0) 108%);
-        -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-        -webkit-mask-composite: xor;
-        mask-composite: exclude;
-        pointer-events: none;
       }
     }
   }
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/seat-hv-control.less b/src/robotaxi/pages/begin-route/components/css-folder/seat-hv-control.less
index 10de5ee..1bb09dc 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/seat-hv-control.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/seat-hv-control.less
@@ -9,16 +9,32 @@
   }
 }
 
+// 座椅区背景图为 750×1232,容器按同比例固定高度:
+// 按钮以百分比定位,容器高度提前确定后,按钮首帧即在正确位置(不再等背景图 widthFix 撑开高度)
+@seat-hv-area-height: 1232px;
+// 可见座椅区高度:弹窗顶部到空调白卡顶部,对应 temperature-control.less 中 .popup-ac 的 margin-top
+@seat-hv-visible-height: 772px;
+
 // 座椅通风加热区(内嵌于「温度调节」面板)
 .seat-hv-area {
   position: relative;
   width: 100%;
+  height: @seat-hv-area-height;
   overflow: hidden;
 
-  // 座舱俯视图背景(widthFix,高度按图片比例自适应,撑起区域高度)
+  // 座舱俯视图背景:绝对定位不参与高度计算,加载完成后渐现
   .seat-hv-bg {
+    position: absolute;
+    top: 0;
+    left: 0;
     width: 100%;
     display: block;
+    opacity: 0;
+    transition: opacity 240ms ease;
+
+    &.is-loaded {
+      opacity: 1;
+    }
   }
 
   // 座椅节点叠放层
@@ -139,13 +155,12 @@
   }
 }
 
-// 无座椅占位
+// 无座椅占位(不可用态):无背景色,铺满整个可见座椅区
 .seat-hv-empty {
-  height: 320px;
+  height: @seat-hv-visible-height;
   display: flex;
   align-items: center;
   justify-content: center;
-  background-color: #E9EBF0;
   font-size: 28px;
   color: rgba(0, 0, 0, 0.4);
 }
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/service-route-change.less b/src/robotaxi/pages/begin-route/components/css-folder/service-route-change.less
index 6a08133..8280cfa 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/service-route-change.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/service-route-change.less
@@ -1,8 +1,8 @@
 @import '../../../../style/ui-variables.less';
 .service-route-change-container {
-    background: #FFFFFF;
-    border-radius: 40px 40px 0 0;
-    padding: 40px 49px 68px 49px;
+    background: @gary-theme;
+    border-radius: 60px 60px 0 0;
+    padding: 40px 40px 80px 40px;
 
     .service-route-select-container {
         display: flex;
@@ -11,8 +11,8 @@
         .service-route-select-title {
             font-size: 28px;
             font-weight: 500;
-            line-height: 40px;
-            color: rgba(0, 0, 0, 0.9);
+            line-height: 52px;
+            color: @black-text-main;
         }
 
         .service-route-select-preference {
@@ -20,11 +20,10 @@
             align-items: center;
 
             .service-route-select-preference-text {
-                font-size: 24px;
+                font-size: 28px;
                 font-weight: 500;
-                line-height: 40px;
-                color: @gold-text;
-                font-weight: 500;
+                line-height: 52px;
+                color: @dark-gold-text;
             }
 
             image {
@@ -41,28 +40,27 @@
     .service-route-select-content {
         display: flex;
         justify-content: space-between;
-        gap: 10px;
-        margin-top: 16px;
+        gap: 14px;
+        margin-top: 34px;
 
         .service-route-select-content-item {
             flex: 1;
             padding: 16px 24px 30px 24px;
-            border-radius: 16px;
+            border-radius: 20px;
             background: #F5F6FA;
-            border: 1px solid #F2F3F7;
 
             .service-route-select-content-item-title {
                 font-size: 20px;
                 line-height: 20px;
-                color: rgba(0, 0, 0, 0.9);
+                color: @black-text-04;
             }
 
             .service-route-select-content-item-time {
-                margin-top: 10px;
+                margin-top: 18px;
                 font-size: 28px;
                 font-weight: 500;
                 line-height: 40px;
-                color: rgba(0, 0, 0, 0.9);
+                color: @black-text-main;
             }
 
             .service-route-select-content-item-distance {
@@ -72,7 +70,7 @@
                 margin-top: 12px;
                 font-size: 20px;
                 line-height: 20px;
-                color: rgba(0, 0, 0, 0.9);
+                color: @black-text-main;
 
                 .service-traffic-signal-item {
                     display: flex;
@@ -88,36 +86,20 @@
             }
 
             .service-route-select-content-item-estimate {
-                margin-top: 34px;
+                margin-top: 38px;
                 font-size: 20px;
                 line-height: 20px;
-                color: #F58900;
+                color: @gold-text;
             }
         }
 
         .active {
-            background: linear-gradient(97deg, rgba(255, 209, 129, 0.2) 0%, rgba(223, 179, 105, 0.2) 103%) !important;
+            background: rgba(223, 179, 105, 0.2);
             box-sizing: border-box;
             position: relative;
 
             .service-route-select-content-item-title {
                 color: @gold-text;
-            }
-
-            &::after {
-                content: "";
-                position: absolute;
-                top: 0;
-                left: 0;
-                right: 0;
-                bottom: 0;
-                border-radius: 16px;
-                padding: 1px;
-                background: linear-gradient(180deg, @gold-light -10%, rgba(223, 179, 105, 0.0469) 108%, rgba(0, 0, 0, 0) 108%);
-                -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-                -webkit-mask-composite: xor;
-                mask-composite: exclude;
-                pointer-events: none;
             }
         }
     }
@@ -126,30 +108,36 @@
         display: flex;
         justify-content: space-between;
         gap: 22px;
-        margin-top: 48px;
+        margin-top: 58px;
         font-size: 32px;
         font-weight: 500;
         text-align: center;
 
         .service-route-select-footer-item-cancel {
-            flex: 1;
-            color: #141E27;
-            border-radius: 1000px;
-            box-sizing: border-box;
-            border: 4px solid #141E27;
-            line-height: 104px;
-            height: 104px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          flex: 1;
+          color: @black-text-main;
+          border-radius: 1000px;
+          box-sizing: border-box;
+          border: 4px solid @black-text-main;
+          line-height: 104px;
+          height: 102px;
         }
 
         .service-route-select-footer-item-confirm {
-            flex: 1;
-            color: #FFFFFF;
-            border-radius: 1000px;
-            line-height: 104px;
-            border: 4px solid #141E27;
-            box-sizing: border-box;
-            height: 104px;
-            background: #141E27;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          flex: 1;
+          color: #FFFFFF;
+          border-radius: 1000px;
+          line-height: 104px;
+          border: 4px solid @black-text-main;
+          box-sizing: border-box;
+          height: 102px;
+          background: @black-text-main;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/begin-route/components/css-folder/temperature-control.less b/src/robotaxi/pages/begin-route/components/css-folder/temperature-control.less
index 8692c79..bb983d9 100644
--- a/src/robotaxi/pages/begin-route/components/css-folder/temperature-control.less
+++ b/src/robotaxi/pages/begin-route/components/css-folder/temperature-control.less
@@ -128,7 +128,8 @@
 
       // 居中选中项:放大加粗(空调开启时高亮)
       &.active {
-        width: 220px;
+        // 220px(原值)+ 左右各 20px:加大选中温度与相邻温度的间距
+        width: 260px;
         .popup-temp-item-value {
           font-size: 64px;
           line-height: 72px;
diff --git a/src/robotaxi/pages/begin-route/components/debug-autoscale-panel.tsx b/src/robotaxi/pages/begin-route/components/debug-autoscale-panel.tsx
deleted file mode 100644
index df4a289..0000000
--- a/src/robotaxi/pages/begin-route/components/debug-autoscale-panel.tsx
+++ /dev/null
@@ -1,329 +0,0 @@
-/**
- * 速度自动缩放调试面板(已下线,文件保留供后续临时调参/回归测试时复用)。
- *
- * ============================================================================
- * 何时再用?
- *   - 调整 LOW_SPEED_MAX_KMH 阈值后想快速验证分档
- *   - 调整 LOW_SPEED_SCALE / HIGH_SPEED_SCALE 缩放等级后看效果
- *   - 排查"速度变了但地图没缩放""车辆离开视区"等问题,临时模拟车速/订单状态/车辆位置
- *
- * 启用步骤:
- *   1. 把 `speed-scale-config.ts` 的 `SPEED_SCALE_DEBUG` 改为 `true`
- *      (会自动切换到 debug 阈值:低速 0~10、中高速 >10 km/h)
- *   2. 在 `index.tsx` 的 `BeginRoute()` 返回 JSX 末尾(与 ModifyDestinationFailPopup 同级)
- *      引入并挂载本组件,传入页面已有的状态:
- *
- *      // 顶部 import:
- *      import DebugAutoscalePanel from './components/debug-autoscale-panel';
- *
- *      // 顶层 fragment 内:
- *      <DebugAutoscalePanel
- *        tripStatus={tripStatus}
- *        mapScale={mapScale}
- *        applyVehicleSpeedSample={applyVehicleSpeedSample}
- *        setTripStatus={setTripStatus}
- *        vehicleLocation={vehicleLocation}
- *        mapLatitude={mapLatitude}
- *        mapLongitude={mapLongitude}
- *        mockVehicleLocation={(lat, lng) =>
- *          vehicleState.updateVehicleLocationThrottled(lat, lng, true)
- *        }
- *      />
- *
- *   3. `applyVehicleSpeedSample` 由 `useSpeedBasedScale` 解构得到;其余 props
- *      (vehicleLocation/mapScale/...) 都是页面已有变量,按上方写法传即可。
- *   4. 测完把 SPEED_SCALE_DEBUG 改回 false 并删除上面 1 个 import + 1 段 JSX。
- *      面板文件本身保留,无需删除。
- *
- * 面板能力:
- *   - 默认收起,左侧悬浮"调速"小按钮,点开展开完整面板
- *   - Mock 行程状态(接驾中 / 行程中 / 还原)
- *   - Mock 车辆位置("把车放到地图中心" / "微移车辆")
- *   - 速度滑块 + 两档预设按钮(低速 / 中高速),喂 1 条或喂满窗口
- *   - 实时显示当前 tripStatus、mapScale、vehicleLocation、当前挡位/目标 scale
- *
- * 配套 console 工具(同样受 SPEED_SCALE_DEBUG 控制):
- *   __mockAutoscaleSpeed(kmh)       // 一次性喂 SPEED_SAMPLE_WINDOW 条样本,立即生效
- *   __mockAutoscaleSpeed(kmh, 1)    // 只喂 1 条,观察样本积累过程
- *
- * 注意:
- *   - 仅在 SPEED_SCALE_DEBUG=true 时渲染,开关为 false 时本组件返回 null
- *   - 面板覆盖层基于普通 <View>(与页面 .map-entry/.origin-box 同方案),
- *     无需 cover-view;如果被原生地图盖住,再考虑切到 cover-view
- * ============================================================================
- */
-import { useMemo, useRef, useState } from 'react';
-import { View, Text, Slider } from '@tarojs/components';
-import { TripStatus } from '../../../constants/trip-status';
-import {
-  HIGH_SPEED_SCALE,
-  LOW_SPEED_MAX_KMH,
-  LOW_SPEED_SCALE,
-  resolveSpeedTierByKmh,
-  SPEED_SAMPLE_WINDOW,
-  SPEED_SCALE_DEBUG
-} from '../hooks/speed-scale-config';
-
-type Props = {
-  /** 当前行程状态。 */
-  tripStatus: TripStatus;
-  /** 当前地图缩放等级(用于展示实际生效值)。 */
-  mapScale: number;
-  /** 喂入一条速度样本(km/h)。 */
-  applyVehicleSpeedSample: (speedKmh: number) => void;
-  /** 设置行程状态(用于本地 mock 接驾中/行程中等态,方便调试)。 */
-  setTripStatus: (status: TripStatus) => void;
-  /** 当前车辆位置(用于面板展示)。 */
-  vehicleLocation: { latitude: number; longitude: number } | null;
-  /** 当前地图受控中心纬度(用于"放到地图中心"按钮)。 */
-  mapLatitude: number | null;
-  /** 当前地图受控中心经度(用于"放到地图中心"按钮)。 */
-  mapLongitude: number | null;
-  /** Mock 车辆位置(直接更新 vehicleLocation 状态)。 */
-  mockVehicleLocation: (latitude: number, longitude: number) => void;
-};
-
-/** 启用速度缩放的行程状态。 */
-const ACTIVE_STATUSES = [TripStatus.VehicleApproaching, TripStatus.InService];
-/** 滑块速度上限(km/h),按低速上限的 4 倍取整,保证能覆盖中高速挡。 */
-const SLIDER_MAX_KMH = Math.ceil(LOW_SPEED_MAX_KMH * 4);
-
-/** 挡位标签配色。 */
-const TIER_COLOR: Record<string, string> = {
-  low: '#36b37e',
-  high: '#ff5630'
-};
-
-/** 行程状态显示名(仅展示用,不影响业务)。 */
-const TRIP_STATUS_LABEL: Record<number, string> = {
-  [TripStatus.PublishRoute]: '发布行程',
-  [TripStatus.Publishing]: '发布中',
-  [TripStatus.CallingCar]: '呼叫车中',
-  [TripStatus.VehicleApproaching]: '接驾中',
-  [TripStatus.VehicleArrivedPickup]: '到达上车点',
-  [TripStatus.InService]: '行程中',
-  [TripStatus.ArrivedDestination]: '到达目的地',
-  [TripStatus.TripFinished]: '行程结束',
-  [TripStatus.PaymentSuccess]: '支付成功',
-  [TripStatus.CancelOnlyPickup]: '已取消'
-};
-
-/**
- * 速度自动缩放调试面板:默认收起,展开后可拖动滑块或点击预设模拟车速,验证地图自动缩放。
- * 仅在 SPEED_SCALE_DEBUG=true 时渲染,关闭开关后自动消失。
- */
-export default function DebugAutoscalePanel(props: Props) {
-  const [expanded, setExpanded] = useState(false);
-  const [speedKmh, setSpeedKmh] = useState(Math.round(LOW_SPEED_MAX_KMH / 2));
-  const originalTripStatusRef = useRef<TripStatus | null>(null); // 首次 mock 前的原状态,用于「还原」
-
-  const isActive = ACTIVE_STATUSES.includes(props.tripStatus);
-  const tier = useMemo(() => resolveSpeedTierByKmh(speedKmh), [speedKmh]);
-
-  if (!SPEED_SCALE_DEBUG) {
-    return null;
-  }
-
-  /** 喂入 N 条当前速度样本(km/h)。 */
-  const feedSamples = (kmh: number, repeat: number) => {
-    for (let index = 0; index < repeat; index += 1) {
-      props.applyVehicleSpeedSample(kmh);
-    }
-  };
-
-  /** 点击预设:设置速度并一次性喂满窗口立即生效。 */
-  const applyPreset = (kmh: number) => {
-    setSpeedKmh(kmh);
-    feedSamples(kmh, SPEED_SAMPLE_WINDOW);
-  };
-
-  /** Mock 行程状态:首次 mock 前先记录原状态,以便「还原」恢复。 */
-  const mockTripStatus = (next: TripStatus) => {
-    if (originalTripStatusRef.current === null) {
-      originalTripStatusRef.current = props.tripStatus;
-    }
-    console.log('[autoscale] 🧪 mock 行程状态', { from: props.tripStatus, to: next });
-    props.setTripStatus(next);
-  };
-
-  /** 还原到首次 mock 前的状态。 */
-  const restoreTripStatus = () => {
-    if (originalTripStatusRef.current === null) {
-      return;
-    }
-    const target = originalTripStatusRef.current;
-    originalTripStatusRef.current = null;
-    console.log('[autoscale] 🧪 还原行程状态', { to: target });
-    props.setTripStatus(target);
-  };
-
-  const lowPreset = Math.max(0, LOW_SPEED_MAX_KMH - 2);
-  const highPreset = LOW_SPEED_MAX_KMH + Math.round(LOW_SPEED_MAX_KMH * 0.5);
-
-  // 收起态:仅显示一个浮动小按钮。
-  if (!expanded) {
-    return (
-      <View
-        onClick={() => setExpanded(true)}
-        style={{
-          position: 'fixed',
-          left: '0',
-          top: '45%',
-          zIndex: 9999,
-          padding: '8px 10px',
-          background: 'rgba(0,0,0,0.7)',
-          color: '#fff',
-          fontSize: '12px',
-          borderTopRightRadius: '16px',
-          borderBottomRightRadius: '16px'
-        }}
-      >
-        调速
-      </View>
-    );
-  }
-
-  const buttonStyle = (background: string) => ({
-    flex: 1,
-    textAlign: 'center' as const,
-    padding: '8px 0',
-    margin: '0 4px',
-    background,
-    color: '#fff',
-    fontSize: '13px',
-    borderRadius: '8px'
-  });
-
-  // 展开态:完整调试面板。
-  return (
-    <View
-      catchMove
-      style={{
-        position: 'fixed',
-        left: '12px',
-        right: '12px',
-        bottom: '24px',
-        zIndex: 9999,
-        padding: '12px 14px',
-        background: 'rgba(20,20,20,0.92)',
-        color: '#fff',
-        borderRadius: '12px',
-        boxShadow: '0 4px 16px rgba(0,0,0,0.4)'
-      }}
-    >
-      <View style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
-        <Text style={{ fontSize: '14px', fontWeight: 'bold' }}>速度缩放调试</Text>
-        <View onClick={() => setExpanded(false)} style={{ padding: '2px 10px', fontSize: '13px', background: 'rgba(255,255,255,0.15)', borderRadius: '8px' }}>
-          收起
-        </View>
-      </View>
-
-      <View style={{ fontSize: '12px', marginBottom: '6px', opacity: 0.85 }}>
-        <Text>{`当前状态:${TRIP_STATUS_LABEL[props.tripStatus] || props.tripStatus} (${props.tripStatus})`}</Text>
-        {originalTripStatusRef.current !== null && (
-          <Text style={{ marginLeft: '8px', color: '#ffab00' }}>· 已 mock</Text>
-        )}
-      </View>
-      <View style={{ display: 'flex', marginBottom: '10px' }}>
-        <View
-          onClick={() => mockTripStatus(TripStatus.VehicleApproaching)}
-          style={buttonStyle(props.tripStatus === TripStatus.VehicleApproaching ? '#4c9aff' : 'rgba(255,255,255,0.2)')}
-        >
-          接驾中
-        </View>
-        <View
-          onClick={() => mockTripStatus(TripStatus.InService)}
-          style={buttonStyle(props.tripStatus === TripStatus.InService ? '#4c9aff' : 'rgba(255,255,255,0.2)')}
-        >
-          行程中
-        </View>
-        <View
-          onClick={restoreTripStatus}
-          style={buttonStyle(originalTripStatusRef.current === null ? 'rgba(255,255,255,0.1)' : 'rgba(255,255,255,0.25)')}
-        >
-          还原
-        </View>
-      </View>
-
-      <View style={{ fontSize: '12px', marginBottom: '6px', opacity: 0.85 }}>
-        <Text>
-          {props.vehicleLocation
-            ? `车辆位置:${props.vehicleLocation.latitude.toFixed(5)}, ${props.vehicleLocation.longitude.toFixed(5)}`
-            : '车辆位置:null(未设置,scale 切换时无法居中)'}
-        </Text>
-      </View>
-      <View style={{ display: 'flex', marginBottom: '10px' }}>
-        <View
-          onClick={() => {
-            const lat = Number(props.mapLatitude);
-            const lng = Number(props.mapLongitude);
-            if (!Number.isFinite(lat) || !Number.isFinite(lng)) {
-              console.log('[autoscale] 🧪 mock 车辆位置失败:地图中心无效', { lat, lng });
-              return;
-            }
-            console.log('[autoscale] 🧪 mock 车辆位置 → 地图中心', { lat, lng });
-            props.mockVehicleLocation(lat, lng);
-          }}
-          style={buttonStyle('rgba(76,154,255,0.8)')}
-        >
-          把车放到地图中心
-        </View>
-        <View
-          onClick={() => {
-            if (!props.vehicleLocation) {
-              return;
-            }
-            // 在当前位置随机偏移约 30 米,验证 scale 切换后仍能跟随车辆居中
-            const lat = props.vehicleLocation.latitude + (Math.random() - 0.5) * 0.0006;
-            const lng = props.vehicleLocation.longitude + (Math.random() - 0.5) * 0.0006;
-            console.log('[autoscale] 🧪 mock 车辆位置 → 微移', { lat, lng });
-            props.mockVehicleLocation(lat, lng);
-          }}
-          style={buttonStyle(props.vehicleLocation ? 'rgba(255,255,255,0.2)' : 'rgba(255,255,255,0.1)')}
-        >
-          微移车辆
-        </View>
-      </View>
-
-      <View style={{ fontSize: '12px', lineHeight: '1.7', marginBottom: '6px', opacity: 0.85 }}>
-        <Text>{`阈值:低速 0~${LOW_SPEED_MAX_KMH} / 中高速 >${LOW_SPEED_MAX_KMH} km/h`}</Text>
-        <Text style={{ display: 'block' }}>{`scale:低速 ${LOW_SPEED_SCALE} / 中高速 ${HIGH_SPEED_SCALE}`}</Text>
-      </View>
-
-      <View style={{ display: 'flex', justifyContent: 'space-between', fontSize: '13px', marginBottom: '4px' }}>
-        <Text>{`当前选择:${speedKmh} km/h`}</Text>
-        <Text style={{ color: TIER_COLOR[tier.key] }}>{`${tier.label} → scale ${tier.scale}`}</Text>
-      </View>
-
-      <Slider
-        min={0}
-        max={SLIDER_MAX_KMH}
-        step={1}
-        value={speedKmh}
-        activeColor='#4c9aff'
-        blockSize={20}
-        onChanging={(event) => setSpeedKmh(Number(event.detail.value))}
-        onChange={(event) => setSpeedKmh(Number(event.detail.value))}
-      />
-
-      <View style={{ display: 'flex', marginTop: '8px' }}>
-        <View onClick={() => applyPreset(lowPreset)} style={buttonStyle(TIER_COLOR.low)}>{`低速 ${lowPreset}`}</View>
-        <View onClick={() => applyPreset(highPreset)} style={buttonStyle(TIER_COLOR.high)}>{`中高速 ${highPreset}`}</View>
-      </View>
-
-      <View style={{ display: 'flex', marginTop: '8px' }}>
-        <View onClick={() => feedSamples(speedKmh, 1)} style={buttonStyle('rgba(255,255,255,0.2)')}>喂 1 条</View>
-        <View onClick={() => feedSamples(speedKmh, SPEED_SAMPLE_WINDOW)} style={buttonStyle('#4c9aff')}>{`喂满 ${SPEED_SAMPLE_WINDOW} 条(立即生效)`}</View>
-      </View>
-
-      <View style={{ fontSize: '12px', marginTop: '8px', lineHeight: '1.6' }}>
-        <Text style={{ opacity: 0.85 }}>{`实际地图 scale:${props.mapScale}`}</Text>
-        {!isActive && (
-          <Text style={{ display: 'block', color: '#ff5630' }}>
-            当前非接驾中/行程中,喂入不会生效,请先点上方按钮 mock 状态
-          </Text>
-        )}
-      </View>
-    </View>
-  );
-}
diff --git a/src/robotaxi/pages/begin-route/components/debug-order-simulator-panel.tsx b/src/robotaxi/pages/begin-route/components/debug-order-simulator-panel.tsx
index d1c6941..5f6f69a 100644
--- a/src/robotaxi/pages/begin-route/components/debug-order-simulator-panel.tsx
+++ b/src/robotaxi/pages/begin-route/components/debug-order-simulator-panel.tsx
@@ -1,6 +1,6 @@
 import { useEffect, useRef, useState } from 'react';
 import { View, Text } from '@tarojs/components';
-import { TripStatus } from '../../../constants/trip-status';
+import { TripStatus, CancelType } from '../../../constants/trip-status';
 import OrderFlowSimulator, {
   buildDefaultRoutes,
   ensureSimulationOrderId,
@@ -29,7 +29,7 @@
   /** 下车点坐标。 */
   endSite: SitePoint;
   /** 设置取消弹窗类型(用于本地手动触发各类取消弹窗文案)。 */
-  setCancelType?: (type: number) => void;
+  setCancelType?: (type: CancelType) => void;
   /** 设置取消弹窗展示状态。 */
   setShowCancelPopup?: (show: boolean) => void;
   /** 设置修改目的地失败弹窗展示状态(用于本地手动触发)。 */
@@ -303,9 +303,9 @@
     setReplaying(false);
   };
 
-  /** 手动触发「附近没有空余车辆,已为您取消订单」弹窗(cancelType 5)。 */
+  /** 手动触发「附近没有空余车辆,已为您取消订单」弹窗(CancelType.NoAvailableVehicle)。 */
   const handleTriggerNoVehicleCancelPopup = () => {
-    props.setCancelType?.(5);
+    props.setCancelType?.(CancelType.NoAvailableVehicle);
     props.setShowCancelPopup?.(true);
   };
 
@@ -538,4 +538,4 @@
       </View>
     </View>
   );
-}
+}
\ No newline at end of file
diff --git a/src/robotaxi/pages/begin-route/components/debug-safearea-panel.tsx b/src/robotaxi/pages/begin-route/components/debug-safearea-panel.tsx
deleted file mode 100644
index fdab9d1..0000000
--- a/src/robotaxi/pages/begin-route/components/debug-safearea-panel.tsx
+++ /dev/null
@@ -1,274 +0,0 @@
-/**
- * 安全区调试面板(调试用,可常驻保留供后续排查跟车视口问题时复用)。
- *
- * ============================================================================
- * 用途:
- *   把 `syncActiveTripViewportToVehicle` 里用于判断「车辆是否需要回中」的安全区,
- *   用红色虚线直接描在地图上,方便肉眼校对死区/边距是否符合预期。
- *
- * 安全区定义(与 use-map-viewport.ts / active-trip-camera-policy.ts 完全一致):
- *   - 纵向:车辆落在 [deadZoneTopY, deadZoneBottomY] 之间不触发回中(纵向死区)
- *   - 横向:车辆落在 [safeXMargin, mapWidth - safeXMargin] 之间不触发回中
- *   - 因此「安全区范围」= 上述纵向死区 ∩ 横向边距 构成的矩形
- *   - 另外叠加展示:可用视区上下边界(safeTopY/safeBottomY) 与 车辆回中目标线(targetScreenY)
- *
- * 挂载方式(在 index.tsx 的 BeginRoute() 顶层 fragment 内,与 ModifyDestinationFailPopup 同级):
- *   // 顶部 import:
- *   import DebugSafeAreaPanel from './components/debug-safearea-panel';
- *   // 顶层 fragment 内:
- *   <DebugSafeAreaPanel
- *     tripStatus={tripStatus}
- *     mapBottomPadding={mapBottomPadding}
- *     resolveSafeTopInset={resolveSafeTopInset}
- *   />
- *
- * 注意:
- *   - 覆盖层基于普通 <View> + position:fixed(与 debug-autoscale-panel 同方案),
- *     坐标取自 `#map` 的 boundingClientRect,故与地图屏幕像素对齐;
- *     如果在真机被原生地图盖住,再考虑切到 cover-view。
- *   - 仅接驾中/行程中(VehicleApproaching/InService)才有安全区;其余状态点击
- *     描边时会按「行程中」做预览,并在面板上标注「预览」。
- * ============================================================================
- */
-import { useState } from 'react';
-import Taro from '@tarojs/taro';
-import { View, Text } from '@tarojs/components';
-import { TripStatus } from '../../../constants/trip-status';
-import {
-  buildActiveTripSafeAreaMetrics,
-  ActiveTripSafeAreaMetrics
-} from '../hooks/active-trip-camera-policy';
-import { ACTIVE_TRIP_VIEWPORT_SAFE_X_MARGIN_RATIO } from '../hooks/use-map-viewport';
-
-type Props = {
-  /** 当前行程状态。 */
-  tripStatus: TripStatus;
-  /** 底部卡片动态留白(来自 useMapViewport)。 */
-  mapBottomPadding: number;
-  /** 顶部安全区高度计算函数(来自 useMapViewport)。 */
-  resolveSafeTopInset: () => number;
-};
-
-/** 启用安全区的行程状态。 */
-const ACTIVE_STATUSES = [TripStatus.VehicleApproaching, TripStatus.InService];
-
-/** 描边结果快照。 */
-type SafeAreaSnapshot = {
-  /** 地图容器矩形(boundingClientRect)。 */
-  rect: { left: number; top: number; width: number; height: number };
-  /** 安全区度量。 */
-  metrics: ActiveTripSafeAreaMetrics;
-  /** 横向单侧边距(px)。 */
-  safeXMargin: number;
-  /** 计算所用的行程状态(可能为预览态)。 */
-  usedStatus: TripStatus;
-  /** 是否为预览(当前非接驾中/行程中)。 */
-  isPreview: boolean;
-};
-
-/**
- * 安全区调试面板:默认仅显示右侧悬浮「安全区」小按钮,点击后用红色虚线把
- * `syncActiveTripViewportToVehicle` 的安全区范围描在地图上,再点一次清除。
- */
-export default function DebugSafeAreaPanel(props: Props) {
-  const [snapshot, setSnapshot] = useState<SafeAreaSnapshot | null>(null);
-
-  /** 查询 #map 容器矩形。 */
-  const queryMapRect = () =>
-    new Promise<{ left: number; top: number; width: number; height: number } | null>((resolve) => {
-      Taro.createSelectorQuery()
-        .select('#map')
-        .boundingClientRect((rect: any) => {
-          if (rect && Number.isFinite(rect.width) && Number.isFinite(rect.height)) {
-            resolve({ left: rect.left, top: rect.top, width: rect.width, height: rect.height });
-            return;
-          }
-          resolve(null);
-        })
-        .exec();
-    });
-
-  /** 计算并描出安全区。 */
-  const drawSafeArea = async () => {
-    const rect = await queryMapRect();
-    if (!rect) {
-      console.log('[safearea] 🧪 获取地图容器失败,无法描边');
-      return;
-    }
-    const isPreview = !ACTIVE_STATUSES.includes(props.tripStatus);
-    const usedStatus = isPreview ? TripStatus.InService : props.tripStatus;
-    const metrics = buildActiveTripSafeAreaMetrics({
-      tripStatus: usedStatus,
-      mapHeight: rect.height,
-      bottomPadding: props.mapBottomPadding,
-      safeTopInset: props.resolveSafeTopInset()
-    });
-    if (!metrics) {
-      console.log('[safearea] 🧪 当前状态无安全区度量');
-      return;
-    }
-    const safeXMargin = rect.width * ACTIVE_TRIP_VIEWPORT_SAFE_X_MARGIN_RATIO;
-    console.log('[safearea] 🧪 安全区度量', { rect, metrics, safeXMargin, usedStatus, isPreview });
-    setSnapshot({ rect, metrics, safeXMargin, usedStatus, isPreview });
-  };
-
-  /** 切换:有快照则清除,无则重新描边。 */
-  const toggle = () => {
-    if (snapshot) {
-      setSnapshot(null);
-      return;
-    }
-    drawSafeArea();
-  };
-
-  const buttonLabel = snapshot ? '清除安全区' : '描安全区';
-
-  return (
-    <>
-      {/* 悬浮触发按钮 */}
-      <View
-        onClick={toggle}
-        style={{
-          position: 'fixed',
-          right: '0',
-          top: '38%',
-          zIndex: 9999,
-          padding: '8px 10px',
-          background: snapshot ? 'rgba(255,60,60,0.85)' : 'rgba(0,0,0,0.7)',
-          color: '#fff',
-          fontSize: '12px',
-          borderTopLeftRadius: '16px',
-          borderBottomLeftRadius: '16px'
-        }}
-      >
-        {buttonLabel}
-      </View>
-
-      {snapshot && (
-        <>
-          {/* 安全区范围:纵向死区 ∩ 横向边距,红色虚线主框 */}
-          <View
-            style={{
-              position: 'fixed',
-              left: `${snapshot.rect.left + snapshot.safeXMargin}px`,
-              top: `${snapshot.rect.top + snapshot.metrics.deadZoneTopY}px`,
-              width: `${snapshot.rect.width - snapshot.safeXMargin * 2}px`,
-              height: `${snapshot.metrics.deadZoneBottomY - snapshot.metrics.deadZoneTopY}px`,
-              border: '2px dashed #ff3c3c',
-              boxSizing: 'border-box',
-              zIndex: 9998,
-              pointerEvents: 'none'
-            }}
-          >
-            <Text
-              style={{
-                position: 'absolute',
-                left: '4px',
-                top: '2px',
-                fontSize: '11px',
-                color: '#ff3c3c',
-                background: 'rgba(255,255,255,0.65)',
-                padding: '0 4px',
-                borderRadius: '4px'
-              }}
-            >
-              安全区
-            </Text>
-          </View>
-
-          {/* 可用视区上下边界:淡红虚线,辅助参考 */}
-          <View
-            style={{
-              position: 'fixed',
-              left: `${snapshot.rect.left}px`,
-              top: `${snapshot.rect.top + snapshot.metrics.safeTopY}px`,
-              width: `${snapshot.rect.width}px`,
-              height: '0',
-              borderTop: '1px dashed rgba(255,60,60,0.5)',
-              zIndex: 9998,
-              pointerEvents: 'none'
-            }}
-          />
-          <View
-            style={{
-              position: 'fixed',
-              left: `${snapshot.rect.left}px`,
-              top: `${snapshot.rect.top + snapshot.metrics.safeBottomY}px`,
-              width: `${snapshot.rect.width}px`,
-              height: '0',
-              borderTop: '1px dashed rgba(255,60,60,0.5)',
-              zIndex: 9998,
-              pointerEvents: 'none'
-            }}
-          />
-
-          {/* 车辆回中目标线:红色虚线 */}
-          <View
-            style={{
-              position: 'fixed',
-              left: `${snapshot.rect.left}px`,
-              top: `${snapshot.rect.top + snapshot.metrics.targetScreenY}px`,
-              width: `${snapshot.rect.width}px`,
-              height: '0',
-              borderTop: '2px dashed rgba(255,60,60,0.85)',
-              zIndex: 9998,
-              pointerEvents: 'none'
-            }}
-          >
-            <Text
-              style={{
-                position: 'absolute',
-                right: '4px',
-                top: '-16px',
-                fontSize: '11px',
-                color: '#ff3c3c',
-                background: 'rgba(255,255,255,0.65)',
-                padding: '0 4px',
-                borderRadius: '4px'
-              }}
-            >
-              回中目标
-            </Text>
-          </View>
-
-          {/* 数值面板 */}
-          <View
-            catchMove
-            style={{
-              position: 'fixed',
-              left: '12px',
-              bottom: '24px',
-              zIndex: 9999,
-              padding: '8px 12px',
-              background: 'rgba(20,20,20,0.9)',
-              color: '#fff',
-              borderRadius: '10px',
-              fontSize: '12px',
-              lineHeight: '1.7',
-              maxWidth: '70%'
-            }}
-          >
-            <Text style={{ display: 'block', fontWeight: 'bold' }}>
-              {`安全区调试${snapshot.isPreview ? '(预览:行程中)' : ''}`}
-            </Text>
-            <Text style={{ display: 'block' }}>
-              {`地图:${Math.round(snapshot.rect.width)}×${Math.round(snapshot.rect.height)}  底部留白 ${Math.round(props.mapBottomPadding)}`}
-            </Text>
-            <Text style={{ display: 'block' }}>
-              {`纵向死区 Y:${Math.round(snapshot.metrics.deadZoneTopY)} ~ ${Math.round(snapshot.metrics.deadZoneBottomY)}`}
-            </Text>
-            <Text style={{ display: 'block' }}>
-              {`横向边距:左右各 ${Math.round(snapshot.safeXMargin)}px`}
-            </Text>
-            <Text style={{ display: 'block' }}>
-              {`可用视区 Y:${Math.round(snapshot.metrics.safeTopY)} ~ ${Math.round(snapshot.metrics.safeBottomY)}`}
-            </Text>
-            <Text style={{ display: 'block' }}>
-              {`回中目标 Y:${Math.round(snapshot.metrics.targetScreenY)}(占比 ${(snapshot.metrics.targetMapRatio * 100).toFixed(1)}%)`}
-            </Text>
-          </View>
-        </>
-      )}
-    </>
-  );
-}
diff --git a/src/robotaxi/pages/begin-route/components/debug-transition-panel.tsx b/src/robotaxi/pages/begin-route/components/debug-transition-panel.tsx
new file mode 100644
index 0000000..758529c
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/components/debug-transition-panel.tsx
@@ -0,0 +1,173 @@
+import { View, Text } from '@tarojs/components';
+import { TripStatus } from '../../../constants/trip-status';
+import { ensureSimulationOrderId } from '../dev/order-flow-simulator';
+
+type Props = {
+  /** 当前页面订单 ID(为空时自动生成)。 */
+  orderId: string;
+  /** 设置页面订单 ID。 */
+  setOrderId: (orderId: string) => void;
+  /** 设置行程状态。 */
+  setTripStatus: (status: TripStatus) => void;
+  /** 绑定 socket 业务监听。 */
+  bindSocketListeners: () => void;
+};
+
+/**
+ * 转场动效调试面板(仅 dev 构建可见)。
+ *
+ * ## 用途
+ * 提供「呼叫中(3)→派单成功(4)」和「送驾中(6)→行程结束(8)/待评价(9)」两个关键转场的独立调试入口,
+ * 固定在地图上方,始终可见,方便反复验证转场动效。
+ *
+ * ## 使用方法
+ * 1. 点击「呼叫中(3)」/「派单成功(4)」/「送驾中(6)」/「行程结束(8)」直接跳到指定态。
+ * 2. 点击「重放转场」自动执行序列:先设前一态 → 400ms 后设后一态,稳定触发转场动效。
+ * 3. 点击「去评价」执行 送驾中(6) → 400ms → 待评价(9),用于验证内部免费订单
+ *    行程结束后跳过待支付、直接进入评价态的转场。
+ * 4. 打开页面前需先清缓存,避免被 redirectTo 首页。
+ */  
+export default function DebugTransitionPanel(props: Props) {
+  if (process.env.NODE_ENV === 'production') {
+    return null;
+  }
+
+  /** 生成或复用订单 ID。 */
+  const resolveOrderId = (): string => {
+    const existing = String(props.orderId || '').trim();
+    if (existing) {
+      return existing;
+    }
+    return `SIM-${Date.now()}`;
+  };
+
+  const prepOrder = () => {
+    props.bindSocketListeners();
+    const orderId = resolveOrderId();
+    ensureSimulationOrderId(orderId);
+    props.setOrderId(orderId);
+  };
+
+  /** 呼叫中(3) */
+  const handleSetCallingCar = () => {
+    prepOrder();
+    props.setTripStatus(TripStatus.CallingCar);
+  };
+
+  /** 派单成功(4) */
+  const handleSetVehicleApproaching = () => {
+    prepOrder();
+    props.setTripStatus(TripStatus.VehicleApproaching);
+  };
+
+  /** 重放 呼叫中→派单成功:3 → 400ms → 4 */
+  const handleReplayCallingToDispatch = () => {
+    prepOrder();
+    props.setTripStatus(TripStatus.CallingCar);
+    setTimeout(() => {
+      props.setTripStatus(TripStatus.VehicleApproaching);
+    }, 400);
+  };
+
+  /** 送驾中(6) */
+  const handleSetInService = () => {
+    prepOrder();
+    props.setTripStatus(TripStatus.InService);
+  };
+
+  /** 行程结束(8) */
+  const handleSetTripFinished = () => {
+    prepOrder();
+    props.setTripStatus(TripStatus.TripFinished);
+  };
+
+  /**
+   * 重放 送驾中→待评价:6 → 400ms → 9。
+   *
+   * 用于测试内部免费订单(无需支付)行程结束后直接进入待评价态的转场,
+   * 跳过 行程结束(8) 待支付中间态。
+   */
+  const handleReplayInServiceToEvaluate = () => {
+    prepOrder();
+    props.setTripStatus(TripStatus.InService);
+    setTimeout(() => {
+      props.setTripStatus(TripStatus.PaymentSuccess);
+    }, 400);
+  };
+
+  const btn = (bg: string) =>
+    ({
+      flex: 1,
+      textAlign: 'center' as const,
+      padding: '7px 0',
+      margin: '0 3px',
+      background: bg,
+      color: '#fff',
+      fontSize: '12px',
+      borderRadius: '6px',
+    } as const);
+
+  return (
+    <View
+      catchMove
+      style={{
+        position: 'fixed',
+        left: '8px',
+        right: '8px',
+        top: '8px',
+        zIndex: 9998,
+        padding: '8px 10px',
+        background: 'rgba(20,20,20,0.88)',
+        color: '#fff',
+        borderRadius: '10px',
+        boxShadow: '0 2px 12px rgba(0,0,0,0.35)',
+      }}
+    >
+      {/* 呼叫派单转场 */}
+      <View
+        style={{
+          fontSize: '11px',
+          lineHeight: '1.5',
+          marginBottom: '4px',
+          opacity: 0.85,
+        }}
+      >
+        <Text style={{ fontWeight: 'bold' }}>呼叫派单转场(呼叫中 → 派单成功)</Text>
+      </View>
+      <View style={{ display: 'flex', marginBottom: '6px' }}>
+        <View onClick={handleSetCallingCar} style={btn('#4b9fd6')}>
+          呼叫中(3)
+        </View>
+        <View onClick={handleSetVehicleApproaching} style={btn('#36b37e')}>
+          派单成功(4)
+        </View>
+        <View onClick={handleReplayCallingToDispatch} style={btn('#ff7a45')}>
+          重放转场
+        </View>
+      </View>
+
+      {/* 送驾到达转场 */}
+      <View
+        style={{
+          fontSize: '11px',
+          lineHeight: '1.5',
+          marginBottom: '4px',
+          opacity: 0.85,
+        }}
+      >
+        <Text style={{ fontWeight: 'bold' }}>送驾到达转场(送驾中 → 行程结束 / 待评价)</Text>
+      </View>
+      <View style={{ display: 'flex' }}>
+        <View onClick={handleSetInService} style={btn('#4b9fd6')}>
+          送驾中(6)
+        </View>
+        <View onClick={handleSetTripFinished} style={btn('#36b37e')}>
+          行程结束(8)
+        </View>
+        <View onClick={handleReplayInServiceToEvaluate} style={btn('#ff7a45')}>
+          去评价
+        </View>
+      </View>
+    </View>
+  );
+}
diff --git a/src/robotaxi/pages/begin-route/components/dispatch-transition-layer.tsx b/src/robotaxi/pages/begin-route/components/dispatch-transition-layer.tsx
new file mode 100644
index 0000000..5522d22
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/components/dispatch-transition-layer.tsx
@@ -0,0 +1,148 @@
+import { useState, useRef, useEffect } from 'react';
+import { Image, Video } from "@tarojs/components";
+import { safeParse } from '../../../../utils/Robotaxi-public-method';
+import { DispatchPhase, DISPATCH_VIDEO_SRC, DISPATCH_VIDEO_POSTER, DISPATCH_VIDEO_END_POSTER } from '../hooks/use-dispatch-transition';
+import { CARD_BG_IMG, DEFAULT_VEHICLE_IMG, USE_DYNAMIC_VEHICLE_IMG } from '../../../constants/assets';
+import { getPreloadedVideoSrc } from '../../../utils/video-preload';
+
+type Props = {
+  shouldPlayEnter: boolean;
+  transitionDone: boolean;
+  dispatchPhase: DispatchPhase;
+  /** 结尾遮挡层是否可见:由 hook 在视频「真正快播放完」时置真(非 settle 起点),避免过早盖住仍在播放的视频。 */
+  endCoverVisible: boolean;
+  vehicleInfo?: {
+    vehiclePictures?: string;
+    vehicleCode?: string;
+    vehicleColor?: string;
+    plateNumber?: string;
+  };
+  onTimeUpdate: (e: any) => void;
+  onEnded: () => void;
+  onError: () => void;
+};
+
+/**
+ * 派单成功转场视觉层:视频 + poster 覆盖层 + 背景预载 + 车模兜底。
+ * 转场期间 getCardBgImg 不返回背景图,此处通过 bg-preload 不可见预载常态背景图,
+ * 保证 settle 切回背景图时无加载间隙。
+ *
+ * ## 黑帧消除(核心)
+ * 微信原生 <video> 的 poster 属性无法可靠衔接「加载完成 → 首帧绘制」之间的空档:
+ * poster 撤下后、首帧绘制前会露出约一帧黑屏。且视频 mp4 无法通过 wx.preloadAssets 预热。
+ * 双重方案:
+ * 1)视频预下载:进入下单流程即用 wx.downloadFile 把 mp4 下到本地临时文件(见 utils/video-preload),
+ *    此处经 getPreloadedVideoSrc 优先用本地路径作 src——本地文件无网络缓冲,首帧秒出,从根源缩短黑帧空档。
+ * 2)poster 覆盖层兜底:在视频之上叠一张真实 <Image>(同一 poster,已在 preload-assets 中预载,命中缓存秒开),
+ *    视频「真正开始播放」(首个 currentTime>0 的 timeupdate)前始终盖住视频区域,首帧到达后再撤下。
+ * 两者叠加,整个加载→首帧空档全程无黑屏。
+ * 注:本页 video 已启用同层渲染(车模 Image 能盖在 video 上即为佐证),故普通 Image 可覆盖 video。
+ */
+export default function DispatchTransitionLayer({
+  shouldPlayEnter,
+  transitionDone,
+  dispatchPhase,
+  endCoverVisible,
+  vehicleInfo,
+  onTimeUpdate,
+  onEnded,
+  onError,
+}: Props) {
+  // 视频是否已真正开始播放(首帧已绘制)。false 时用 poster 覆盖层遮挡视频,杜绝黑帧。
+  const [videoStarted, setVideoStarted] = useState(false);
+  // 视频是否已卸载。settle 阶段结尾遮挡层挂上后延一帧置真,先撤视频——保证遮挡先上屏、视频后拆,杜绝卸载黑帧。
+  const [videoRemoved, setVideoRemoved] = useState(false);
+  // 每轮转场开始(shouldPlayEnter 上升沿)在渲染期同步复位,使二次派单重新用 poster 遮挡、不透出黑帧。
+  // 采用渲染期 latch 而非 useEffect:effect 在绘制后才执行,会漏掉新一轮首帧导致闪黑。
+  const prevShouldPlayRef = useRef(false);
+  if (shouldPlayEnter && !prevShouldPlayRef.current) {
+    prevShouldPlayRef.current = true;
+    if (videoStarted) setVideoStarted(false);
+    if (videoRemoved) setVideoRemoved(false);
+  } else if (!shouldPlayEnter && prevShouldPlayRef.current) {
+    prevShouldPlayRef.current = false;
+  }
+
+  // 结尾遮挡层可见(视频真正快播放完)后:本次渲染已挂上遮挡层(盖在视频之上),
+  // 延一帧再卸原生视频——遮挡先上屏、视频后拆,原生层销毁的黑帧被遮挡层挡住,杜绝结尾黑帧。
+  // 改由 endCoverVisible 驱动(而非 settle):settle 起即收缩但视频仍在播,此时不能卸载视频。
+  useEffect(() => {
+    if (endCoverVisible && !videoRemoved) {
+      const t = setTimeout(() => setVideoRemoved(true), 50);
+      return () => clearTimeout(t);
+    }
+  }, [endCoverVisible, videoRemoved]);
+
+  if (!shouldPlayEnter || transitionDone) return null;
+
+  // 当前只有一种车型,产品要求统一用默认车图(USE_DYNAMIC_VEHICLE_IMG=false);
+  // 后续有新车型时把该开关改回 true 即恢复接口动态车图。详见 constants/assets.ts。
+  const vehiclePicSrc = USE_DYNAMIC_VEHICLE_IMG
+    ? (safeParse(vehicleInfo?.vehiclePictures, {})?.url || DEFAULT_VEHICLE_IMG)
+    : DEFAULT_VEHICLE_IMG;
+
+  // 优先用预下载好的本地视频路径(downloadFile 落地,无网络缓冲);未就绪回退 CDN URL。
+  const videoSrc = getPreloadedVideoSrc(DISPATCH_VIDEO_SRC);
+
+  // 视频真正开始播放(首个 currentTime>0 的 timeupdate):撤下 poster 覆盖层,交由视频接管。
+  const handleTimeUpdate = (e: any) => {
+    if (!videoStarted && (e?.detail?.currentTime ?? 0) > 0) setVideoStarted(true);
+    onTimeUpdate(e);
+  };
+
+  return (
+    <>
+      {/* 预载常态背景图:转场期间不可见(bg-preload 类),保证 settle 切回背景图时无加载间隙。
+           勿用内联 opacity:0——Taro 序列化会丢弃数字 0 导致其可见。 */}
+      <Image
+        className='calling-car-bg bg-preload'
+        src={CARD_BG_IMG}
+      />
+      {!videoRemoved && (
+        <Video
+          className='calling-car-video'
+          src={videoSrc}
+          poster={DISPATCH_VIDEO_POSTER}
+          autoplay
+          muted
+          loop={false}
+          controls={false}
+          showCenterPlayBtn={false}
+          showPlayBtn={false}
+          showFullscreenBtn={false}
+          showProgress={false}
+          objectFit='cover'
+          onTimeUpdate={handleTimeUpdate}
+          onEnded={onEnded}
+          onError={onError}
+        />
+      )}
+      {/* Poster 覆盖层:视频真正开始播放前,用预载封面图盖住视频区域,消除「加载完成→首帧绘制」间的黑帧。
+           首帧到达(首个 currentTime>0 的 timeupdate)后撤下,交由视频接管。 */}
+      {!videoStarted && (
+        <Image
+          className='calling-car-poster'
+          src={DISPATCH_VIDEO_POSTER}
+        />
+      )}
+      {/* 结尾遮挡层:视频「真正快播放完」时(endCoverVisible,非 settle 起点)用预载尾帧图盖在视频之上
+           (z-index 与 poster 同为 0),先于视频上屏、视频卸载后仍在,挡住原生 <video> 卸载黑帧;
+           transitionDone 时随本层卸载,露出已预载的常态背景图。
+           改用 endCoverVisible 而非 dispatchPhase==='settle':settle 起即开始背景收缩但视频仍在播放,
+           此时盖上遮挡层会抹掉视频尾段效果;改由 hook 在距结束 END_COVER_LEAD_SEC 时才置真。 */}
+      {endCoverVisible && (
+        <Image
+          className='calling-car-end-cover'
+          src={DISPATCH_VIDEO_END_POSTER}
+        />
+      )}
+      {/* 转场期间车模兜底渲染,承接视频卸载后与静态背景图的衔接 */}
+      {dispatchPhase !== 'idle' && (
+        <Image
+          className='calling-car-vehicle-pic'
+          src={vehiclePicSrc}
+        />
+      )}
+    </>
+  );
+}
diff --git a/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx b/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
index 7005b97..9296f89 100644
--- a/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
+++ b/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
@@ -4,7 +4,7 @@
 import { useEffect, useRef, useState } from 'react';
 import './css-folder/evaluate-popup.less'
 import { getOrderCommentType, submitOrderComment } from '../../../api/begin-route';
-import { submitComplaint } from '../../../api/feedback';
+import { submitOrderComplaint } from '../../../api/feedback';
 import { _ROBOTAXI_HOST } from '../../../../constants/api';
 import useImageUpload from '../../../hooks/use-image-upload';
 import { getOrderId } from '../../../utils/order';
@@ -16,7 +16,7 @@
     onConfirm?: () => void;
     orderId?: string;
     orderRating?: number;
-    submitMode?: 'comment' | 'complaint';
+    submitMode?: 'comment' | 'orderComplaint';
     /**
      * 是否隐藏问题类型选择
      */
@@ -25,6 +25,11 @@
      * 是否禁止切换满意度评分(点击图标/左右滑动均禁用),固定为传入的 orderRating
      */
     disableRatingSwitch?: boolean;
+    /**
+     * 是否为「订单车费反馈」入口(车费说明页 complaint-btn 触发):
+     * 标题居中展示为「订单车费反馈」,隐藏满意度切换区(图标+文案)
+     */
+    isFareFeedback?: boolean;
 }
 
 export default function EvaluatePopup({
@@ -35,7 +40,8 @@
     orderRating,
     submitMode = 'comment',
     hideFeedbackType = false,
-    disableRatingSwitch = false
+    disableRatingSwitch = false,
+    isFareFeedback = false
 }: Props) {
     const bad = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/bad.png';
     const normal = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/normal.png';
@@ -67,6 +73,8 @@
         uploadUrl: _ROBOTAXI_HOST + '/operation/base/v1/mini-program/oss/upload'
     }); // 图片上传状态与操作
 
+    const toastCommomText = submitMode === 'orderComplaint' ? '反馈' : '评价';
+
     const currentOrderRating = Number(selectedRating) || 1;
     // 根据当前选择的评分,动态展示评价弹窗的文案
     const popupDisplayConfig = {
@@ -97,7 +105,7 @@
     };
     const evaluateDimension = evaluateDimensionMap[currentOrderRating] || 3; // 接口维度:1 满意,2 一般,3 不满意
     const shouldRequireFeedbackType = !hideFeedbackType;
-    const shouldHideFeedbackTextarea = submitMode === 'comment' && [2, 3].includes(currentOrderRating);
+    const shouldHideFeedbackTextarea = [2, 3].includes(currentOrderRating);
     const hasFeedbackContent = !!feedbackContent.trim();
     const canSubmit = shouldRequireFeedbackType
         ? selectedTypeIds.length > 0 && (shouldHideFeedbackTextarea || hasFeedbackContent)
@@ -210,7 +218,7 @@
         const targetOrderId = getOrderId(orderId);
         if (!targetOrderId) {
             showToast({
-                title: '订单信息缺失,无法提交评价',
+                title: `订单信息缺失,无法提交${toastCommomText}`,
                 icon: 'none'
             });
             return;
@@ -230,7 +238,7 @@
             return;
         }
         isSubmittingRef.current = true;
-        const submitter = submitMode === 'complaint' ? submitComplaint : submitOrderComment;
+        const submitter = submitMode === 'orderComplaint' ? submitOrderComplaint : submitOrderComment;
         const picturePayload = JSON.stringify(
             pictures
                 .filter(p => p.status === 'success' && p.server && p.server.id && p.server.url)
@@ -249,20 +257,19 @@
             const isFailure = res?.success === false || (typeof res?.code === 'number' && res.code !== 0);
             if (isFailure) {
                 showToast({
-                    title: '评价提交失败,请稍后重试',
+                    title: `${toastCommomText}提交失败,请稍后重试`,
                     icon: 'none'
                 });
                 return;
             }
-            const successTitle = submitMode === 'complaint' ? '投诉已提交' : '评价已提交';
             showToast({
-                title: successTitle,
+                title: `${toastCommomText}已提交`,
                 icon: 'none'
             });
             onConfirm?.();
         }).catch(() => {
             showToast({
-                title: '评价提交失败,请稍后重试',
+                title: `${toastCommomText}提交失败,请稍后重试`,
                 icon: 'none'
             });
         }).finally(() => {
@@ -330,7 +337,7 @@
         <View className='evaluate-popup' catchMove onClick={(e) => e.stopPropagation()}>
             <View className='evaluate-mask' onClick={onClose}></View>
             <View
-                className={`evaluate-content ${isRatingSwitching ? 'is-rating-switching' : ''}`}
+                className={`evaluate-content ${isRatingSwitching ? 'is-rating-switching' : ''} ${isFareFeedback ? 'is-fare-feedback' : ''}`}
                 animation={animationData}
                 onTouchStart={handleRatingTouchStart}
                 onTouchMove={handleRatingTouchMove}
@@ -340,33 +347,37 @@
                     <Image onClick={onClose} src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/close-fill-20260726.svg" />
                 </View>
                 <View className='evaluate-rating-switcher'>
-                    <View className='evaluate-rating-switcher-title'>您对本次行程满意吗?</View>
-                    <View className={`evaluate-rating-switcher-content ${disableRatingSwitch ? 'is-single' : ''}`}>
-                        {(!disableRatingSwitch || selectedRating === 1) && (
-                            <View className={`evaluate-rating-switcher-content-item ${disableRatingSwitch ? 'is-disabled' : ''}`} onClick={() => handleSelectRating(1)}>
-                                <Image src={selectedRating === 1 ? activeBad : bad} className='evaluate-rating-switcher-content-item-img' />
-                                <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 1 ? 'active-text' : ''}`}>不满意</Text>
+                    <View className={`evaluate-rating-switcher-title ${isFareFeedback ? 'is-centered' : ''}`}>{isFareFeedback ? '订单车费反馈' : '您对本次行程满意吗?'}</View>
+                    {!isFareFeedback && (
+                        <>
+                            <View className={`evaluate-rating-switcher-content ${disableRatingSwitch ? 'is-single' : ''}`}>
+                                {(!disableRatingSwitch || selectedRating === 1) && (
+                                    <View className={`evaluate-rating-switcher-content-item ${disableRatingSwitch ? 'is-disabled' : ''}`} onClick={() => handleSelectRating(1)}>
+                                        <Image src={selectedRating === 1 ? activeBad : bad} className='evaluate-rating-switcher-content-item-img' />
+                                        <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 1 ? 'active-text' : ''}`}>不满意</Text>
+                                    </View>
+                                )}
+                                {(!disableRatingSwitch || selectedRating === 2) && (
+                                    <View className={`evaluate-rating-switcher-content-item ${disableRatingSwitch ? 'is-disabled' : ''}`} onClick={() => handleSelectRating(2)}>
+                                        <Image src={selectedRating === 2 ? activeNormal : normal} className='evaluate-rating-switcher-content-item-img' />
+                                        <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 2 ? 'active-text' : ''}`}>一般般</Text>
+                                    </View>
+                                )}
+                                {(!disableRatingSwitch || selectedRating === 3) && (
+                                    <View className={`evaluate-rating-switcher-content-item ${disableRatingSwitch ? 'is-disabled' : ''}`} onClick={() => handleSelectRating(3)}>
+                                        <Image src={selectedRating === 3 ? activeGood : good} className='evaluate-rating-switcher-content-item-img' />
+                                        <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 3 ? 'active-text' : ''}`}>很满意</Text>
+                                    </View>
+                                )}
                             </View>
-                        )}
-                        {(!disableRatingSwitch || selectedRating === 2) && (
-                            <View className={`evaluate-rating-switcher-content-item ${disableRatingSwitch ? 'is-disabled' : ''}`} onClick={() => handleSelectRating(2)}>
-                                <Image src={selectedRating === 2 ? activeNormal : normal} className='evaluate-rating-switcher-content-item-img' />
-                                <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 2 ? 'active-text' : ''}`}>一般般</Text>
-                            </View>
-                        )}
-                        {(!disableRatingSwitch || selectedRating === 3) && (
-                            <View className={`evaluate-rating-switcher-content-item ${disableRatingSwitch ? 'is-disabled' : ''}`} onClick={() => handleSelectRating(3)}>
-                                <Image src={selectedRating === 3 ? activeGood : good} className='evaluate-rating-switcher-content-item-img' />
-                                <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 3 ? 'active-text' : ''}`}>很满意</Text>
-                            </View>
-                        )}
-                    </View>
-                    <View className='evaluate-rating-switcher-summary'>{popupDisplayConfig.summary}</View>
+                            <View className='evaluate-rating-switcher-summary'>{popupDisplayConfig.summary}</View>
+                        </>
+                    )}
                 </View>
                 <View className='content-box'>
                     {!hideFeedbackType && (
                         <View className='evaluate-problem-type'>
-                           
+
                             <View className='evaluate-problem-type-content'>
                                 {typeOptions.map((item) => (
                                     <View key={item?.id} className={`evaluate-problem-type-content-item ${selectedTypeIds.includes(normalizeFeedbackTypeId(item?.id)) && 'active-tag'}`} onClick={() => toggleFeedbackType(item?.id)}>
diff --git a/src/robotaxi/pages/begin-route/components/modify-destination-review.tsx b/src/robotaxi/pages/begin-route/components/modify-destination-review.tsx
index b0e218e..5e89257 100644
--- a/src/robotaxi/pages/begin-route/components/modify-destination-review.tsx
+++ b/src/robotaxi/pages/begin-route/components/modify-destination-review.tsx
@@ -5,6 +5,7 @@
 import { getUpdateAddressNum } from "../../../api/begin-route";
 import "./css-folder/modify-destination-review.less";
 import RouteSelectionPanel from "./route-selection-panel";
+import SiteRouteCard from "../../../components/site-route-card";
 
 type Props = {
   orderId?: string;
@@ -361,25 +362,16 @@
                 {modifyDestinationTipText}
               </View>
             ) : null}
-            <View className="modify-destination-review-select-site">
-              <View className="modify-destination-review-select-site-icon">
-                <View className="modify-destination-review-select-site-icon-start"></View>
-                <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png" />
-                <View className="modify-destination-review-select-site-icon-end"></View>
-              </View>
-              <View className="modify-destination-review-select-site-content">
-                <View className="modify-destination-review-select-site-start">
-                  {startSite?.name || "-"}
-                </View>
-                <View className="modify-destination-review-select-site-divider"></View>
-                <View
-                  className="modify-destination-review-select-site-end"
-                  onClick={() => onEditDestination?.()}
-                >
-                  {nextEndSite?.name || "-"}
-                </View>
-              </View>
-            </View>
+            <SiteRouteCard
+              className="modify-destination-review-select-site"
+              sites={[
+                { name: startSite?.name || "-" },
+                {
+                  name: nextEndSite?.name || "-",
+                  onClick: () => onEditDestination?.()
+                }
+              ]}
+            />
             <View className="modify-destination-review-footer">
               <View className="modify-destination-review-fare">
                 {isOperateFree ? (
diff --git a/src/robotaxi/pages/begin-route/components/route-preference-popup/index.less b/src/robotaxi/pages/begin-route/components/route-preference-popup/index.less
index aa8e2f8..77ba0c8 100644
--- a/src/robotaxi/pages/begin-route/components/route-preference-popup/index.less
+++ b/src/robotaxi/pages/begin-route/components/route-preference-popup/index.less
@@ -28,12 +28,13 @@
     .popup-content {
         position: relative;
         z-index: 103;
-        background: #FFFFFF;
+        background: @gary-theme;
         border-radius: 40px 40px 0 0;
         width: 100%;
         display: flex;
         flex-direction: column;
         transform: translateY(100%);
+        min-height: 672px;
     }
 
     .popup-header {
@@ -48,7 +49,7 @@
             font-size: 32px;
             font-weight: 500;
             line-height: 32px;
-            color: rgba(0, 0, 0, 0.9);
+            color: @black-text-main;
         }
 
         .close-icon {
diff --git a/src/robotaxi/pages/begin-route/components/trip-finished-fireworks/index.tsx b/src/robotaxi/pages/begin-route/components/trip-finished-fireworks/index.tsx
new file mode 100644
index 0000000..17cbb26
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/components/trip-finished-fireworks/index.tsx
@@ -0,0 +1,60 @@
+import { useEffect, useRef } from 'react';
+import Fireworks, { FireworksRef } from '../../../../components/fireworks';
+import { TripStatus } from '../../../../constants/trip-status';
+
+type Props = {
+  /** 当前行程状态,由父页面传入 */
+  tripStatus: TripStatus;
+};
+
+/**
+ * 到达结束态全屏礼花组件(begin-route 页面专有)。
+ *
+ * ## 职责
+ * - 监听 tripStatus 变化,检测「送驾中(6) → 行程结束(8)」上升沿
+ * - 进入结束态后尽快发射一簇礼花(礼花先行播放),卡片/里程延迟约 700ms 才升起
+ * - 仅发射一次,prevStatus 去重防重复播放
+ * - tripStatus 非 TripFinished 时卸载 canvas(条件渲染)
+ */
+export default function TripFinishedFireworks({ tripStatus }: Props) {
+  const fireworksRef = useRef<FireworksRef | null>(null);
+  const prevStatusRef = useRef<TripStatus>(tripStatus);
+
+  // 送驾(6) → 结束(8) 转场:进入到达结束态时播放一次全屏礼花(顶层、pointer-events:none 不阻挡操作)
+  useEffect(() => {
+    const prevStatus = prevStatusRef.current;
+    prevStatusRef.current = tripStatus;
+    if (tripStatus !== TripStatus.TripFinished || prevStatus === TripStatus.TripFinished) {
+      return;
+    }
+    // 礼花先行:进入结束态后尽快发射(仅留极短延迟等 canvas 挂载),卡片随后延迟升起
+    const timer = setTimeout(() => {
+      // 仅发射一簇(single burst),避免连发造成「播放多次」的观感
+      fireworksRef.current?.shoot({
+        origin: { x: 0.5, y: 0.4 },
+        angle: 90,
+        particleCount: 180,
+        spread: 360,
+        startVelocity: 42,
+        gravity: 1.1,
+        scalar: 0.9,
+        // ticks 控制粒子存活帧数(≈ticks/60 秒)。卡片动效约 1.7s 收尾,
+        // 礼花缩短到约 1.5s(ticks 90)先行结束,避免动效结束后礼花还在飘。
+        ticks: 90,
+        colors: ['#FFD666', '#F9A76A', '#FFF1B8', '#7FE6F2', '#95CF29'],
+      });
+    }, 60);
+    return () => clearTimeout(timer);
+  }, [tripStatus]);
+
+  if (tripStatus !== TripStatus.TripFinished) {
+    return null;
+  }
+
+  return (
+    <Fireworks
+      ref={(node) => { fireworksRef.current = node; }}
+      zIndex={99999}
+    />
+  );
+}
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/camera-policy.types.ts b/src/robotaxi/pages/begin-route/hooks/camera/camera-policy.types.ts
new file mode 100644
index 0000000..634dd23
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/camera-policy.types.ts
@@ -0,0 +1,61 @@
+import { TripStatus } from '../../../../constants/trip-status';
+import { LatLng } from './map-camera';
+
+/**
+ * 相机上下文:策略据此计算「该看哪些点/哪个中心」。
+ * 由编排层(useMapCameraController)在每次触发时实时构造,保证数据最新。
+ */
+export interface CameraContext {
+  /** 当前行程状态。 */
+  tripStatus: TripStatus;
+  /** 上车点(站点对象,可能含 latitude/longitude)。 */
+  startSite: any;
+  /** 下车点。 */
+  endSite: any;
+  /** 车行路线点。 */
+  routePoints: LatLng[];
+  /** 步行路线点。 */
+  walkingPoints: LatLng[];
+  /** 最近一次用户定位(GPS/IP/兜底),首帧 focus 用。 */
+  userLocation: LatLng | null;
+  /** 车辆当前位置(接驾/行程态跟车与 fit 用)。 */
+  vehicleLocation: LatLng | null;
+}
+
+/**
+ * 相机意图:策略返回「想做什么」,由编排层按触发来源决定 isAuto 后交给相机执行。
+ * 这样策略保持纯函数(只描述期望视口),触发语义(自动/用户)与执行细节留给编排层。
+ */
+export type CameraIntent =
+  | {
+      type: 'focus';
+      point: LatLng;
+      /** 去重签名(自动 focus 用,同 fit)。 */
+      signature?: string;
+      /** 是否把点居中到「可视区域」(扣除底部卡片/顶部安全区)而非整张地图几何中心。 */
+      centerInVisibleArea?: boolean;
+    }
+  | { type: 'fit'; points: LatLng[]; signature?: string }
+  | { type: 'none' };
+
+/** 空意图常量。 */
+export const NO_CAMERA_INTENT: CameraIntent = { type: 'none' };
+
+/**
+ * 相机策略:每个 TripStatus 一份,声明各触发点下期望的相机意图。
+ * 编排层只认策略、不写死状态分支;新增状态(接驾页等)新增一份策略即可。
+ */
+export interface CameraPolicy {
+  /** 首帧(定位就绪后)。 */
+  onInitial?(ctx: CameraContext): CameraIntent;
+  /** 关键元素(上下车点/路线)到齐或变化后(自动,带签名去重)。 */
+  onElementsReady?(ctx: CameraContext): CameraIntent;
+  /** 用户点回中按钮(显式,必然生效)。 */
+  onRecenter?(ctx: CameraContext): CameraIntent;
+  /** 手势后空闲回中冷却(毫秒);<=0 或未配置则不做空闲回中。 */
+  idleRecenterMs?: number;
+  /** 空闲回中意图(默认复用 onRecenter)。 */
+  onIdleRecenter?(ctx: CameraContext): CameraIntent;
+  /** 页面恢复(待定,预留钩子)。 */
+  onRestore?(ctx: CameraContext): CameraIntent;
+}
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/collect-viewport-points.ts b/src/robotaxi/pages/begin-route/hooks/camera/collect-viewport-points.ts
new file mode 100644
index 0000000..ec4812e
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/collect-viewport-points.ts
@@ -0,0 +1,73 @@
+import { CameraContext } from './camera-policy.types';
+import { LatLng } from './map-camera';
+
+/** 站点 → 坐标点(非法返回 null)。 */
+export const siteToPoint = (site: any): LatLng | null => {
+  const latitude = Number(site?.latitude);
+  const longitude = Number(site?.longitude);
+  if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
+    return null;
+  }
+  return { latitude, longitude };
+};
+
+/** 过滤合法坐标点。 */
+const filterValidPoints = (points: LatLng[] | undefined): LatLng[] =>
+  (points || []).filter(
+    (p) => p && Number.isFinite(p.latitude) && Number.isFinite(p.longitude)
+  );
+
+/**
+ * 收集「起点 + 终点 + 车行路线 + 步行路线」四要素点集(发单页视口)。
+ * 供 fit 使用;不同状态可另写收集函数。
+ */
+export const collectRouteViewportPoints = (ctx: CameraContext): LatLng[] => {
+  const points: LatLng[] = [];
+  points.push(...filterValidPoints(ctx.routePoints));
+  points.push(...filterValidPoints(ctx.walkingPoints));
+  const start = siteToPoint(ctx.startSite);
+  if (start) points.push(start);
+  const end = siteToPoint(ctx.endSite);
+  if (end) points.push(end);
+  return points;
+};
+
+/**
+ * 收集「接驾中」视口点集:接驾路线点(ws order_route 推送的 polyline,车→上车点) + 上车点。
+ * 不含车辆实时位置(polyline 首点即车辆所在,实时车点会抖动/偏离路线,把 fit 外接矩形撑歪);
+ * 不含下车点(接驾只看车到上车点);不含步行线(仅展示,用户定位异常时会撑歪视口)。
+ */
+export const collectPickupViewportPoints = (ctx: CameraContext): LatLng[] => {
+  const points: LatLng[] = [];
+  points.push(...filterValidPoints(ctx.routePoints));
+  const pickup = siteToPoint(ctx.startSite);
+  if (pickup) points.push(pickup);
+  return points;
+};
+
+/**
+ * 收集「送驾中(InService)」视口点集:行驶路线点(ws order_route 推送的 polyline,车→下车点) + 下车点。
+ * 不含车辆实时位置(polyline 首点即车辆所在,实时车点会抖动/偏离路线,把 fit 外接矩形撑歪);
+ * 不含上车点(已过)、不含步行线。
+ */
+export const collectInServiceViewportPoints = (ctx: CameraContext): LatLng[] => {
+  const points: LatLng[] = [];
+  points.push(...filterValidPoints(ctx.routePoints));
+  const dropoff = siteToPoint(ctx.endSite);
+  if (dropoff) points.push(dropoff);
+  return points;
+};
+
+/**
+ * 收集「完成/支付态(order_detail)」视口点集:全程轨迹 + 上车点 + 下车点。
+ * 无车(showCar:false)、无步行线;用于展示整趟行程概览。
+ */
+export const collectOrderDetailViewportPoints = (ctx: CameraContext): LatLng[] => {
+  const points: LatLng[] = [];
+  points.push(...filterValidPoints(ctx.routePoints));
+  const pickup = siteToPoint(ctx.startSite);
+  if (pickup) points.push(pickup);
+  const dropoff = siteToPoint(ctx.endSite);
+  if (dropoff) points.push(dropoff);
+  return points;
+};
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/map-camera.ts b/src/robotaxi/pages/begin-route/hooks/camera/map-camera.ts
new file mode 100644
index 0000000..b393ce9
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/map-camera.ts
@@ -0,0 +1,455 @@
+import Taro from '@tarojs/taro';
+import { mapPerfLog } from './map-perf-log';
+
+/**
+ * MapCamera —— 地图「相机」,统一封装并串行化所有原生视口 API。
+ *
+ * 设计要点:
+ * 1. 框架无关的类;上层用 useRef 持有单例,绑定到某个 <Map id>。
+ * 2. 只统筹「视口类」原生 API(includePoints / moveToLocation / getScale /
+ *    getCenterLocation / getRegion)。车辆 translateMarker / eraseLines 不在此列,
+ *    但可通过 isBusy() 在相机动画窗口内避让。
+ * 3. 串行执行 + 最新意图优先 + 签名去重 + holdAuto 避让 + 安全超时。
+ * 4. 绝不回写 React 受控 props:相机只命令原生地图;需要当前视口值时用 getViewport() 现取。
+ *
+ * 已知限制:WeChat/Taro 无命令式 setScale。focus() 仅移动中心,scale 维持 <Map> 挂载值;
+ * fit() 通过 includePoints 间接决定 scale。
+ */
+
+/** 经纬度点。 */
+export type LatLng = { latitude: number; longitude: number };
+
+/** includePoints 留白:[上, 右, 下, 左](像素)。 */
+export type CameraPadding = [number, number, number, number];
+
+/**
+ * fit 留白输入:支持直接传值 / Promise / 惰性函数。
+ * 传函数时在命令真正执行(includePoints 前)才解析,保证拿到的是执行那一刻的最新留白
+ * (如底部卡片高度的即时测量),避免入队与执行之间的时间差导致 padding 过期。
+ */
+export type CameraPaddingInput =
+  | CameraPadding
+  | Promise<CameraPadding>
+  | (() => CameraPadding | Promise<CameraPadding> | undefined);
+
+/** fit 参数。 */
+export interface FitOptions {
+  /** includePoints 留白(由上层按底部卡片/安全区算好传入,支持惰性/异步解析)。 */
+  padding?: CameraPaddingInput;
+  /** 是否为「自动」意图:可被 holdAuto 避让、且参与签名去重。用户显式操作传 false。 */
+  isAuto?: boolean;
+  /** 去重签名:相同签名的自动 fit 不重复执行。 */
+  signature?: string;
+}
+
+/** focus 参数。 */
+export interface FocusOptions {
+  /** 是否为「自动」意图(可被 holdAuto 避让)。 */
+  isAuto?: boolean;
+  /** 期望缩放(信息性:当前无命令式 setScale,focus 仅移动中心)。 */
+  scale?: number;
+  /** 去重签名:相同签名的自动 focus 不重复执行(用户显式意图不去重)。 */
+  signature?: string;
+  /**
+   * 传入后按 padding 上下不对称量纵向偏移中心,使目标点居中在「可视区域」
+   * (扣除底部卡片/顶部安全区)而非整张地图的几何中心。
+   * 背景:moveToLocation / 单点 includePoints 都只会把点放到整张地图中心,
+   * 底部卡片较高时点会视觉上贴近卡片。
+   */
+  padding?: CameraPaddingInput;
+}
+
+/** 当前真实视口读数。 */
+export interface CameraViewport {
+  scale: number | null;
+  center: LatLng | null;
+  region: any | null;
+}
+
+/** 相机构造参数。 */
+export interface MapCameraOptions {
+  /** 默认 includePoints 留白。 */
+  defaultPadding?: CameraPadding;
+  /** 单条命令原生回调的安全超时(毫秒),防止队列卡死。 */
+  commandTimeoutMs?: number;
+}
+
+type CommandKind = 'fit' | 'focus';
+
+interface InternalCommand {
+  kind: CommandKind;
+  isAuto: boolean;
+  signature: string;
+  /** 本次意图涉及的点集(用于调试日志打印具体 points)。 */
+  points: LatLng[];
+  run: () => Promise<void>;
+}
+
+const DEFAULT_PADDING: CameraPadding = [60, 50, 60, 50];
+const DEFAULT_COMMAND_TIMEOUT_MS = 2000;
+
+export class MapCamera {
+  private readonly mapId: string;
+  private readonly defaultPadding: CameraPadding;
+  private readonly commandTimeoutMs: number;
+
+  /** 待执行意图队列(最多保留 1 个 pending:最新意图覆盖旧意图)。 */
+  private queue: InternalCommand[] = [];
+  /** 是否有命令正在执行。 */
+  private running = false;
+  /** 避让截止时间戳:此前忽略所有 isAuto 意图。 */
+  private holdUntilTs = 0;
+  /** 最近一次成功应用的 fit 签名(用于自动 fit 去重)。 */
+  private lastAppliedSignature = '';
+  /** 正在执行中的命令签名。 */
+  private currentSignature = '';
+  /** 地图像素高缓存(focus 可视区域居中换算用)。 */
+  private mapHeightPx = 0;
+
+  constructor(mapId: string, options?: MapCameraOptions) {
+    this.mapId = mapId;
+    this.defaultPadding = options?.defaultPadding ?? DEFAULT_PADDING;
+    this.commandTimeoutMs = options?.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
+  }
+
+  private getCtx() {
+    return Taro.createMapContext(this.mapId);
+  }
+
+  /** 把一组点纳入视野(回中/路线到齐/切路线统一入口)。 */
+  fit(points: LatLng[], options?: FitOptions): void {
+    const validPoints = (points || []).filter(
+      (p) => p && Number.isFinite(p.latitude) && Number.isFinite(p.longitude)
+    );
+    if (validPoints.length === 0) {
+      return;
+    }
+    const isAuto = options?.isAuto ?? true;
+    const signature = options?.signature ?? '';
+    const paddingInput = options?.padding;
+
+    // mapPerfLog('fit:request', { count: validPoints.length, points: validPoints, isAuto, signature });
+    this.enqueue({
+      kind: 'fit',
+      isAuto,
+      signature,
+      points: validPoints,
+      // 执行时才解析 padding(可能触发底部卡片的即时测量),保证 includePoints 用最新留白
+      run: () =>
+        this.resolvePadding(paddingInput).then((padding) => {
+          mapPerfLog('fit:exec', {
+            signature,
+            pointsCount: validPoints.length,
+            padding,
+          });
+          return this.runNative((done) => {
+            this.getCtx().includePoints({
+              points: validPoints,
+              padding,
+              complete: (res: any) => {
+                // mapPerfLog('fit:complete', {
+                //   signature,
+                //   errMsg: res?.errMsg || 'ok',
+                // });
+                done();
+              },
+            } as any);
+          });
+        }),
+    });
+  }
+
+  /** 解析 padding 输入(值 / Promise / 惰性函数),异常或空值时回退默认留白。 */
+  private resolvePadding(input?: CameraPaddingInput): Promise<CameraPadding> {
+    try {
+      const value = typeof input === 'function' ? input() : input;
+      return Promise.resolve(value)
+        .then((padding) => padding ?? this.defaultPadding)
+        .catch(() => this.defaultPadding);
+    } catch (error) {
+      return Promise.resolve(this.defaultPadding);
+    }
+  }
+
+  /** 居中到单点(首帧定位、单点场景);传入 padding 时居中到可视区域而非整张地图。 */
+  focus(point: LatLng, options?: FocusOptions): void {
+    if (!point || !Number.isFinite(point.latitude) || !Number.isFinite(point.longitude)) {
+      return;
+    }
+    const isAuto = options?.isAuto ?? true;
+    const signature = options?.signature ?? '';
+    const paddingInput = options?.padding;
+    // mapPerfLog('focus:request', { point, isAuto, signature });
+    this.enqueue({
+      kind: 'focus',
+      isAuto,
+      signature,
+      points: [point],
+      // 执行时才解析 padding 并换算纵向偏移,保证用的是执行那一刻的卡片高度/缩放
+      run: () =>
+        this.resolveFocusCenter(point, paddingInput).then((center) => {
+          // mapPerfLog('focus:exec', { point, center });
+          return this.runNative((done) => {
+            this.getCtx().moveToLocation({
+              latitude: center.latitude,
+              longitude: center.longitude,
+              complete: done,
+            } as any);
+          });
+        }),
+    });
+  }
+
+  /**
+   * 计算 focus 的实际中心:按 padding 上下不对称量把地图中心向南偏移,
+   * 使目标点落在可视区域(扣除底部卡片)的垂直中心。
+   * 换算:用 getRegion 的纬度跨度 / 地图像素高 得到当前缩放下每像素对应的纬度,
+   * 再把 (bottom - top) / 2 像素换算成纬度偏移。任一环节失败则回退几何居中。
+   */
+  private resolveFocusCenter(point: LatLng, paddingInput?: CameraPaddingInput): Promise<LatLng> {
+    if (paddingInput === undefined) {
+      return Promise.resolve(point);
+    }
+    return this.resolvePadding(paddingInput)
+      .then((padding) => {
+        const [top, , bottom] = padding;
+        const offsetPx = (Number(bottom) - Number(top)) / 2;
+        if (!Number.isFinite(offsetPx) || offsetPx === 0) {
+          // mapPerfLog('focus:fallback', { reason: 'zero-offset', offsetPx, padding });
+          return point;
+        }
+        return Promise.all([this.getRegionSafe(), this.getMapHeightPx()]).then(
+          ([region, heightPx]) => {
+            const latSpan =
+              (region?.northeast?.latitude ?? NaN) - (region?.southwest?.latitude ?? NaN);
+            if (!Number.isFinite(latSpan) || latSpan <= 0 || !heightPx) {
+              // mapPerfLog('focus:fallback', { reason: 'region-or-height-invalid', latSpan, heightPx });
+              return point;
+            }
+            const degPerPx = latSpan / heightPx;
+            const center = {
+              latitude: point.latitude - offsetPx * degPerPx,
+              longitude: point.longitude,
+            };
+            // mapPerfLog('focus:visible-center', { point, center, offsetPx, latSpan, heightPx, degPerPx });
+            return center;
+          }
+        );
+      })
+      .catch(() => point);
+  }
+
+  /** 读当前 region(失败返 null,不抛异常)。 */
+  private getRegionSafe(): Promise<any> {
+    return new Promise((resolve) => {
+      try {
+        this.getCtx().getRegion({
+          success: (res: any) => resolve(res || null),
+          fail: () => resolve(null),
+        } as any);
+      } catch (error) {
+        resolve(null);
+      }
+    });
+  }
+
+  /** 地图像素高:优先实测 #mapId(结果缓存),失败回退 windowHeight。 */
+  private getMapHeightPx(): Promise<number> {
+    if (this.mapHeightPx) {
+      return Promise.resolve(this.mapHeightPx);
+    }
+    return new Promise((resolve) => {
+      const fallback = () => {
+        try {
+          resolve(Taro.getSystemInfoSync()?.windowHeight || 0);
+        } catch (error) {
+          resolve(0);
+        }
+      };
+      try {
+        Taro.createSelectorQuery()
+          .select(`#${this.mapId}`)
+          .boundingClientRect()
+          .exec((res: any[]) => {
+            const height = res?.[0]?.height;
+            if (Number.isFinite(height) && height > 0) {
+              this.mapHeightPx = height;
+              resolve(height);
+            } else {
+              fallback();
+            }
+          });
+      } catch (error) {
+        fallback();
+      }
+    });
+  }
+
+  /** 读取当前真实视口(不入队,立即读取)。 */
+  getViewport(): Promise<CameraViewport> {
+    const ctx = this.getCtx();
+    const readScale = new Promise<number | null>((resolve) => {
+      ctx.getScale({
+        success: (res: any) => resolve(Number.isFinite(res?.scale) ? res.scale : null),
+        fail: () => resolve(null),
+      } as any);
+    });
+    const readCenter = new Promise<LatLng | null>((resolve) => {
+      ctx.getCenterLocation({
+        success: (res: any) =>
+          resolve(
+            Number.isFinite(res?.latitude) && Number.isFinite(res?.longitude)
+              ? { latitude: res.latitude, longitude: res.longitude }
+              : null
+          ),
+        fail: () => resolve(null),
+      } as any);
+    });
+    const readRegion = new Promise<any>((resolve) => {
+      ctx.getRegion({
+        success: (res: any) => resolve(res || null),
+        fail: () => resolve(null),
+      } as any);
+    });
+    return Promise.all([readScale, readCenter, readRegion]).then(
+      ([scale, center, region]) => ({ scale, center, region })
+    );
+  }
+
+  /** 进入避让窗口:期间忽略所有 isAuto 意图,并清掉队列中已排队的 auto 意图。 */
+  holdAuto(ms: number): void {
+    const safeMs = Math.max(0, Number(ms) || 0);
+    this.holdUntilTs = Date.now() + safeMs;
+    this.queue = this.queue.filter((cmd) => !cmd.isAuto);
+  }
+
+  /** 取消所有未执行意图(正在执行的无法取消)。 */
+  cancelPending(): void {
+    this.queue = [];
+  }
+
+  /** 重置 fit 去重签名:状态切换时调用,保证同一元素集合能重新 fit(如「再来一单」复用同路线)。 */
+  resetFitDedup(): void {
+    this.lastAppliedSignature = '';
+  }
+
+  /** 是否有命令正在执行(供车辆动画等在相机忙时避让)。 */
+  isBusy(): boolean {
+    return this.running;
+  }
+
+  private isHolding(): boolean {
+    return Date.now() < this.holdUntilTs;
+  }
+
+  private enqueue(cmd: InternalCommand): void {
+    // 避让窗口内忽略自动意图
+    if (cmd.isAuto && this.isHolding()) {
+      // mapPerfLog('skip:hold', { kind: cmd.kind, signature: cmd.signature, points: cmd.points });
+      return;
+    }
+    // 自动意图去重(fit/focus):相同签名且已应用/在途/排队则跳过(用户显式意图不去重)
+    if (cmd.isAuto && cmd.signature) {
+      const duplicate =
+        cmd.signature === this.currentSignature ||
+        cmd.signature === this.lastAppliedSignature ||
+        this.queue.some((q) => q.signature === cmd.signature);
+      if (duplicate) {
+        // mapPerfLog('skip:dedup', { signature: cmd.signature, points: cmd.points });
+        return;
+      }
+    }
+    // 最新意图优先:丢弃未执行的旧意图,只保留最新一个 pending
+    if (this.queue.length > 0) {
+      // mapPerfLog('supersede', { dropped: this.queue.length, points: cmd.points });
+    }
+    this.queue = [cmd];
+    this.processQueue();
+  }
+
+  private processQueue(): void {
+    if (this.running) {
+      return;
+    }
+    const cmd = this.queue.shift();
+    if (!cmd) {
+      return;
+    }
+    this.running = true;
+    this.currentSignature = cmd.signature;
+    cmd
+      .run()
+      .catch(() => {})
+      .finally(() => {
+        if (cmd.kind === 'fit' || cmd.signature) {
+          this.lastAppliedSignature = cmd.signature;
+        }
+        this.running = false;
+        this.currentSignature = '';
+        // fit 执行完成后读取原生地图的真实 scale,验证 includePoints 是否产生了预期缩放
+        if (cmd.kind === 'fit') {
+          this.getCtx().getScale({
+            success: (res: any) => {
+              // mapPerfLog('fit:result-scale', {
+              //   signature: cmd.signature,
+              //   scale: res?.scale,
+              // });
+            },
+            fail: () => {
+              // mapPerfLog('fit:result-scale', { signature: cmd.signature, scale: 'read-failed' });
+            },
+          } as any);
+        }
+        // 执行期间可能有更新的意图入队,继续处理
+        this.processQueue();
+      });
+  }
+
+  /**
+   * 执行一条原生命令:用 complete 回调结束;附带安全超时,避免回调不触发导致队列卡死。
+   */
+  private runNative(executor: (done: () => void) => void): Promise<void> {
+    return new Promise<void>((resolve) => {
+      let settled = false;
+      const done = () => {
+        if (settled) return;
+        settled = true;
+        clearTimeout(timer);
+        resolve();
+      };
+      const timer = setTimeout(done, this.commandTimeoutMs);
+      try {
+        executor(done);
+      } catch (error) {
+        done();
+      }
+    });
+  }
+}
+
+/** 便捷工厂。 */
+export const createMapCamera = (mapId: string, options?: MapCameraOptions): MapCamera =>
+  new MapCamera(mapId, options);
+
+/**
+ * 由点集构建去重签名(供策略生成 fit 的 signature)。
+ * 采样「数量 + 首/中/尾坐标」,坐标变化即签名变化。
+ */
+export const buildPointsSignature = (points: LatLng[]): string => {
+  if (!points || points.length === 0) {
+    return '';
+  }
+  const fmt = (v: number) => (Number(v) || 0).toFixed(5);
+  const first = points[0];
+  const mid = points[Math.floor(points.length / 2)];
+  const last = points[points.length - 1];
+  return [
+    points.length,
+    fmt(first.latitude),
+    fmt(first.longitude),
+    fmt(mid.latitude),
+    fmt(mid.longitude),
+    fmt(last.latitude),
+    fmt(last.longitude),
+  ].join('|');
+};
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/map-perf-log.ts b/src/robotaxi/pages/begin-route/hooks/camera/map-perf-log.ts
new file mode 100644
index 0000000..084c5fa
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/map-perf-log.ts
@@ -0,0 +1,12 @@
+/**
+ * 地图性能/渲染调试开关。
+ * 联调时置 true 观察日志;上线前改回 false(所有日志均受此开关控制,零残留)。
+ */
+export const MAP_PERF_LOG = true;
+
+/** 统一前缀日志(受 MAP_PERF_LOG 控制)。 */
+export const mapPerfLog = (...args: any[]): void => {
+  if (!MAP_PERF_LOG) return;
+  // eslint-disable-next-line no-console
+  console.log('[MapPerf]', ...args);
+};
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/policies/calling.policy.ts b/src/robotaxi/pages/begin-route/hooks/camera/policies/calling.policy.ts
new file mode 100644
index 0000000..5510007
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/policies/calling.policy.ts
@@ -0,0 +1,32 @@
+import { CameraContext, CameraIntent, CameraPolicy, NO_CAMERA_INTENT } from '../camera-policy.types';
+import { siteToPoint } from '../collect-viewport-points';
+
+/** 呼叫态空闲回中冷却。 */
+const IDLE_RECENTER_MS = 5000;
+
+/** 居中到上车点(focus + centerInVisibleArea:居中到扣除底部卡片后的可视区域)。
+ * 不用单点 fit:includePoints 只有一个点时不理会不对称 padding,
+ * 会把点放到整张地图几何中心,视觉上贴近底部卡片。 */
+const buildPickupFocusIntent = (ctx: CameraContext): CameraIntent => {
+  const point = siteToPoint(ctx.startSite);
+  if (!point) {
+    return NO_CAMERA_INTENT;
+  }
+  return {
+    type: 'focus',
+    point,
+    centerInVisibleArea: true,
+    signature: `pickup|${point.latitude.toFixed(5)}|${point.longitude.toFixed(5)}`,
+  };
+};
+
+/**
+ * 呼叫态(Publishing / CallingCar)相机策略:把上车点居中到视口。
+ * - 首帧 / 元素到齐 / 回中 / 空闲:均 focus 上车点
+ */
+export const callingPolicy: CameraPolicy = {
+  onInitial: buildPickupFocusIntent,
+  onElementsReady: buildPickupFocusIntent,
+  onRecenter: buildPickupFocusIntent,
+  idleRecenterMs: IDLE_RECENTER_MS,
+};
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/policies/drive.policy.ts b/src/robotaxi/pages/begin-route/hooks/camera/policies/drive.policy.ts
new file mode 100644
index 0000000..d4e70f9
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/policies/drive.policy.ts
@@ -0,0 +1,77 @@
+import { CameraContext, CameraIntent, CameraPolicy, NO_CAMERA_INTENT } from '../camera-policy.types';
+import { LatLng, buildPointsSignature } from '../map-camera';
+import { collectInServiceViewportPoints, siteToPoint } from '../collect-viewport-points';
+
+/** 送驾态空闲回中冷却(用户手势后 5s 无操作则回中)。 */
+const IDLE_RECENTER_MS = 5000;
+/** fit 至少需要的点数。 */
+const MIN_FIT_POINTS = 2;
+
+/**
+ * 送驾态稳定签名:基于「行驶路线点」生成。
+ * - 路线未到达时为空签名 → 早期兜底 fit 后,路线到达(签名变化)会重新 fit 完整行驶路线;
+ * - 路线到达后 routePoints 不变 → 签名稳定 → 车辆移动不反复 re-fit。
+ */
+const stableDropoffSignature = (prefix: string, ctx: CameraContext): string =>
+  `${prefix}:${buildPointsSignature(ctx.routePoints || [])}`;
+
+/**
+ * 送驾中(InService) fit 意图:fit 行驶路线点(ws polyline,车→下车点) + 下车点 (viewport:'route')。
+ * @param stable true=自动触发(带稳定签名,进入时一次);false=回中/空闲(不去重,重新 fit 当前路线)
+ */
+const buildInServiceIntent = (ctx: CameraContext, stable: boolean): CameraIntent => {
+  const points = collectInServiceViewportPoints(ctx);
+  if (points.length >= MIN_FIT_POINTS) {
+    return {
+      type: 'fit',
+      points,
+      signature: stable ? stableDropoffSignature('in-service', ctx) : undefined,
+    };
+  }
+  // 点不足 2 个(routePoints 被过渡性清空/尚未到达)时不动相机。
+  // 不能 focus(车/下车点):过渡性清空路线会触发该分支,moveToLocation 把中心挪走、
+  // 路线被推出视口,随后同签名的 fit 又被去重吞掉,视口停在切一半的状态。
+  // 保持现状等 baseline 路线到达(签名变化)再 fit 全貌即可。
+  return NO_CAMERA_INTENT;
+};
+
+/**
+ * 送驾中相机策略:进入时 fit 行驶路线一次;手势后空闲回中;回中按钮重新 fit。
+ */
+export const inServicePolicy: CameraPolicy = {
+  onInitial: (ctx) => buildInServiceIntent(ctx, true),
+  onElementsReady: (ctx) => buildInServiceIntent(ctx, true),
+  onRecenter: (ctx) => buildInServiceIntent(ctx, false),
+  idleRecenterMs: IDLE_RECENTER_MS,
+};
+
+/**
+ * 到达目的地(ArrivedDestination) 意图:一次性居中车+下车点 (viewport:'car_user')。
+ * 注意:不使用 userLocation——到达点时用户定位仍是上车时的旧值,会把视口撑歪。
+ * 车已到下车点,二者接近 → fit(车+下车点) 得到贴近的近景;缺一则 focus 单点。
+ */
+const buildArrivedDestinationIntent = (ctx: CameraContext, stable: boolean): CameraIntent => {
+  const dropoff = siteToPoint(ctx.endSite);
+  const car = siteToPoint(ctx.vehicleLocation) || dropoff; // 车辆缺失用下车点兜底
+  const points: LatLng[] = [];
+  if (car) points.push(car);
+  if (dropoff) points.push(dropoff);
+  if (points.length >= MIN_FIT_POINTS) {
+    return {
+      type: 'fit',
+      points,
+      signature: stable ? 'arrived-destination' : undefined,
+    };
+  }
+  return car ? { type: 'focus', point: car } : NO_CAMERA_INTENT;
+};
+
+/**
+ * 到达目的地相机策略:把车+下车点居中一次;手势后空闲回中。
+ */
+export const arrivedDestinationPolicy: CameraPolicy = {
+  onInitial: (ctx) => buildArrivedDestinationIntent(ctx, true),
+  onElementsReady: (ctx) => buildArrivedDestinationIntent(ctx, true),
+  onRecenter: (ctx) => buildArrivedDestinationIntent(ctx, false),
+  idleRecenterMs: IDLE_RECENTER_MS,
+};
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/policies/index.ts b/src/robotaxi/pages/begin-route/hooks/camera/policies/index.ts
new file mode 100644
index 0000000..b0f07da
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/policies/index.ts
@@ -0,0 +1,23 @@
+import { TripStatus } from '../../../../../constants/trip-status';
+import { CameraPolicy } from '../camera-policy.types';
+import { publishRoutePolicy } from './publish-route.policy';
+import { callingPolicy } from './calling.policy';
+import { approachingPolicy, arrivedPickupPolicy } from './pickup.policy';
+import { inServicePolicy, arrivedDestinationPolicy } from './drive.policy';
+import { orderDetailPolicy } from './order-detail.policy';
+
+/**
+ * begin-route 各状态相机策略映射(可拓展核心)。
+ * 新增状态(接驾页等)在此登记对应策略即可,编排层与相机无需改动。
+ */
+export const beginRoutePolicies: Partial<Record<TripStatus, CameraPolicy>> = {
+  [TripStatus.PublishRoute]: publishRoutePolicy,
+  [TripStatus.Publishing]: callingPolicy,
+  [TripStatus.CallingCar]: callingPolicy,
+  [TripStatus.VehicleApproaching]: approachingPolicy,
+  [TripStatus.VehicleArrivedPickup]: arrivedPickupPolicy,
+  [TripStatus.InService]: inServicePolicy,
+  [TripStatus.ArrivedDestination]: arrivedDestinationPolicy,
+  [TripStatus.TripFinished]: orderDetailPolicy,
+  [TripStatus.PaymentSuccess]: orderDetailPolicy,
+};
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/policies/order-detail.policy.ts b/src/robotaxi/pages/begin-route/hooks/camera/policies/order-detail.policy.ts
new file mode 100644
index 0000000..0693ff0
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/policies/order-detail.policy.ts
@@ -0,0 +1,35 @@
+import { CameraContext, CameraIntent, CameraPolicy, NO_CAMERA_INTENT } from '../camera-policy.types';
+import { buildPointsSignature } from '../map-camera';
+import { collectOrderDetailViewportPoints } from '../collect-viewport-points';
+
+/** 完成/支付态空闲回中冷却。 */
+const IDLE_RECENTER_MS = 5000;
+/** fit 至少需要的点数。 */
+const MIN_FIT_POINTS = 2;
+
+/**
+ * 完成/支付态(order_detail) fit 意图:fit 全程轨迹 + 上车点 + 下车点(无车)。
+ * 完成态路线是静态轨迹(行程已结束、不再移动),签名天然稳定 → 进入时 fit 一次;
+ * 用户主动回中走 onRecenter,不去重并重新 fit。
+ */
+const buildOrderDetailIntent = (ctx: CameraContext, stable: boolean): CameraIntent => {
+  const points = collectOrderDetailViewportPoints(ctx);
+  if (points.length < MIN_FIT_POINTS) {
+    return NO_CAMERA_INTENT;
+  }
+  return {
+    type: 'fit',
+    points,
+    signature: stable ? `order-detail:${buildPointsSignature(points)}` : undefined,
+  };
+};
+
+/**
+ * 完成/支付态相机策略:进入时 fit 整趟;纠偏/回中重新 fit。
+ */
+export const orderDetailPolicy: CameraPolicy = {
+  onInitial: (ctx) => buildOrderDetailIntent(ctx, true),
+  onElementsReady: (ctx) => buildOrderDetailIntent(ctx, true),
+  onRecenter: (ctx) => buildOrderDetailIntent(ctx, false),
+  idleRecenterMs: IDLE_RECENTER_MS,
+};
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/policies/pickup.policy.ts b/src/robotaxi/pages/begin-route/hooks/camera/policies/pickup.policy.ts
new file mode 100644
index 0000000..aa40764
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/policies/pickup.policy.ts
@@ -0,0 +1,78 @@
+import { CameraContext, CameraIntent, CameraPolicy, NO_CAMERA_INTENT } from '../camera-policy.types';
+import { buildPointsSignature } from '../map-camera';
+import { collectPickupViewportPoints, siteToPoint } from '../collect-viewport-points';
+import { mapPerfLog } from '../map-perf-log';
+
+/** 接驾态空闲回中冷却(用户手势后 5s 无操作则回中)。 */
+const IDLE_RECENTER_MS = 5000;
+/** fit 至少需要的点数。 */
+const MIN_FIT_POINTS = 2;
+
+/**
+ * 接驾态稳定签名:基于「接驾路线点」生成。
+ * - 路线未到达时为空签名 → 早期用车+上车点兜底 fit 后,路线到达(签名变化)会重新 fit 完整路线;
+ * - 路线到达后,增量擦除不改 routePoints → 签名稳定 → 车辆移动不会反复 re-fit(防抖动)。
+ */
+const stablePickupSignature = (prefix: string, ctx: CameraContext): string =>
+  `${prefix}:${buildPointsSignature(ctx.routePoints || [])}`;
+
+/**
+ * 接驾中(VehicleApproaching) fit 意图:fit 接驾路线点(ws polyline) + 上车点 (viewport:'route')。
+ * @param stable true=自动触发(带稳定签名,进入时一次);false=回中/空闲(不去重,重新 fit 当前路线)
+ */
+const buildApproachingFitIntent = (ctx: CameraContext, stable: boolean): CameraIntent => {
+  const points = collectPickupViewportPoints(ctx);
+  if (points.length >= MIN_FIT_POINTS) {
+    return {
+      type: 'fit',
+      points,
+      signature: stable ? stablePickupSignature('approaching', ctx) : undefined,
+    };
+  }
+  // 点不足 2 个(routePoints 被过渡性清空/尚未到达)时不动相机。
+  // 不能 focus(上车点):ORDER_STATUS 清空路线会触发该分支,moveToLocation 把中心
+  // 挪到上车点、路线被推出视口,随后同签名的 fit 又被去重吞掉,视口停在切一半的状态。
+  // 保持现状等 baseline 路线到达(签名变化)再 fit 全貌即可。
+  return NO_CAMERA_INTENT;
+};
+
+/**
+ * 接驾中相机策略:进入时 fit 接驾路线一次;手势后空闲回中;回中按钮重新 fit。
+ */
+export const approachingPolicy: CameraPolicy = {
+  onInitial: (ctx) => buildApproachingFitIntent(ctx, true),
+  onElementsReady: (ctx) => buildApproachingFitIntent(ctx, true),
+  onRecenter: (ctx) => buildApproachingFitIntent(ctx, false),
+  idleRecenterMs: IDLE_RECENTER_MS,
+};
+
+/**
+ * 车辆已到(VehicleArrivedPickup) 意图:focus 上车点,centerInVisibleArea 居中到
+ * 扣除底部卡片后的可视区域。车已到上车点,二者接近,直接居中上车点即可。
+ */
+const buildArrivedPickupIntent = (ctx: CameraContext, stable: boolean): CameraIntent => {
+  const pickup = siteToPoint(ctx.startSite);
+  // mapPerfLog('policy:arrived-pickup-points', {
+  //   stable,
+  //   pickup: { got: !!pickup, point: pickup },
+  // });
+  if (!pickup) {
+    return NO_CAMERA_INTENT;
+  }
+  return {
+    type: 'focus',
+    point: pickup,
+    centerInVisibleArea: true,
+    signature: stable ? 'arrived-pickup' : undefined,
+  };
+};
+
+/**
+ * 车辆已到上车点相机策略:focus 上车点到可视区域中心;手势后空闲回中。
+ */
+export const arrivedPickupPolicy: CameraPolicy = {
+  onInitial: (ctx) => buildArrivedPickupIntent(ctx, true),
+  onElementsReady: (ctx) => buildArrivedPickupIntent(ctx, true),
+  onRecenter: (ctx) => buildArrivedPickupIntent(ctx, false),
+  idleRecenterMs: IDLE_RECENTER_MS,
+};
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/policies/publish-route.policy.ts b/src/robotaxi/pages/begin-route/hooks/camera/policies/publish-route.policy.ts
new file mode 100644
index 0000000..eb2f037
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/policies/publish-route.policy.ts
@@ -0,0 +1,54 @@
+import { CameraContext, CameraIntent, CameraPolicy } from '../camera-policy.types';
+import { buildPointsSignature } from '../map-camera';
+import { collectRouteViewportPoints } from '../collect-viewport-points';
+
+/** 发单页空闲回中冷却。 */
+const IDLE_RECENTER_MS = 5000;
+/** fit 至少需要的点数(少于此仅靠 focus/兜底)。 */
+const MIN_FIT_POINTS = 2;
+
+/** 空意图。 */
+const NONE: CameraIntent = { type: 'none' };
+
+/** 构造四要素 fit 意图(点数不足返回 none)。 */
+const buildRouteFitIntent = (ctx: CameraContext, withSignature: boolean): CameraIntent => {
+  const points = collectRouteViewportPoints(ctx);
+  if (points.length < MIN_FIT_POINTS) {
+    return NONE;
+  }
+  return {
+    type: 'fit',
+    points,
+    signature: withSignature ? buildPointsSignature(points) : undefined,
+  };
+};
+
+/**
+ * 发单页(PublishRoute)相机策略。
+ * - 首帧:路线已到则直接 fit;否则 focus 定位处(scale 由 <Map> 挂载值 14 决定)
+ * - 元素到齐/变化:fit 起点+终点+车行+步行(自动,带签名去重)
+ * - 回中按钮:fit 当前四要素(用户显式,不去重)
+ * - 空闲 5s:复用回中
+ */
+export const publishRoutePolicy: CameraPolicy = {
+  onInitial(ctx) {
+    // 路线已到:直接 fit,避免 focus 用户位置覆盖已完成的路线居中
+    const routeFitIntent = buildRouteFitIntent(ctx, false);
+    if (routeFitIntent.type !== 'none') {
+      return routeFitIntent;
+    }
+    // 路线尚未到:先 focus 用户位置,让用户看到自己所在区域
+    if (ctx.userLocation) {
+      return { type: 'focus', point: ctx.userLocation };
+    }
+    return NONE;
+  },
+  onElementsReady(ctx) {
+    return buildRouteFitIntent(ctx, true);
+  },
+  onRecenter(ctx) {
+    return buildRouteFitIntent(ctx, false);
+  },
+  idleRecenterMs: IDLE_RECENTER_MS,
+  // onIdleRecenter 未配置 → 编排层默认复用 onRecenter
+};
diff --git a/src/robotaxi/pages/begin-route/hooks/camera/use-map-camera-controller.ts b/src/robotaxi/pages/begin-route/hooks/camera/use-map-camera-controller.ts
new file mode 100644
index 0000000..294f322
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/camera/use-map-camera-controller.ts
@@ -0,0 +1,258 @@
+import { useCallback, useEffect, useRef } from 'react';
+import { TripStatus } from '../../../../constants/trip-status';
+import { getUserLocation } from '../../../../utils/user-location';
+import { CameraPadding, LatLng, MapCamera } from './map-camera';
+import { CameraContext, CameraIntent, CameraPolicy } from './camera-policy.types';
+import { mapPerfLog } from './map-perf-log';
+
+const DEFAULT_MAP_ID = 'map';
+const DEFAULT_IDLE_RECENTER_MS = 5000; // 手势后空闲回中默认冷却
+const HOLD_BUFFER_MS = 1000; // 避让窗口在空闲冷却基础上的缓冲
+const BASE_GESTURE_HOLD_MS = 2000; // 未启用空闲回中时,手势后仍短暂避让自动意图
+
+const USER_GESTURE_CAUSES = new Set(['drag', 'scale', 'gesture']);
+
+export interface UseMapCameraControllerParams {
+  /** <Map id>,默认 'map'。 */
+  mapId?: string;
+  /** 当前行程状态。 */
+  tripStatus: TripStatus;
+  /** 状态 → 策略映射(可拓展核心)。 */
+  policies: Partial<Record<TripStatus, CameraPolicy>>;
+  /** 上车点。 */
+  startSite: any;
+  /** 下车点。 */
+  endSite: any;
+  /** 车行路线点。 */
+  routePoints: LatLng[];
+  /** 步行路线点。 */
+  walkingPoints: LatLng[];
+  /** 车辆当前位置(接驾/行程态 fit 用)。 */
+  vehicleLocation: LatLng | null;
+  /** 最新用户定位(接驾态 fit 用,由外部刷新传入;为空时回退首帧定位值)。 */
+  userLocation?: LatLng | null;
+  /** includePoints 留白(按底部卡片/安全区算好;支持异步,即 fit 执行前即时测量)。 */
+  getPadding?: () => CameraPadding | Promise<CameraPadding>;
+  /**
+   * focus 可视区域居中专用留白(口径同 getPadding,但不做鸿蒙 padding 补偿)。
+   * focus 的可视区域居中换算(MapCamera.resolveFocusCenter)全程基于真实 DOM 像素
+   * 计算偏移,不经过 includePoints 原生 API,不能套用「补偿 includePoints 像素解释
+   * 偏差」的鸿蒙补偿系数,否则会导致偏移量在鸿蒙上被错误放大数倍。未提供时回退 getPadding。
+   */
+  getFocusPadding?: () => CameraPadding | Promise<CameraPadding>;
+  /** 是否启用首帧定位(默认 true)。 */
+  enableInitialLocate?: boolean;
+  /** 首帧定位拿到坐标后回调(用于移除进入页面的 loading 占位)。 */
+  onLocated?: (loc: { latitude: number; longitude: number; source: string }) => void;
+}
+
+export interface MapCameraController {
+  /** 相机实例(供需要时直接调用,如车辆动画避让 camera.isBusy())。 */
+  camera: MapCamera;
+  /** 绑定到 <Map onRegionChange>。 */
+  onRegionChange: (e: any) => void;
+  /** 回中按钮或业务纠偏(用户显式意图),source 用于 MapPerf 归因。 */
+  recenter: (source?: string) => void;
+  /** 用户切换路线后调用(用户显式意图,本质同回中)。 */
+  onRouteSwitched: () => void;
+  /** 读取当前真实视口。 */
+  getViewport: () => ReturnType<MapCamera['getViewport']>;
+}
+
+/**
+ * useMapCameraController —— 相机编排层。
+ *
+ * 职责:监听 首帧定位 / 元素变化 / 用户手势 / 回中按钮 等信号,查 policies[tripStatus]
+ * 得到相机意图,按触发来源决定 isAuto,交给唯一的 MapCamera 执行。
+ *
+ * - 首帧:getUserLocation() → policy.onInitial(isAuto:true)
+ * - 元素/状态变化:policy.onElementsReady(isAuto:true,相机按签名去重)
+ * - 回中按钮 / 切路线:policy.onRecenter(isAuto:false,必然生效)
+ * - 手势 begin:holdAuto 避让 + 清空闲定时器;end:起空闲定时器 → 空闲回中
+ *
+ * 说明:空闲回中执行时用 isAuto:false,其「被新手势打断」由定时器清除保证
+ * (手势 begin 会清掉定时器),无需依赖 holdAuto 抑制,避免被自身避让窗口误吞。
+ */
+export function useMapCameraController(params: UseMapCameraControllerParams): MapCameraController {
+  const { mapId = DEFAULT_MAP_ID, enableInitialLocate = true } = params;
+
+  const cameraRef = useRef<MapCamera | null>(null);
+  if (!cameraRef.current) {
+    cameraRef.current = new MapCamera(mapId);
+  }
+  const userLocationRef = useRef<LatLng | null>(null);
+  const idleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const initializedRef = useRef(false);
+  const prevTripStatusRef = useRef(params.tripStatus);
+
+  // 最新数据放 ref,供事件/定时器读取最新值,避免闭包过期
+  const dataRef = useRef(params);
+  dataRef.current = params;
+
+  const buildContext = useCallback((): CameraContext => {
+    const d = dataRef.current;
+    return {
+      tripStatus: d.tripStatus,
+      startSite: d.startSite,
+      endSite: d.endSite,
+      routePoints: d.routePoints || [],
+      walkingPoints: d.walkingPoints || [],
+      userLocation: d.userLocation ?? userLocationRef.current,
+      vehicleLocation: d.vehicleLocation ?? null,
+    };
+  }, []);
+
+  const getPolicy = useCallback((): CameraPolicy | undefined => {
+    const d = dataRef.current;
+    return d.policies?.[d.tripStatus];
+  }, []);
+
+  const executeIntent = useCallback(
+    (intent: CameraIntent | undefined, isAuto: boolean) => {
+      const camera = cameraRef.current;
+      if (!camera || !intent) return;
+      if (intent.type === 'focus') {
+        camera.focus(intent.point, {
+          isAuto,
+          signature: intent.signature,
+          // centerInVisibleArea:惰性传入 padding,相机执行时按底部卡片高度把点
+          // 居中到可视区域(而非整张地图几何中心)
+          padding: intent.centerInVisibleArea
+            ? () => dataRef.current.getFocusPadding?.() ?? dataRef.current.getPadding?.()
+            : undefined,
+        });
+      } else if (intent.type === 'fit') {
+        camera.fit(intent.points, {
+          isAuto,
+          signature: intent.signature,
+          // 惰性传入:相机在 includePoints 执行前才调用,保证拿到执行那一刻的最新留白
+          // (getPadding 可为异步的即时测量)。同时通过 dataRef 读取最新 getPadding,
+          // 避免 useCallback 依赖 getPadding 导致引用变化,进而触发首帧定位 effect
+          // 重跑、cancelled=true、onLocated 丢失的 bug
+          padding: () => dataRef.current.getPadding?.(),
+        });
+      }
+    },
+    [] // stable — getPadding 通过 dataRef.current 动态读取,无需作为依赖
+  );
+
+  const clearIdleTimer = useCallback(() => {
+    if (idleTimerRef.current) {
+      clearTimeout(idleTimerRef.current);
+      idleTimerRef.current = null;
+    }
+  }, []);
+
+  // 首帧定位 → onInitial(方案 B:<Map> 已用兜底坐标挂载,这里 focus 一次)
+  useEffect(() => {
+    if (!enableInitialLocate || initializedRef.current) return;
+    initializedRef.current = true;
+    let cancelled = false;
+    getUserLocation()
+      .then((loc) => {
+        if (cancelled) return;
+        userLocationRef.current = { latitude: loc.latitude, longitude: loc.longitude };
+        // mapPerfLog('trigger:initial', { source: loc.source });
+        executeIntent(getPolicy()?.onInitial?.(buildContext()), true);
+        // 拿到坐标后通知外部移除进入页面的 loading 占位
+        dataRef.current.onLocated?.(loc);
+      })
+      .catch(() => {});
+    return () => {
+      cancelled = true;
+    };
+  }, [enableInitialLocate, getPolicy, executeIntent, buildContext]);
+
+  // 元素/状态变化 → onElementsReady(自动 fit,相机按签名去重;数据不足由策略返回 none)
+  useEffect(() => {
+    // tripStatus 变化属于明确的应用事件:释放上一段手势遗留的避让窗口,
+    // 并清掉上一状态遗留的空闲回中定时器,保证进入新状态的居中/全览意图(focus/fit)
+    // 一定生效、且不会被上一状态的空闲定时器跨状态误触发。
+    if (prevTripStatusRef.current !== params.tripStatus) {
+      prevTripStatusRef.current = params.tripStatus;
+      cameraRef.current?.holdAuto(0);
+      cameraRef.current?.resetFitDedup();
+      clearIdleTimer();
+    }
+    executeIntent(getPolicy()?.onElementsReady?.(buildContext()), true);
+  }, [
+    params.tripStatus,
+    params.startSite,
+    params.endSite,
+    params.routePoints,
+    params.walkingPoints,
+    params.vehicleLocation,
+    params.userLocation,
+    getPolicy,
+    executeIntent,
+    buildContext,
+    clearIdleTimer,
+  ]);
+
+  // 卸载清理
+  useEffect(() => () => clearIdleTimer(), [clearIdleTimer]);
+
+  const recenter = useCallback((source = 'unspecified') => {
+    clearIdleTimer();
+    // mapPerfLog('trigger:recenter', {
+    //   source,
+    //   tripStatus: dataRef.current.tripStatus
+    // });
+    executeIntent(getPolicy()?.onRecenter?.(buildContext()), false);
+  }, [clearIdleTimer, getPolicy, executeIntent, buildContext]);
+
+  const onRouteSwitched = useCallback(() => {
+    clearIdleTimer();
+    // mapPerfLog('trigger:recenter', {
+    //   source: 'route-switched',
+    //   tripStatus: dataRef.current.tripStatus
+    // });
+    executeIntent(getPolicy()?.onRecenter?.(buildContext()), false);
+  }, [clearIdleTimer, getPolicy, executeIntent, buildContext]);
+
+  const onRegionChange = useCallback(
+    (e: any) => {
+      const camera = cameraRef.current;
+      if (!camera) return;
+      const causedBy = String(e?.causedBy || e?.detail?.causedBy || '').toLowerCase();
+      const eventType = String(e?.type || e?.detail?.type || '').toLowerCase();
+      if (!USER_GESTURE_CAUSES.has(causedBy)) return;
+
+      const policy = getPolicy();
+      const idleMs = policy?.idleRecenterMs ?? DEFAULT_IDLE_RECENTER_MS;
+      const holdMs = (idleMs > 0 ? idleMs : BASE_GESTURE_HOLD_MS) + HOLD_BUFFER_MS;
+
+      if (eventType === 'begin') {
+        // 用户开始拖/缩:避让自动意图,取消未触发的空闲回中
+        // mapPerfLog('trigger:gesture-begin', { causedBy });
+        camera.holdAuto(holdMs);
+        clearIdleTimer();
+      } else if (eventType === 'end') {
+        // 手势结束:继续避让到空闲窗口结束,并起空闲回中定时器
+        // mapPerfLog('trigger:gesture-end', { causedBy, idleMs });
+        camera.holdAuto(holdMs);
+        clearIdleTimer();
+        const canIdleRecenter = idleMs > 0 && !!(policy?.onIdleRecenter || policy?.onRecenter);
+        if (canIdleRecenter) {
+          idleTimerRef.current = setTimeout(() => {
+            idleTimerRef.current = null;
+            // mapPerfLog('trigger:idle-recenter');
+            const p = getPolicy();
+            const intent = (p?.onIdleRecenter || p?.onRecenter)?.(buildContext());
+            // 空闲回中:用 isAuto:false 保证不被避让窗口吞掉;被新手势打断已由清定时器保证
+            executeIntent(intent, false);
+          }, idleMs);
+        }
+      }
+    },
+    [clearIdleTimer, getPolicy, executeIntent, buildContext]
+  );
+
+  return {
+    camera: cameraRef.current,
+    onRegionChange,
+    recenter,
+    onRouteSwitched,
+    getViewport: () => cameraRef.current!.getViewport(),
+  };
+}
diff --git a/src/robotaxi/pages/begin-route/hooks/speed-scale-config.ts b/src/robotaxi/pages/begin-route/hooks/speed-scale-config.ts
deleted file mode 100644
index 3b113f7..0000000
--- a/src/robotaxi/pages/begin-route/hooks/speed-scale-config.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * 速度自动缩放配置(纯常量与映射,无副作用),供 hook 与调试面板共享,避免阈值散落多处。
- *
- * 速度分级与缩放等级(按 km/h,非 debug 模式,仅低速 / 中高速两档):
- *   - 低速 0~20:scale 19(看清路况细节)
- *   - 中高速 >20:scale 16(看清路线全貌)
- *
- * 注意:后端 ORDER_ROUTE 消息 route.speed 字段单位为 km/h(不是 m/s),全链路按 km/h 处理。
- */
-
-/**
- * 调试开关:实车难以跑到高速,开启后用更低的速度阈值方便测试,并启用页面调速面板。
- * 测试完成后改回 false 即可恢复线上正常阈值,并自动隐藏调试面板。
- *   - false(线上):低速 0~20km/h,中高速 >20km/h
- *   - true (测试):低速 0~10km/h,中高速 >10km/h
- */
-export const SPEED_SCALE_DEBUG = false;
-
-/** 速度滑动平均窗口,取最近 N 条消息求均值,避免临界值来回闪烁。 */
-export const SPEED_SAMPLE_WINDOW = 1;
-
-/** 低速挡上限(km/h),达到或超过即进入中高速挡。 */
-export const LOW_SPEED_MAX_KMH = SPEED_SCALE_DEBUG ? 10 : 20;
-
-/** 低速挡缩放等级。 */
-export const LOW_SPEED_SCALE = SPEED_SCALE_DEBUG ? 19 : 19;
-
-/** 中高速挡缩放等级(合并原中速、高速两档)。 */
-export const HIGH_SPEED_SCALE = SPEED_SCALE_DEBUG ? 16 : 16;
-
-
-/** 速度挡位描述。 */
-export type SpeedTier = {
-  key: 'low' | 'high';
-  label: string;
-  scale: number;
-};
-
-/**
- * 根据速度(km/h)解析挡位与对应缩放等级。
- */
-export const resolveSpeedTierByKmh = (kmh: number): SpeedTier => {
-  const safeKmh = Math.max(0, Number(kmh) || 0);
-  if (safeKmh < LOW_SPEED_MAX_KMH) {
-    return { key: 'low', label: '低速', scale: LOW_SPEED_SCALE };
-  }
-  return { key: 'high', label: '中高速', scale: HIGH_SPEED_SCALE };
-};
-
-/**
- * 根据速度(km/h)解析目标缩放等级。
- */
-export const resolveScaleByKmh = (kmh: number): number => resolveSpeedTierByKmh(kmh).scale;
diff --git a/src/robotaxi/pages/begin-route/hooks/use-active-trip-scale.ts b/src/robotaxi/pages/begin-route/hooks/use-active-trip-scale.ts
deleted file mode 100644
index 01ff6b2..0000000
--- a/src/robotaxi/pages/begin-route/hooks/use-active-trip-scale.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import Taro from '@tarojs/taro';
-import { useCallback, useMemo } from 'react';
-import { TripStatus } from '../../../constants/trip-status';
-import { ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING, ROUTE_VIEWPORT_RIGHT_SIDE_PADDING } from './use-route-manager.shared';
-
-type RoutePoint = {
-  latitude: number;
-  longitude: number;
-};
-
-type Deps = {
-  /** 当前行程状态。 */
-  tripStatus: TripStatus;
-  /** 计算 includePoints 所需底部留白。 */
-  computeBottomPadding: (points: any[], viewportType: string, tripStatus: TripStatus, disableAdaptive?: boolean) => number;
-  /** 读取顶部安全区高度。 */
-  resolveSafeTopInset: () => number;
-  /** 暂停动态缩放。 */
-  holdDynamicScale: (durationMs?: number) => void;
-};
-
-const ACTIVE_TRIP_STATUSES = [TripStatus.VehicleApproaching, TripStatus.InService]; // 启用视口编排的行程状态
-const USER_GESTURE_CAUSED_BY = new Set(['gesture', 'drag', 'scale']); // 需要暂停动态缩放的用户手势来源
-const USER_GESTURE_HOLD_MS = 8000; // 用户手势后暂停动态缩放时长
-const ORDER_UPDATE_LINE_HOLD_MS = 2500; // ORDER_UPDATE_LINE 全路线入屏后暂停动态缩放时长
-const ORDER_UPDATE_LINE_SIDE_PADDING = 50; // ORDER_UPDATE_LINE 路线入屏左右留白
-const ORDER_UPDATE_LINE_TOP_PADDING = 24; // ORDER_UPDATE_LINE 路线入屏最小顶部留白
-const ORDER_UPDATE_LINE_TOP_EXTRA_PADDING = 32; // ORDER_UPDATE_LINE 顶部安全区附加留白
-
-/**
- * 接驾中/行程中视口编排入口:仅保留视口编排职责。
- */
-export default function useActiveTripScale(deps: Deps) {
-  const isActiveStatus = useMemo(() => {
-    return ACTIVE_TRIP_STATUSES.includes(deps.tripStatus);
-  }, [deps.tripStatus]);
-
-  /**
-   * 处理地图手势,手动缩放/拖拽后短暂暂停视口同步。
-   */
-  const handleRegionChange = useCallback((event: any) => {
-    const causedBy = String(event?.causedBy || event?.detail?.causedBy || '').toLowerCase();
-    if (!USER_GESTURE_CAUSED_BY.has(causedBy)) {
-      return;
-    }
-    deps.holdDynamicScale(USER_GESTURE_HOLD_MS);
-  }, [deps.holdDynamicScale]);
-
-  /**
-   * ORDER_UPDATE_LINE 渲染完成后,自动执行一次全路线入屏。
-   * 注:后端推送order_update_line消息,代表车偏航,重新规划的路线
-   */
-  const handleOrderUpdateLineRendered = useCallback((points: RoutePoint[]) => {
-    if (!isActiveStatus) {
-      return;
-    }
-    const safePoints = Array.isArray(points) ? points : [];
-    if (safePoints.length < 2) {
-      return;
-    }
-    const mapCtx = Taro.createMapContext('map');
-    const bottomPadding = deps.computeBottomPadding(safePoints, 'route', deps.tripStatus, true) + ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING;
-    const topPadding = Math.max(ORDER_UPDATE_LINE_TOP_PADDING, deps.resolveSafeTopInset() + ORDER_UPDATE_LINE_TOP_EXTRA_PADDING);
-    deps.holdDynamicScale(ORDER_UPDATE_LINE_HOLD_MS);
-    Taro.nextTick(() => {
-      setTimeout(() => {
-        mapCtx.includePoints({
-          points: safePoints,
-          padding: [topPadding, ROUTE_VIEWPORT_RIGHT_SIDE_PADDING, bottomPadding, ORDER_UPDATE_LINE_SIDE_PADDING]
-        });
-      }, 0);
-    });
-  }, [deps.computeBottomPadding, deps.holdDynamicScale, deps.resolveSafeTopInset, isActiveStatus]);
-
-  return {
-    handleRegionChange,
-    handleOrderUpdateLineRendered
-  };
-}
diff --git a/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts b/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
index 04bb97a..0eb8e25 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
@@ -6,7 +6,6 @@
   shouldShowWaitingRadar: boolean;
   isAndroid: boolean;
   tripStatus: TripStatus;
-  mapScale: number;
   /** 修改目的地预览态下隐藏车辆气泡。 */
   isModifyDestinationReviewVisible: boolean;
   startSite: any;
@@ -232,12 +231,24 @@
 
   useEffect(() => {
     if (!deps.isAndroid || deps.tripStatus !== TripStatus.ArrivedDestination) return;
-    if (deps.mapScale < 20) return;
-    const timer = setTimeout(() => {
-      updateArriveCalloutPosition();
-    }, 80);
-    return () => clearTimeout(timer);
-  }, [deps.isAndroid, deps.tripStatus, deps.mapScale, deps.endSite?.latitude, deps.endSite?.longitude]);
+    let cancelled = false;
+    let timer: ReturnType<typeof setTimeout> | null = null;
+    // 改用原生 getScale 读取真实缩放:达到阈值再刷新到达气泡位置,不再依赖受控 mapScale。
+    Taro.createMapContext('map').getScale({
+      success: (res) => {
+        if (cancelled) return;
+        if (Number(res?.scale) < 20) return;
+        timer = setTimeout(() => {
+          updateArriveCalloutPosition();
+        }, 80);
+      },
+      fail: () => {}
+    });
+    return () => {
+      cancelled = true;
+      if (timer) clearTimeout(timer);
+    };
+  }, [deps.isAndroid, deps.tripStatus, deps.endSite?.latitude, deps.endSite?.longitude]);
 
   return {
     waitingRadarStyle,
diff --git a/src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts b/src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts
index da79801..9168a40 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts
@@ -13,6 +13,8 @@
   skipBluetoothAuth?: boolean;
   /** 车端钥匙下发失败(getUnlocktToken 接口非 200),蓝牙不可解锁 */
   unlockTokenFailed?: boolean;
+  /** 派单成功转场动效是否进行中:为 true 时接驾中(4)暂缓初始化蓝牙,待动效完成后再初始化 */
+  dispatchTransitionActive?: boolean;
 };
 
 type BluetoothAccessStatus = 'system_off' | 'unauthorized' | 'authorized';
@@ -148,7 +150,8 @@
   bluetoothAuthSuccess,
   startBluetoothAuth,
   skipBluetoothAuth = false,
-  unlockTokenFailed = false
+  unlockTokenFailed = false,
+  dispatchTransitionActive = false
 }: Deps) {
   const [status, setStatus] = useState<BluetoothAccessStatus>('system_off'); // 当前蓝牙访问态:系统未开 / 小程序未授权 / 已授权
   const [statusReady, setStatusReady] = useState(false); // 蓝牙状态是否已完成首轮判定
@@ -556,6 +559,12 @@
         logBluetoothAccess('等待 orderId 就绪');
         return;
       }
+      // 接驾中(4)派单成功转场期间:与车控卡片一致,待转场动效完成后再初始化蓝牙,
+      // 避免转场动画播放时触发系统蓝牙授权弹窗。到达上车点(5)不受此限制(需尽快可开门)。
+      if (tripStatus === TripStatus.VehicleApproaching && dispatchTransitionActive) {
+        logBluetoothAccess('派单成功转场动效未完成,暂缓初始化蓝牙');
+        return;
+      }
       logBluetoothAccess('进入蓝牙 UI 维护态', {
         tripStatus,
         orderId
@@ -588,7 +597,7 @@
     stopOpenSettingPollingRef.current();
     setShowAuthBluetoothPopup(false);
     setShowSuccessAuthPopup(false);
-  }, [tripStatus, orderId, refreshBluetoothState]);
+  }, [tripStatus, orderId, refreshBluetoothState, dispatchTransitionActive]);
 
   // 跳转系统蓝牙设置,并在返回后触发一次状态刷新
   const handleOpenSystemBluetoothSetting = useCallback(async () => {
diff --git a/src/robotaxi/pages/begin-route/hooks/use-credit-pay-return.ts b/src/robotaxi/pages/begin-route/hooks/use-credit-pay-return.ts
index eb8f364..fae7f30 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-credit-pay-return.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-credit-pay-return.ts
@@ -1,6 +1,6 @@
 import { useCallback, useEffect, useRef } from 'react';
 import Taro from '@tarojs/taro';
-import { showToast } from '../../../components/toast';
+import { showToast, hideToast } from '../../../components/toast';
 import { queryLedgerCreditOrderResult } from '../../../api/begin-route';
 
 type Deps = {
@@ -61,7 +61,7 @@
     pollingOrderIdRef.current = '';
     clearPollingTimer();
     if (hideLoading) {
-      Taro.hideLoading();
+      hideToast();
     }
   }, [clearPollingTimer]);
 
@@ -143,7 +143,7 @@
         return;
       }
 
-      Taro.showLoading({ title: '授权结果查询中', mask: true });
+      showToast({ title: '授权结果查询中', icon: 'loading', mask: true });
 
       const maxPollingCount = 120;
       let currentPollingCount = 0;
@@ -169,7 +169,7 @@
 
           if (status === 'DOING' || result?.code === 20710) {
             stopPollingSession(true);
-            Taro.showToast({ title: '授权成功', icon: 'none' });
+            showToast({ title: '授权成功', icon: 'none' });
             void deps.onAuthorizedReturn(queryId);
             return;
           }
@@ -205,7 +205,7 @@
           if (errorCode === '20710') {
             console.log(`[信用分授权查询遇到20710] 第${currentPollingCount + 1}次,当作授权成功处理`);
             stopPollingSession(true);
-            Taro.showToast({ title: '授权成功', icon: 'none' });
+            showToast({ title: '授权成功', icon: 'none' });
             void deps.onAuthorizedReturn(queryId);
             return;
           }
diff --git a/src/robotaxi/pages/begin-route/hooks/use-defer-until-dispatch-settled.ts b/src/robotaxi/pages/begin-route/hooks/use-defer-until-dispatch-settled.ts
new file mode 100644
index 0000000..1af7290
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/use-defer-until-dispatch-settled.ts
@@ -0,0 +1,51 @@
+import { useState, useEffect, useRef, useCallback } from 'react';
+import { TripStatus } from '../../../constants/trip-status';
+
+/**
+ * 页面级「派单成功转场延后闸门」。
+ *
+ * 背景:真正的转场动画状态机 `useDispatchTransition` 位于子组件 `CallingCar` 内(绑定视频、且随
+ * `key={tripStatus}` 重挂载),页面级逻辑(如蓝牙初始化 `useBluetoothAccess`)读不到它的
+ * `transitionDone`。本 hook 在页面级维护一个布尔闸门,作为「是否要暂缓某些页面级逻辑」的信号:
+ *
+ * - **开闸时机(本 hook 自算)**:进入接驾中(4)边沿时,在渲染期同步置 `active=true`
+ *   (必须同步——确保早于同一提交里的页面级 effect(如蓝牙)读取,避免竞态导致转场中弹系统弹窗)。
+ * - **关闸时机(外部告知)**:动画真正播完由子组件 `useDispatchTransition.transitionDone` 判定,
+ *   经 `CallingCar` 的 `onDispatchTransitionSettled` 回调调用本 hook 的 `markSettled` 置回 false。
+ * - 离开接驾态时自动复位为 false。
+ *
+ * 数据流:
+ *   [子] useDispatchTransition.transitionDone
+ *        └─(contentReady → markSettled)→ [页] useDeferUntilDispatchSettled.dispatchTransitionActive
+ *        └─→ [页] useBluetoothAccess 据此决定是否暂缓初始化
+ *
+ * @param tripStatus 当前行程状态
+ * @returns dispatchTransitionActive 转场是否进行中(true 时应暂缓页面级逻辑);markSettled 动效完成回调
+ */
+export function useDeferUntilDispatchSettled(tripStatus: TripStatus) {
+  const [dispatchTransitionActive, setDispatchTransitionActive] = useState(false);
+  const prevTripStatusRef = useRef<TripStatus | undefined>(undefined);
+
+  // 渲染期同步置位:进入接驾中(4)边沿即开闸(早于 effect,规避竞态);离开接驾态则关闸复位。
+  if (
+    tripStatus === TripStatus.VehicleApproaching &&
+    prevTripStatusRef.current !== TripStatus.VehicleApproaching
+  ) {
+    if (!dispatchTransitionActive) {
+      setDispatchTransitionActive(true);
+    }
+  } else if (tripStatus !== TripStatus.VehicleApproaching && dispatchTransitionActive) {
+    setDispatchTransitionActive(false);
+  }
+
+  useEffect(() => {
+    prevTripStatusRef.current = tripStatus;
+  }, [tripStatus]);
+
+  // 转场动效完成(或本次未播放转场)时由子组件回调,放行被暂缓的页面级逻辑。
+  const markSettled = useCallback(() => {
+    setDispatchTransitionActive(false);
+  }, []);
+
+  return { dispatchTransitionActive, markSettled };
+}
diff --git a/src/robotaxi/pages/begin-route/hooks/use-dispatch-transition.ts b/src/robotaxi/pages/begin-route/hooks/use-dispatch-transition.ts
new file mode 100644
index 0000000..d0895e0
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/use-dispatch-transition.ts
@@ -0,0 +1,170 @@
+import { useState, useEffect, useRef } from 'react';
+import { VEHICLE_APPROACHING_VIDEO, VEHICLE_APPROACHING_POSTER, VEHICLE_APPROACHING_END_POSTER } from '../../../constants/assets';
+
+/** 派单成功转场视频(源自 constants/assets.ts 单一数据源) */
+export const DISPATCH_VIDEO_SRC = VEHICLE_APPROACHING_VIDEO;
+export const DISPATCH_VIDEO_POSTER = VEHICLE_APPROACHING_POSTER;
+/** 转场视频尾帧:settle 阶段作结尾遮挡层,挡住原生 video 卸载黑帧 */
+export const DISPATCH_VIDEO_END_POSTER = VEHICLE_APPROACHING_END_POSTER;
+/** 兜底超时(ms):防卡死安全阀——视频加载/播放失败时 onTimeUpdate/onEnded 都不触发,超过此时长强制归位 */
+export const DISPATCH_FALLBACK_MS = 10000;
+/**
+ * 结尾遮挡层提前量(秒):结尾遮挡层只需在视频「真正快播放完」时才盖上(此值即距视频自然结束的秒数)。
+ * 必须显著小于 BG_SHRINK_LEAD_SEC——否则遮挡层会在背景刚开始收缩、视频仍在播时就盖住,抹掉视频尾段效果。
+ */
+export const END_COVER_LEAD_SEC = 0.4;
+
+export type DispatchPhase = 'idle' | 'video' | 'settle';
+
+/**
+ * 派单成功转场状态机:管理视频播放阶段、定时器、事件处理。
+ * 挂载时先保持 idle(面板处于最终位),下一帧再切 video 才能让面板下移产生缓动过渡。
+ * 父层已捕捉边沿并通过 playDispatchTransition 传入(本组件被 key 重挂载,无法自判边沿)。
+ */
+export function useDispatchTransition(playDispatchTransition: boolean) {
+  const [dispatchPhase, setDispatchPhase] = useState<DispatchPhase>('idle');
+  const [transitionDone, setTransitionDone] = useState(false);
+  // 结尾遮挡层是否可见。独立于 settle:settle 起即开始背景收缩(视频仍在播),
+  // 而遮挡层延后到视频「真正快播放完」(距结束 END_COVER_LEAD_SEC)才置真,避免过早盖住视频尾段。
+  const [endCoverVisible, setEndCoverVisible] = useState(false);
+  // shouldPlayEnter 控制转场期间的内容延迟、背景图门槛与整卡升起 keyframe。
+  // 过去依赖 CallingCar 的 key={tripStatus} 重挂载做「挂载快照」,去 key 后组件不再重挂载,
+  // 改为在渲染期同步捕捉 playDispatchTransition 的上升沿(false→true)置真——
+  // 同步置真而非放到 effect,是为避免整卡升起前先渲染一帧静止态导致的位移闪跳;
+  // 又不能直接绑定 playDispatchTransition(其在父层下一次渲染即翻 false,会把 keyframe 类中途卸掉扼杀动画),
+  // 故用独立状态 latch,转场完成后回落 false,使同一实例内下一单再次进入派单成功时能重新触发。
+  const [shouldPlayEnter, setShouldPlayEnter] = useState(false);
+  const prevPlayRef = useRef(false);
+  // 渲染期同步捕捉上升沿并置类(React 官方「prop 变化时调整 state」模式,重渲染早于绘制,无中间帧)
+  if (playDispatchTransition && !prevPlayRef.current) {
+    prevPlayRef.current = true;
+    if (!shouldPlayEnter) setShouldPlayEnter(true);
+  } else if (!playDispatchTransition && prevPlayRef.current) {
+    prevPlayRef.current = false;
+  }
+
+  const enterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const settleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const endCoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const fallbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const settleTriggeredRef = useRef(false);
+
+  // 背景缩小精确计时:微信 video 的 timeupdate 间隔约 265ms,无法直接捕获目标时刻。
+  // 方案:timeupdate 记录 {播放时间, 墙上时钟} 作同步锚点,自适应 setTimeout 在两次事件间
+  // 通过墙上时钟插值估算播放进度,提前唤醒、递归逼近,最终触发精度约 4ms。
+  const lastTimeSyncRef = useRef({ currentTime: 0, wallClock: 0 });
+  const videoDurationRef = useRef(0);
+  const shrinkTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const shrinkTriggeredRef = useRef(false);
+
+  /** 背景缩小提前量(秒):在视频结束前触发 settle。视频全长 3.333s,即 currentTime ≈ 2.278s 处。 */
+  const BG_SHRINK_LEAD_SEC = 1.055;
+
+  // 进入归位落定阶段(幂等,只生效一次)
+  const enterSettle = () => {
+    if (settleTriggeredRef.current) return;
+    settleTriggeredRef.current = true;
+    if (fallbackTimerRef.current) clearTimeout(fallbackTimerRef.current);
+    setDispatchPhase('settle');
+    // 结尾遮挡层延后显示:settle 起先跑背景收缩(视频照常播放),待收缩接近尾声、视频「真正快播放完」
+    // (距结束 END_COVER_LEAD_SEC)时才盖上遮挡层。settle 由精确自适应定时器在 duration-BG_SHRINK_LEAD_SEC
+    // 触发,故此处再等 (BG_SHRINK_LEAD_SEC-END_COVER_LEAD_SEC) 即精确落在 duration-END_COVER_LEAD_SEC。
+    // 该时刻仍早于本层卸载(settleTimer 800ms),遮挡层可先于视频卸载上屏、挡住卸载黑帧。
+    // 兜底路径(onError/超时触发的 settle)同样适用:遮挡层在 settle 后固定延时出现,早于 settleTimer。
+    if (endCoverTimerRef.current) clearTimeout(endCoverTimerRef.current);
+    endCoverTimerRef.current = setTimeout(
+      () => setEndCoverVisible(true),
+      Math.max((BG_SHRINK_LEAD_SEC - END_COVER_LEAD_SEC) * 1000, 0),
+    );
+    if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
+    settleTimerRef.current = setTimeout(() => {
+      setDispatchPhase('idle');
+      setTransitionDone(true);
+      // 回落转场门槛:--enter keyframe 已在前 0.8s 播完(此时约 3.9s),清除类名不影响观感,
+      // 且为同一实例内下一次上升沿重新触发 keyframe 留出空间(false→true 才会重播)。
+      setShouldPlayEnter(false);
+    }, 800);
+  };
+
+  /**
+   * 自适应缩小定时器。每次 timeupdate 或定时器到期时调用,计算距目标还有多久:
+   * - 已到达 → 调用 enterSettle 进入 settle
+   * - 未到达 → setTimeout 提前 10ms 唤醒,到期后用墙上时钟插值修正、递归检查
+   */
+  const scheduleShrinkCheck = (currentTime: number) => {
+    if (shrinkTriggeredRef.current) return;
+
+    const targetSec = videoDurationRef.current - BG_SHRINK_LEAD_SEC;
+    const remaining = targetSec - currentTime;
+
+    if (remaining <= 0) {
+      shrinkTriggeredRef.current = true;
+      if (shrinkTimerRef.current) clearTimeout(shrinkTimerRef.current);
+      enterSettle();
+      return;
+    }
+
+    if (shrinkTimerRef.current) clearTimeout(shrinkTimerRef.current);
+    shrinkTimerRef.current = setTimeout(() => {
+      const { currentTime: lastCT, wallClock: lastWC } = lastTimeSyncRef.current;
+      if (lastWC === 0) return;
+      const estimated = lastCT + (Date.now() - lastWC) / 1000;
+      scheduleShrinkCheck(estimated);
+    }, Math.max(remaining * 1000 - 10,30));
+  };
+
+  const handleDispatchVideoTimeUpdate = (e: any) => {
+    const { currentTime, duration } = e?.detail || {};
+    if (typeof currentTime !== 'number' || typeof duration !== 'number' || duration <= 0) return;
+
+    lastTimeSyncRef.current = { currentTime, wallClock: Date.now() };
+    videoDurationRef.current = duration;
+
+    scheduleShrinkCheck(currentTime);
+  };
+
+  // shouldPlayEnter 置真(上升沿)后启动转场状态机;复位所有一次性标记,保证同一实例内可重复播放。
+  useEffect(() => {
+    if (!shouldPlayEnter) return;
+
+    if (enterTimerRef.current) clearTimeout(enterTimerRef.current);
+    if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
+    if (endCoverTimerRef.current) clearTimeout(endCoverTimerRef.current);
+    if (fallbackTimerRef.current) clearTimeout(fallbackTimerRef.current);
+    if (shrinkTimerRef.current) clearTimeout(shrinkTimerRef.current);
+    settleTriggeredRef.current = false;
+    shrinkTriggeredRef.current = false;
+    lastTimeSyncRef.current = { currentTime: 0, wallClock: 0 };
+    videoDurationRef.current = 0;
+    setTransitionDone(false);
+    setEndCoverVisible(false);
+    setDispatchPhase('idle');
+
+    enterTimerRef.current = setTimeout(() => {
+      // 视频已在 32ms 内报错/结束(极端)时勿覆盖 settle 阶段
+      if (settleTriggeredRef.current) return;
+      setDispatchPhase('video');
+    }, 32);
+    fallbackTimerRef.current = setTimeout(() => enterSettle(), DISPATCH_FALLBACK_MS);
+  }, [shouldPlayEnter]);
+
+  // 卸载时清理所有定时器
+  useEffect(() => {
+    return () => {
+      if (enterTimerRef.current) clearTimeout(enterTimerRef.current);
+      if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
+      if (endCoverTimerRef.current) clearTimeout(endCoverTimerRef.current);
+      if (fallbackTimerRef.current) clearTimeout(fallbackTimerRef.current);
+      if (shrinkTimerRef.current) clearTimeout(shrinkTimerRef.current);
+    };
+  }, []);
+
+  return {
+    dispatchPhase,
+    transitionDone,
+    shouldPlayEnter,
+    endCoverVisible,
+    enterSettle,
+    handleDispatchVideoTimeUpdate,
+  };
+}
diff --git a/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts b/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
index e413b24..3e0ff8a 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -11,7 +11,7 @@
   "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/change-site-up.png"; // 行程中修改目的地时的上车点图标
 // 接驾态(前往上车点)上车点三角图标
 const PICKUP_APPROACHING_ICON_URL =
-  "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-20260722.png";
+  "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-20260812.png";
 // 送驾态(前往目的地)下车点三角图标
 const DROPOFF_INSERVICE_ICON_URL =
   "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/down-triangle-20260722.png";
@@ -218,8 +218,11 @@
       !shouldUseChangePickupIcon;
     // 修改目的地预览:起点(上车点)统一用三角图标 up-triangle + 名称显示在图标下方
     const isPickupModifyPreview = deps.isModifyDestinationReviewVisible;
+    // 发布路线态:上车点同样展示三角图标(仅 iOS,Android 不展示)
+    const isPickupPublishRoute =
+      deps.tripStatus === TripStatus.PublishRoute && !shouldUseChangePickupIcon && !deps.isAndroid;
     // 接驾态路线切换卡片展示中:地图状态被切换为 PublishRoute 展示态,但上车点名称仍需按三角图标态展示
-    const isPickupTriangle = isPickupApproaching || isPickupArrived || isPickupSettled || isPickupModifyPreview || deps.showTrip1StyleEndpoint;
+    const isPickupTriangle = isPickupApproaching || isPickupArrived || isPickupSettled || isPickupModifyPreview || isPickupPublishRoute || deps.showTrip1StyleEndpoint;
     // 名称在图标下方:接驾态 11px,到达/待支付/完成态 22px
     const pickupNameLabelConfig = isPickupApproaching
       ? SITE_NAME_LABEL_BASE
@@ -312,12 +315,14 @@
       : dropoffSite.name || "目的地";
     // 送驾态(前往目的地):下车点改用三角图标,名称移到图标下方,浮窗展示 end-site 图片;
     // 待支付/完成态:下车点同样三角图标 + end-site 浮窗,名称移到图标左侧。
+    // 发布路线态:下车点同样展示三角图标。
     const isDropoffInService = deps.tripStatus === TripStatus.InService;
     const isDropoffSettled = [
       TripStatus.TripFinished,
       TripStatus.PaymentSuccess
     ].includes(deps.tripStatus);
-    const isDropoffTriangle = isDropoffInService || isDropoffSettled;
+    const isDropoffPublishRoute = deps.tripStatus === TripStatus.PublishRoute && !deps.isAndroid;
+    const isDropoffTriangle = isDropoffInService || isDropoffSettled || isDropoffPublishRoute;
     const useDropoffRichCallout = isDropoffTriangle; // cover-view 富浮窗
     const getCustomCalloutAnchorY = () => {
       if (!deps.isAndroid && isDropoffSettled) {
diff --git a/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts b/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
index f6af6db..c5ac9bd 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
@@ -1,18 +1,7 @@
 import Taro from "@tarojs/taro";
-import { useState, useRef, useCallback, useMemo, useEffect } from "react";
-import {
-  computeAdaptiveBottomPadding,
-  debounce
-} from "../../../../utils/Robotaxi-public-method";
+import { useState, useRef, useCallback, useEffect } from "react";
 import { TripStatus } from "../../../constants/trip-status";
-import { buildActiveTripSafeAreaMetrics } from "./active-trip-camera-policy";
-import {
-  ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING,
-  ROUTE_VIEWPORT_MIN_TOP_PADDING,
-  ROUTE_VIEWPORT_RIGHT_SIDE_PADDING,
-  ROUTE_VIEWPORT_SIDE_PADDING,
-  ROUTE_VIEWPORT_TOP_EXTRA_PADDING
-} from "./use-route-manager.shared";
+import { mapPerfLog } from "./camera/map-perf-log";
 
 type Deps = {
   tripStatus: TripStatus;
@@ -24,12 +13,6 @@
   vehicleLocation: { latitude: number; longitude: number } | null;
   longitude: number | null;
   latitude: number | null;
-  setLongitude: (value: number | null) => void;
-  setLatitude: (value: number | null) => void;
-  recordLongitude: (value: number | null) => void;
-  recordLatitude: (value: number | null) => void;
-  orderDetailViewportRefreshVersion: number;
-  markViewportFitSyncing?: () => void;
 };
 
 const defaultDeps: Deps = {
@@ -41,46 +24,11 @@
   endSite: null,
   vehicleLocation: null,
   longitude: null,
-  latitude: null,
-  setLongitude: () => {},
-  setLatitude: () => {},
-  recordLongitude: () => {},
-  recordLatitude: () => {},
-  orderDetailViewportRefreshVersion: 0
+  latitude: null
 };
 
-const DROPOFF_MAX_SCALE = 20;
-const DEFAULT_DYNAMIC_SCALE_HOLD_MS = 2500; // 一次性视口调整后,暂停速度缩放的默认冷却时长
-const ACTIVE_TRIP_VIEWPORT_SYNC_MIN_DELTA = 0.00001; // 车辆中心跟随的最小经纬度变化,约 1 米,避免重复写入中心点状态
-const ACTIVE_TRIP_VIEWPORT_SYNC_INTERVAL_MS = 600; // 行程安全区跟随的最小同步间隔,避免地图频繁漂移
-const ACTIVE_TRIP_VIEWPORT_SYNC_ANIMATION_MS = 300; // 跟车中心移动缓动时长,等待原生地图动画后再回写受控中心点
-const ORDER_DETAIL_SCALE_SYNC_DELAY_MS = 600; // 完成态 includePoints 后等待原生地图落稳,再同步实际 scale
-const VIEWPORT_FIT_DEBOUNCE_MS = 300; // 视口 fit 统一防抖:状态切换瞬间路线点/视口类型会先残留旧值再被正确值覆盖,
-// 防抖把窗口内的多次 includePoints/moveToLocation 合并为最后一次,避免连续命令式调用动画相互打断导致缩放卡死
-export const ACTIVE_TRIP_VIEWPORT_SAFE_X_MARGIN_RATIO = 0.2; // 横向两侧保留的边距比例,调大可让车辆更早触发横向回中,避免跑到贴边只剩一小段线路
-
-type ViewportPoint = {
-  latitude: number;
-  longitude: number;
-};
-
-type ViewportPayload = {
-  points: ViewportPoint[];
-  keyParts: any[];
-};
-
-/**
- * 将合法站点转换为 includePoints 需要的坐标点。
- */
-const buildSiteViewportPoint = (site: any) => {
-  if (!site?.latitude || !site?.longitude) {
-    return null;
-  }
-  return {
-    latitude: site.latitude,
-    longitude: site.longitude
-  };
-};
+// 横向两侧保留的边距比例(供 debug-safearea-panel 复用,保留导出)。
+export const ACTIVE_TRIP_VIEWPORT_SAFE_X_MARGIN_RATIO = 0.2;
 
 export default function useMapViewport(rawDeps?: Partial<Deps>) {
   const deps = { ...defaultDeps, ...(rawDeps || {}) } as Deps;
@@ -91,104 +39,14 @@
   const [carCalloutVisible, setCarCalloutVisible] = useState(false);
   const [arriveCalloutStyle, setArriveCalloutStyle] = useState<any>({});
   const [arriveCalloutVisible, setArriveCalloutVisible] = useState(false);
-  const [mapScale, setMapScaleState] = useState(14);
   const [originBoxHidden, setOriginBoxHidden] = useState(false);
   const [originOpacity, setOriginOpacity] = useState(1);
   const [waitingRadarStyle, setWaitingRadarStyle] = useState<any>({});
   const [waitingRadarVisible, setWaitingRadarVisible] = useState(true);
   const [showLocationFlag, setShowLocationFlag] = useState(true);
 
-  const mapRectRef = useRef<any>(null);
-  const viewportKeyRef = useRef<string>("");
-  const orderDetailViewportLockedRef = useRef(false);
-  const tripStatus4ViewportLockedRef = useRef(false); // 车辆接驾中,且视口类型为 route 时,若已锁定视口,则不再重复 updateViewportByPayload,避免反复触发地图缩放
-  const lastRouteViewportPointsLengthRef = useRef(-1); // 接驾/送驾态上一次执行 updateViewportByPayload 时的 routePoints 长度:长度变化才再次执行
-  const tripStatus6ViewportLockedRef = useRef(false); // 车辆送驾中,且视口类型为 route 时,若已锁定视口,则不再重复 updateViewportByPayload,避免反复触发地图缩放
-  const arrivedPickupCenteredRef = useRef(false); // 到达上车点后仅在上车点坐标准备好时回中一次
-  const orderDetailEnterSignatureRef = useRef("");
-  const orderDetailRouteUpdatedAfterEnterRef = useRef(false);
-  const dynamicScaleHoldUntilRef = useRef(0); // 速度缩放暂停截止时间
-  const lastActiveTripViewportSyncAtRef = useRef(0); // 最近一次安全区跟随时间戳
-  const activeTripViewportSyncingRef = useRef(false); // 当前是否有进行中的安全区跟随任务
-  const activeTripViewportSyncTimerRef = useRef<ReturnType<
-    typeof setTimeout
-  > | null>(null); // 跟车中心移动延时回写定时器
-  const orderDetailScaleSyncTimerRef = useRef<ReturnType<
-    typeof setTimeout
-  > | null>(null); // 完成态 includePoints 后同步原生 scale 的延时器
-  const orderDetailScaleSyncVersionRef = useRef(0); // 完成态 scale 回写版本,只允许最后一次 includePoints 生效
-  const vehicleLocationRef = useRef(deps.vehicleLocation); // 车辆位置 ref,供延时回调读取最新值
-  const latitudeRef = useRef(deps.latitude); // 当前中心纬度 ref
-  const longitudeRef = useRef(deps.longitude); // 当前中心经度 ref
-
-  // 记录 scale 的最新值,供跨渲染读取;更新 ref 本身不会触发重新渲染
-  const mapScaleRef = useRef<number | null>(null);
-  // recordMapScale:仅记录最新值,不触发重新渲染,等下次渲染时直接读取 ref 中记录的最新值
-  const recordMapScale = useCallback((value: number | null) => {
-    mapScaleRef.current = value;
-  }, []);
-  // setMapScale:更新 state 触发渲染的同时同步记录最新值,保证 ref 始终为最新(兼容函数式更新)
-  const setMapScale = useCallback(
-    (value: number | ((prev: number) => number)) => {
-      setMapScaleState(prev => {
-        const next = typeof value === "function" ? value(prev) : value;
-        mapScaleRef.current = next;
-        return next;
-      });
-    },
-    []
-  );
-  useEffect(() => {
-    vehicleLocationRef.current = deps.vehicleLocation;
-  }, [deps.vehicleLocation]);
-  useEffect(() => {
-    latitudeRef.current = deps.latitude;
-  }, [deps.latitude]);
-  useEffect(() => {
-    longitudeRef.current = deps.longitude;
-  }, [deps.longitude]);
-  useEffect(
-    () => () => {
-      if (activeTripViewportSyncTimerRef.current) {
-        clearTimeout(activeTripViewportSyncTimerRef.current);
-        activeTripViewportSyncTimerRef.current = null;
-      }
-      if (orderDetailScaleSyncTimerRef.current) {
-        clearTimeout(orderDetailScaleSyncTimerRef.current);
-        orderDetailScaleSyncTimerRef.current = null;
-      }
-    },
-    []
-  );
-
-  const isAndroid = useMemo(() => {
-    try {
-      const info = Taro.getSystemInfoSync();
-      return (
-        info?.platform === "android" || /android/i.test(info?.system || "")
-      );
-    } catch (error) {
-      return false;
-    }
-  }, [deps.tripStatus]);
-
-  /**
-   * 暂停速度驱动缩放,避免一次性视口调整后立刻被比例尺逻辑抢回。
-   */
-  const holdDynamicScale = useCallback(
-    (durationMs: number = DEFAULT_DYNAMIC_SCALE_HOLD_MS) => {
-      const safeDurationMs = Math.max(0, Number(durationMs) || 0);
-      dynamicScaleHoldUntilRef.current = Date.now() + safeDurationMs;
-    },
-    []
-  );
-
-  /**
-   * 获取速度缩放暂停截止时间。
-   */
-  const getDynamicScaleHoldUntil = useCallback(() => {
-    return dynamicScaleHoldUntilRef.current;
-  }, []);
+  // 车辆送驾中视口锁定标记:仅供外部(trip-runtime)读写,保留兼容。
+  const tripStatus6ViewportLockedRef = useRef(false);
 
   // 获取底部安全区高度
   const resolveSafeBottomInset = () => {
@@ -205,58 +63,86 @@
     return 0;
   };
 
-  // 获取顶部安全区高度
   const resolveSafeTopInset = () => {
+    const DefaultSafeTop = 50;
     try {
       const info = Taro.getSystemInfoSync();
       const safeTop = info?.safeArea?.top;
       if (Number.isFinite(safeTop)) {
-        return Math.max(0, safeTop);
+        return Math.max(DefaultSafeTop, safeTop);
       }
     } catch (error) {
-      return 0;
+      return DefaultSafeTop;
     }
-    return 0;
+    return DefaultSafeTop;
   };
 
-  // 计算 includePoints 使用的底部 padding
+  // 计算 includePoints 使用的底部 padding:直接用实测的底部卡片高度,不做自适应缩放
   const computeBottomPadding = useCallback(
-    (
-      points: any[],
-      viewportType: string,
-      tripStatus: TripStatus,
-      disableAdaptive?: boolean
-    ) => {
-      let basePadding =
-        viewportType === "order_detail"
-          ? Math.max(200, mapBottomPaddingRef.current)
-          : mapBottomPaddingRef.current;
-      if (disableAdaptive) {
-        // computeAdaptiveBottomPadding 的逻辑有时会造成问题,此时可以关闭
-        return basePadding;
-      }
-      return computeAdaptiveBottomPadding(points, basePadding);
+    () => {
+      return mapBottomPaddingRef.current;
     },
     [mapBottomPaddingRef]
   );
 
-  // 计算 includePoints 使用的d底部 padding,考虑行程安全区
-  const safeUpdateMapBottomPadding = () => {
-    Taro.nextTick(() => {
-      const query = Taro.createSelectorQuery();
-      query.select(".container").boundingClientRect();
-      query.exec(res => {
-        const rect = res?.[0];
-        const safeInset = resolveSafeBottomInset();
-        const measured = rect?.height ? rect.height + safeInset + 16 : 0;
-        const nextPadding = Math.max(280, Math.round(measured));
-        if (Math.abs(mapBottomPaddingRef.current - nextPadding) > 20) {
-          setMapBottomPadding(nextPadding);
-          mapBottomPaddingRef.current = nextPadding;
+  /**
+   * 即时测量底部卡片高度(供相机 fit 前调用)。
+   * 与 updateMapViewportPadding 的区别:这是「按需」测量——在 includePoints 真正执行前
+   * 现测一次,保证拿到执行那一刻的卡片高度,消除「先 fit 后测量」的竞态。
+   * 测量失败或超时(selector 回调偶发不触发)时回退上次缓存值,绝不阻塞 fit。
+   *
+   * 首帧场景:.container 可能尚未渲染,首次测量 containerHeight 为 undefined。
+   * 此时若缓存值仍是初始默认 280,则 200ms 后重试一次,避免因 DOM 未就绪导致
+   * 后续 fit 使用过期 padding,进而引发视口跳变。
+   */
+  const measureBottomPadding = useCallback(() => {
+    return new Promise<number>(resolve => {
+      let settled = false;
+      const finish = (value: number, source: string) => {
+        if (settled) return;
+        settled = true;
+        clearTimeout(timer);
+        // mapPerfLog('padding:measure', { value, source });
+        resolve(value);
+      };
+      // 超时从 300ms 延长到 500ms,为首帧重试留出 200ms 窗口
+      const timer = setTimeout(() => finish(mapBottomPaddingRef.current, 'timeout'), 500);
+
+      const doMeasure = (attempt: number) => {
+        try {
+          const query = Taro.createSelectorQuery();
+          query.select(".container").boundingClientRect();
+          query.exec(res => {
+            const rect = res?.[0];
+            const safeInset = resolveSafeBottomInset();
+            const measured = rect?.height ? rect.height + safeInset : 0;
+            // mapPerfLog('padding:measure-raw', {
+            //   attempt,
+            //   containerHeight: rect?.height,
+            //   safeBottomInset: safeInset,
+            //   measuredRaw: measured,
+            // });
+            if (measured > 0) {
+              const nextPadding = Math.max(200, Math.round(measured));
+              setMapBottomPadding(nextPadding);
+              mapBottomPaddingRef.current = nextPadding;
+              finish(nextPadding, `measured-attempt${attempt}`);
+            } else if (attempt === 1 && mapBottomPaddingRef.current === 280) {
+              // 首帧 .container 尚未渲染,200ms 后重试一次
+              // mapPerfLog('padding:measure-retry', { reason: 'container-not-ready' });
+              setTimeout(() => doMeasure(2), 200);
+            } else {
+              finish(mapBottomPaddingRef.current, 'fallback-zero');
+            }
+          });
+        } catch (error) {
+          finish(mapBottomPaddingRef.current, 'error');
         }
-      });
+      };
+
+      doMeasure(1);
     });
-  };
+  }, []);
 
   // 更新地图视口留白并同步起点卡片遮挡状态
   const updateMapViewportPadding = () => {
@@ -267,13 +153,9 @@
       query.select(".origin-box").boundingClientRect();
       query.select(".map-entry").boundingClientRect();
       query.exec(res => {
-        const mapRect = res?.[0];
         const rect = res?.[1];
         const originRect = res?.[2];
         const mapEntryRect = res?.[3];
-        if (mapRect) {
-          mapRectRef.current = mapRect;
-        }
         const safeInset = resolveSafeBottomInset();
         const measured = rect?.height ? rect.height + safeInset + 16 : 0;
         const nextPadding = Math.max(280, Math.round(measured));
@@ -303,771 +185,10 @@
   };
 
   /**
-   * 获取并缓存地图容器矩形。
+   * 定位按钮点击处理:地图相机架构下,回中统一交由 useMapCameraController.recenter 处理,
+   * 此处保留空实现以兼容旧调用点(如未接入相机控制器的场景)。
    */
-  const ensureMapRect = useCallback(() => {
-    if (mapRectRef.current) {
-      return Promise.resolve(mapRectRef.current);
-    }
-    return new Promise<any>(resolve => {
-      Taro.createSelectorQuery()
-        .select("#map")
-        .boundingClientRect(rect => {
-          if (rect) {
-            mapRectRef.current = rect;
-          }
-          resolve(rect);
-        })
-        .exec();
-    });
-  }, []);
-
-  /**
-   * 获取当前地图视野范围。
-   */
-  const getMapRegion = useCallback(() => {
-    return new Promise<any>(resolve => {
-      Taro.createMapContext("map").getRegion({
-        success: res => resolve(res),
-        fail: () => resolve(null)
-      });
-    });
-  }, []);
-
-  /**
-   * 获取当前原生地图中心点,避免视口刚被 includePoints/moveToLocation 调整后仍读取到旧的 React 中心点。
-   */
-  const getMapCenterLocation = useCallback(() => {
-    return new Promise<any>(resolve => {
-      Taro.createMapContext("map").getCenterLocation({
-        success: res => resolve(res),
-        fail: () => resolve(null)
-      });
-    });
-  }, []);
-
-  /**
-   * 获取当前原生地图比例尺。
-   */
-  const getMapScale = useCallback(() => {
-    return new Promise<any>(resolve => {
-      Taro.createMapContext("map").getScale({
-        success: res => resolve(res),
-        fail: () => resolve(null)
-      });
-    });
-  }, []);
-
-  /**
-   * 读取原生地图实际的中心点与缩放级别,并通过 record* 系列函数同步到对应 ref。
-   * 仅记录最新值、不触发重新渲染,等下次渲染时由 context 读取这些 ref 中的最新值。
-   * 适用于 includePoints 等命令式调整视口后,让受控的经纬度/缩放与原生地图保持一致。
-   */
-  const recordViewportFromNative = useCallback(() => {
-    return Promise.all([getMapScale(), getMapCenterLocation()])
-      .then(([scaleRes, centerRes]) => {
-        const actualScale = Number(scaleRes?.scale);
-        if (Number.isFinite(actualScale)) {
-          recordMapScale(Math.round(actualScale));
-        }
-        const centerLatitude = Number(centerRes?.latitude);
-        const centerLongitude = Number(centerRes?.longitude);
-        if (Number.isFinite(centerLatitude)) {
-          deps.recordLatitude(centerLatitude);
-        }
-        if (Number.isFinite(centerLongitude)) {
-          deps.recordLongitude(centerLongitude);
-        }
-      })
-      .catch(() => {});
-  }, [
-    getMapScale,
-    getMapCenterLocation,
-    recordMapScale,
-    deps.recordLatitude,
-    deps.recordLongitude
-  ]);
-
-  /**
-   * 将当前原生地图视口同步到受控 state,避免纯 UI 重渲染把地图拉回旧的 scale/center。
-   */
-  const syncNativeViewportToControlledState = useCallback(() => {
-    return Promise.all([getMapScale(), getMapCenterLocation()])
-      .then(([scaleRes, centerRes]) => {
-        const actualScale = Number(scaleRes?.scale);
-        const roundedScale = Number.isFinite(actualScale)
-          ? Math.round(actualScale)
-          : null;
-        if (Number.isFinite(actualScale)) {
-          setMapScale(prev => (prev === roundedScale ? prev : roundedScale));
-        }
-        const centerLatitude = Number(centerRes?.latitude);
-        const centerLongitude = Number(centerRes?.longitude);
-        if (Number.isFinite(centerLatitude)) {
-          deps.setLatitude(centerLatitude);
-        }
-        if (Number.isFinite(centerLongitude)) {
-          deps.setLongitude(centerLongitude);
-        }
-      })
-      .catch(() => {});
-  }, [getMapScale, getMapCenterLocation, setMapScale, deps.setLatitude, deps.setLongitude, deps.tripStatus]);
-
-  /**
-   * 将经纬度映射为当前地图上的屏幕坐标。
-   */
-  const computeMapPointScreenPosition = useCallback(
-    (target: ViewportPoint, rect: any, region: any) => {
-      const southwest = region?.southwest;
-      const northeast = region?.northeast;
-      const latRange =
-        Number(northeast?.latitude) - Number(southwest?.latitude);
-      const lngRange =
-        Number(northeast?.longitude) - Number(southwest?.longitude);
-      if (
-        !rect ||
-        !Number.isFinite(latRange) ||
-        !Number.isFinite(lngRange) ||
-        latRange <= 0 ||
-        lngRange <= 0
-      ) {
-        return null;
-      }
-      return {
-        x: ((target.longitude - southwest.longitude) / lngRange) * rect.width,
-        y: ((northeast.latitude - target.latitude) / latRange) * rect.height,
-        latitudeSpan: latRange
-      };
-    },
-    []
-  );
-
-  // 解析当前上下车点
-  const resolveViewportSites = () => {
-    const pickupSite =
-      deps.startSite?.latitude && deps.startSite?.longitude
-        ? deps.startSite
-        : Taro.getStorageSync("startSite");
-    const dropoffSite =
-      deps.endSite?.latitude && deps.endSite?.longitude
-        ? deps.endSite
-        : Taro.getStorageSync("endSite");
-    return { pickupSite, dropoffSite };
-  };
-
-  /**
-   * 构建步行路线视口数据。
-   */
-  const buildRouteWalkingViewportPayload = useCallback(
-    (viewportType: string): ViewportPayload => {
-      const points: ViewportPoint[] = [];
-      if (deps.routePoints.length > 0) {
-        points.push(...deps.routePoints);
-      }
-      if (deps.walkingPoints.length > 0) {
-        points.push(...deps.walkingPoints);
-      }
-      return {
-        points,
-        keyParts: [
-          viewportType === "order_detail"
-            ? TripStatus.TripFinished
-            : deps.tripStatus,
-          viewportType,
-          deps.routePoints.length,
-          deps.walkingPoints.length
-        ]
-      };
-    },
-    [deps.routePoints, deps.tripStatus, deps.walkingPoints]
-  );
-
-  /**
-   * 构建上车点视口数据。
-   */
-  const buildPickupViewportPayload = useCallback(
-    (viewportType: string, pickupSite: any): ViewportPayload => {
-      const pickupPoint = buildSiteViewportPoint(pickupSite);
-      return {
-        points: pickupPoint ? [pickupPoint] : [],
-        keyParts: [
-          viewportType === "order_detail"
-            ? TripStatus.TripFinished
-            : deps.tripStatus,
-          viewportType,
-          pickupSite?.latitude,
-          pickupSite?.longitude
-        ]
-      };
-    },
-    [deps.tripStatus]
-  );
-
-  /**
-   * 构建主路线视口数据。
-   */
-  const buildRouteViewportPayload = useCallback(
-    (viewportType: string): ViewportPayload => {
-      const points: ViewportPoint[] = [];
-      if (deps.routePoints.length > 0) {
-        points.push(...deps.routePoints);
-      }
-      return {
-        points,
-        keyParts: [
-          viewportType === "order_detail"
-            ? TripStatus.TripFinished
-            : deps.tripStatus,
-          viewportType,
-          deps.routePoints.length
-        ]
-      };
-    },
-    [deps.routePoints, deps.tripStatus]
-  );
-
-  /**
-   * 构建车辆与用户双点视口数据。
-   */
-  const buildCarUserViewportPayload = useCallback(
-    (viewportType: string): ViewportPayload => {
-      const points: ViewportPoint[] = [];
-      if (Number.isFinite(deps.latitude) && Number.isFinite(deps.longitude)) {
-        points.push({
-          latitude: deps.latitude as number,
-          longitude: deps.longitude as number
-        });
-      }
-      if (deps.vehicleLocation?.latitude && deps.vehicleLocation?.longitude) {
-        points.push({
-          latitude: deps.vehicleLocation.latitude,
-          longitude: deps.vehicleLocation.longitude
-        });
-      }
-      return {
-        points,
-        keyParts: [
-          viewportType === "order_detail"
-            ? TripStatus.TripFinished
-            : deps.tripStatus,
-          viewportType,
-          deps.latitude,
-          deps.longitude,
-          deps.vehicleLocation?.latitude,
-          deps.vehicleLocation?.longitude
-        ]
-      };
-    },
-    [
-      deps.latitude,
-      deps.longitude,
-      deps.tripStatus,
-      deps.vehicleLocation?.latitude,
-      deps.vehicleLocation?.longitude
-    ]
-  );
-
-  /**
-   * 构建终点视口数据。
-   */
-  const buildDropoffViewportPayload = useCallback(
-    (viewportType: string, dropoffSite: any): ViewportPayload => {
-      const dropoffPoint = buildSiteViewportPoint(dropoffSite);
-      return {
-        points: dropoffPoint ? [dropoffPoint] : [],
-        keyParts: [
-          viewportType === "order_detail"
-            ? TripStatus.TripFinished
-            : deps.tripStatus,
-          viewportType,
-          dropoffSite?.latitude,
-          dropoffSite?.longitude
-        ]
-      };
-    },
-    [deps.tripStatus]
-  );
-
-  /**
-   * 构建订单详情视口数据。
-   */
-  const buildOrderDetailViewportPayload = useCallback(
-    (
-      viewportType: string,
-      pickupSite: any,
-      dropoffSite: any
-    ): ViewportPayload => {
-      const points: ViewportPoint[] = [];
-      const pickupPoint = buildSiteViewportPoint(pickupSite);
-      const dropoffPoint = buildSiteViewportPoint(dropoffSite);
-      if (deps.routePoints.length > 0) {
-        points.push(...deps.routePoints);
-      }
-      if (pickupPoint) {
-        points.push(pickupPoint);
-      }
-      if (dropoffPoint) {
-        points.push(dropoffPoint);
-      }
-      return {
-        points,
-        keyParts: [
-          viewportType === "order_detail"
-            ? TripStatus.TripFinished
-            : deps.tripStatus,
-          viewportType,
-          deps.routePoints.length,
-          pickupSite?.latitude,
-          pickupSite?.longitude,
-          dropoffSite?.latitude,
-          dropoffSite?.longitude
-        ]
-      };
-    },
-    [deps.routePoints, deps.tripStatus]
-  );
-
-  // 按视口类型构建 includePoints 入参
-  const buildViewportPayload = (
-    viewportType: string,
-    pickupSite: any,
-    dropoffSite: any
-  ) => {
-    if (viewportType === "route_walking") {
-      return buildRouteWalkingViewportPayload(viewportType);
-    }
-    if (viewportType === "pickup") {
-      return buildPickupViewportPayload(viewportType, pickupSite);
-    }
-    if (viewportType === "route") {
-      return buildRouteViewportPayload(viewportType);
-    }
-    if (viewportType === "car_user") {
-      return buildCarUserViewportPayload(viewportType);
-    }
-    if (viewportType === "dropoff") {
-      return buildDropoffViewportPayload(viewportType, dropoffSite);
-    }
-    if (viewportType === "order_detail") {
-      return buildOrderDetailViewportPayload(
-        viewportType,
-        pickupSite,
-        dropoffSite
-      );
-    }
-    return {
-      points: [],
-      keyParts: [
-        viewportType === "order_detail"
-          ? TripStatus.TripFinished
-          : deps.tripStatus,
-        viewportType
-      ]
-    };
-  };
-
-  const buildOrderDetailRouteSignature = (
-    pickupSite: any,
-    dropoffSite: any
-  ) => {
-    const firstPoint = deps.routePoints[0];
-    const lastPoint = deps.routePoints[deps.routePoints.length - 1];
-    return [
-      deps.routePoints.length,
-      firstPoint?.latitude ?? "",
-      firstPoint?.longitude ?? "",
-      lastPoint?.latitude ?? "",
-      lastPoint?.longitude ?? "",
-      pickupSite?.latitude ?? "",
-      pickupSite?.longitude ?? "",
-      dropoffSite?.latitude ?? "",
-      dropoffSite?.longitude ?? ""
-    ].join("|");
-  };
-
-  /**
-   * 完成态 includePoints 后回写原生地图实际比例与中心点,避免后续重渲染被旧受控视口覆盖。
-   */
-  const syncOrderDetailViewportFromNative = useCallback(() => {
-    orderDetailScaleSyncVersionRef.current += 1;
-    const currentSyncVersion = orderDetailScaleSyncVersionRef.current;
-    if (orderDetailScaleSyncTimerRef.current) {
-      clearTimeout(orderDetailScaleSyncTimerRef.current);
-    }
-    orderDetailScaleSyncTimerRef.current = setTimeout(() => {
-      if (
-        ![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(
-          deps.tripStatus
-        )
-      ) {
-        orderDetailScaleSyncTimerRef.current = null;
-        return;
-      }
-      Promise.all([getMapScale(), getMapCenterLocation()])
-        .then(([scaleRes, centerRes]) => {
-          if (currentSyncVersion !== orderDetailScaleSyncVersionRef.current) {
-            return;
-          }
-          const actualScale = Number(scaleRes?.scale);
-          if (Number.isFinite(actualScale)) {
-            const roundedScale = Math.round(actualScale);
-            recordMapScale(roundedScale);
-          }
-          const centerLatitude = Number(centerRes?.latitude);
-          const centerLongitude = Number(centerRes?.longitude);
-          const currentLatitude = Number(latitudeRef.current);
-          const currentLongitude = Number(longitudeRef.current);
-          if (
-            Number.isFinite(centerLatitude) &&
-            (!Number.isFinite(currentLatitude) ||
-              currentLatitude !== centerLatitude)
-          ) {
-            deps.recordLatitude(centerLatitude);
-          }
-          if (
-            Number.isFinite(centerLongitude) &&
-            (!Number.isFinite(currentLongitude) ||
-              currentLongitude !== centerLongitude)
-          ) {
-            deps.recordLongitude(centerLongitude);
-          }
-        })
-        .finally(() => {
-          orderDetailScaleSyncTimerRef.current = null;
-        });
-    }, ORDER_DETAIL_SCALE_SYNC_DELAY_MS);
-  }, [
-    deps.recordLatitude,
-    deps.recordLongitude,
-    recordMapScale,
-    deps.tripStatus,
-    getMapCenterLocation,
-    getMapScale
-  ]);
-
-  const updateViewportByPayloadRaw = (viewportType: string, points: any[]) => {
-    const mapCtx = Taro.createMapContext("map");
-    const bottomPadding =
-      computeBottomPadding(points, viewportType, deps.tripStatus, true) +
-      ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING;
-    const baseTopPadding =
-      viewportType === "order_detail" ? 60 : ROUTE_VIEWPORT_MIN_TOP_PADDING;
-    // order_detail 视口(行程已结束/支付成功)下的上车点 marker 会显示向上突出的 customCallout 气泡,
-    // 需要在系统安全区之上再叠加 ≈灵动岛 + 气泡高度的缓冲,避免气泡被灵动岛/状态栏遮挡。
-    // 数值与接驾态 ACTIVE_TRIP_CAMERA_TOP_EXTRA_PADDING 保持一致(80),其它视口沿用原有 32。
-    const topInsetExtra = ROUTE_VIEWPORT_TOP_EXTRA_PADDING
-    const topPadding = Math.max(
-      baseTopPadding,
-      resolveSafeTopInset() + topInsetExtra
-    );
-    const padding = [
-      topPadding,
-      ROUTE_VIEWPORT_RIGHT_SIDE_PADDING,
-      bottomPadding,
-      ROUTE_VIEWPORT_SIDE_PADDING
-    ];
-    holdDynamicScale();
-    Taro.nextTick(() => {
-      if (viewportType === "dropoff") {
-        mapCtx.moveToLocation({
-          latitude: points[0].latitude,
-          longitude: points[0].longitude
-        });
-        setTimeout(() => {
-          setMapScale(DROPOFF_MAX_SCALE);
-        }, 50);
-        return;
-      } else {
-        mapCtx.includePoints({ points, padding });
-        safeUpdateMapBottomPadding();
-        if (
-          viewportType === "order_detail" &&
-          [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(
-            deps.tripStatus
-          )
-        ) {
-          syncOrderDetailViewportFromNative();
-        } else {
-          deps.markViewportFitSyncing?.();
-        }
-      }
-    });
-  };
-
-  // 防抖包裹:状态切换瞬间多次连续调用只执行最后一次,避免 includePoints 动画相互打断
-  const updateViewportByPayload = debounce(
-    updateViewportByPayloadRaw,
-    VIEWPORT_FIT_DEBOUNCE_MS
-  );
-
-  const toOrigin = () => {
-    const mapCtx = Taro.createMapContext("map");
-    const includeRouteToVisibleCenter = (
-      targetSite: any,
-      shouldLockInService: boolean
-    ) => {
-      const targetPoint = buildSiteViewportPoint(targetSite);
-      const pointsToInclude = [...deps.routePoints];
-      if (targetPoint) {
-        pointsToInclude.push(targetPoint);
-      }
-      // 接驾/送驾点击回中时统一走整路线居中,并适当收窄底部留白,避免路线整体偏上。
-      const bottomPadding =
-        computeBottomPadding(pointsToInclude, "route", deps.tripStatus, true) +
-        ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING;
-      const topPadding = Math.max(
-        ROUTE_VIEWPORT_MIN_TOP_PADDING,
-        resolveSafeTopInset() + ROUTE_VIEWPORT_TOP_EXTRA_PADDING
-      );
-      holdDynamicScale();
-      mapCtx.includePoints({
-        points: pointsToInclude,
-        padding: [
-          topPadding,
-          ROUTE_VIEWPORT_RIGHT_SIDE_PADDING,
-          bottomPadding,
-          ROUTE_VIEWPORT_SIDE_PADDING
-        ]
-      });
-      if (shouldLockInService) {
-        tripStatus6ViewportLockedRef.current = true;
-      }
-      // 标记 fit 同步,在 regionchange end 时捕获实际 scale/center
-      deps.markViewportFitSyncing?.();
-    };
-    if (
-      deps.tripStatus === TripStatus.InService &&
-      deps.routePoints.length > 0
-    ) {
-      const { dropoffSite } = resolveViewportSites();
-      includeRouteToVisibleCenter(dropoffSite, true);
-      return;
-    }
-    if (
-      deps.tripStatus === TripStatus.VehicleApproaching &&
-      deps.routePoints.length > 0
-    ) {
-      const { pickupSite } = resolveViewportSites();
-      includeRouteToVisibleCenter(pickupSite, false);
-      return;
-    }
-    holdDynamicScale();
-    mapCtx.moveToLocation();
-  };
-
-  /**
-   * 获取当前用户位置并更新地图中心点。
-   * 仅在页面恢复时使用
-   */
-  const getLocationInfo = () => {
-    Taro.getLocation({ type: "gcj02" })
-      .then(res => {
-        if (deps.tripStatus === TripStatus.ArrivedDestination) {
-          return;
-        }
-        deps.setLongitude(res.longitude);
-        deps.setLatitude(res.latitude);
-        const includeRouteToVisibleCenter = (
-          targetSite: any,
-          shouldLockInService: boolean
-        ) => {
-          const targetPoint = buildSiteViewportPoint(targetSite);
-          const pointsToInclude = [...deps.routePoints];
-          if (targetPoint) {
-            pointsToInclude.push(targetPoint);
-          }
-          // 与 toOrigin 保持一致,接驾/送驾定位回中时统一按整路线居中。
-          const bottomPadding =
-            computeBottomPadding(
-              pointsToInclude,
-              "route",
-              deps.tripStatus,
-              true
-            ) + ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING;
-          const topPadding = Math.max(
-            ROUTE_VIEWPORT_MIN_TOP_PADDING,
-            resolveSafeTopInset() + ROUTE_VIEWPORT_TOP_EXTRA_PADDING
-          );
-          holdDynamicScale();
-          Taro.createMapContext("map").includePoints({
-            points: pointsToInclude,
-            padding: [
-              topPadding,
-              ROUTE_VIEWPORT_RIGHT_SIDE_PADDING,
-              bottomPadding,
-              ROUTE_VIEWPORT_SIDE_PADDING
-            ]
-          });
-          if (shouldLockInService) {
-            tripStatus6ViewportLockedRef.current = true;
-          }
-          // 标记 fit 同步,在 regionchange end 时捕获实际 scale/center
-          deps.markViewportFitSyncing?.();
-        };
-        if (
-          deps.tripStatus === TripStatus.InService &&
-          deps.routePoints.length > 0
-        ) {
-          const { dropoffSite } = resolveViewportSites();
-          includeRouteToVisibleCenter(dropoffSite, true);
-          return;
-        }
-        if (
-          deps.tripStatus === TripStatus.VehicleApproaching &&
-          deps.routePoints.length > 0
-        ) {
-          const { pickupSite } = resolveViewportSites();
-          includeRouteToVisibleCenter(pickupSite, false);
-          return;
-        }
-        holdDynamicScale();
-        Taro.createMapContext("map").moveToLocation({
-          latitude: res.latitude,
-          longitude: res.longitude
-        });
-      })
-      .catch(() => {});
-  };
-
-  /**
-   * 行程动态缩放生效时,将地图中心跟随到车辆位置,确保车辆留在当前视野内。
-   * @param options.force 跳过节流/冷却/syncing-in-progress/死区四道闸,强制按安全区目标重定位。
-   *   仅供速度驱动缩放后强制居中使用,其他调用点不传该参数即沿用原有行为。
-   *
-   * 已没有使用
-   */
-  const syncActiveTripViewportToVehicle = useCallback(
-    (options?: { force?: boolean }) => {
-      const force = options?.force === true;
-      if (
-        ![TripStatus.VehicleApproaching, TripStatus.InService].includes(
-          deps.tripStatus
-        )
-      ) {
-        return;
-      }
-      const vehicleLatitude = Number(vehicleLocationRef.current?.latitude);
-      const vehicleLongitude = Number(vehicleLocationRef.current?.longitude);
-      if (
-        !Number.isFinite(vehicleLatitude) ||
-        !Number.isFinite(vehicleLongitude)
-      ) {
-        return;
-      }
-      const now = Date.now();
-      if (!force) {
-        if (activeTripViewportSyncingRef.current) {
-          return;
-        }
-        if (
-          now - lastActiveTripViewportSyncAtRef.current <
-          ACTIVE_TRIP_VIEWPORT_SYNC_INTERVAL_MS
-        ) {
-          return;
-        }
-        if (getDynamicScaleHoldUntil() > now) {
-          return;
-        }
-      }
-      activeTripViewportSyncingRef.current = true;
-      Promise.all([ensureMapRect(), getMapRegion(), getMapCenterLocation()])
-        .then(([mapRect, region, mapCenterLocation]) => {
-          const safeAreaMetrics = buildActiveTripSafeAreaMetrics({
-            tripStatus: deps.tripStatus,
-            mapHeight: Number(mapRect?.height),
-            bottomPadding: mapBottomPaddingRef.current,
-            safeTopInset: resolveSafeTopInset()
-          });
-          if (!safeAreaMetrics) {
-            return;
-          }
-          const vehicleScreenPosition = computeMapPointScreenPosition(
-            {
-              latitude: vehicleLatitude,
-              longitude: vehicleLongitude
-            },
-            mapRect,
-            region
-          );
-          if (!vehicleScreenPosition) {
-            return;
-          }
-          // 横向死区:仅保留左右较小边距,让大部分可见区域都属于安全区,减少无操作情况下的突然横向纠偏。
-          const mapWidth = Number(mapRect?.width) || 0;
-          const safeXMargin =
-            mapWidth * ACTIVE_TRIP_VIEWPORT_SAFE_X_MARGIN_RATIO;
-          const isXOutOfBounds =
-            mapWidth > 0 &&
-            (vehicleScreenPosition.x < safeXMargin ||
-              vehicleScreenPosition.x > mapWidth - safeXMargin);
-          // 纵向死区检查
-          const isYInDeadZone =
-            vehicleScreenPosition.y >= safeAreaMetrics.deadZoneTopY &&
-            vehicleScreenPosition.y <= safeAreaMetrics.deadZoneBottomY;
-          if (!force && isYInDeadZone && !isXOutOfBounds) {
-            return;
-          }
-          const nativeCenterLatitude = Number(mapCenterLocation?.latitude);
-          const nativeCenterLongitude = Number(mapCenterLocation?.longitude);
-          const fallbackCenterLatitude = Number(latitudeRef.current);
-          const fallbackCenterLongitude = Number(longitudeRef.current);
-          const currentLatitude = Number.isFinite(nativeCenterLatitude)
-            ? nativeCenterLatitude
-            : fallbackCenterLatitude;
-          const currentLongitude = Number.isFinite(nativeCenterLongitude)
-            ? nativeCenterLongitude
-            : fallbackCenterLongitude;
-          // force=true 时纵横向都按安全区目标重定位;
-          // 其余情况(force=false)保持原有死区半纠偏行为不变。
-          const desiredCenterLatitude =
-            force || !isYInDeadZone
-              ? vehicleLatitude -
-                (0.5 - safeAreaMetrics.targetMapRatio) *
-                  vehicleScreenPosition.latitudeSpan
-              : currentLatitude;
-          const desiredCenterLongitude =
-            force || isXOutOfBounds ? vehicleLongitude : currentLongitude;
-          const hasLatitudeDelta =
-            !Number.isFinite(currentLatitude) ||
-            Math.abs(currentLatitude - desiredCenterLatitude) >=
-              ACTIVE_TRIP_VIEWPORT_SYNC_MIN_DELTA;
-          const hasLongitudeDelta =
-            !Number.isFinite(currentLongitude) ||
-            Math.abs(currentLongitude - desiredCenterLongitude) >=
-              ACTIVE_TRIP_VIEWPORT_SYNC_MIN_DELTA;
-          if (!hasLatitudeDelta && !hasLongitudeDelta) {
-            return;
-          }
-          lastActiveTripViewportSyncAtRef.current = Date.now();
-          const mapCtx = Taro.createMapContext("map");
-          mapCtx.moveToLocation({
-            latitude: desiredCenterLatitude,
-            longitude: desiredCenterLongitude
-          });
-          if (activeTripViewportSyncTimerRef.current) {
-            clearTimeout(activeTripViewportSyncTimerRef.current);
-          }
-          activeTripViewportSyncTimerRef.current = setTimeout(() => {
-            deps.setLatitude(desiredCenterLatitude);
-            deps.setLongitude(desiredCenterLongitude);
-            activeTripViewportSyncTimerRef.current = null;
-          }, ACTIVE_TRIP_VIEWPORT_SYNC_ANIMATION_MS);
-        })
-        .finally(() => {
-          activeTripViewportSyncingRef.current = false;
-        });
-    },
-    [
-      computeMapPointScreenPosition,
-      deps.setLatitude,
-      deps.setLongitude,
-      deps.tripStatus,
-      ensureMapRect,
-      getMapCenterLocation,
-      getDynamicScaleHoldUntil,
-      getMapRegion
-    ]
-  );
+  const toOrigin = () => {};
 
   useEffect(() => {
     if (
@@ -1084,287 +205,6 @@
     setShowLocationFlag(true);
   }, [deps.tripStatus]);
 
-  // 车辆到达上车点后,自动把上车点(车辆)居中到可用视区中央。
-  // 注意:原生 moveToLocation 只是命令式移动,必须同时回写受控的 latitude/longitude,
-  // 否则到达态每秒刷新的倒计时会触发重渲染,把受控 <Map> 拉回接驾态遗留的旧中心点(表现为车辆贴顶)。
-  useEffect(() => {
-    if (deps.tripStatus !== TripStatus.VehicleArrivedPickup) {
-      arrivedPickupCenteredRef.current = false;
-      return;
-    }
-    const pickupLatitude = Number(deps.startSite?.latitude);
-    const pickupLongitude = Number(deps.startSite?.longitude);
-    if (!Number.isFinite(pickupLatitude) || !Number.isFinite(pickupLongitude)) {
-      return;
-    }
-    if (arrivedPickupCenteredRef.current) {
-      return;
-    }
-    arrivedPickupCenteredRef.current = true; // 提前置位,避免异步回中期间重复触发
-    holdDynamicScale();
-    const moveAndCommitCenter = (
-      centerLatitude: number,
-      centerLongitude: number
-    ) => {
-      Taro.createMapContext("map").moveToLocation({
-        latitude: centerLatitude,
-        longitude: centerLongitude
-      });
-      // 等动画结束后一次性回写受控中心点和 scale,避免多次 setState 触发重渲染
-      setTimeout(() => {
-        getMapScale().then(res => {
-          const actualScale = Number(res?.scale);
-          if (Number.isFinite(actualScale)) {
-            const rounded = Math.round(actualScale);
-            setMapScale(prev => (prev === rounded ? prev : rounded));
-          }
-          deps.setLatitude(centerLatitude);
-          deps.setLongitude(centerLongitude);
-        });
-      }, ACTIVE_TRIP_VIEWPORT_SYNC_ANIMATION_MS);
-    };
-    Promise.all([ensureMapRect(), getMapRegion()])
-      .then(([mapRect, region]) => {
-        const safeAreaMetrics = buildActiveTripSafeAreaMetrics({
-          tripStatus: TripStatus.VehicleArrivedPickup,
-          mapHeight: Number(mapRect?.height),
-          bottomPadding: mapBottomPaddingRef.current,
-          safeTopInset: resolveSafeTopInset()
-        });
-        const pickupScreenPosition = computeMapPointScreenPosition(
-          { latitude: pickupLatitude, longitude: pickupLongitude },
-          mapRect,
-          region
-        );
-        // 安全区与视野可解析时,按可用视区中央纵向偏移中心点(与 syncActiveTripViewportToVehicle 一致),
-        // 避免上车点落在被底部卡片遮挡的几何中心;否则兜底为上车点几何居中。
-        const centerLatitude =
-          safeAreaMetrics && pickupScreenPosition
-            ? pickupLatitude -
-              (0.5 - safeAreaMetrics.targetMapRatio) *
-                pickupScreenPosition.latitudeSpan
-            : pickupLatitude;
-        moveAndCommitCenter(centerLatitude, pickupLongitude);
-      })
-      .catch(() => {
-        moveAndCommitCenter(pickupLatitude, pickupLongitude);
-      });
-  }, [
-    deps.tripStatus,
-    deps.startSite?.latitude,
-    deps.startSite?.longitude,
-    holdDynamicScale,
-    ensureMapRect,
-    getMapRegion,
-    computeMapPointScreenPosition,
-    deps.setLatitude,
-    deps.setLongitude
-  ]);
-
-  // 疑似死代码,已经没有 viewportType === 'dropoff' 这种情况了
-  useEffect(() => {
-    const viewportType = deps.statusRule.viewport;
-    if (viewportType !== "dropoff") return;
-    const { pickupSite, dropoffSite } = resolveViewportSites();
-    const { points, keyParts } = buildViewportPayload(
-      viewportType,
-      pickupSite,
-      dropoffSite
-    );
-    if (points.length === 0) return;
-    const nextKey = keyParts.join("|");
-    // 处理的是到达下车点时,如果缩放的比例不对,就重置回最大比例
-    if (nextKey === viewportKeyRef.current) {
-      if (mapScale < DROPOFF_MAX_SCALE) {
-        setMapScale(DROPOFF_MAX_SCALE);
-      }
-      return;
-    }
-    viewportKeyRef.current = nextKey;
-    updateViewportByPayload(viewportType, points);
-  }, [
-    deps.tripStatus,
-    deps.statusRule?.viewport,
-    deps.endSite?.latitude,
-    deps.endSite?.longitude,
-    mapScale
-  ]);
-
-  useEffect(() => {
-    if (
-      ![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(
-        deps.tripStatus
-      )
-    ) {
-      orderDetailViewportLockedRef.current = false;
-      orderDetailRouteUpdatedAfterEnterRef.current = false;
-      orderDetailEnterSignatureRef.current = "";
-      orderDetailScaleSyncVersionRef.current = 0;
-      return;
-    }
-    const { pickupSite, dropoffSite } = resolveViewportSites();
-    orderDetailEnterSignatureRef.current = buildOrderDetailRouteSignature(
-      pickupSite,
-      dropoffSite
-    );
-    orderDetailRouteUpdatedAfterEnterRef.current = false;
-    orderDetailViewportLockedRef.current = false;
-    viewportKeyRef.current = "";
-    orderDetailScaleSyncVersionRef.current = 0;
-  }, [deps.tripStatus]);
-
-  useEffect(() => {
-    if (
-      ![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(
-        deps.tripStatus
-      )
-    )
-      return;
-    const { pickupSite, dropoffSite } = resolveViewportSites();
-    const currentSignature = buildOrderDetailRouteSignature(
-      pickupSite,
-      dropoffSite
-    );
-    if (!orderDetailEnterSignatureRef.current) {
-      orderDetailEnterSignatureRef.current = currentSignature;
-      return;
-    }
-    if (currentSignature === orderDetailEnterSignatureRef.current) return;
-    const hasOrderDetailRoute = deps.routePoints.length > 0;
-    orderDetailRouteUpdatedAfterEnterRef.current = hasOrderDetailRoute;
-    orderDetailViewportLockedRef.current = false;
-    viewportKeyRef.current = "";
-  }, [
-    deps.tripStatus,
-    deps.routePoints,
-    deps.startSite?.latitude,
-    deps.startSite?.longitude,
-    deps.endSite?.latitude,
-    deps.endSite?.longitude
-  ]);
-
-  useEffect(() => {
-    if (
-      ![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(
-        deps.tripStatus
-      )
-    ) {
-      return;
-    }
-    if (deps.orderDetailViewportRefreshVersion <= 0) {
-      return;
-    }
-    orderDetailViewportLockedRef.current = false;
-    viewportKeyRef.current = "";
-  }, [deps.tripStatus, deps.orderDetailViewportRefreshVersion]);
-
-  useEffect(() => {
-    const { pickupSite, dropoffSite } = resolveViewportSites();
-    const viewportType = deps.statusRule.viewport;
-    if (viewportType === "dropoff") return;
-    // 车辆已到达上车点:交由专用的上车点居中逻辑(按安全区居中并回写受控中心点)处理,
-    // 跳过 car_user includePoints,避免其因底部大留白把车辆挤到地图顶部。
-    // 注意 car_user 同样用于「到达目的地」状态,此处仅对到达上车点生效。
-    if (
-      viewportType === "car_user" &&
-      deps.tripStatus === TripStatus.VehicleArrivedPickup
-    )
-      return;
-    const { points, keyParts } = buildViewportPayload(
-      viewportType,
-      pickupSite,
-      dropoffSite
-    );
-    if (points.length === 0) return;
-    const shouldWaitForOrderDetailRoute =
-      [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(
-        deps.tripStatus
-      ) &&
-      viewportType === "order_detail" &&
-      deps.routePoints.length === 0 &&
-      deps.orderDetailViewportRefreshVersion <= 0;
-    if (shouldWaitForOrderDetailRoute) {
-      return;
-    }
-    if (
-      [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(
-        deps.tripStatus
-      ) &&
-      viewportType === "order_detail" &&
-      orderDetailViewportLockedRef.current &&
-      orderDetailRouteUpdatedAfterEnterRef.current
-    ) {
-      return;
-    }
-    // 车辆接驾/送驾中:已运行过 updateViewportByPayload 后,仅当 routePoints 长度发生变化(线路变化)
-    // 时才再次运行;长度相同说明线路未变,交由 useSpeedBasedScale 的 fitRouteIntoView 主导视口,避免相互干扰。
-    const hasRunUpdateViewport =
-      (deps.tripStatus === TripStatus.VehicleApproaching &&
-        tripStatus4ViewportLockedRef.current) ||
-      (deps.tripStatus === TripStatus.InService &&
-        tripStatus6ViewportLockedRef.current);
-    if (
-      hasRunUpdateViewport &&
-      viewportType === "route" &&
-      deps.routePoints.length === lastRouteViewportPointsLengthRef.current
-    ) {
-      return;
-    }
-    const nextKey = keyParts.join("|");
-    if (nextKey === viewportKeyRef.current) return;
-    viewportKeyRef.current = nextKey;
-    // 接驾中/行程中路线点不足 3 个时跳过,避免不完整路线导致地图异常放大,
-    // 且不锁定视口,等路线就绪后仍可再次进入
-    if (
-      [TripStatus.InService, TripStatus.VehicleApproaching].includes(
-        deps.tripStatus
-      ) &&
-      viewportType === "route" &&
-      points.length < 3
-    ) {
-      return;
-    }
-    if (
-      deps.tripStatus === TripStatus.VehicleApproaching &&
-      viewportType === "route"
-    ) {
-      tripStatus4ViewportLockedRef.current = true;
-      lastRouteViewportPointsLengthRef.current = deps.routePoints.length;
-    }
-    if (deps.tripStatus === TripStatus.InService && viewportType === "route") {
-      tripStatus6ViewportLockedRef.current = true;
-      lastRouteViewportPointsLengthRef.current = deps.routePoints.length;
-    }
-    // updateViewportByPayload 已用 debounce 包裹:状态切换瞬间多次调用只执行最后一次
-    updateViewportByPayload(viewportType, points);
-    if (
-      [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(
-        deps.tripStatus
-      ) &&
-      viewportType === "order_detail"
-    ) {
-      const hasOrderDetailRouteAfterEnter =
-        orderDetailRouteUpdatedAfterEnterRef.current &&
-        deps.routePoints.length > 0;
-      orderDetailViewportLockedRef.current = hasOrderDetailRouteAfterEnter;
-    }
-  }, [
-    deps.tripStatus,
-    deps.statusRule,
-    deps.routePoints,
-    deps.walkingPoints,
-    deps.startSite?.latitude,
-    deps.startSite?.longitude,
-    deps.endSite?.latitude,
-    deps.endSite?.longitude,
-    deps.vehicleLocation,
-    deps.longitude,
-    deps.latitude,
-    mapBottomPadding,
-    deps.orderDetailViewportRefreshVersion,
-    tripStatus6ViewportLockedRef
-  ]);
-
   return {
     mapBottomPadding,
     carCalloutStyle,
@@ -1375,10 +215,6 @@
     setArriveCalloutStyle,
     arriveCalloutVisible,
     setArriveCalloutVisible,
-    mapScale,
-    setMapScale,
-    mapScaleRef,
-    recordMapScale,
     originBoxHidden,
     setOriginBoxHidden,
     originOpacity,
@@ -1389,20 +225,12 @@
     setWaitingRadarVisible,
     showLocationFlag,
     setShowLocationFlag,
-    mapRectRef,
-    viewportKeyRef,
-    orderDetailViewportLockedRef,
     tripStatus6ViewportLockedRef,
-    isAndroid,
     resolveSafeBottomInset,
     resolveSafeTopInset,
     computeBottomPadding,
-    holdDynamicScale,
-    getDynamicScaleHoldUntil,
-    syncActiveTripViewportToVehicle,
-    syncNativeViewportToControlledState,
+    measureBottomPadding,
     updateMapViewportPadding,
-    toOrigin,
-    getLocationInfo
+    toOrigin
   };
 }
diff --git a/src/robotaxi/pages/begin-route/hooks/use-modify-destination-flow.ts b/src/robotaxi/pages/begin-route/hooks/use-modify-destination-flow.ts
index 1907346..5c946e3 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-modify-destination-flow.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-modify-destination-flow.ts
@@ -1,5 +1,5 @@
 import Taro from '@tarojs/taro';
-import { showToast } from '../../../components/toast';
+import { showToast, hideToast } from '../../../components/toast';
 import { useEffect, useMemo, useState } from 'react';
 import { orderChange } from '../../../api/begin-route';
 import { TripStatus } from '../../../constants/trip-status';
@@ -385,7 +385,7 @@
     try {
       setIsConfirmingModifyDestination(true);
       setFlowState(FLOW_STATES.CONFIRMING);
-      Taro.showLoading({ title: '正在修改目的地', mask: true }); // 修改目的地请求进行中全局 loading
+      showToast({ title: '正在修改目的地', icon: 'loading', mask: true }); // 修改目的地请求进行中全局 loading
 
       // 如果后端需要字符串格式可以显式转String,如果后端支持number直接传值,保留原始格式防止隐式转换截断
       // 如果原本因为 Number() 导致大整数丢失精度,去掉 Number() 即可
@@ -416,7 +416,7 @@
 
       exitModifyDestinationReview({ shouldRestorePreviousEndSite: false });
 
-      Taro.hideLoading();
+      hideToast();
       showToast({
         title: '行程已更新',
         icon: 'none'
@@ -426,7 +426,7 @@
         deps.onModifySuccess(oldRoutePoints);
       }
     } catch (e: any) {
-      Taro.hideLoading();
+      hideToast();
       throw e;
     } finally {
       setIsConfirmingModifyDestination(false);
diff --git a/src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts b/src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts
index 05cf4d7..cb3417c 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts
@@ -1,6 +1,6 @@
 import { SignedOrderPaymentValue } from '../../../api/types/begin-route';
 import type { OrderDistanceConfig } from './use-order-distance-config';
-import { TripStatus } from '../constants/trip-status';
+import { TripStatus, CancelType } from '../constants/trip-status';
 
 /** 下单防抖时长,单位毫秒。 */
 export const ORDER_SUBMIT_DEBOUNCE_MS = 500;
@@ -29,7 +29,7 @@
   /** 取消前页面真实行程状态。 */
   tripStatus: TripStatus;
   /** 期望展示的取消弹窗类型。 */
-  cancelType: number;
+  cancelType: CancelType;
   /** 取消来源。 */
   source: PendingCancelSource;
   /** 上下文创建时间戳。 */
@@ -68,7 +68,7 @@
   /** 下单提交中。 */
   callSubmitting: boolean;
   /** 取消类型。 */
-  cancelType: number;
+  cancelType: CancelType;
   /** 待确认取消上下文。 */
   pendingCancelContextRef: React.MutableRefObject<PendingCancelContext | null>;
   /** 当前车辆信息。 */
@@ -89,7 +89,7 @@
   /** 设置下单提交状态。 */
   setCallSubmitting: (value: boolean) => void;
   /** 设置取消类型。 */
-  setCancelType: (value: number) => void;
+  setCancelType: (value: CancelType) => void;
   /** 设置取消弹窗。 */
   setShowCancelPopup: (value: boolean) => void;
   /** 设置取消弹窗等待分钟数。 */
@@ -98,10 +98,6 @@
   setWalkingPoints: (value: any[]) => void;
   /** 设置步行时长。 */
   setWalkingTime: (value: number) => void;
-  /** 设置纬度。 */
-  setLatitude: (value: number) => void;
-  /** 设置经度。 */
-  setLongitude: (value: number) => void;
   /** 设置结束行程弹窗。 */
   setShowOverRoutePopup: (value: boolean) => void;
   /** 打开隐私弹窗。 */
diff --git a/src/robotaxi/pages/begin-route/hooks/use-order-cancel.ts b/src/robotaxi/pages/begin-route/hooks/use-order-cancel.ts
index 5db4f7b..65a8ca2 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-order-cancel.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-order-cancel.ts
@@ -3,7 +3,7 @@
 import { clearOrderTripStorage, reportEvent } from '../../../../utils/Robotaxi-public-method';
 import { cancelOrder } from '../../../api/begin-route';
 import { getOrderId } from '../../../utils/order';
-import { TripStatus } from '../../../constants/trip-status';
+import { TripStatus, CancelType } from '../../../constants/trip-status';
 import {
   WAITING_FOR_DISPATCH_CANCEL_BUTTON_EVENT_ID,
   WAITING_FOR_PICKUP_CANCEL_BUTTON_EVENT_ID,
@@ -30,7 +30,7 @@
   /** 重启发单规划。 */
   restartPlanningRoute: () => void;
   /** 订单超时再来一单。 */
-  handleOpenCancelPopupWithType: (type: number) => void;
+  handleOpenCancelPopupWithType: (type: CancelType) => void;
   /** 根据状态打开取消弹窗。 */
   handleOpenCancelPopupByTripStatus: (status: TripStatus, displayInfo?: any) => void;
   /** 上车倒计时超时后发起自动取消。 */
@@ -121,37 +121,19 @@
    * 重启发单规划。
    */
   const restartPlanningRoute = () => {
+    // 重置到发单态:全新挂载 begin-route 页面(不携带上一次的受控地图状态,避免受控属性弹回),
+    // 保留起终点、清订单态,让新页面按发单态重新请求并绘制路线。
     deps.setShowCancelPopup(false);
     deps.setCancelPopupWaitMinutes(null);
-    deps.setTripStatus(TripStatus.PublishRoute);
-    const startSite = Taro.getStorageSync('startSite');
-    const endSite = Taro.getStorageSync('endSite');
-    
-    // 如果缓存中有合法的上车点,将其同步到内存状态
-    if (startSite?.latitude && startSite?.longitude) {
-      // 触发内存状态同步,避免再来一单时仍使用旧订单取消前的闭包旧上车点
-      if (typeof deps.setStartSite === 'function') {
-        deps.setStartSite(startSite);
-      }
-      deps.routeUpdateWalkingRouteForStart(startSite);
-    }
-    
-    if (startSite?.latitude && startSite?.longitude && endSite?.longitude && endSite?.latitude) {
-      deps.routeRequestRouteAndFare(
-        startSite.latitude,
-        startSite.longitude,
-        endSite.latitude,
-        endSite.longitude,
-        startSite?.id,
-        true
-      );
-    }
+    Taro.setStorageSync('tripStatus', TripStatus.PublishRoute);
+    Taro.removeStorageSync('orderId');
+    Taro.redirectTo({ url: '/robotaxi/pages/begin-route/index' });
   };
 
   /**
    * 订单超时再来一单。
    */
-  const handleOpenCancelPopupWithType = (type: number) => {
+  const handleOpenCancelPopupWithType = (type: CancelType) => {
     clearActiveOrderRuntime();
     deps.setCancelType(type);
     restartPlanningRoute();
@@ -165,11 +147,11 @@
     deps.pendingCancelContextRef.current = {
       orderId: targetOrderId,
       tripStatus: deps.tripStatus,
-      cancelType: 4,
+      cancelType: CancelType.OrderCanceled,
       source: 'pickup_timeout',
       createdAt: Date.now()
     };
-    handleOpenCancelPopupWithType(4);
+    handleOpenCancelPopupWithType(CancelType.OrderCanceled);
   };
 
   /**
@@ -177,30 +159,33 @@
    */
   const handleOpenCancelPopupByTripStatus = (status: TripStatus, displayInfo?: any) => {
     const waitMinutes = Number(displayInfo?.waitMinutes);
-    // if ([TripStatus.CallingCar, TripStatus.VehicleApproaching].includes(status) && Number.isFinite(waitMinutes)) {
-    if (status === TripStatus.VehicleApproaching && Number.isFinite(waitMinutes)) {
+    if ([TripStatus.CallingCar, TripStatus.VehicleApproaching].includes(status) && Number.isFinite(waitMinutes)) {
+    // if (status === TripStatus.VehicleApproaching && Number.isFinite(waitMinutes)) {
       deps.setCancelPopupWaitMinutes(waitMinutes);
     } else {
       deps.setCancelPopupWaitMinutes(null);
     }
 
     if ([TripStatus.Publishing, TripStatus.CallingCar].includes(status)) {
-      deps.setCancelType(1);
+      deps.setCancelType(CancelType.CallingCar);
       deps.setShowCancelPopup(true);
       return;
     }
 
     if (displayInfo?.durationDisplay && ![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(status)) {
-      deps.setCancelType(2);
+      deps.setCancelType(CancelType.VehicleApproaching);
       deps.setShowCancelPopup(true);
       return;
     }
 
     if (status === TripStatus.VehicleApproaching) {
-      deps.setCancelType(2);
+      deps.setCancelType(CancelType.VehicleApproaching);
     }
     if (status === TripStatus.VehicleArrivedPickup) {
-      deps.setCancelType(3);
+      deps.setCancelType(CancelType.VehicleArrivedPickup);
+    }
+    if (status === TripStatus.InService) {
+      deps.setCancelType(CancelType.InService);
     }
     deps.setShowCancelPopup(true);
   };
@@ -209,13 +194,13 @@
    * 确认取消。
    */
   const handleConfirmCancel = async () => {
-    if (deps.cancelType === 4) {
+    if (deps.cancelType === CancelType.OrderCanceled) {
       restartPlanningRoute();
       return;
     }
 
-    if (deps.cancelType === 5) {
-      handleOpenCancelPopupWithType(5);
+    if (deps.cancelType === CancelType.NoAvailableVehicle) {
+      handleOpenCancelPopupWithType(CancelType.NoAvailableVehicle);
       return;
     }
 
@@ -226,15 +211,15 @@
     }
 
     const shouldReportWaitingForDispatchCancel = (
-      deps.cancelType === 1
+      deps.cancelType === CancelType.CallingCar
       && [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus)
     );
     const shouldReportWaitingForPickupCancel = (
-      deps.cancelType === 2
+      deps.cancelType === CancelType.VehicleApproaching
       && deps.tripStatus === TripStatus.VehicleApproaching
     );
     const shouldReportPickupArrivedCancelTrip = (
-      deps.cancelType === 3
+      deps.cancelType === CancelType.VehicleArrivedPickup
       && deps.tripStatus === TripStatus.VehicleArrivedPickup
     );
 
@@ -252,7 +237,7 @@
       showToast({ title: '行程已取消', icon: 'success' });
       clearActiveOrderRuntime();
       deps.setTripStatus(TripStatus.CancelOnlyPickup);
-      deps.setCancelType(4);
+      deps.setCancelType(CancelType.OrderCanceled);
     } catch (error) {
       showToast({ title: '取消订单失败,请重试', icon: 'none' });
     }
diff --git a/src/robotaxi/pages/begin-route/hooks/use-order-payment-result-query.ts b/src/robotaxi/pages/begin-route/hooks/use-order-payment-result-query.ts
index b4ee7be..6e57ba8 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-order-payment-result-query.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-order-payment-result-query.ts
@@ -1,6 +1,5 @@
 import { useCallback } from 'react';
-import Taro from '@tarojs/taro';
-import { showToast } from '../../../components/toast';
+import { showToast, hideToast } from '../../../components/toast';
 import { queryOrderPaymentResultFromOrderId } from '../../../api/begin-route';
 import { RobotaxiOrderStatus } from '../../../api/types/begin-route';
 
@@ -24,7 +23,7 @@
       return;
     }
 
-    Taro.showLoading({ title: '支付结果查询中' });
+    showToast({ title: '支付结果查询中', icon: 'loading' });
     try {
       const result = await queryOrderPaymentResultFromOrderId(deps.orderId);
       const nextStatus = Number(result?.status);
@@ -43,7 +42,7 @@
     } catch (error) {
       showToast({ title: '支付结果查询失败,请稍后重试', icon: 'none' });
     } finally {
-      Taro.hideLoading();
+      hideToast();
     }
   }, [deps.onPaySuccess, deps.orderId]);
 
diff --git a/src/robotaxi/pages/begin-route/hooks/use-order-payment.ts b/src/robotaxi/pages/begin-route/hooks/use-order-payment.ts
index 42a9c03..15f817a 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-order-payment.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-order-payment.ts
@@ -1,5 +1,5 @@
 import Taro from '@tarojs/taro';
-import { showToast } from '../../../components/toast';
+import { showToast, hideToast } from '../../../components/toast';
 import { useRef } from 'react';
 import { reportEvent } from '../../../../utils/Robotaxi-public-method';
 import {
@@ -56,8 +56,9 @@
    * 根据订单详情状态决定结束行程后的页面态。
    */
   const resolvePostEndOrderTripStatus = async (targetOrderId: string) => {
-    Taro.showLoading({
+    showToast({
       title: '订单明细查询中',
+      icon: 'loading',
       mask: true
     });
     try {
@@ -79,7 +80,7 @@
         nextTripStatus: TripStatus.TripFinished
       };
     } finally {
-      Taro.hideLoading();
+      hideToast();
     }
   };
 
@@ -199,8 +200,9 @@
       return;
     }
     paySubmittingRef.current = true;
-    Taro.showLoading({
+    showToast({
       title: '支付中',
+      icon: 'loading',
       mask: false
     });
     try {
@@ -214,7 +216,7 @@
       if (paymentStatus === RobotaxiPaymentEntryStatus.Unsigned) {
         const cashierToken = paymentResult?.value?.token;
         if (!cashierToken) {
-          Taro.showToast({ title: '支付凭证缺失,请稍后重试', icon: 'none' });
+          showToast({ title: '支付凭证缺失,请稍后重试', icon: 'none' });
           return;
         }
         await deps.handleUnsignedPayment(cashierToken);
@@ -224,18 +226,18 @@
       if (paymentStatus === RobotaxiPaymentEntryStatus.Signed) {
         const signedPaymentValue = paymentResult?.value as SignedOrderPaymentValue;
         if (!signedPaymentValue?.packageValue) {
-          Taro.showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' });
+          showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' });
           return;
         }
         await deps.handleSignedCreditPayment(signedPaymentValue);
         return;
       }
 
-      Taro.showToast({ title: '暂不支持当前支付方式', icon: 'none' });
+      showToast({ title: '暂不支持当前支付方式', icon: 'none' });
     } catch (error) {
       showToast({ title: '支付失败,请重试', icon: 'none' });
     } finally {
-      Taro.hideLoading();
+      hideToast();
       paySubmittingRef.current = false;
     }
   };
diff --git a/src/robotaxi/pages/begin-route/hooks/use-order-status.ts b/src/robotaxi/pages/begin-route/hooks/use-order-status.ts
index 10a10b6..490de8e 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-order-status.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-order-status.ts
@@ -1,5 +1,6 @@
 import Taro from '@tarojs/taro';
 import { useState, useRef, useEffect } from 'react';
+import { CancelType } from '../../../constants/trip-status';
 
 export default function useOrderStatus() {
     const [tripStatus, setTripStatus] = useState<number>(Number(Taro.getStorageSync('tripStatus')) || 1);
@@ -9,7 +10,7 @@
     const [vehicleInfo, setVehicleInfo] = useState<any>({});
     const [trunkStatus, setTrunkStatus] = useState(false);
     const [finishCountdownEnabled, setFinishCountdownEnabled] = useState(false);
-    const [cancelType, setCancelType] = useState(1);
+    const [cancelType, setCancelType] = useState<CancelType>(CancelType.CallingCar);
 
     const tripStatusRef = useRef(tripStatus);
     const lastTripStatusRef = useRef(tripStatus);
diff --git a/src/robotaxi/pages/begin-route/hooks/use-order-submit.ts b/src/robotaxi/pages/begin-route/hooks/use-order-submit.ts
index b00f9c2..def0312 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-order-submit.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-order-submit.ts
@@ -1,5 +1,5 @@
 import Taro from '@tarojs/taro';
-import { showToast } from '../../../components/toast';
+import { showToast, hideToast } from '../../../components/toast';
 import { useCallback, useRef } from 'react';
 import { formatDateTime, debounce, reportEvent } from '../../../../utils/Robotaxi-public-method';
 import { createCreditPayOrder, saveOrder, routeStaleCheck, queryCreditPaySign } from '../../../api/begin-route';
@@ -141,16 +141,16 @@
    * 查询长期签约回跳后的最新支付分状态,并决定是否继续信用下单。
    */
   const resolveCreditPayModeAfterSignReturn = useCallback(async () => {
-    Taro.showLoading({ title: '授权结果查询中', mask: true });
+    showToast({ title: '授权结果查询中', icon: 'loading', mask: true });
     let shouldHideLoading = true;
     try {
       const signResult = await queryCreditPaySign();
       const latestStatus = signResult?.data?.status ?? signResult?.status;
       const resolvedCreditPaySubmitMode = resolveCreditPaySubmitMode(latestStatus);
       if (resolvedCreditPaySubmitMode) {
-        Taro.hideLoading();
+        hideToast();
         shouldHideLoading = false;
-        Taro.showToast({ title: '授权成功', icon: 'none' });
+        showToast({ title: '授权成功', icon: 'none' });
         return resolvedCreditPaySubmitMode;
       }
       return resolvedCreditPaySubmitMode;
@@ -158,7 +158,7 @@
       return null;
     } finally {
       if (shouldHideLoading) {
-        Taro.hideLoading();
+        hideToast();
       }
     }
   }, []);
@@ -499,7 +499,7 @@
       const nextCreditPaySubmitMode = await resolveCreditPayModeAfterSignReturn();
       if (!nextCreditPaySubmitMode) {
         stopCallSubmitting();
-        Taro.showToast({ title: '支付分授权未完成,请重试', icon: 'none' });
+        showToast({ title: '支付分授权未完成,请重试', icon: 'none' });
         return;
       }
 
@@ -511,7 +511,7 @@
     onLaunchFail: () => {
       waitingCreditPayReturnRef.current = false;
       stopCallSubmitting();
-      Taro.showToast({ title: '拉起签约失败', icon: 'none' });
+      showToast({ title: '拉起签约失败', icon: 'none' });
     }
   });
 
@@ -640,7 +640,7 @@
         || submitMode === 'credit_pay_skip_auth_submit';
 
       if (isCreditPaySubmit) {
-        Taro.showLoading({ title: '加载中', mask: true });
+        showToast({ title: '加载中', icon: 'loading', mask: true });
       }
 
       const submitResult = isCreditPaySubmit
@@ -648,7 +648,7 @@
         : await submitOrder(startSite, openCityId);
 
       if (!submitResult?.orderId) {
-        Taro.hideLoading();
+        hideToast();
         stopCallSubmitting();
         return;
       }
@@ -663,7 +663,7 @@
       if (isCreditPaySubmit) {
         Taro.setStorageSync('creditPayOrderId', submitResult.orderId);
         if (submitMode === 'credit_pay_skip_auth_submit') {
-          Taro.hideLoading();
+          hideToast();
           // status=2 代表当前已是免确认模式,信用下单成功后直接进入发单态。
           deps.setTripStatus(TripStatus.Publishing);
           return;
@@ -671,7 +671,7 @@
 
         const creditPayPackageValue = String(submitResult.creditPayPackageValue || '').trim();
         if (!creditPayPackageValue) {
-          Taro.hideLoading();
+          hideToast();
           showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' });
           return;
         }
@@ -684,7 +684,7 @@
           packageValue: creditPayPackageValue
         }, submitResult.orderId);
 
-        Taro.hideLoading();
+        hideToast();
 
         // 只有拉起失败才立刻进入发单状态(或者依靠 abort 逻辑终止)
         if (!launched) {
@@ -694,10 +694,10 @@
       }
       deps.setTripStatus(TripStatus.Publishing);
     } catch (error) {
-      Taro.hideLoading();
+      hideToast();
       const errorMessage = String((error as any)?.msg || (error as any)?.message || '下单失败,请重试');
       if (submitMode === 'credit_pay_submit' || submitMode === 'credit_pay_skip_auth_submit') {
-        Taro.showToast({ title: errorMessage, icon: 'none' });
+        showToast({ title: errorMessage, icon: 'none' });
       }
       reportPayResultEvent(resolvePayResultStatus(errorMessage));
       stopCallSubmitting();
diff --git a/src/robotaxi/pages/begin-route/hooks/use-page-restore.ts b/src/robotaxi/pages/begin-route/hooks/use-page-restore.ts
index d5d6251..68afa7c 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-page-restore.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-page-restore.ts
@@ -1,4 +1,5 @@
-import Taro from '@tarojs/taro';
+import Taro from '@tarojs/taro';
+import { useRef } from 'react';
 import { showToast } from '../../../components/toast';
 import wsManager from '../../../../utils/websocket';
 import { mapOrderStatusToTripStatus, clearOrderTripStorage } from '../../../../utils/Robotaxi-public-method';
@@ -42,9 +43,6 @@
   setEndSite: (value: any) => void;
   setVehicleMoveAlongPoints: (value: any[]) => void;
   setTrunkStatus: (value: boolean) => void;
-  setLatitude: (value: number | null) => void;
-  setLongitude: (value: number | null) => void;
-  getLocationInfo: () => void;
   routeRequestRouteAndFare: (
     originLat: number,
     originLng: number,
@@ -83,6 +81,8 @@
  * 页面恢复与初始化编排:负责缓存恢复、订单同步、定位初始化与 socket 准备。
  */
 export default function usePageRestore(deps: Deps) {
+  const hasShownActiveTripToastRef = useRef(false); // 同一页面实例内只提示一次,避免回前台重复闪 toast
+
   /**
    * 清理已选优惠券缓存。
    */
@@ -234,6 +234,8 @@
         openCityId,
         startPointId: restoredStartSite?.id || '',
         orderId: restoredOrderId
+      }, {
+        showToast: false
       });
       const responseRoutes = Array.isArray(response?.routes)
         ? response.routes
@@ -385,28 +387,10 @@
 
   /**
    * 同步地图定位并按状态初始化路线。
+   * 中心点逻辑已移除:地图中心不再由代码定位(走 <Map> 的兜底坐标 + useMapCameraController
+   * 首帧定位),此处仅保留按状态初始化路线。
    */
   const syncLocationAndInitRoute = async (s: any, e: any, cachedOrderId: string, cachedTripStatus: TripStatus) => {
-    // 到达目的地阶段直接以下车点为中心,避免 getLocationInfo 抢占视口中心
-    if (cachedTripStatus === TripStatus.ArrivedDestination) {
-      const dropoffLat = Number(e?.latitude);
-      const dropoffLng = Number(e?.longitude);
-      if (Number.isFinite(dropoffLat) && Number.isFinite(dropoffLng)) {
-        deps.setLatitude(dropoffLat);
-        deps.setLongitude(dropoffLng);
-      }
-    } else if (s && s.latitude && s.longitude) {
-      if([TripStatus.VehicleApproaching, TripStatus.InService].includes(cachedTripStatus)) {
-        deps.getLocationInfo();
-      } else if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(cachedTripStatus)) {
-        // 完成态(TripFinished/PaymentSuccess)地图中心由 useMapViewport 的 order_detail includePoints 主导,
-        // 这里不再用上车点覆盖,否则受控中心会被拉到路线一端导致路线偏离屏幕中央。
-        deps.setLatitude(s.latitude);
-        deps.setLongitude(s.longitude);
-      }
-    } else {
-      deps.getLocationInfo();
-    }
     const originLng = s?.longitude || deps.getCurrentLongitude();
     const originLat = s?.latitude || deps.getCurrentLatitude();
     if (cachedTripStatus === TripStatus.PublishRoute && originLng && originLat && e?.longitude && e?.latitude) {
@@ -479,7 +463,13 @@
     const isModifyDestinationSessionActive = !!Taro.getStorageSync(MODIFY_DESTINATION_SESSION_KEY)?.active; // 修改目的地返回链路不提示“已有进行中订单”
     const isModifyDestinationReturn = String(fromModifyDestination || '') === '1';
     const shouldShowPendingSameSiteToast = isModifyDestinationReturn && !!Taro.getStorageSync(MODIFY_DESTINATION_PENDING_SAME_SITE_TOAST_KEY);
-    if (skipPageFlag && hasActiveTripOrderInStorage() && !isModifyDestinationReturn && !isModifyDestinationSessionActive) {
+    const shouldShowActiveTripToast = Boolean(skipPageFlag)
+      && !hasShownActiveTripToastRef.current
+      && hasActiveTripOrderInStorage()
+      && !isModifyDestinationReturn
+      && !isModifyDestinationSessionActive;
+    if (shouldShowActiveTripToast) {
+      hasShownActiveTripToastRef.current = true;
       showToast({
         title: getActiveTripOrderToastTitle(Taro.getStorageSync('tripStatus')),
         icon: 'none',
diff --git a/src/robotaxi/pages/begin-route/hooks/use-popup-manager.ts b/src/robotaxi/pages/begin-route/hooks/use-popup-manager.ts
index 2c3f6b2..988b878 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-popup-manager.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-popup-manager.ts
@@ -1,4 +1,5 @@
 import { useState } from 'react';
+import { CancelType } from '../../../constants/trip-status';
 
 export default function usePopupManager() {
     const [showCustomerService, setShowCustomerService] = useState(false);
@@ -7,7 +8,7 @@
     const [showAnnouncementsPopup, setShowAnnouncementsPopup] = useState(false);
     const [showOverRoutePopup, setShowOverRoutePopup] = useState(false);
     const [showPassengerNotice, setShowPassengerNotice] = useState(false);
-    const [cancelType, setCancelType] = useState(1);
+    const [cancelType, setCancelType] = useState<CancelType>(CancelType.CallingCar);
     const [showEvaluatePopup, setShowEvaluatePopup] = useState(false);
     const [showTipPopup, setShowTipPopup] = useState(false);
     const [tipType, setTipType] = useState('');
diff --git a/src/robotaxi/pages/begin-route/hooks/use-route-interaction.ts b/src/robotaxi/pages/begin-route/hooks/use-route-interaction.ts
index a5595b1..b1a9315 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-route-interaction.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-route-interaction.ts
@@ -8,11 +8,6 @@
   Deps,
   ROUTE_POLYLINE_BASE_ID,
   ROUTE_TAP_MAX_DISTANCE_METERS,
-  ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING,
-  ROUTE_VIEWPORT_MIN_TOP_PADDING,
-  ROUTE_VIEWPORT_RIGHT_SIDE_PADDING,
-  ROUTE_VIEWPORT_SIDE_PADDING,
-  ROUTE_VIEWPORT_TOP_EXTRA_PADDING,
   RouteDistanceValue,
 } from './use-route-manager.shared';
 
@@ -46,12 +41,10 @@
   setRouteDuration: React.Dispatch<React.SetStateAction<number | null>>;
 };
 
-/** 路线交互 Hook 返回值。 */
+/** 路线交互 Hook 入参。 */
 type HandleSelectRouteOptions = {
   /** 是否同步更新起终点。 */
   syncSites?: boolean;
-  /** 是否同步更新地图视口。 */
-  syncViewport?: boolean;
 };
 
 /** 路线交互 Hook 返回值。 */
@@ -74,16 +67,12 @@
   handlePolylineTap: (event: any) => void;
   /** 处理地图点击。 */
   handleMapTap: (event: any) => void;
-  /** 同步多路线视口。 */
-  syncRouteViewport: (routePointGroups: any[][], nextWalkingPoints?: any[]) => void;
 };
 
 /** 统一获取路线降采样上限。 */
 const resolveRoutePointLimit = (isAndroid: boolean) => (
   isAndroid ? ANDROID_ROUTE_POINT_LIMIT : DEFAULT_ROUTE_POINT_LIMIT
 );
-
-const ROUTE_VIEWPORT_SYNC_DELAY_MS = 320;
 
 /**
  * 管理路线选择、点击交互与视口联动。
@@ -97,7 +86,6 @@
     setSelectedRouteIndex,
     allRoutePoints,
     setAllRoutePoints,
-    walkingPoints,
     applyRoutePoints,
     reducePoints,
     setFareEstimate,
@@ -106,45 +94,13 @@
   } = params;
 
   /**
-   * 同步地图视口,确保路线和步行线都在可视范围内。
-   * 注:仅在获取到地图数据、切换路线时使用
-   */
-  const syncRouteViewport = (routePointGroups: any[][], nextWalkingPoints: any[] = walkingPoints) => {
-    if (!deps.allowSelectableRoutes) {
-      return;
-    }
-    const routeViewportPoints: any[] = [];
-    routePointGroups.forEach((points) => {
-      if (Array.isArray(points)) {
-        routeViewportPoints.push(...points);
-      }
-    });
-    const allPoints = [...routeViewportPoints, ...(Array.isArray(nextWalkingPoints) ? nextWalkingPoints : [])];
-    if (allPoints.length === 0) {
-      return;
-    }
-    const topPadding = Math.max(
-      ROUTE_VIEWPORT_MIN_TOP_PADDING,
-      deps.resolveSafeTopInset() + ROUTE_VIEWPORT_TOP_EXTRA_PADDING
-    );
-    const bottomPadding = deps.computeBottomPadding(allPoints, 'route_walking', deps.tripStatus, true) + ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING;
-    Taro.createMapContext('map').includePoints({
-      points: allPoints,
-      padding: [topPadding, ROUTE_VIEWPORT_RIGHT_SIDE_PADDING, bottomPadding, ROUTE_VIEWPORT_SIDE_PADDING]
-    });
-    setTimeout(() => {
-      deps.syncMapCenterAfterViewportFit?.();
-    }, ROUTE_VIEWPORT_SYNC_DELAY_MS);
-  };
-
-  /**
    * 处理路线选择后的业务联动。
    */
   const handleSelectRoute = (index: number, options: HandleSelectRouteOptions = {}) => {
     if (!routeOptions[index] || index === selectedRouteIndex) {
       return;
     }
-    const { syncSites = true, syncViewport = true } = options;
+    const { syncSites = true } = options;
 
     const selectedRoute = routeOptions[index];
     setSelectedRouteIndex(index);
@@ -224,9 +180,6 @@
 
     if (points.length > 0) {
       applyRoutePoints(points);
-      if (syncViewport) {
-        syncRouteViewport(allRoutePoints);
-      }
       return;
     }
     applyRoutePoints([]);
@@ -367,7 +320,6 @@
     setAllRoutePoints,
     handleSelectRoute,
     handlePolylineTap,
-    handleMapTap,
-    syncRouteViewport
+    handleMapTap
   };
 }
diff --git a/src/robotaxi/pages/begin-route/hooks/use-route-manager.ts b/src/robotaxi/pages/begin-route/hooks/use-route-manager.ts
index 7458e82..215bd45 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-route-manager.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-route-manager.ts
@@ -78,7 +78,6 @@
     currentFullRouteRef: renderState.currentFullRouteRef,
     lastErasedIndexRef: renderState.lastErasedIndexRef,
     orderRouteInitializedRef: renderState.orderRouteInitializedRef,
-    syncRouteViewport: interactionState.syncRouteViewport,
     syncSmoothRouteBaselineRef: options?.syncSmoothRouteBaselineRef,
     lastSocketDirectionAtRef: options?.lastSocketDirectionAtRef
   });
diff --git a/src/robotaxi/pages/begin-route/hooks/use-route-request.ts b/src/robotaxi/pages/begin-route/hooks/use-route-request.ts
index d37c3b4..f801aee 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-route-request.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-route-request.ts
@@ -55,8 +55,6 @@
   lastErasedIndexRef: React.MutableRefObject<number>;
   /** 订单路线初始化引用。 */
   orderRouteInitializedRef: React.MutableRefObject<boolean>;
-  /** 同步多路线视口。 */
-  syncRouteViewport: (routePointGroups: any[][], nextWalkingPoints?: any[]) => void;
   /** 接口回显主路线后,同步初始化平滑擦线基线。 */
   syncSmoothRouteBaselineRef?: React.MutableRefObject<((snapshot: RouteSnapshot) => void) | null>;
   /** 最近一次由 socket 写入车辆朝向的时间。 */
@@ -221,7 +219,6 @@
     currentFullRouteRef,
     lastErasedIndexRef,
     orderRouteInitializedRef,
-    syncRouteViewport,
     syncSmoothRouteBaselineRef,
     lastSocketDirectionAtRef
   } = params;
@@ -538,7 +535,6 @@
         const points = Array.isArray(route?.polyline) ? route.polyline : [];
         if (points.length > 0) {
           applyRoutePoints(points);
-          syncRouteViewport(allRoutePointGroups, walkingPoints);
         } else {
           applyRoutePoints([]);
         }
@@ -654,7 +650,6 @@
       if (Array.isArray(points) && points.length > 0) {
         setWalkingPoints(points);
         setWalkingTime(Math.ceil(Number(duration) / 60));
-        syncRouteViewport([], points);
         return;
       }
 
diff --git a/src/robotaxi/pages/begin-route/hooks/use-speed-based-scale.ts b/src/robotaxi/pages/begin-route/hooks/use-speed-based-scale.ts
deleted file mode 100644
index ee6127e..0000000
--- a/src/robotaxi/pages/begin-route/hooks/use-speed-based-scale.ts
+++ /dev/null
@@ -1,217 +0,0 @@
-import Taro from '@tarojs/taro';
-import { useCallback, useEffect, useRef } from 'react';
-import { TripStatus } from '../../../constants/trip-status';
-import { subscribeDisplayRoutePoints } from './route-display-channel';
-import { ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING, ROUTE_VIEWPORT_MIN_TOP_PADDING, ROUTE_VIEWPORT_RIGHT_SIDE_PADDING, ROUTE_VIEWPORT_SIDE_PADDING, ROUTE_VIEWPORT_TOP_EXTRA_PADDING } from './use-route-manager.shared';
-
-type Deps = {
-  /** 当前行程状态。 */
-  tripStatus: TripStatus;
-  /** 获取速度缩放暂停截止时间,用于和一次性视口调整/用户手势冷却互斥。 */
-  getDynamicScaleHoldUntil: () => number;
-  /** 车辆当前位置:仅用于短路线兜底把车辆居中(不参与 fit 签名)。 */
-  vehicleLocation: { latitude: number; longitude: number } | null;
-  /** 后端推送的剩余距离(来自 ORDER_ROUTE.route.distance,单位 m 或 km)。 */
-  routeDistance: { value: number; unit: 'km' | 'm' } | null;
-  /** 计算 includePoints 所需底部留白。 */
-  computeBottomPadding: (points: any[], viewportType: string) => number;
-  /** 读取顶部安全区高度。 */
-  resolveSafeTopInset: () => number;
-  /** 设置受控地图中心纬度。 */
-  setLatitude: (latitude: number) => void;
-  /** 设置受控地图中心经度。 */
-  setLongitude: (longitude: number) => void;
-};
-
-/** 启用自动 fit 的行程状态:仅接驾中、行程中。 */
-const ACTIVE_STATUSES = [TripStatus.VehicleApproaching, TripStatus.InService];
-
-const ROUTE_DISTANCE_BUCKET_METERS = 10;
-const SIDE_PADDING = 50;
-const TOP_PADDING_MIN = 32;
-const TOP_EXTRA_PADDING = 50;
-const SHORT_ROUTE_FALLBACK_POINT_THRESHOLD = 3;
-const USER_MAP_INTERACTION_HOLD_MS = 8000;
-/**
- * 两次 includePoints 之间的最小间隔(毫秒)。
- *
- * 受控 <Map scale/lat/lng> 与命令式 includePoints 之间存在竞争:
- * fit 后异步用 getScale/getCenterLocation 同步 React 状态需要 ~100ms,
- * 间隔太短会让多次 fit 的 race 窗口叠加。这里取 1500ms 让每次 fit 有充裕
- * 的同步时间,相当于把抖动从"持续震"压成"偶尔轻微闪一下"。
- */
-const MIN_FIT_INTERVAL_MS = 5000;
-
-const buildRouteSignature = (points: { latitude: number; longitude: number }[]) => {
-  if (!Array.isArray(points) || points.length === 0) return '';
-  const first = points[0];
-  const middle = points[Math.floor(points.length / 2)];
-  const last = points[points.length - 1];
-  const fmt = (value: number) => Number(value || 0).toFixed(6);
-  return [
-    points.length,
-    fmt(first?.latitude), fmt(first?.longitude),
-    fmt(middle?.latitude), fmt(middle?.longitude),
-    fmt(last?.latitude), fmt(last?.longitude),
-  ].join('|');
-};
-
-const buildRouteDistanceBucket = (
-  routeDistance: { value: number; unit: 'km' | 'm' } | null
-) => {
-  if (!routeDistance) return 'null';
-  const meters = routeDistance.unit === 'km'
-    ? Number(routeDistance.value) * 1000
-    : Number(routeDistance.value);
-  if (!Number.isFinite(meters) || meters < 0) return 'invalid';
-  return String(Math.floor(meters / ROUTE_DISTANCE_BUCKET_METERS));
-};
-
-const buildFitSignature = (
-  points: { latitude: number; longitude: number }[],
-  routeDistance: { value: number; unit: 'km' | 'm' } | null
-) => {
-  const routeSig = buildRouteSignature(points);
-  if (!routeSig) return '';
-  return `${routeSig}|d:${buildRouteDistanceBucket(routeDistance)}`;
-};
-
-const fitRouteIntoView = (
-  points: { latitude: number; longitude: number }[],
-  computeBottomPadding: (points: any[], viewportType: string, tripStatus: TripStatus, disableAdaptive?: boolean) => number,
-  resolveSafeTopInset: () => number,
-  tripStatus: TripStatus
-) => {
-  if (!Array.isArray(points) || points.length < 2) {
-    return false;
-  }
-  const mapCtx = Taro.createMapContext('map');
-  const bottomPadding = computeBottomPadding(points, 'route', tripStatus, true) + ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING ;
-  const topPadding = Math.max(ROUTE_VIEWPORT_MIN_TOP_PADDING, resolveSafeTopInset() + ROUTE_VIEWPORT_TOP_EXTRA_PADDING);
-  mapCtx.includePoints({
-    points,
-    padding: [topPadding, ROUTE_VIEWPORT_RIGHT_SIDE_PADDING, bottomPadding, ROUTE_VIEWPORT_SIDE_PADDING]
-  });
-  return true;
-};
-
-/**
- * 接驾中/行程中自动整路线入屏:当路线签名或剩余距离桶变化时,自动重算视野。
- */
-export default function useSpeedBasedScale(deps: Deps) {
-  const lastFittedFitSignatureRef = useRef<string>('');
-  const idleRestoreTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
-  /** 由 channel 推送过来的最新 displayRoutePoints,存在 ref 中避免触发重渲染。 */
-  const liveRoutePointsRef = useRef<{ latitude: number; longitude: number }[]>([]);
-  /** 上次 includePoints 触发的时间戳,用于最小 fit 间隔节流。 */
-  const lastFitAtRef = useRef<number>(0);
-  /**
-   * fit 同步待办标志:performFit 调用 includePoints 后置为 true,
-   * 下一次 onRegionChange 'end' 事件读取实际 scale/center 同步回 React state,
-   * 然后清旗。比异步 getScale 时机更准(fit 动画结束才 end)。
-   */
-  const pendingFitSyncRef = useRef<boolean>(false);
-
-  const depsRef = useRef(deps);
-  useEffect(() => {
-    depsRef.current = deps;
-  });
-
-  const performFit = useCallback((force: boolean, points?: { latitude: number; longitude: number }[]) => {
-    const d = depsRef.current;
-    if (!ACTIVE_STATUSES.includes(d.tripStatus)) {
-      return;
-    }
-
-    const routePoints = points ?? liveRoutePointsRef.current;
-    const currentFitSig = buildFitSignature(routePoints, d.routeDistance);
-    if (!currentFitSig) {
-      return;
-    }
-
-    if (!force) {
-      if (currentFitSig === lastFittedFitSignatureRef.current) {
-        return;
-      }
-      const holdUntil = d.getDynamicScaleHoldUntil();
-      if (holdUntil > Date.now()) {
-        return;
-      }
-      // 最小 fit 间隔节流:上次 includePoints 不到 MIN_FIT_INTERVAL_MS 不再 fit,
-      // 避免 emit 高频触发 → 频繁 includePoints → 与受控 Map 属性的 race 叠加。
-      if (Date.now() - lastFitAtRef.current < MIN_FIT_INTERVAL_MS) {
-        return;
-      }
-    }
-
-    if (
-      routePoints.length < SHORT_ROUTE_FALLBACK_POINT_THRESHOLD
-      && d.vehicleLocation
-      && Number.isFinite(Number(d.vehicleLocation.latitude))
-      && Number.isFinite(Number(d.vehicleLocation.longitude))
-    ) {
-      d.setLatitude(Number(d.vehicleLocation.latitude));
-      d.setLongitude(Number(d.vehicleLocation.longitude));
-      lastFittedFitSignatureRef.current = currentFitSig;
-      return;
-    }
-
-    const fitted = fitRouteIntoView(routePoints, d.computeBottomPadding, d.resolveSafeTopInset, d.tripStatus);
-    if (!fitted) {
-      return;
-    }
-    lastFittedFitSignatureRef.current = currentFitSig;
-    lastFitAtRef.current = Date.now();
-    // includePoints 是命令式调用,会修改原生地图的 scale/center,
-    // 但受控 <Map scale={mapScale} latitude={mapLatitude} longitude={mapLongitude}> 在
-    // 之后任意一次 React 重渲染都会把原生视口拉回 React 旧值,造成视觉抖动。
-    // 这里只置一个标志位,等 onRegionChange 'end'(fit 动画真正结束)时再读取
-    // 实际值同步回 React state,让受控属性与原生视口对齐,后续重渲染不再回弹。
-    pendingFitSyncRef.current = true;
-  }, []);
-
-  const clearIdleRestoreTimer = useCallback(() => {
-    if (idleRestoreTimerRef.current) {
-      clearTimeout(idleRestoreTimerRef.current);
-      idleRestoreTimerRef.current = null;
-    }
-  }, []);
-
-  const notifyUserMapGesture = useCallback(() => {
-    const d = depsRef.current;
-    if (!ACTIVE_STATUSES.includes(d.tripStatus)) {
-      return;
-    }
-    clearIdleRestoreTimer();
-    idleRestoreTimerRef.current = setTimeout(() => {
-      idleRestoreTimerRef.current = null;
-      performFit(true);
-    }, USER_MAP_INTERACTION_HOLD_MS);
-  }, [clearIdleRestoreTimer, performFit]);
-
-  // 订阅全局 displayRoutePoints channel,收到新路线点时直接触发 fit 判断
-  useEffect(() => {
-    return subscribeDisplayRoutePoints((points) => {
-      liveRoutePointsRef.current = points;
-      performFit(false, points);
-    });
-  }, [performFit]);
-
-  // tripStatus 退出激活态时清空记录的签名和定时器
-  useEffect(() => {
-    if (!ACTIVE_STATUSES.includes(deps.tripStatus)) {
-      lastFittedFitSignatureRef.current = '';
-      liveRoutePointsRef.current = [];
-      lastFitAtRef.current = 0;
-      pendingFitSyncRef.current = false;
-      clearIdleRestoreTimer();
-    }
-  }, [deps.tripStatus, clearIdleRestoreTimer]);
-
-  useEffect(() => clearIdleRestoreTimer, [clearIdleRestoreTimer]);
-
-  return {
-    notifyUserMapGesture,
-    pendingFitSyncRef
-  };
-}
diff --git a/src/robotaxi/pages/begin-route/hooks/use-transition-asset-preload.ts b/src/robotaxi/pages/begin-route/hooks/use-transition-asset-preload.ts
new file mode 100644
index 0000000..ef918f5
--- /dev/null
+++ b/src/robotaxi/pages/begin-route/hooks/use-transition-asset-preload.ts
@@ -0,0 +1,54 @@
+import { useEffect, useRef } from 'react';
+import { TripStatus } from '../../../constants/trip-status';
+import { preloadAssets } from '../../../../utils/preload-assets';
+import { preloadVideo } from '../../../utils/video-preload';
+import { VEHICLE_APPROACHING_VIDEO } from '../../../constants/assets';
+import {
+  DISPATCH_SUCCESS_ASSETS,
+  TRIP_FINISHED_ASSETS,
+} from '../../../constants/preload-assets';
+
+/** Hook 入参。 */
+type Params = {
+  /** 当前行程状态。 */
+  tripStatus: TripStatus;
+};
+
+/**
+ * 转场动效素材预加载(按需触发)。
+ *
+ * ## 触发策略(必要时才预载,避免无谓消耗)
+ * - 进入「发布行程(1) / 发布中(2) / 呼叫中(3)」→ 预载「派单成功」素材 + 转场视频(downloadFile 落地本地)
+ *   (越早进入下单流程越早预热,第三次之后动效明显更顺畅正是因为素材已有缓存;
+ *    提前到发布行程即开始,尽量把预热窗口拉满)。
+ * - 进入「送驾中(6)」→ 预载「行程结束」素材(距行程结束有整段行程)。
+ *
+ * ## 视频预下载(黑帧消除关键)
+ * wx.preloadAssets 不支持视频,转场 mp4 直接用 CDN URL 播放时首帧要经网络缓冲、露出黑帧。
+ * 此处额外用 preloadVideo(wx.downloadFile)把 mp4 提前下到本地临时文件,转场组件优先用本地路径播放。
+ *
+ * ## 去重
+ * 各阶段用 ref 标记「已发起预载」,状态反复切换不重复调用。
+ * 预加载底层失败静默(见 preloadAssets),此处无需错误处理。
+ */
+export default function useTransitionAssetPreload(params: Params): void {
+  const { tripStatus } = params;
+
+  const orderingPreloadedRef = useRef(false); // 派单成功素材是否已发起预载
+  const finishedPreloadedRef = useRef(false); // 行程结束素材是否已发起预载
+
+  useEffect(() => {
+    // 进入下单流程(发布行程/发布中/呼叫中):预载派单成功素材 + 转场视频
+    if ([TripStatus.PublishRoute, TripStatus.Publishing, TripStatus.CallingCar].includes(tripStatus) && !orderingPreloadedRef.current) {
+      orderingPreloadedRef.current = true;
+      preloadAssets(DISPATCH_SUCCESS_ASSETS);
+      preloadVideo(VEHICLE_APPROACHING_VIDEO); // downloadFile 提前落地本地,转场首帧无网络缓冲、消除黑帧
+    }
+
+    // 进入送驾中:预载行程结束素材
+    if (tripStatus === TripStatus.InService && !finishedPreloadedRef.current) {
+      finishedPreloadedRef.current = true;
+      preloadAssets(TRIP_FINISHED_ASSETS);
+    }
+  }, [tripStatus]);
+}
diff --git a/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts b/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
index 3d6bdfb..f02b3ac 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
@@ -2,7 +2,7 @@
 import { useEffect } from 'react';
 import { getOrderDetail } from '../../../api/order';
 import { parseLocationCoordinateString } from '../../../../utils/Robotaxi-public-method';
-import { TripStatus } from '../../../constants/trip-status';
+import { TripStatus, CancelType } from '../../../constants/trip-status';
 
 type Deps = {
   /** 等待计时开始 key */
@@ -45,10 +45,6 @@
   elapsedSeconds: number;
   /** 等待秒数设置 */
   setElapsedSeconds: (value: number) => void;
-  /** 设置纬度 */
-  setLatitude: (value: number) => void;
-  /** 设置经度 */
-  setLongitude: (value: number) => void;
   /** 设置步行点 */
   setWalkingPoints: (value: any[]) => void;
   /** 设置步行时长 */
@@ -74,7 +70,7 @@
   /** 设置路线时长 */
   setRouteDuration: (value: any) => void;
   /** 设置取消类型 */
-  setCancelType: (value: number) => void;
+  setCancelType: (value: CancelType) => void;
   /** 设置取消弹窗 */
   setShowCancelPopup: (value: boolean) => void;
   /** 显示乘客须知 */
@@ -175,12 +171,6 @@
    * 启动等待计时。
    */
   const startWaitingTimer = () => {
-    const s = Taro.getStorageSync('startSite');
-    const hasPlannedRoute = Array.isArray(deps.routePoints) && deps.routePoints.length > 0;
-    if (!hasPlannedRoute && s && s.latitude && s.longitude) {
-      deps.setLatitude(s.latitude);
-      deps.setLongitude(s.longitude);
-    }
     const cachedStartAt = Number(Taro.getStorageSync(deps.waitingTimerStartKey));
     const cachedElapsed = Number(Taro.getStorageSync(deps.waitingTimerElapsedKey));
     let startAt = cachedStartAt;
@@ -276,11 +266,6 @@
       deps.requoteTriggeredRef.current = false;
       deps.setWalkingPoints([]);
       deps.setWalkingTime(0);
-      const e = Taro.getStorageSync('endSite');
-      if (e && e.latitude && e.longitude) {
-        deps.setLatitude(e.latitude);
-        deps.setLongitude(e.longitude);
-      }
       deps.setElapsedSeconds(0);
       clearWaitingTimerStorage();
     } else if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus)) {
diff --git a/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx b/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
index 71ddae5..ed30bf8 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
+++ b/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -23,8 +23,8 @@
   isAndroid: boolean;
   /** 行程状态 */
   tripStatus: TripStatus;
-  /** 地图缩放等级 */
-  mapScale: number;
+  /** 派单成功转场动效完成(或本次未播放转场)时回调,用于放行页面级蓝牙初始化 */
+  onDispatchTransitionSettled?: () => void;
   /** 地图 markers */
   markers: any[];
   /** 是否显示等待雷达 */
@@ -142,6 +142,8 @@
   trunkStatus: boolean;
   /** 当前页面内是否已经解锁过车门 */
   doorUnlockedInCurrentPage: boolean;
+  /** 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮 */
+  forceShowCancelTrip?: boolean;
   /** 手动开门成功后同步页面级开门状态 */
   onDoorUnlockSuccess: () => void;
   /** 路线选项集合 */
@@ -261,9 +263,7 @@
               {routeDistance?.value ?? 0}
             </View>
             <View style={{ flexShrink: 0 }}>
-              {isPickup
-                ? routeDistance?.unit === "m" ? "M" : "KM"
-                : routeDistance?.unit === "m" ? "米" : "公里"}
+              {routeDistance?.unit === "m" ? "米" : "公里"}
             </View>
             {!isPickup && (
               <>
@@ -311,6 +311,34 @@
     priceUpdateVersion: deps.priceUpdateVersion,
     hasPickupException: deps.hasPickupException
   });
+  // 捕捉"首次进入派单成功(4)"边沿:CallingCar 用 key={tripStatus} 会随状态切换重挂载,
+  // 组件内部 ref 会被重置而无法自判边沿,故在不会重挂载的父 hook 中记录上一状态,
+  // 通过 playDispatchTransition 告知 CallingCar 挂载时是否播放派单成功转场。
+  // 触发条件:进入 VehicleApproaching(4) 且上一态不是 VehicleApproaching(覆盖正常单/快速派单/改派)。
+  // 用「真实初始状态」初始化 prev(tripStatus 在页面挂载时已同步从缓存读入,见 index useState 初值),
+  // 使「进入即已是某状态」时首帧为 B→B 无边沿、不播动效;仅页内真实 A→B 流转才触发。
+  const prevTripStatusRef = useRef<TripStatus | undefined>(deps.tripStatus);
+  const playDispatchTransition =
+    deps.tripStatus === TripStatus.VehicleApproaching &&
+    prevTripStatusRef.current !== TripStatus.VehicleApproaching;
+  // 捕捉「送驾中(6) 直达待评价(9)」边沿:内部免费订单无需支付,行程结束后跳过待支付(8)直接进入待评价。
+  // 该路径原本没有任何入场动效,且待评价态内容比送驾态更高(多出费用明细卡与评价卡),
+  // 底部锚定容器(.container position:fixed bottom:0)会瞬间向上顶,观感生硬。
+  // 与 playDispatchTransition 同理:CallingCar 被 key 重挂载无法自判边沿,由父层捕捉后传入,
+  // 组件内做挂载快照,再用挂载即播的 keyframes 实现整卡升起 + 内容分层浮现。
+  // 限定上一态为 InService:不影响支付后 8→9 的既有表现;进入即已是待评价态时 prev 也为该态、不会误播。
+  const playEvaluateEnter =
+    deps.tripStatus === TripStatus.PaymentSuccess &&
+    prevTripStatusRef.current === TripStatus.InService;
+  // 捕捉「进入行程结束(8)」边沿:控制结束态背景卡升起(finished-bg-rise)与里程渐显(finished-distance-fade)。
+  // 进入即已是结束态时首帧为 8→8 无边沿、不播升起;仅真实 live 跃迁到 8 才播放。
+  const playFinishedEnter =
+    deps.tripStatus === TripStatus.TripFinished &&
+    prevTripStatusRef.current !== TripStatus.TripFinished;
+  useEffect(() => {
+    prevTripStatusRef.current = deps.tripStatus;
+  }, [deps.tripStatus]);
+
   /** 时长文案 */
   const durationDisplay = useMemo(
     () => formatRemainingDurationDisplay(deps.routeDuration),
@@ -375,11 +403,19 @@
         .join("|"),
     [deps.markers]
   );
+  /**
+   * 触发一次 customCallout 回流重绘:翻转 borderRadius 兜底原生地图对该 cover-view 的重绘。
+   */
   const toggleCalloutRepaint = useCallback(() => {
     setCalloutRepaintStyle((prev) => ({ borderRadius: prev.borderRadius === '0px' ? '0.5px' : '0px' }));
   }, []);
   useEffect(() => {
     // 仅 Android 保留一次性重绘兜底;iOS 完全不做 borderRadius 重绘(避免闪动)。
+    // 依赖里额外加入 isModifyDestinationReviewVisible / isPickupRouteChangeVisible:
+    // 这两个状态切换时 customCallout 内容会切成目的地黑色浮窗(endpoint-summary),
+    // 但 marker 的 id/name(markersRepaintSignature)和 tripStatus 通常不变,
+    // 若不监听会导致本次内容切换错过重绘时机,出现"气泡背景已渲染但内部文字未绘制"。
+
     if (!deps.isAndroid) {
       return undefined;
     }
@@ -396,7 +432,14 @@
         calloutRepaintTimerRef.current = null;
       }
     };
-  }, [markersRepaintSignature, deps.tripStatus, deps.isAndroid, toggleCalloutRepaint]);
+  }, [
+    markersRepaintSignature,
+    deps.tripStatus,
+    deps.isAndroid,
+    deps.isModifyDestinationReviewVisible,
+    deps.isPickupRouteChangeVisible,
+    toggleCalloutRepaint
+  ]);
 
 
   /**
@@ -418,6 +461,27 @@
     toggleCalloutRepaint();
     return undefined;
   }, [deps.elapsedSeconds, deps.tripStatus, deps.isAndroid, toggleCalloutRepaint]);
+
+  /**
+   * Android 接驾到达态(VehicleArrivedPickup)上车点倒计时气泡(site-countdown-callout)
+   * 同样存在偶现不绘制、倒计时文案不刷新的问题:pickupCountdownSeconds 按秒递减更新,
+   * 但此前只有 Publishing/CallingCar 的 elapsedSeconds 变化会触发每秒重绘兜底,
+   * VehicleArrivedPickup 未被覆盖,导致该气泡文案长时间卡住不变,直到其他依赖
+   * (markers/tripStatus 等)触发的单次重绘才一次性跳变。这里单独为该状态按秒重绘兜底。
+   */
+  useEffect(() => {
+    if (!deps.isAndroid) {
+      return undefined;
+    }
+    if (deps.tripStatus !== TripStatus.VehicleArrivedPickup) {
+      return undefined;
+    }
+    if (pickupCountdownSeconds === null || pickupCountdownSeconds === undefined) {
+      return undefined;
+    }
+    toggleCalloutRepaint();
+    return undefined;
+  }, [pickupCountdownSeconds, deps.tripStatus, deps.isAndroid, toggleCalloutRepaint]);
 
 
 
@@ -700,6 +764,7 @@
             {item.id === 1 &&
               deps.tripStatus === TripStatus.VehicleApproaching &&
               !deps.isModifyDestinationReviewVisible &&
+              !deps.isPickupRouteChangeVisible &&
               !item.hideNameCallout && (
                 <>
                 <CoverView
@@ -711,7 +776,7 @@
                     src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/golden-car-icon-20260708.png"
                   />
                   <CoverView className="site-status-time">
-                    限停{deps.startSite?.time}MIN
+                    限停{deps.startSite?.time}分钟
                   </CoverView>
                 </CoverView>
                 <CoverImage
@@ -723,6 +788,7 @@
             {item.id === 1 &&
               deps.tripStatus === TripStatus.VehicleArrivedPickup &&
               !deps.isModifyDestinationReviewVisible &&
+              !deps.doorUnlockedInCurrentPage &&
               !item.hideNameCallout &&
               Number(pickupCountdownSeconds) > 0 && (
                 <CoverView
@@ -741,7 +807,8 @@
             {item.id === 1 &&
               ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(
                 deps.tripStatus
-              ) || deps.isModifyDestinationReviewVisible) &&
+              ) || deps.isModifyDestinationReviewVisible
+                || (deps.tripStatus === TripStatus.VehicleApproaching && deps.isPickupRouteChangeVisible)) &&
               !item.hideNameCallout && (
                 <CoverImage
                   className="site-status-end-img"
@@ -758,7 +825,7 @@
               !item.hideNameCallout && (
                 <CoverImage
                   className="site-status-end-img"
-                  src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-20260722.png"
+                  src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-20260812.png"
                 />
               )}
             {item.name === "车辆位置" &&
@@ -788,7 +855,7 @@
                         {deps.routeDistance?.value ?? 0}
                       </CoverView>
                       <CoverView className="car-before-order-tip-unit">
-                        {deps.routeDistance?.unit === "m" ? "M" : "KM"}
+                        {deps.routeDistance?.unit === "m" ? "米" : "公里"}
                       </CoverView>
                       <CoverView className="car-before-order-tip-value">
                         {vehicleMetricDurationDisplay.value}
@@ -803,7 +870,7 @@
                         {deps.routeDistance?.value ?? 0}
                       </CoverView>
                       <CoverView>
-                        {deps.routeDistance?.unit === "m" ? "M" : "KM"}
+                        {deps.routeDistance?.unit === "m" ? "米" : "公里"}
                       </CoverView>
                     </CoverView>
                   )
@@ -823,6 +890,7 @@
     [
       deps.arrivedTooltipReady,
       deps.customCalloutIconSrc,
+      deps.doorUnlockedInCurrentPage,
       deps.hasBeforeUserEndSite,
       deps.isAndroid,
       deps.isModifyDestinationReviewVisible,
@@ -1019,7 +1087,10 @@
     ) {
       return (
         <CallingCar
-          key={deps.tripStatus}
+          playDispatchTransition={playDispatchTransition}
+          playEvaluateEnter={playEvaluateEnter}
+          playFinishedEnter={playFinishedEnter}
+          onDispatchTransitionSettled={deps.onDispatchTransitionSettled}
           orderId={deps.orderId}
           hasBeforeUserEndSite={deps.hasBeforeUserEndSite}
           hasPreviousOrder={deps.hasPreviousOrder}
@@ -1058,6 +1129,7 @@
           onToggleDetailFade={deps.handleToggleDetailFade}
           orderTrunkStatus={deps.trunkStatus}
           doorUnlockedInCurrentPage={deps.doorUnlockedInCurrentPage}
+          forceShowCancelTrip={deps.forceShowCancelTrip}
           onDoorUnlockSuccess={deps.onDoorUnlockSuccess}
           bluetoothStatusReady={deps.bluetoothStatusReady}
           bluetoothCardDescription={deps.bluetoothCardDescription}
diff --git a/src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts b/src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
index 3394644..c39eb66 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -1,4 +1,5 @@
 import { useEffect, useRef } from 'react';
+import { CancelType } from '../../../constants/trip-status';
 import wsManager from '../../../../utils/websocket';
 import {
     createOpenTrunkSocketHandler,
@@ -47,6 +48,8 @@
     setPriceUpdateVersion: (updater: (v: number) => number) => void;
     setFinishCountdownEnabled: (value: boolean) => void;
     setDoorUnlockedInCurrentPage: React.Dispatch<React.SetStateAction<boolean>>;
+    /** 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮(无视原有行程状态/车门解锁判断)。 */
+    setForceShowCancelTrip?: (value: boolean) => void;
     setShowAnnouncementsPopup: (value: boolean) => void;
     setRoutePoints: (points: any[]) => void;
     setPolyline: (lines: any[]) => void;
@@ -59,9 +62,9 @@
     currentFullRouteRef: any;
     lastErasedIndexRef: any;
     orderRouteInitializedRef: any;
-    setCancelType: (value: number) => void;
+    setCancelType: (value: CancelType) => void;
     setShowCancelPopup: (value: boolean) => void;
-    cancelTypeRef: React.MutableRefObject<number>;
+    cancelTypeRef: React.MutableRefObject<CancelType>;
     showCancelPopupRef: React.MutableRefObject<boolean>;
     checkAndShowPassengerNotice: () => void;
     handlePickupException?: (pickupException: PickupExceptionInfo | null) => void;
diff --git a/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts b/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts
index c40f904..d752a8a 100644
--- a/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts
+++ b/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts
@@ -1,7 +1,7 @@
 import Taro from '@tarojs/taro';
 import { queryVehicleByOrderId } from '../../../../api/begin-route';
 import { MODIFY_DESTINATION_SOCKET_STATUS_KEY } from '../../../../constants/modify-destination';
-import { TripStatus } from '../../../../constants/trip-status';
+import { TripStatus, CancelType } from '../../../../constants/trip-status';
 import {
   executeWhenOrderMatched,
   resolvePayloadOrderIdFromContentOnly,
@@ -110,8 +110,8 @@
     onMatched: ({ currentOrderId }) => {
       const currentDeps = depsRef.current;
       lastFetchedVehicleOrderIdRef.current = null;
-      // 改派成功,仅在正展示"正在为您重新派车中"弹窗(cancelType=6)时关闭,避免误关其他取消弹窗
-      if (currentDeps.showCancelPopupRef.current && currentDeps.cancelTypeRef.current === 6) {
+      // 改派成功,仅在正展示"正在为您重新派车中"弹窗(CancelType.Redispatching)时关闭,避免误关其他取消弹窗
+      if (currentDeps.showCancelPopupRef.current && currentDeps.cancelTypeRef.current === CancelType.Redispatching) {
         currentDeps.setShowCancelPopup(false);
       }
       const requestedOrderId = String(currentOrderId || '');
@@ -146,7 +146,7 @@
       }
       lastFetchedVehicleOrderIdRef.current = null;
       rollbackOrderToCallingCar(currentDeps);
-      currentDeps.setCancelType(6);
+      currentDeps.setCancelType(CancelType.Redispatching);
       currentDeps.setShowCancelPopup(true);
     }
   });
diff --git a/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts b/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
index 7c0d7b9..e834f94 100644
--- a/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
+++ b/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
@@ -134,6 +134,10 @@
   }
   const beforeUserSite = parseBeforeUserEndSite(payload);
   currentDeps.setBeforeUserEndSite(beforeUserSite);
+  // 后端推送 oddOut === 1 时,无视行程状态/车门解锁判断,强制展示取消行程按钮;
+  // oddOut 恢复为非 1 时同步写回 false,避免同一订单内状态无法自动复位(该状态仅在订单切换时被重置)。
+  const oddOut = Number(payload?.content?.oddOut);
+  currentDeps.setForceShowCancelTrip?.(oddOut === 1);
   const currentTripStatus = currentDeps.tripStatusRef.current;
   const isModifyDestinationPreviewActive = Boolean(currentDeps.isModifyDestinationReviewVisible);
   const isPickupRouteChangePreviewActive = Boolean(
diff --git a/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts b/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts
index cda82e7..8ceace3 100644
--- a/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts
+++ b/src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts
@@ -3,7 +3,7 @@
 import { queryVehicleByOrderId } from '../../../../api/begin-route';
 import { clearOrderTripStorage, reportEvent } from '../../../../../utils/Robotaxi-public-method';
 import { getOrderId } from '../../../../utils/order';
-import { SocketOrderStatus, TripStatus, toTripStatus } from '../../../../constants/trip-status';
+import { SocketOrderStatus, TripStatus, toTripStatus, CancelType } from '../../../../constants/trip-status';
 import { ARRIVED_DROPOFF_PAGE_VIEW_EVENT_ID, PICKUP_ARRIVED_PAGE_VIEW_EVENT_ID, TRIP_IN_PROGRESS_PAGE_VIEW_EVENT_ID, TRIP_IN_PROGRESS_COMPLETE_TRIP_BUTTON_EVENT_ID } from '../../../../constants/report-events';
 import { createTripInProgressCompleteTripReportPayload } from '../../../../constants/confirm-ride-report';
 import { clearPassedRouteCache } from '../passed-route-cache';
@@ -194,7 +194,7 @@
   }
   const matchedPendingCancelContext = isPendingCancelContextMatched(payload, pendingCancelContext);
   if (matchedPendingCancelContext) {
-    deps.setCancelType(pendingCancelContext?.cancelType || 4);
+    deps.setCancelType(pendingCancelContext?.cancelType || CancelType.OrderCanceled);
     deps.pendingCancelContextRef.current = null;
   } else if (changeStatusOld === TripStatus.VehicleApproaching || changeStatusOld === TripStatus.VehicleArrivedPickup) {
     deps.setOrderId('');
@@ -203,9 +203,9 @@
     Taro.removeStorageSync('pickupEndTime');
     Taro.removeStorageSync('waitingTimerStartAt');
     Taro.removeStorageSync('waitingTimerElapsed');
-    deps.setCancelType(4);
+    deps.setCancelType(CancelType.OrderCanceled);
   } else {
-    deps.setCancelType(5);
+    deps.setCancelType(CancelType.NoAvailableVehicle);
   }
   deps.setTripStatus(TripStatus.CancelOnlyPickup);
   deps.setShowCancelPopup(true);
@@ -403,10 +403,10 @@
   reportTripInProgressEventIfNeeded(effectiveTripStatus, changeStatusOld, effectiveOrderId, currentDeps);
   reportTripCompleteEventIfNeeded(effectiveTripStatus, changeStatusOld, effectiveOrderId, currentDeps);
   handleStatusSideEffects(effectiveTripStatus, changeStatusOld, currentDeps);
-  // 改派场景兜底:若当前展示的是"正在为您重新派车中"弹窗(cancelType=6),
+  // 改派场景兜底:若当前展示的是"正在为您重新派车中"弹窗(CancelType.Redispatching),
   // 一旦订单状态推进回接驾中/已到达上车点,说明改派成功,自动关闭弹窗。
   // 用同步 ref 镜像判断,避免依赖经 useEffect 异步回流的 cancelType 造成漏关。
-  const isReassignPopupShowing = currentDeps.showCancelPopupRef.current && currentDeps.cancelTypeRef.current === 6;
+  const isReassignPopupShowing = currentDeps.showCancelPopupRef.current && currentDeps.cancelTypeRef.current === CancelType.Redispatching;
   const isAdvancingIntoPickup = (
     effectiveTripStatus === TripStatus.VehicleApproaching
     || effectiveTripStatus === TripStatus.VehicleArrivedPickup
diff --git a/src/robotaxi/pages/begin-route/index.less b/src/robotaxi/pages/begin-route/index.less
index 52c555b..97cc77f 100644
--- a/src/robotaxi/pages/begin-route/index.less
+++ b/src/robotaxi/pages/begin-route/index.less
@@ -584,3 +584,16 @@
 .pr-0 {
     padding-right: 0;
 }
+
+// 进入页面的地图 loading 占位(网格图,样式与首页保持一致),覆盖在 <Map> 上,拿到定位后移除
+.map-placeholder-grid {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 99;
+    background-color: #F2F3F7;
+    background-image: linear-gradient(#E5E7EB 2px, transparent 2px), linear-gradient(90deg, #E5E7EB 2px, transparent 2px);
+    background-size: 40px 40px;
+}
diff --git a/src/robotaxi/pages/begin-route/index.tsx b/src/robotaxi/pages/begin-route/index.tsx
index cef976b..d4ed4b6 100644
--- a/src/robotaxi/pages/begin-route/index.tsx
+++ b/src/robotaxi/pages/begin-route/index.tsx
@@ -1,4 +1,5 @@
 import Taro, { useDidShow, useShareAppMessage } from "@tarojs/taro"
+import { View } from '@tarojs/components';
 import { useEffect, useState, useRef, useMemo,useCallback } from 'react';
 import './index.less';
 
@@ -8,9 +9,12 @@
 import ModifyDestinationFailPopup from './components/modify-destination-fail-popup';
 import PickupExceptionPopup from './components/pickup-exception-popup';
 import CreditPayPopup from './components/credit-pay-popup';
-import CouponSelectPopup from '../../components/coupon-select-popup';
 // 调试面板:模拟完整行程流转(仅 dev 构建可见)
 // import DebugOrderSimulatorPanel from './components/debug-order-simulator-panel';
+// 转场动效调试面板:呼叫中→派单成功、送驾中→行程结束(仅 dev 构建可见)
+// import DebugTransitionPanel from './components/debug-transition-panel';
+// 到达结束态全屏礼花动效
+import TripFinishedFireworks from './components/trip-finished-fireworks';
 // 弹窗状态管理
 import usePopupManager from './hooks/use-popup-manager';
 // WebSocket 事件编排
@@ -28,9 +32,12 @@
 // 地图视口状态管理
 import { ROUTE_POLYLINE_BASE_ID } from './hooks/use-route-manager.shared';
 import useMapViewport from './hooks/use-map-viewport';
-import useActiveTripScale from './hooks/use-active-trip-scale';
-// 速度驱动的地图自动缩放
-import useSpeedBasedScale from './hooks/use-speed-based-scale';
+// 地图相机(统一编排原生视口操作)
+import { useMapCameraController } from './hooks/camera/use-map-camera-controller';
+import { beginRoutePolicies } from './hooks/camera/policies';
+import { CameraPadding } from './hooks/camera/map-camera';
+import { applyHarmonyPaddingCompensation } from '../../utils/platform-env';
+import { mapPerfLog } from './hooks/camera/map-perf-log';
 // 页面恢复与初始化编排
 import usePageRestore from './hooks/use-page-restore';
 // 页面动作编排
@@ -54,8 +61,11 @@
 import useModifyDestinationFlow from './hooks/use-modify-destination-flow';
 import useModifyDestinationPreviewRoute from './hooks/use-modify-destination-preview-route';
 import usePreloadedMapAssets from './hooks/use-preloaded-map-assets';
+import useTransitionAssetPreload from './hooks/use-transition-asset-preload';
 // 蓝牙状态与弹窗编排
 import useBluetoothAccess from './hooks/use-bluetooth-access';
+// 页面级派单转场延后闸门(转场进行中暂缓蓝牙初始化)
+import { useDeferUntilDispatchSettled } from './hooks/use-defer-until-dispatch-settled';
 // 蓝牙车控连接与鉴权编排
 import useBluetoothCarControl from './hooks/use-bluetooth-car-control';
 import { toTripStatus, TripStatus } from '../../constants/trip-status';
@@ -87,12 +97,10 @@
   resetConfirmRideSelectionContext
 } from '../../constants/confirm-ride-report';
 import { withRobotaxiWhitelist } from '../../../utils/whitelist';
-import { withToast } from '../../components/toast/with-toast';
-import { showToast } from '../../components/toast';
+import { showToast, hideToast } from '../../components/toast';
 import type { PickupExceptionInfo } from './pickup-exception';
 
 const MODIFY_DESTINATION_PICKUP_STATUS_GUARD_MS = 5000;
-const COUPON_POPUP_CLOSE_VIEWPORT_REFRESH_DELAY_MS = 320; // 优惠券弹窗退场动画结束后,再刷新完成态视口
 const ROUTE_SELECTABLE_TRIP_STATUSES = [TripStatus.PublishRoute, TripStatus.VehicleApproaching] as const; // 允许展示并切换候选路线的行程状态
 const PICKUP_ROUTE_CHANGE_ELIGIBLE_ORDER_ID_KEY = 'pickupRouteChangeEligibleOrderId'; // 接驾态允许切路线的订单 ID,仅在当前订单生命周期内有效
 const PASSENGER_NOTICE_REGIONCHANGE_COOLDOWN_MS = 1000;
@@ -241,45 +249,13 @@
     handleCloseNoRoutePopup,
     privacyUpdateInfo
   } = usePopupManager(); // 弹窗状态编排
-  const [longitude, setLongitudeState] = useState<number | null>(null); // 经度
-  const [latitude, setLatitudeState] = useState<number | null>(null); // 纬度
-  // 记录 latitude / longitude 的最新值,供跨渲染读取;更新 ref 本身不会触发重新渲染
-  // (mapScale 的同类 ref / recordMapScale / setMapScale 已下沉到 useMapViewport 内导出)
-  const latitudeRef = useRef<number | null>(null);
-  const longitudeRef = useRef<number | null>(null);
-  // record* 系列:仅记录最新值,不触发重新渲染,等下次渲染时直接读取 ref 中记录的最新值
-  const recordLatitude = useCallback((value: number | null) => {
-    latitudeRef.current = value;
-  }, []);
-  const recordLongitude = useCallback((value: number | null) => {
-    longitudeRef.current = value;
-  }, []);
-  const globalFitSyncRef = useRef(false); // 标记全局 includePoints 触发,用于在 regionchange end 时同步原生缩放
+  const [longitude] = useState<number | null>(null); // 经度(中心点业务 state 已移除,首帧使用业务点位兜底)
+  const [latitude] = useState<number | null>(null); // 纬度(中心点业务 state 已移除,首帧使用业务点位兜底)
+  const initialMapCenterRef = useRef<{ longitude: number; latitude: number } | null>(null); // 仅首次挂载初始化,运行时相机变化不回写
   const passengerNoticeRegionChangeIgnoreUntilRef = useRef(0); // 乘车须知开关后的地图 regionchange(update) 冷却截止时间
-  const markViewportFitSyncing = useCallback(() => {
-    globalFitSyncRef.current = true;
-  }, []);
   // 用户手势进行中标记:从 regionchange 'begin'(用户拖拽/缩放手势) 到 'end' 之间为 true。
   // 供平滑运动在手势期间暂停车辆位置回灌,避免高频 setState 触发重渲染把受控相机弹回。
   const userGestureActiveRef = useRef(false);
-  // 经纬度 setter:更新 state 触发渲染的同时同步记录最新值,保证 ref 始终为最新(兼容函数式更新)
-  const setLatitude = useCallback((value: number | null | ((prev: number | null) => number | null)) => {
-    setLatitudeState((prev) => {
-      const next = typeof value === 'function' ? value(prev) : value;
-      latitudeRef.current = next;
-      return next;
-    });
-  }, []);
-  const setLongitude = useCallback((value: number | null | ((prev: number | null) => number | null)) => {
-    setLongitudeState((prev) => {
-      const next = typeof value === 'function' ? value(prev) : value;
-      longitudeRef.current = next;
-      return next;
-    });
-  }, []);
-  const syncMapCenterAfterViewportFit = useCallback(() => {
-    markViewportFitSyncing();
-  }, [markViewportFitSyncing]);
   const [startSite, setStartSite] = useState<any>(null); // 上车点
   const [endSite, setEndSite] = useState<any>({}); // 下车点
   const [tripStatus, setTripStatus] = useState<TripStatus>(toTripStatus(Taro.getStorageSync('tripStatus'))); // 当前行程状态
@@ -297,6 +273,7 @@
   const [callSubmitting, setCallSubmitting] = useState(false); // 呼叫车辆提交中状态
   const [orderId, setOrderId] = useState<string>(''); // 订单ID
   const [doorUnlockedInCurrentPage, setDoorUnlockedInCurrentPage] = useState(false); // 当前页面内是否已解锁过车门
+  const [forceShowCancelTrip, setForceShowCancelTrip] = useState(false); // 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮
   const [finishCountdownEnabled, setFinishCountdownEnabled] = useState(false); // 完成倒计时是否启用
   const customCalloutIconUrl = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-tooltip-right.png'; // 自定义气泡图标URL
   const waitingCalloutBgUrl = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/tooltip-bg-20260715.png'; // 等待气泡背景图URL
@@ -368,7 +345,6 @@
   }, [orderId]);
   const pendingPickupRouteChangeEligibilityRef = useRef(false); // 预估态多路线资格待绑定到订单的标记
   const pickupRouteChangeSnapshotRef = useRef<PickupRouteChangeSnapshot | null>(null); // 接驾态打开路线切换前的页面快照,用于取消切换后恢复
-  const couponCloseViewportRefreshTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // 优惠券弹窗关闭后的完成态视口刷新定时器
   const pickupArrivedReportedOrderIdRef = useRef(''); // 到达上车点页面浏览埋点去重
   const tripInProgressReportedOrderIdRef = useRef(''); // 行程中页面浏览埋点去重
   const arrivedDestinationReportedOrderIdRef = useRef(''); // 到达目的地完成行程埋点去重,避免同一订单重复 socket 上报
@@ -460,6 +436,9 @@
       waitingRadarUrl
     }
   }); // 地图气泡资源预加载
+
+  // 转场动效素材预加载(按行程状态空闲预载:呼叫中→派单成功素材、送驾中→行程结束素材)
+  useTransitionAssetPreload({ tripStatus });
   const {
     openService,
     toCoupon,
@@ -696,7 +675,6 @@
     setEndSite,
     setBeforeUserEndSite,
     setHasPreviousOrder,
-    syncMapCenterAfterViewportFit,
     updateVehicleLocationThrottled: vehicleState.updateVehicleLocationThrottled,
     vehicleMoveAlongPoints,
     setVehicleDirection,
@@ -915,8 +893,9 @@
 
     try {
       setIsConfirmingPickupRouteChange(true);
-      Taro.showLoading({
+      showToast({
         title: '正在修改路线',
+        icon: 'loading',
         mask: true
       });
       await selectNavigationRoute({
@@ -927,7 +906,7 @@
       setIsPickupRouteChangeVisible(false);
       pickupRouteChangeSnapshotRef.current = null;
       routeRequestOrderRoute(orderId, tripStatus);
-      Taro.hideLoading();
+      hideToast();
       setTimeout(() => {
         showToast({
           title: '切换成功,将按新路线行驶',
@@ -935,7 +914,7 @@
         });
       }, 0);
     } catch (error) {
-      Taro.hideLoading();
+      hideToast();
     } finally {
       setIsConfirmingPickupRouteChange(false);
     }
@@ -1020,52 +999,92 @@
     endSite: effectiveEndSite,
     vehicleLocation,
     longitude,
-    latitude,
-    setLongitude,
-    setLatitude,
-    recordLongitude,
-    recordLatitude,
-    orderDetailViewportRefreshVersion,
-    markViewportFitSyncing
+    latitude
   }); // 地图视口状态容器
   const {
     mapBottomPadding,
-    mapScale,
-    setMapScale,
-    mapScaleRef,
-    recordMapScale,
     originBoxHidden,
     originOpacity,
     setOriginOpacity,
     showLocationFlag,
     tripStatus6ViewportLockedRef,
-    holdDynamicScale,
-    getDynamicScaleHoldUntil,
     updateMapViewportPadding,
-    toOrigin,
-    getLocationInfo,
-    computeBottomPadding,
-    resolveSafeTopInset,
-    syncNativeViewportToControlledState
+    measureBottomPadding,
+    resolveSafeTopInset
   } = viewportState; // 视口状态解构
-  const activeTripScale = useActiveTripScale({
+
+  // 进入页面的地图 loading 占位(网格图):挂载即 true,首帧定位拿到坐标后置 false
+  const [mapLoading, setMapLoading] = useState(true);
+  // 地图相机:统一编排原生视口操作(当前仅发单页策略生效,其它状态无策略则不动地图)
+  // includePoints 执行前即时测量底部卡片高度,消除「先 fit 后测量」的竞态;
+  // 测量失败/超时回退上次缓存值(computeBottomPadding),不阻塞 fit。
+  // 注:resolveSafeTopInset()/measureBottomPadding() 内部已按鸿蒙补偿系数处理,此处的
+  // 固定 50(左右留白)在鸿蒙下同样需要补偿,避免和 top/bottom 的像素基准不一致。
+  const cameraGetPadding = useCallback(async (): Promise<CameraPadding> => {
+    const sidePadding = 60;
+    const topPadding = 70;
+    const sideInset = applyHarmonyPaddingCompensation(sidePadding, 'side');
+    const top = applyHarmonyPaddingCompensation(resolveSafeTopInset() + topPadding, 'top');
+    const bottomTemp = await measureBottomPadding();
+    const bottom = applyHarmonyPaddingCompensation(bottomTemp, 'bottom');
+    return [top, sideInset, bottom, sideInset];
+  }, [measureBottomPadding, resolveSafeTopInset]);
+  // focus 可视区域居中专用 padding:与 cameraGetPadding 参数口径一致,但不做鸿蒙补偿。
+  // focus 的可视区域居中换算(resolveFocusCenter,见 map-camera.ts)全程基于真实 DOM
+  // 像素(getRegion/getMapHeightPx)计算纵向偏移,不经过 includePoints 原生 API,因此
+  // 不能套用「补偿 includePoints 像素解释偏差」的鸿蒙补偿系数——否则会导致偏移量在
+  // 鸿蒙上被错误放大数倍,上车点无法正确居中到可视区域(这正是鸿蒙上呼单态上车点
+  // 未居中、而 iOS/安卓正常的原因:两端未做该补偿,偏移换算是准的)。
+  const cameraGetFocusPadding = useCallback(async (): Promise<CameraPadding> => {
+    const sidePadding = 60;
+    const topPadding = 70;
+    const top = resolveSafeTopInset() + topPadding;
+    const bottom = await measureBottomPadding();
+    return [top, sidePadding, bottom, sidePadding];
+  }, [measureBottomPadding, resolveSafeTopInset]);
+  const cameraController = useMapCameraController({
+    mapId: 'map',
     tripStatus: mapPresentationTripStatus,
-    computeBottomPadding,
-    resolveSafeTopInset,
-    holdDynamicScale
-  }); // 行程中 ORDER_UPDATE_LINE 视口编排
-  const { notifyUserMapGesture, pendingFitSyncRef } = useSpeedBasedScale({
-    tripStatus: mapPresentationTripStatus,
-    getDynamicScaleHoldUntil,
+    policies: beginRoutePolicies,
+    startSite,
+    endSite: effectiveEndSite,
+    routePoints,
+    walkingPoints,
     vehicleLocation,
-    routeDistance,
-    computeBottomPadding,
-    resolveSafeTopInset,
-    setLatitude,
-    setLongitude
-  }); // 接驾中/行程中按路线缩短自动调整视图
+    userLocation: currentUserLocation,
+    getPadding: cameraGetPadding,
+    getFocusPadding: cameraGetFocusPadding,
+    onLocated: () => setMapLoading(false),
+  });
+  // 关闭取消弹窗(再等等/关闭)会触发重渲染,受控 <Map> 可能被弹回兜底(北京);
+  // 关闭后按当前状态主动回中一次纠正(effect 在提交后执行,晚于弹回)。
+  const prevShowCancelPopupRef = useRef(showCancelPopup);
+  useEffect(() => {
+    const wasOpen = prevShowCancelPopupRef.current;
+    prevShowCancelPopupRef.current = showCancelPopup;
+    if (wasOpen && !showCancelPopup) {
+      cameraController.recenter('cancel-popup-close');
+    }
+  }, [showCancelPopup]);
   const arrivedMarkerReady = mapPresentationTripStatus !== TripStatus.ArrivedDestination || typeof vehicleDirection === 'number';
-  const arrivedTooltipReady = mapPresentationTripStatus !== TripStatus.ArrivedDestination || (arrivedMarkerReady && mapScale >= 20);
+  // 到达气泡的缩放就绪判断改用原生 getScale 读取真实缩放,不再依赖受控 mapScale state。
+  const [arrivedTooltipScaleReady, setArrivedTooltipScaleReady] = useState(false);
+  const refreshArrivedTooltipScaleReady = useCallback(() => {
+    Taro.createMapContext('map').getScale({
+      success: (res) => {
+        setArrivedTooltipScaleReady(Number(res?.scale) >= 20);
+      },
+      fail: () => {}
+    });
+  }, []);
+  useEffect(() => {
+    if (mapPresentationTripStatus !== TripStatus.ArrivedDestination) {
+      setArrivedTooltipScaleReady(false);
+      return;
+    }
+    refreshArrivedTooltipScaleReady();
+  }, [mapPresentationTripStatus, refreshArrivedTooltipScaleReady]);
+  const arrivedTooltipReady = mapPresentationTripStatus !== TripStatus.ArrivedDestination || (arrivedMarkerReady && arrivedTooltipScaleReady);
   const markerState = useMapMarkers({
     tripStatus: mapPresentationTripStatus,
     statusRule: effectiveStatusRule,
@@ -1099,7 +1118,6 @@
     shouldShowWaitingRadar,
     isAndroid,
     tripStatus: mapPresentationTripStatus,
-    mapScale,
     isModifyDestinationReviewVisible,
     startSite,
     endSite: effectiveEndSite,
@@ -1230,9 +1248,6 @@
     setEndSite,
     setVehicleMoveAlongPoints,
     setTrunkStatus,
-    setLatitude,
-    setLongitude,
-    getLocationInfo,
     routeRequestRouteAndFare,
     routeRequestOrderRoute,
     routeUpdateWalkingRouteForStart,
@@ -1243,6 +1258,13 @@
     updatePickupRouteChangeEligibleOrder
   }); // 页面恢复与初始化编排
   const router = Taro.useRouter();
+  const didShowRouteParamsRef = useRef<{
+    skipPageFlag?: string;
+    fromModifyDestination?: string;
+  }>({
+    skipPageFlag: router.params?.skipPageFlag,
+    fromModifyDestination: router.params?.fromModifyDestination
+  }); // begin-route 路由参数只消费一次,避免回前台时重复触发“已有订单”提示
   const tripRuntime = useTripRuntime({
     waitingTimerStartKey: WAITING_TIMER_START_KEY,
     waitingTimerElapsedKey: WAITING_TIMER_ELAPSED_KEY,
@@ -1264,8 +1286,6 @@
     latitude,
     elapsedSeconds,
     setElapsedSeconds,
-    setLatitude,
-    setLongitude,
     setWalkingPoints,
     setWalkingTime,
     setRoutePoints,
@@ -1304,10 +1324,10 @@
     lastPolylineUpdateAtRef.current = 0;
   };
   /**
-   * 手动切线前先暂停速度缩放,避免一次性全览后立即被速度接管。
+   * 手动切线前先避让相机自动意图,避免一次性全览后立即被自动 fit 接管。
    */
   const handleSelectRoute = (index: number, options?: any) => {
-    holdDynamicScale();
+    cameraController.camera.holdAuto(0);
     baseHandleSelectRoute(index, options);
   };
 
@@ -1315,7 +1335,7 @@
    * 处理路线 polyline 点击切换。
    */
   const handlePolylineTap = (event: any) => {
-    holdDynamicScale();
+    cameraController.camera.holdAuto(0);
     baseHandlePolylineTap(event);
   };
 
@@ -1323,7 +1343,7 @@
    * 处理地图点击切换路线。
    */
   const handleMapTap = (event: any) => {
-    holdDynamicScale();
+    cameraController.camera.holdAuto(0);
     baseHandleMapTap(event);
   };
 
@@ -1489,6 +1509,7 @@
     setPriceUpdateVersion,
     setFinishCountdownEnabled,
     setDoorUnlockedInCurrentPage,
+    setForceShowCancelTrip,
     setShowAnnouncementsPopup,
     setRoutePoints,
     setPolyline,
@@ -1524,7 +1545,6 @@
     erasePassedRouteSegments: routeErasePassedRouteSegments,
     applyRoutePoints: routeApplyRoutePoints,
     updateVehicleLocationThrottled: vehicleState.updateVehicleLocationThrottled,
-    onOrderUpdateLineRendered: activeTripScale.handleOrderUpdateLineRendered,
     applySmoothRouteSnapshot,
     resetSmoothRouteMotion,
     modifyDestinationPickupStatusGuardMs: MODIFY_DESTINATION_PICKUP_STATUS_GUARD_MS,
@@ -1574,6 +1594,13 @@
     setDoorUnlockedInCurrentPage(readDoorUnlockedStateForOrder(orderId));
   }, [orderId, readDoorUnlockedStateForOrder]);
 
+  /**
+   * 订单切换后重置强制展示取消行程按钮的标记,避免串单。
+   */
+  useEffect(() => {
+    setForceShowCancelTrip(false);
+  }, [orderId]);
+
   useEffect(() => {
     if (!doorUnlockedInCurrentPage || !orderId) {
       return;
@@ -1605,7 +1632,7 @@
     }
     creditPayFallbackActiveOrderIdRef.current = '';
     if (hideLoading) {
-      Taro.hideLoading();
+      hideToast();
     }
   }, []);
 
@@ -1624,7 +1651,7 @@
 
     clearCreditPayFallbackRuntime(false);
     creditPayFallbackActiveOrderIdRef.current = resolvedOrderId;
-    Taro.showLoading({ title: '扣款结果查询中,请勿重复支付', mask: true });
+    showToast({ title: '扣款结果查询中,请勿重复支付', icon: 'loading', mask: true });
 
     const checkCreditPayFallback = async () => {
       try {
@@ -1633,7 +1660,7 @@
           Taro.removeStorageSync('creditPayOrderId');
           clearCreditPayFallbackRuntime();
           setCreditPayFallbackRefreshVersion((prev) => prev + 1);
-          Taro.showToast({ title: '免密扣款失败,请手动支付', icon: 'none' });
+          showToast({ title: '免密扣款失败,请手动支付', icon: 'none' });
         }
       } catch (error) {
       }
@@ -1658,8 +1685,13 @@
 
   useEffect(() => {
     if (tripStatus === TripStatus.TripFinished) {
-      void handleCreditPayTripFinished(orderId);
+      // 等行程结束动效(礼花 + 卡片/里程升起,约 700ms 延迟 + 1s 升起 ≈ 1.7s)播完再调起免密支付,
+      // 避免「扣款结果查询中」Toast/遮罩在动效期间弹出打断观感。
+      const payTimer = setTimeout(() => {
+        void handleCreditPayTripFinished(orderId);
+      }, 2100);
       return () => {
+        clearTimeout(payTimer);
         clearCreditPayFallbackRuntime();
       };
     } else {
@@ -1695,7 +1727,7 @@
 
     try {
       await cancelOrder(targetOrderId);
-      // Taro.showToast({
+      // showToast({
       //   title: reason === 'launch_failed' ? '拉起信用分失败,已取消发单' : '未完成信用分授权,已取消发单',
       //   icon: 'none'
       // });
@@ -1712,7 +1744,7 @@
         reason,
         error
       });
-      Taro.showToast({ title: '取消订单失败,请重试', icon: 'none' });
+      showToast({ title: '取消订单失败,请重试', icon: 'none' });
     }
   }, [orderId, syncWaitingSignedCreditPayResult]);
 
@@ -1808,8 +1840,6 @@
     setCancelPopupWaitMinutes,
     setWalkingPoints,
     setWalkingTime,
-    setLatitude,
-    setLongitude,
     setShowOverRoutePopup,
     handleOpenPrivacyPopup,
     handleOpenCreditPayPopup,
@@ -1849,6 +1879,9 @@
     tripStatus
   });
   const shouldSkipBluetoothAuth = doorUnlockedInCurrentPage || bluetoothDoorUnlockSuccess;
+  // 页面级派单转场延后闸门:进入接驾中(4)暂缓蓝牙初始化,避免转场动画中弹系统授权弹窗;
+  // 动效完成由 CallingCar 的 onDispatchTransitionSettled 回调 markSettled 放行。
+  const { dispatchTransitionActive, markSettled: handleDispatchTransitionSettled } = useDeferUntilDispatchSettled(tripStatus);
   const {
     bluetoothStatusReady,
     bluetoothCardDescription,
@@ -1868,7 +1901,8 @@
     bluetoothAuthSuccess,
     startBluetoothAuth,
     skipBluetoothAuth: shouldSkipBluetoothAuth,
-    unlockTokenFailed
+    unlockTokenFailed,
+    dispatchTransitionActive
   });
   const { handleConfirmRequoteIfNeeded } = useModifyDestinationPreviewRoute({
     tripStatus,
@@ -1902,13 +1936,13 @@
       updateMapViewportPadding();
       return;
     }
-    handleDidShow(router.params?.skipPageFlag, router.params?.fromModifyDestination);
+    const didShowRouteParams = didShowRouteParamsRef.current;
+    handleDidShow(didShowRouteParams.skipPageFlag, didShowRouteParams.fromModifyDestination);
+    didShowRouteParamsRef.current = {
+      skipPageFlag: undefined,
+      fromModifyDestination: undefined
+    };
     updateMapViewportPadding();
-    // 完成态下隐藏小程序再恢复时,受控中心点可能与原生地图实际中心脱钩,主动触发一次
-    // order_detail includePoints 纠偏,避免路线被压在底部支付卡片附近。
-    if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatusRef.current)) {
-      setOrderDetailViewportRefreshVersion((v) => v + 1);
-    }
   });
 
   /**
@@ -1983,20 +2017,16 @@
   };
 
   const handleOpenPassengerNotice = useCallback(() => {
-    pendingFitSyncRef.current = false;
-    globalFitSyncRef.current = false;
     passengerNoticeRegionChangeIgnoreUntilRef.current =
       Date.now() + PASSENGER_NOTICE_REGIONCHANGE_COOLDOWN_MS;
     rawHandleOpenPassengerNotice();
-  }, [rawHandleOpenPassengerNotice, pendingFitSyncRef]);
+  }, [rawHandleOpenPassengerNotice]);
 
   const handleClosePassengerNotice = useCallback(() => {
-    pendingFitSyncRef.current = false;
-    globalFitSyncRef.current = false;
     passengerNoticeRegionChangeIgnoreUntilRef.current =
       Date.now() + PASSENGER_NOTICE_REGIONCHANGE_COOLDOWN_MS;
     rawHandleClosePassengerNotice();
-  }, [rawHandleClosePassengerNotice, pendingFitSyncRef]);
+  }, [rawHandleClosePassengerNotice]);
 
   const handleCloseEvaluatePopup = () => {
     setEvaluateCloseVersion(v => v + 1);
@@ -2015,17 +2045,24 @@
 
   const handleRegionChange = (e) => {
     const causedBy = normalizeRegionChangeCause(e?.causedBy || e?.detail?.causedBy || '');
+    const eventType = String(e?.type || e?.detail?.type || '').toLowerCase();
+    const nativeCenter = e?.detail?.centerLocation;
+    // 诊断:打印每个 regionchange 的类型/来源 + 原生实际 scale/center,用于定位"弹回北京/14"的触发。
+    // mapPerfLog('regionchange', {
+    //   type: eventType,
+    //   causedBy,
+    //   scale: e?.detail?.scale,
+    //   center: nativeCenter,
+    // });
+
     const isPassengerNoticeRegionChangeCoolingDown =
       passengerNoticeRegionChangeIgnoreUntilRef.current > Date.now();
     if (causedBy === 'update' && isPassengerNoticeRegionChangeCoolingDown) {
       return;
     }
-    activeTripScale.handleRegionChange(e);
     handleAndroidOverlayRegionChange(e);
+    cameraController.onRegionChange(e);
 
-    // 用户手势导致的缩放/拖拽结束后,把原生地图的实际 scale 同步回 React 状态,
-    // 避免受控 <Map scale={mapScale}> 与原生实际缩放漂移,影响后续视口编排判断。
-    const eventType = String(e?.type || e?.detail?.type || '').toLowerCase();
     // 维护用户手势进行中标记:手势 begin 置位、end 复位,供平滑运动在手势期间暂停车辆位置回灌。
     const isUserGesture = causedBy === 'scale' || causedBy === 'drag' || causedBy === 'gesture';
     if (eventType === 'begin') {
@@ -2038,53 +2075,9 @@
       userGestureActiveRef.current = false;
     }
 
-
-    if(eventType === 'begin' && isUserGesture) {
-      notifyUserMapGesture();
-    }
-
-    if (eventType === 'end' && isUserGesture) {
-
-        const actualScale = Number(e?.detail?.scale);
-        if (!Number.isFinite(actualScale)) {
-          return;
-        }
-        const rounded = Math.round(actualScale);
-        if (rounded !== mapScaleRef.current) {
-          recordMapScale(rounded);
-        }
-
-        const center = e?.detail?.centerLocation;
-        const lat = Number(center?.latitude);
-        const lng = Number(center?.longitude);
-        if (Number.isFinite(lat) && Number.isFinite(lng)) {
-          recordLatitude(lat);
-          recordLongitude(lng);
-        }
-    }
-
-    // useSpeedBasedScale.performFit 调用 includePoints 后会置 pendingFitSyncRef=true,
-    // globalFitSyncRef.current = true 表示全局其他地方(如 PublishRoute 的 includePoints)
-    // 等本次 fit 动画真正结束(type==='end')时直接从事件 detail 读取实际 scale/center
-    // 同步回 React 受控状态,避免后续 re-render 把原生视口拉回 React 旧值造成抖动。
-    if (eventType === 'end' && (pendingFitSyncRef.current || globalFitSyncRef.current)) {
-      pendingFitSyncRef.current = false;
-      globalFitSyncRef.current = false;
-      const detail = e?.detail || e;
-      const fittedScale = Number(detail?.scale);
-      if (Number.isFinite(fittedScale)) {
-        const rounded = Math.floor(fittedScale);
-        if (rounded !== mapScaleRef.current) {
-          recordMapScale(rounded);
-        }
-      }
-      const fittedCenter = detail?.centerLocation;
-      const fittedLat = Number(fittedCenter?.latitude);
-      const fittedLng = Number(fittedCenter?.longitude);
-      if (Number.isFinite(fittedLat) && Number.isFinite(fittedLng)) {
-        recordLatitude(fittedLat);
-        recordLongitude(fittedLng);
-      }
+    // 到达目的地态下,视口变化结束后用原生 getScale 读取真实缩放,刷新到达气泡就绪判断。
+    if (eventType === 'end' && mapPresentationTripStatus === TripStatus.ArrivedDestination) {
+      refreshArrivedTooltipScaleReady();
     }
   };
 
@@ -2256,17 +2249,7 @@
   };
   // 关闭优惠券选择弹窗
   const handleCloseCouponSelect = () => {
-    if (couponCloseViewportRefreshTimerRef.current) {
-      clearTimeout(couponCloseViewportRefreshTimerRef.current);
-      couponCloseViewportRefreshTimerRef.current = null;
-    }
     setShowCouponSelectPopup(false);
-    if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus)) {
-      couponCloseViewportRefreshTimerRef.current = setTimeout(() => {
-        setOrderDetailViewportRefreshVersion((version) => version + 1);
-        couponCloseViewportRefreshTimerRef.current = null;
-      }, COUPON_POPUP_CLOSE_VIEWPORT_REFRESH_DELAY_MS);
-    }
   };
   // 支付成功后关闭优惠券选择弹窗
   useEffect(() => {
@@ -2275,12 +2258,6 @@
     }
   }, [tripStatus]);
 
-  useEffect(() => () => {
-    if (couponCloseViewportRefreshTimerRef.current) {
-      clearTimeout(couponCloseViewportRefreshTimerRef.current);
-      couponCloseViewportRefreshTimerRef.current = null;
-    }
-  }, []);
   // 选择优惠券后仅刷新价格,不刷新地图路线
   const handleSelectCoupon = (item: any) => {
     const selectedCouponId = String(item?.operationCouponId || item?.couponId || '');
@@ -2408,7 +2385,7 @@
   const { renderMapOverlays, renderCarCalloutOverlay, renderTripCard, fareBreakdown } = useViewDelegates({
     isAndroid,
     tripStatus,
-    mapScale,
+    onDispatchTransitionSettled: handleDispatchTransitionSettled,
     markers: markers,
     isRouteSelectExpanded,
     isPickupRouteChangeVisible,
@@ -2468,6 +2445,7 @@
     handleShareTrip,
     trunkStatus,
     doorUnlockedInCurrentPage,
+    forceShowCancelTrip,
     onDoorUnlockSuccess: handleDoorUnlockSuccess,
     routeOptions,
     selectedRouteIndex,
@@ -2523,15 +2501,20 @@
     }
     pendingFareCouponSelectionRef.current = { couponId: '', couponName: '' };
   }, [tripStatus, fareBreakdown?.userCouponId]);
-  // 渲染时优先使用 record*/set* 记录的最新值(ref),ref 为空再回退到 state,保证下次渲染拿到最新值
-  // 【修复频繁切换目的地后地图闪现北京故宫的问题】
-  // 当页面重新挂载且定位接口(getLocation)还未返回时,longitude/latitude 状态为 null。
-  // 若直接使用 116.397128(故宫) 兜底,地图会先飞到北京,等定位回来后又瞬间拉回,造成视觉跳跃。
-  // 解决方案:在定位空窗期,优先复用本地缓存已恢复的业务点位(上车点 -> 下车点 -> 车辆位置)作为过渡中心点。
-  const fallbackLongitude = startSite?.longitude ?? effectiveEndSite?.longitude ?? vehicleLocation?.longitude ?? 116.397128;
-  const fallbackLatitude = startSite?.latitude ?? effectiveEndSite?.latitude ?? vehicleLocation?.latitude ?? 39.916527;
-  const mapLongitude = longitudeRef.current ?? longitude ?? fallbackLongitude;
-  const mapLatitude = latitudeRef.current ?? latitude ?? fallbackLatitude;
+  // <Map> 经纬度只用于首次挂载:优先使用已恢复的业务点位,之后保持引用不变。
+  // 运行时视口统一交给 MapContext(includePoints / moveToLocation / recenter)和用户手势,
+  // 不再把 regionchange centerLocation 回写给 React,避免原生 update 与页面渲染形成反馈漂移。
+  const fallbackCenterLongitude = startSite?.longitude ?? effectiveEndSite?.longitude ?? vehicleLocation?.longitude;
+  const fallbackCenterLatitude = startSite?.latitude ?? effectiveEndSite?.latitude ?? vehicleLocation?.latitude;
+  const hasFallbackCenter = Number.isFinite(Number(fallbackCenterLatitude)) && Number.isFinite(Number(fallbackCenterLongitude));
+  if (!initialMapCenterRef.current && (longitude !== null || latitude !== null || hasFallbackCenter)) {
+    initialMapCenterRef.current = {
+      longitude: longitude ?? (hasFallbackCenter ? Number(fallbackCenterLongitude) : 116.397128),
+      latitude: latitude ?? (hasFallbackCenter ? Number(fallbackCenterLatitude) : 39.916527),
+    };
+  }
+  const mapLongitude = initialMapCenterRef.current?.longitude ?? 116.397128;
+  const mapLatitude = initialMapCenterRef.current?.latitude ?? 39.916527;
   const canToggleRouteSelect = isPickupRouteChangeMode
     ? isPickupRouteChangeEligible
     : allowSelectableRoutes && routeOptions.length > 1;
@@ -2584,10 +2567,10 @@
   const beginRouteViewContextValue = {
     mapLongitude,
     mapLatitude,
-    mapScale: mapScaleRef.current ?? mapScale,
     markers: markers,
     polyline: displayPolyline,
-    showLocation: !!longitude && showLocationFlag,
+    showLocation: showLocationFlag,
+    mapLoading,
     handleCalloutTap,
     handleRegionChange,
     onPolylineTap: handlePolylineTap,
@@ -2605,7 +2588,7 @@
     shouldShowOrigin,
     originBoxHidden,
     originOpacity,
-    toOrigin,
+    toOrigin: () => cameraController.recenter('user-location-button'),
     handleWalkNavigation,
     showWalkNavigationButton,
     selectRouteIcon: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/select-route-20260716-2.png',
@@ -2626,6 +2609,11 @@
     walkIcon: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/walk-icon-20260716.png',
     originIcon: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/origin-icon-20260716.png',
     renderTripCard,
+    showCouponSelectPopup,
+    couponOrderMoney,
+    activeCouponId,
+    handleCloseCouponSelect,
+    handleSelectCoupon,
     showCustomerService,
     handleCloseCustomerService,
     showCancelPopup,
@@ -2667,31 +2655,37 @@
       <BeginRouteViewProvider value={beginRouteViewContextValue}>
         <BeginRouteView />
       </BeginRouteViewProvider>
-      <CouponSelectPopup
-        show={showCouponSelectPopup}
-        orderMoney={couponOrderMoney}
-        initialSelectedCouponId={activeCouponId}
-        onClose={handleCloseCouponSelect}
-        onSelect={handleSelectCoupon}
-      />
-      <ModifyDestinationFailPopup
-        show={showModifyDestinationFailPopup}
-        content={modifyDestinationFailMessage}
-        onConfirm={handleAcknowledgeModifyDestinationFail}
-      />
-      <PickupExceptionPopup
-        show={showPickupExceptionPopup}
-        pickupExceptionInfo={pickupExceptionInfo}
-        onClose={handleClosePickupExceptionPopup}
-        onConfirm={handleConfirmPickupExceptionNavigation}
-      />
-      <CreditPayPopup
-        show={showCreditPayPopup}
-        onClose={handleCloseCreditPayPopup}
-        onConfirm={handleConfirmCreditPayPopup}
-      />
-      {/* 模拟器入口已关闭,如需本地调试可取消下方注释
-      <DebugOrderSimulatorPanel
+      <View>
+        {/* 与 BeginRoutePopupArea 同理:以下弹窗组件隐藏时 return null,节点挂载/卸载
+            不再连坐 <BeginRouteView> 中的 <Map>,避免 map 全量重应用 lat/lng/scale
+            导致视口跳变。所有弹窗均为 position:fixed,外层 View 不影响定位。 */}
+        <ModifyDestinationFailPopup
+          show={showModifyDestinationFailPopup}
+          content={modifyDestinationFailMessage}
+          onConfirm={handleAcknowledgeModifyDestinationFail}
+        />
+        <PickupExceptionPopup
+          show={showPickupExceptionPopup}
+          pickupExceptionInfo={pickupExceptionInfo}
+          onClose={handleClosePickupExceptionPopup}
+          onConfirm={handleConfirmPickupExceptionNavigation}
+        />
+        <CreditPayPopup
+          show={showCreditPayPopup}
+          onClose={handleCloseCreditPayPopup}
+          onConfirm={handleConfirmCreditPayPopup}
+        />
+        <TripFinishedFireworks tripStatus={tripStatus} />
+      </View>
+      {/* 转场动效调试面板 — 固定在地图上方,始终可见(临时开启,调试完成后请重新注释) */}
+      {/* <DebugTransitionPanel
+        orderId={orderId}
+        setOrderId={setOrderId}
+        setTripStatus={setTripStatus}
+        bindSocketListeners={bindSocketListeners}
+      /> */}
+      {/* 模拟器入口已关闭,如需本地调试可取消下方注释 */}
+      {/* <DebugOrderSimulatorPanel
         orderId={orderId}
         setOrderId={setOrderId}
         setTripStatus={setTripStatus}
@@ -2702,10 +2696,10 @@
         setShowCancelPopup={setShowCancelPopup}
         setShowModifyDestinationFailPopup={setShowModifyDestinationFailPopup}
         setModifyDestinationFailMessage={setModifyDestinationFailMessage}
-      />
-      */}
+      /> */}
+
     </>
   )
 }
 
-export default withRobotaxiWhitelist(withToast(BeginRoute));
+export default withRobotaxiWhitelist(BeginRoute);
diff --git a/src/robotaxi/pages/coupon/index.tsx b/src/robotaxi/pages/coupon/index.tsx
index 54383ce..1439991 100644
--- a/src/robotaxi/pages/coupon/index.tsx
+++ b/src/robotaxi/pages/coupon/index.tsx
@@ -12,7 +12,7 @@
   CouponDetail
 } from "../../../robotaxi/api/coupon";
 import { withRobotaxiWhitelist } from "../../../utils/whitelist";
-import { showToast } from "../../components/toast";
+import { showToast, hideToast } from "../../components/toast";
 import { withToast } from "../../components/toast/with-toast";
 
 function CouponPage() {
@@ -126,7 +126,7 @@
 
   const openPopup = async (item: CouponItem) => {
     try {
-      Taro.showLoading({ title: "加载中..." });
+      showToast({ title: "加载中...", icon: 'loading' });
       const detail = await getCouponDetail(item.couponId);
       detail.couponDesc = decodeURIComponent(detail.couponDesc || "");
 
@@ -137,7 +137,7 @@
         );
       }
       setCurrentCoupon(detail);
-      Taro.hideLoading();
+      hideToast();
 
       const animation = Taro.createAnimation({
         duration: 300,
@@ -147,7 +147,7 @@
       setAnimationData(animation.export());
       setVisible(true);
     } catch (error) {
-      Taro.hideLoading();
+      hideToast();
       showToast({ title: "获取详情失败", icon: "none" });
     }
   };
diff --git a/src/robotaxi/pages/expense-description/index.less b/src/robotaxi/pages/expense-description/index.less
index 2f621a3..1c66afc 100644
--- a/src/robotaxi/pages/expense-description/index.less
+++ b/src/robotaxi/pages/expense-description/index.less
@@ -1,12 +1,14 @@
+@import '../../style/ui-variables.less';
+
 .expense-detail-page {
-  background: #F2F3F7;
+  background: @gary-theme;
   min-height: 100vh;
   display: flex;
   flex-direction: column;
   padding: 32px 24px;
 
   .order-info {
-    border-radius: 24px;
+    border-radius: 60px;
     background: #FFFFFF;
     padding: 32px 28px 30px 28px;
 
@@ -16,7 +18,7 @@
       font-weight: 500;
       line-height: 48px;
       font-variation-settings: "opsz" auto;
-      color: rgba(0, 0, 0, 0.9);
+      color: @black-text-main;
       margin-left: 4px;
     }
 
@@ -31,7 +33,7 @@
         font-weight: normal;
         line-height: 36px;
         text-align: right;
-        color: rgba(0, 0, 0, 0.4);
+        color: @black-text-04;
       }
 
       .arrow-icon {
@@ -46,8 +48,8 @@
 
   .price-card {
     margin-top: 24px;
-    border-radius: 16px;
-    background: #F5F6FA;
+    border-radius: 40px;
+    background: @gary-theme;
     padding: 24px 32px;
 
     .mb-16 {
@@ -60,7 +62,7 @@
       font-weight: normal;
       line-height: 40px;
       /* 文字色/黑色/重要字 90% */
-      color: rgba(0, 0, 0, 0.9);
+      color: @black-text-main;
 
     }
 
@@ -72,7 +74,7 @@
       text-align: right;
       font-variation-settings: "opsz" auto;
       /* 文字色/黑色/重要字 90% */
-      color: rgba(0, 0, 0, 0.9);
+      color: @black-text-main;
     }
 
     .price-notice-label {
@@ -84,7 +86,7 @@
 
       font-variation-settings: "opsz" auto;
       /* 文字色/黑色/辅助文字 40% */
-      color: rgba(0, 0, 0, 0.4);
+      color: @black-text-04;
 
     }
 
@@ -96,13 +98,14 @@
       text-align: right;
       font-variation-settings: "opsz" auto;
       /* 文字色/黑色/辅助文字 40% */
-      color: rgba(0, 0, 0, 0.4);
+      color: @black-text-04;
     }
   }
 
   .total {
     margin-top: 32px;
     justify-content: flex-end;
+    align-items: first baseline;
 
     .total-text {
       font-family: PingFang SC;
@@ -110,16 +113,15 @@
       font-weight: normal;
       line-height: 40px;
       font-variation-settings: "opsz" auto;
-      color: rgba(0, 0, 0, 0.9);
+      color: @black-text-main;
     }
 
     .num {
-
       font-size: 48px;
       line-height: 40px;
       font-variation-settings: "opsz" auto;
-      color: rgba(0, 0, 0, 0.9);
-      margin: 0 4px;
+      color: @black-text-main;
+      margin: 0  4px;
     }
 
     .unit {
@@ -128,12 +130,12 @@
       font-weight: normal;
       line-height: 40px;
       font-variation-settings: "opsz" auto;
-      color: rgba(0, 0, 0, 0.9);
+      color: @black-text-main;
     }
   }
 
   .faq {
-    border-radius: 24px;
+    border-radius: 60px;
     background: #FFFFFF;
     padding: 32px;
     margin-top: 24px;
@@ -143,7 +145,7 @@
       font-size: 32px;
       font-weight: 500;
       line-height: 48px;
-      color: rgba(0, 0, 0, 0.9);
+      color: @black-text-main;
 
     }
 
@@ -164,7 +166,7 @@
           line-height: 40px;
           font-variation-settings: "opsz" auto;
           /* 文字色/黑色/重要字 90% */
-          color: rgba(0, 0, 0, 0.9);
+          color: @black-text-main;
         }
 
         .arrow-icon {
@@ -199,13 +201,14 @@
         font-size: 24px;
         font-weight: normal;
         line-height: 36px;
-        color: rgba(0, 0, 0, 0.4);
+        color: @black-text-04;
       }
 
       .arrow-icon {
         width: 20px;
         height: 13.6px;
         margin-left: 14px;
+        margin-top: 2px;
         transition: transform 0.3s ease;
 
         &.rotated {
@@ -216,10 +219,25 @@
   }
 
   .complaint {
+    display: flex;
+    align-items: center;
+    justify-content: center;
     margin: 0 auto;
-    margin-top: 30px;
-    width: 549px;
-    height: 92px;
+    margin-top: 42px;
+    width: 654px;
+    height: 102px;
+    border-radius: 2000px;
+    box-sizing: border-box;
+    border: 4px solid @black-text-main;
+    font-size: 32px;
+    font-weight: 500;
+    line-height: normal;
+    text-align: center;
+    color: @black-text-main;
+    &.disabled {
+      border: 4px solid @black-text-04;
+      color: @black-text-04;
+    }
   }
 
 }
@@ -237,6 +255,10 @@
   display: flex;
   align-items: center;
   justify-content: space-between;
+  margin-bottom: 4px;
+  &.flex-between-card:nth-last-of-type(1) {
+    margin-bottom: 0;
+  }
 }
 
 .color-red {
@@ -245,4 +267,4 @@
 
 .mt-16 {
   margin-top: 16px;
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/expense-description/index.tsx b/src/robotaxi/pages/expense-description/index.tsx
index fb0486a..b952f77 100644
--- a/src/robotaxi/pages/expense-description/index.tsx
+++ b/src/robotaxi/pages/expense-description/index.tsx
@@ -20,6 +20,7 @@
   const [faqData, setFaqData] = useState<FAQItem[]>([]);
   const [showEvaluatePopup, setShowEvaluatePopup] = useState(false);
   const [orderId, setOrderId] = useState('');
+  const [disabledComplaint, setDisabledComplaint] = useState(false);
 
   useEffect(() => {
     setOrderId(router.params?.orderId || '');
@@ -36,6 +37,7 @@
       }
       const res = await getFareBreakdown(payload);
       setFareData(res);
+      setDisabledComplaint(res && res.hasComplaint || false);
     } catch (error) {
       console.error('获取费用明细失败', error);
       showToast({ title: '获取费用明细失败', icon: 'none' });
@@ -98,6 +100,17 @@
   const toRulePage = () => {
     Taro.navigateTo({ url: '/robotaxi/pages/pricing-rules/index' });
   };
+
+  const handleClickComplaint = () => {
+    if (disabledComplaint) {
+      showToast({
+        title: '订单已有车费反馈记录,无需重复操作',
+        icon: 'none'
+      });
+      return;
+    }
+    setShowEvaluatePopup(true);
+  }
 
   if (!fareData) return null;
 
@@ -258,18 +271,22 @@
         </View>
       )}
       {/* 去投诉 */}
-      <Image className='complaint' onClick={() => setShowEvaluatePopup(true)} src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/complaint-btn.png' />
+      <View className={`complaint ${disabledComplaint ? 'disabled': ''} `} onClick={handleClickComplaint}>
+        不认可车费,去投诉
+      </View>
 
       {/* 评价弹窗 */}
       <EvaluatePopup
         show={showEvaluatePopup}
         orderId={orderId}
         orderRating={1}
-        submitMode="complaint"
+        submitMode="orderComplaint"
         hideFeedbackType
         disableRatingSwitch
+        isFareFeedback
         onClose={() => setShowEvaluatePopup(false)}
         onConfirm={() => {
+          setDisabledComplaint(true);
           setShowEvaluatePopup(false);
         }}
       />
diff --git a/src/robotaxi/pages/feedback/hooks/use-media-upload.ts b/src/robotaxi/pages/feedback/hooks/use-media-upload.ts
index afbba91..d5e82ae 100644
--- a/src/robotaxi/pages/feedback/hooks/use-media-upload.ts
+++ b/src/robotaxi/pages/feedback/hooks/use-media-upload.ts
@@ -1,5 +1,6 @@
 import Taro from '@tarojs/taro';
 import { useCallback, useMemo, useState } from 'react';
+import { showToast } from '../../../components/toast';
 import { getStorage } from '../../../../utils/tools';
 
 /**
@@ -162,7 +163,7 @@
         ...item,
         status: 'failed'
       }));
-      Taro.showToast({
+      showToast({
         title: error?.message || '上传失败,请重试',
         icon: 'none'
       });
@@ -186,7 +187,7 @@
   const chooseAndUpload = useCallback(async () => {
     const remainCount = Math.max(0, options.maxCount - items.length);
     if (remainCount <= 0) {
-      Taro.showToast({
+      showToast({
         title: `最多可上传${options.maxCount}个图片或视频`,
         icon: 'none'
       });
@@ -207,7 +208,7 @@
       for (const file of files) {
         const mediaType: MediaType = file.fileType === 'video' ? 'video' : 'image';
         if (mediaType === 'video' && Number(file.size || 0) > videoSizeLimitBytes) {
-          Taro.showToast({
+          showToast({
             title: '视频不能超过20MB',
             icon: 'none'
           });
diff --git a/src/robotaxi/pages/feedback/index.tsx b/src/robotaxi/pages/feedback/index.tsx
index 6bc49b8..2cdda2f 100644
--- a/src/robotaxi/pages/feedback/index.tsx
+++ b/src/robotaxi/pages/feedback/index.tsx
@@ -3,6 +3,8 @@
 import { useEffect, useMemo, useRef, useState } from 'react';
 import CustomNavbar from '../../../components/custom-navbar';
 import { withRobotaxiWhitelist } from '../../../utils/whitelist';
+import { showToast, hideToast } from '../../components/toast';
+import { withToast } from '../../components/toast/with-toast';
 import { getFeedbackTypeList, submitComplaint } from '../../api/feedback';
 import { _ROBOTAXI_HOST } from '../../../constants/api';
 import useMediaUpload from './hooks/use-media-upload';
@@ -149,7 +151,7 @@
     }
 
     isSubmittingRef.current = true;
-    Taro.showLoading({ title: '提交中...', mask: true });
+    showToast({ title: '提交中...', icon: 'loading', mask: true });
     try {
       const networkTypeRes = await Taro.getNetworkType();
       if (networkTypeRes.networkType === 'none') {
@@ -191,7 +193,7 @@
       showFeedbackToast(errorTitle);
     } finally {
       isSubmittingRef.current = false;
-      Taro.hideLoading();
+      hideToast();
     }
   };
 
@@ -447,4 +449,4 @@
   );
 }
 
-export default withRobotaxiWhitelist(FeedbackPage);
+export default withRobotaxiWhitelist(withToast(FeedbackPage));
diff --git a/src/robotaxi/pages/order/detail/index.tsx b/src/robotaxi/pages/order/detail/index.tsx
index 80c4c18..7d03386 100644
--- a/src/robotaxi/pages/order/detail/index.tsx
+++ b/src/robotaxi/pages/order/detail/index.tsx
@@ -149,10 +149,10 @@
         id: 1,
         longitude: displayStartPoint.longitude,
         latitude: displayStartPoint.latitude,
-        width: 34,
-        height: 46,
+        width: 36,
+        height: 56,
         anchor: { x: 0.7, y: 0.8 },
-        iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/start-site-order-20260804-v2.png',
+        iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/start-site-order-20260812.png',
         viewportLongitude: startPoint?.longitude,
         viewportLatitude: startPoint?.latitude,
       } : null,
@@ -160,10 +160,10 @@
         id: 2,
         longitude: endPoint.longitude,
         latitude: endPoint.latitude,
-        width: 34,
-        height: 46,
+        width: 36,
+        height: 56,
         anchor: { x: 0.7, y: 0.8 },
-        iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-order-20260804-v2.png',
+        iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-order-20260812.png',
       } : null
     ].filter(Boolean) as any[];
     setMarkers(markerList);
diff --git a/src/robotaxi/pages/payment-setting-detail/index.tsx b/src/robotaxi/pages/payment-setting-detail/index.tsx
index efe5863..6ba7b67 100644
--- a/src/robotaxi/pages/payment-setting-detail/index.tsx
+++ b/src/robotaxi/pages/payment-setting-detail/index.tsx
@@ -41,7 +41,7 @@
     try {
       const activeOrder = await getOrderActive();
       if (activeOrder) {
-        Taro.showToast({
+        showToast({
           title: '有进行中的订单,暂无法关闭服务',
           icon: 'none'
         });
@@ -49,7 +49,7 @@
       }
       return true;
     } catch (error) {
-      Taro.showToast({
+      showToast({
         title: '查询订单状态失败,请稍后重试',
         icon: 'none'
       });
diff --git a/src/robotaxi/pages/personal-center/index.tsx b/src/robotaxi/pages/personal-center/index.tsx
index 8ab905d..557a4aa 100644
--- a/src/robotaxi/pages/personal-center/index.tsx
+++ b/src/robotaxi/pages/personal-center/index.tsx
@@ -145,7 +145,7 @@
       return;
     }
 
-    Taro.showToast({ title: "功能建设中", icon: "none" });
+    showToast({ title: "功能建设中", icon: "none" });
   };
 
   return (
diff --git a/src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx b/src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx
index 842aed2..2e19c82 100644
--- a/src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx
+++ b/src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx
@@ -97,13 +97,13 @@
             </View>
           </View>
         )}
-        {/* 地图浮层入口 - 选点页不展示 */}
-        {deps.nearbyLoading && deps.isAndroid && (
-          <Image className="waiting-radar-img" src={deps.waitingRadarSrc} />
-        )}
         {/* 指针 */}
         <View className="pointer-box">
-          {deps.nearbyLoading && !deps.isAndroid && (
+          {/* 雷达只在真正命中吸附、指针跳动到具体站点时才展示(由 bounce 驱动),与首页保持一致 */}
+          {deps.bounce && deps.isAndroid && (
+            <Image className="waiting-radar-img" src={deps.waitingRadarSrc} />
+          )}
+          {deps.bounce && !deps.isAndroid && (
             <Image className="waiting-radar" src={deps.waitingRadarSrc} />
           )}
           {((deps.showToolTip && shouldShowSelectedSiteEcho) || showRangeWarning) && (
diff --git a/src/robotaxi/pages/select-map-site/hooks/use-select-map-site.ts b/src/robotaxi/pages/select-map-site/hooks/use-select-map-site.ts
index b61cb19..a5982f9 100644
--- a/src/robotaxi/pages/select-map-site/hooks/use-select-map-site.ts
+++ b/src/robotaxi/pages/select-map-site/hooks/use-select-map-site.ts
@@ -59,6 +59,8 @@
 const AUTO_SCALE_MAX = 17; // 自动缩放最大等级
 const AUTO_SCALE_MIN = 11; // 自动缩放最小等级
 const INCLUDE_POINTS_PADDING = [100, 100, 100, 100] as [number, number, number, number]; // 地图回视图内边距
+/** 指针跳动到附近站点的动效总时长:指针跳动(600ms)+ 雷达扫描确认(320ms),需与 index.less 中对应动画时长保持一致,与首页保持一致。 */
+const POINTER_BOUNCE_SEQUENCE_DURATION_MS = 920;
 const CROSS_OPERATING_AREA_FILL_COLOR = '#0000001a'; // 非可选运营区围栏填充色
 const CROSS_OPERATING_AREA_STROKE_COLOR = '#DFB369'; // 非可选运营区围栏描边色
 const CROSS_OPERATING_AREA_STROKE_WIDTH = 1.5; // 非可选运营区围栏描边宽度
@@ -517,7 +519,6 @@
       return;
     }
     if (type !== 'end') return;
-    state.setBounce(true);
 
     const mapCtx = Taro.createMapContext('select-site-map');
     // 获取真实的缩放等级并通过回调传递出去,彻底阻断与 mapScale 状态的绑定,避免触发原生组件抖动
@@ -543,6 +544,8 @@
           const selectableMarkers = getSelectableMarkers(newMarkers, sites);
           const { targetMarker, minDistance } = findNearestMarker(lat, lng, selectableMarkers);
           if (targetMarker && minDistance < state.distanceConfig.adsorptionDistance) {
+            // 命中吸附:指针跳动 + 雷达确认动效(与地图硬切同一时刻触发),920ms 播完后才展示 tooltip,与首页保持一致
+            state.setBounce(true);
             mapCtx.moveToLocation({
               latitude: targetMarker.latitude,
               longitude: targetMarker.longitude,
@@ -552,8 +555,13 @@
             state.setLatitude(activeSite?.latitude || targetMarker.latitude);
             state.setLongitude(activeSite?.longitude || targetMarker.longitude);
             syncPinLocation(activeSite?.latitude || targetMarker.latitude, activeSite?.longitude || targetMarker.longitude);
-            state.setShowToolTip(true);
+            setTimeout(() => {
+              if (currentRegionSeq !== state.regionSeqRef.current) return;
+              state.setBounce(false);
+              state.setShowToolTip(true);
+            }, POINTER_BOUNCE_SEQUENCE_DURATION_MS);
           } else {
+            // 未命中吸附:不播放跳动/雷达动效,立即展示提示,避免无意义等待
             resetStationSelection();
             state.setSelectedSite({});
             state.setRangeStatus(
@@ -574,10 +582,6 @@
         }
       });
     }
-    setTimeout(() => {
-      state.setBounce(false);
-      state.setShowToolTip(true);
-    }, 600);
   };
 
   /**
@@ -591,6 +595,7 @@
     const targetSite = findSiteByMarker(targetMarker, state.nearbySites) || targetMarker;
     state.pickupConfirmTypeRef.current = 'click';
     state.setShowToolTip(false);
+    // 点击 marker 是明确的跳转动作,指针跳动 + 雷达确认动效播完(920ms)后才展示 tooltip,与首页保持一致
     state.setBounce(true);
     state.setLatitude(targetMarker.latitude);
     state.setLongitude(targetMarker.longitude);
@@ -610,7 +615,7 @@
       state.setLongitude(activeSite?.longitude || targetMarker.longitude);
       syncPinLocation(activeSite?.latitude || targetMarker.latitude, activeSite?.longitude || targetMarker.longitude);
       state.setShowToolTip(true);
-    }, 600);
+    }, POINTER_BOUNCE_SEQUENCE_DURATION_MS);
   };
 
   /**
diff --git a/src/robotaxi/pages/select-map-site/index.less b/src/robotaxi/pages/select-map-site/index.less
index 1f0de37..08fa69f 100644
--- a/src/robotaxi/pages/select-map-site/index.less
+++ b/src/robotaxi/pages/select-map-site/index.less
@@ -126,6 +126,8 @@
     .toolTip {
         position: relative;
         z-index: 10;
+        animation: tooltip-fade-in 240ms ease-out;
+        will-change: transform, opacity;
 
         .toolTip-bg {
             position: relative;
@@ -215,6 +217,8 @@
         width: 64px;
         height: 86px;
         margin-top: -54px;
+        opacity: 1;
+        transition: opacity 240ms ease-out;
 
         &.bounce {
             animation: pointer-bounce 600ms ease-in-out;
@@ -224,28 +228,32 @@
           opacity: 0;
         }
     }
-}
 
-.waiting-radar {
-    position: fixed;
-    width: 700px;
-    height: 350px;
-    top: 48%;
-    left: 50%;
-    transform: translate(-50%, -48%);
-    pointer-events: none;
-    z-index: 1;
-}
+    // 雷达以站点位置(.pointer-box 底边,即 translate(-50%,-100%) 后与地图中心点对齐的边)为圆心展开,
+    // 用绝对定位直接锚定该边,不受内部 flex 子元素排列影响,与首页保持一致
+    .waiting-radar {
+        position: absolute;
+        width: 700px;
+        height: 350px;
+        bottom: 0;
+        left: 50%;
+        transform: translate(-50%, 50%);
+        pointer-events: none;
+        z-index: 1;
+        animation: radar-fade-in 200ms ease-out;
+    }
 
-.waiting-radar-img {
-    position: fixed;
-    width: 700px;
-    height: 350px;
-    top: 48%;
-    left: 50%;
-    transform: translate(-50%, -48%);
-    pointer-events: none;
-    z-index: 1;
+    .waiting-radar-img {
+        position: absolute;
+        width: 700px;
+        height: 350px;
+        bottom: 0;
+        left: 50%;
+        transform: translate(-50%, 50%);
+        pointer-events: none;
+        z-index: 1;
+        animation: radar-fade-in 200ms ease-out;
+    }
 }
 
 @keyframes pointer-bounce {
@@ -266,6 +274,28 @@
     }
 }
 
+@keyframes tooltip-fade-in {
+    0% {
+        opacity: 0;
+        transform: translateY(6px);
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+@keyframes radar-fade-in {
+    0% {
+        opacity: 0;
+    }
+
+    100% {
+        opacity: 1;
+    }
+}
+
 
 .home-container {
     display: flex;
diff --git a/src/robotaxi/pages/share-trip/components/share-trip-view.tsx b/src/robotaxi/pages/share-trip/components/share-trip-view.tsx
index edd8155..a748c37 100644
--- a/src/robotaxi/pages/share-trip/components/share-trip-view.tsx
+++ b/src/robotaxi/pages/share-trip/components/share-trip-view.tsx
@@ -2,6 +2,7 @@
 import { safeParse } from '../../../../utils/Robotaxi-public-method';
 import { formatRemainingDurationDisplay } from '../../../utils/format';
 import { TripStatus } from '../../../constants/trip-status';
+import SiteRouteCard from '../../../components/site-route-card';
 
 type Props = {
   isExpired: boolean;
@@ -61,6 +62,9 @@
   if (isExpired) {
     return (
       <View className='share-trip-expired'>
+        <View onClick={goHome} className='select-city'>
+          <Image className='select-city-icon' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/back-home.png' />
+        </View>
         <View className='expired-content'>
           <Image className='expired-img' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/share-contact.png'  />
           <View className='expired-text'>行程结束已超过24小时</View>
@@ -164,32 +168,25 @@
                   </View>
                 </View>
                 {/* 站点信息卡:参考 cancel-site-box 布局与背景卡片样式 */}
-                <View className='share-site-box'>
-                  <View className='share-site-box-icon'>
-                    <View className='share-site-box-icon-start'></View>
-                    <Image src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png' />
-                    <View className='share-site-box-icon-end'></View>
-                  </View>
-                  <View className='share-site-box-content'>
-                    <View className='share-site-row'>
-                      <View className={`share-site-name ${isStartSiteGray ? 'gray-text' : ''}`}>
-                        {tripData?.startPointName}
-                      </View>
-                      {showBoardingTime && (
+                <SiteRouteCard
+                  className='share-site-box'
+                  sites={[
+                    {
+                      name: tripData?.startPointName,
+                      nameClassName: isStartSiteGray ? 'gray-text' : '',
+                      extra: showBoardingTime && (
                         <View className='share-site-time'>{tripData.boardingTime} 上车</View>
-                      )}
-                    </View>
-                    <View className='share-site-box-divider'></View>
-                    <View className='share-site-row'>
-                      <View className={`share-site-name ${isEndSiteGray ? 'gray-text' : ''}`}>
-                        {tripData?.endPointName}
-                      </View>
-                      {showGetoffTime && (
+                      )
+                    },
+                    {
+                      name: tripData?.endPointName,
+                      nameClassName: isEndSiteGray ? 'gray-text' : '',
+                      extra: showGetoffTime && (
                         <View className='share-site-time'>{tripData.getoffTime} 到达</View>
-                      )}
-                    </View>
-                  </View>
-                </View>
+                      )
+                    }
+                  ]}
+                />
               </View>
             </View>
           </View>
diff --git a/src/robotaxi/pages/share-trip/hooks/use-map-markers.ts b/src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
index df1dc1e..fe6c871 100644
--- a/src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
+++ b/src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
@@ -4,7 +4,7 @@
 
 // 接驾态(前往上车点)/到达终点态 上车点三角图标(与 begin-route 对齐)
 const PICKUP_APPROACHING_ICON_URL =
-    'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-20260722.png';
+    'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-20260812.png';
 // 送驾态(前往目的地)/到达终点态 下车点三角图标(与 begin-route 对齐)
 const DROPOFF_INSERVICE_ICON_URL =
     'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/down-triangle-20260722.png';
diff --git a/src/robotaxi/pages/share-trip/hooks/use-view-delegates.tsx b/src/robotaxi/pages/share-trip/hooks/use-view-delegates.tsx
index 6788a41..be67ceb 100644
--- a/src/robotaxi/pages/share-trip/hooks/use-view-delegates.tsx
+++ b/src/robotaxi/pages/share-trip/hooks/use-view-delegates.tsx
@@ -48,9 +48,7 @@
       <View className='car-time' style={{ alignItems: 'baseline', paddingBottom: 0 }}>
         <View className='car-time-num'>{routeDistance?.value ?? 0}</View>
         <View style={{ flexShrink: 0 }}>
-          {isPickup
-            ? (routeDistance?.unit === 'm' ? 'M' : 'KM')
-            : (routeDistance?.unit === 'm' ? '米' : '公里')}
+          {routeDistance?.unit === 'm' ? '米' : '公里'}
         </View>
         {!isPickup && (
           <>
@@ -151,7 +149,7 @@
           >
             <CoverView className='car-time'>
               <CoverView className='car-time-num'>{deps.routeDistance?.value ?? 0}</CoverView>
-              <CoverView>{deps.routeDistance?.unit === 'm' ? 'M' : 'KM'}</CoverView>
+              <CoverView>{deps.routeDistance?.unit === 'm' ? '米' : '公里'}</CoverView>
             </CoverView>
           </CoverView>
         )}
@@ -162,7 +160,7 @@
                 className='site-status-car-icon'
                 src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/golden-car-icon-20260708.png'
               />
-              <CoverView className='site-status-time'>限停{deps.startSite?.time}MIN</CoverView>
+              <CoverView className='site-status-time'>限停{deps.startSite?.time}分钟</CoverView>
             </CoverView>
             <CoverImage
               className='callout-diamond-icon'
@@ -179,7 +177,7 @@
         {(item.id === 2 && [TripStatus.InService, TripStatus.ArrivedDestination, TripStatus.TripFinished, TripStatus.CancelOnlyPickup].includes(deps.tripStatus) && !item.hideNameCallout) && (
           <CoverImage
             className='site-status-end-img'
-            src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-20260722.png'
+            src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-20260812.png'
           />
         )}
           </>
diff --git a/src/robotaxi/pages/share-trip/index.less b/src/robotaxi/pages/share-trip/index.less
index c04d97d..d3d3d62 100644
--- a/src/robotaxi/pages/share-trip/index.less
+++ b/src/robotaxi/pages/share-trip/index.less
@@ -227,7 +227,7 @@
   .calling-car-vehicle-pic {
     position: absolute;
     top: -4px;
-    right: 116px;
+    right: 80px;
     width: 342px;
     height: 294px;
     z-index: 10;
@@ -352,77 +352,26 @@
     }
   }
 
-  // 站点信息卡:参考 cancel-site-box 的布局与背景卡片样式,独立于车辆信息卡
+  // 站点信息卡:参考 cancel-site-box 的布局与背景卡片样式,独立于车辆信息卡;
+  // 结构已抽离为 SiteRouteCard 公共组件,此处只保留容器级样式与业务特有的置灰/时间文案样式
   .share-site-box {
     margin-top: 32px;
-    display: flex;
-    gap: 16px;
     border-radius: 40px;
     background: rgba(204, 208, 215, 0.25);
     padding: 48px 32px 42px 32px;
 
-    .share-site-box-icon {
-      padding: 16px 0;
-      flex-shrink: 0;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-
-      .share-site-box-icon-start {
-        width: 16px;
-        height: 16px;
-        background: @gold-light;
-        border-radius: 50%;
-      }
-
-      .share-site-box-icon-end {
-        width: 16px;
-        height: 16px;
-        background: @black-text-main;
-        border-radius: 50%;
-      }
-
-      image {
-        flex: 1;
-        width: 2px;
-        margin: 7px 0;
-        height: fit-content;
-      }
+    .site-route-card-item-text.gray-text {
+      color: @black-text-04;
     }
 
-    .share-site-box-content {
-      flex: 1;
-
-      .share-site-row {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-      }
-
-      .share-site-name {
-        font-size: 32px;
-        line-height: 48px;
-        color: @black-text-main;
-
-        &.gray-text {
-          color: @black-text-04;
-        }
-      }
-
-      .share-site-time {
-        flex-shrink: 0;
-        min-width: 120px;
-        margin-left: 12px;
-        text-align: right;
-        font-size: 24px;
-        line-height: 40px;
-        color: @black-text-04;
-      }
-
-      .share-site-box-divider {
-        margin: 34px 32px 40px 0;
-        border-top: 1px solid rgba(204, 208, 215, 0.4);
-      }
+    .share-site-time {
+      flex-shrink: 0;
+      min-width: 120px;
+      margin-left: 12px;
+      text-align: right;
+      font-size: 24px;
+      line-height: 40px;
+      color: @black-text-04;
     }
   }
 }
diff --git a/src/robotaxi/style/ui-variables.less b/src/robotaxi/style/ui-variables.less
index 4cac50d..82efe45 100644
--- a/src/robotaxi/style/ui-variables.less
+++ b/src/robotaxi/style/ui-variables.less
@@ -20,6 +20,7 @@
 
 @gold-text: #A68042;  // 文字金
 @gold-light: #DFB369; // 主题金
+@dark-gold-text: #8F754A; // 黑金
 @black-text-main: #141E27; // 重要文字
 @black-text-04: rgba(20, 30, 39, 0.4);  // 次要文字
 @gary-theme: #F1F5FA; // 浅色面板
diff --git a/src/robotaxi/utils/platform-env.ts b/src/robotaxi/utils/platform-env.ts
new file mode 100644
index 0000000..27ee7aa
--- /dev/null
+++ b/src/robotaxi/utils/platform-env.ts
@@ -0,0 +1,67 @@
+import Taro from '@tarojs/taro';
+
+/**
+ * 鸿蒙环境探测。
+ *
+ * 背景:地图 includePoints 的 padding 数值在鸿蒙小程序容器下与安卓/iOS 的像素解释基准不一致
+ * (鸿蒙上偏小,导致同样的 padding 视觉留白不足,出现路线端点被推出可视区域的问题),
+ * 需要对鸿蒙环境的 padding 做补偿放大。
+ */
+
+let cachedIsHarmony: boolean | null = null;
+
+/** 判断当前是否运行在鸿蒙环境(HarmonyOS / HarmonyOS NEXT 元服务容器)。结果按次进程缓存。 */
+export function isHarmonyPlatform(): boolean {
+  if (cachedIsHarmony !== null) {
+    return cachedIsHarmony;
+  }
+  let result = false;
+  try {
+    const info: any = Taro.getSystemInfoSync();
+    // 不同宿主/基础库版本下,鸿蒙标识可能出现在 platform / system / host.env / appName 等不同字段,
+    // 这里做多字段文本匹配兜底判断。
+    const candidates = [
+      info?.platform,
+      info?.system,
+      info?.host?.env,
+      info?.hostName,
+      info?.appName,
+      info?.brand,
+      info?.model,
+    ]
+      .filter(Boolean)
+      .map((v) => String(v).toLowerCase());
+    result = candidates.some((v) => v.includes('harmony') || v.includes('ohos'));
+  } catch (error) {
+    result = false;
+  }
+  cachedIsHarmony = result;
+  return result;
+}
+
+/** 鸿蒙地图 padding 补偿系数:仅在 isHarmonyPlatform() 为 true 时生效。 */
+export const HARMONY_PADDING_COMPENSATION_RATIO_TOP = 3;
+export const HARMONY_PADDING_COMPENSATION_RATIO_BOTTOM = 2.5;
+export const HARMONY_PADDING_COMPENSATION_RATIO_SIDE = 3;
+
+
+/** 按平台对 padding 数值做补偿:鸿蒙环境乘补偿系数,其它平台原样返回。 */
+export function applyHarmonyPaddingCompensation(value: number, direction: 'top' | 'bottom' | 'side'): number {
+  if (!Number.isFinite(value)) {
+    return value;
+  }
+  if (!isHarmonyPlatform()) {
+    return value;
+  }
+  switch (direction) {
+    case 'top':
+      return Math.round(value * HARMONY_PADDING_COMPENSATION_RATIO_TOP);
+    case 'bottom':
+      return Math.round(value * HARMONY_PADDING_COMPENSATION_RATIO_BOTTOM);
+    case 'side':
+      return Math.round(value * HARMONY_PADDING_COMPENSATION_RATIO_SIDE);
+    default:
+      return Math.round(value * HARMONY_PADDING_COMPENSATION_RATIO_SIDE);
+  }
+}
+
diff --git a/src/robotaxi/utils/user-location.ts b/src/robotaxi/utils/user-location.ts
new file mode 100644
index 0000000..0d753d0
--- /dev/null
+++ b/src/robotaxi/utils/user-location.ts
@@ -0,0 +1,79 @@
+import Taro from '@tarojs/taro';
+import { getLocationByIP } from '../../utils/Robotaxi-public-method';
+
+/** 定位来源:gps 精确定位 / ip 粗定位 / fallback 兜底坐标。 */
+export type UserLocationSource = 'gps' | 'ip' | 'fallback';
+
+/** 统一的用户定位结果。 */
+export interface UserLocation {
+  latitude: number;
+  longitude: number;
+  /** 本次坐标的来源,便于上层按精度差异做不同处理。 */
+  source: UserLocationSource;
+  /** 仅 ip 来源可能带回的城市名。 */
+  city?: string;
+  /** 仅 ip 来源可能带回的行政区编码。 */
+  adcode?: string;
+}
+
+/** 默认兜底坐标(北京),与项目其它地方保持一致。 */
+export const DEFAULT_FALLBACK_LOCATION: UserLocation = {
+  latitude: 39.916527,
+  longitude: 116.397128,
+  source: 'fallback',
+};
+
+const isValidCoordinate = (value: unknown): value is number =>
+  typeof value === 'number' && Number.isFinite(value);
+
+/**
+ * 获取用户定位(公共函数)。
+ *
+ * 优先级:
+ *   1. Taro.getLocation 精确 GPS 定位(source: 'gps')
+ *   2. 失败退回 getLocationByIP 的 IP 粗定位(source: 'ip')
+ *   3. 再失败退回默认兜底坐标(source: 'fallback')
+ *
+ * 该函数**永不 reject**,始终 resolve 出一个可用坐标,调用方可据 `source` 判断精度。
+ *
+ * @param options.highAccuracy 是否开启高精度 GPS(默认 false)。
+ */
+export const getUserLocation = async (
+  options?: { highAccuracy?: boolean }
+): Promise<UserLocation> => {
+  // 1) GPS 精确定位
+  try {
+    const res: any = await Taro.getLocation({
+      type: 'gcj02',
+      isHighAccuracy: options?.highAccuracy ?? false,
+    });
+    if (isValidCoordinate(res?.latitude) && isValidCoordinate(res?.longitude)) {
+      return {
+        latitude: res.latitude,
+        longitude: res.longitude,
+        source: 'gps',
+      };
+    }
+  } catch (error) {
+    // 忽略,进入 IP 兜底
+  }
+
+  // 2) IP 粗定位
+  try {
+    const res: any = await getLocationByIP();
+    if (isValidCoordinate(res?.latitude) && isValidCoordinate(res?.longitude)) {
+      return {
+        latitude: res.latitude,
+        longitude: res.longitude,
+        source: 'ip',
+        city: res.city || '',
+        adcode: res.adcode || '',
+      };
+    }
+  } catch (error) {
+    // 忽略,进入默认兜底
+  }
+
+  // 3) 默认兜底
+  return { ...DEFAULT_FALLBACK_LOCATION };
+};
diff --git a/src/robotaxi/utils/video-preload.ts b/src/robotaxi/utils/video-preload.ts
new file mode 100644
index 0000000..d1e134b
--- /dev/null
+++ b/src/robotaxi/utils/video-preload.ts
@@ -0,0 +1,69 @@
+/**
+ * 视频预下载缓存:用 wx.downloadFile 提前把转场 mp4 落地为本地临时文件,
+ * 转场时改用本地路径作 <video src>,消除首帧网络缓冲导致的黑帧。
+ *
+ * ## 背景
+ * wx.preloadAssets 仅支持 image / font,**不支持视频**。派单转场 mp4 直接用 CDN URL 作
+ * <video src> 时,首帧需先经网络请求 + 缓冲,「加载完成 → 首帧绘制」间会露出约一帧黑屏。
+ * 方案:进入下单流程(发布中/呼叫中)即 downloadFile 把 mp4 下到本地临时路径,转场时用本地
+ * 路径播放——本地文件无网络缓冲,首帧秒出。
+ *
+ * ## 使用
+ * - preloadVideo(url):幂等发起下载,成功后缓存 `CDN URL → 本地 tempFilePath`。
+ * - getPreloadedVideoSrc(url):取本地路径;未就绪(下载中/失败/非 weapp)则回退原 CDN URL,不阻塞。
+ *
+ * 参考:https://developers.weixin.qq.com/miniprogram/dev/api/network/download/wx.downloadFile.html
+ */
+
+/** 判断当前是否为微信小程序环境。 */
+const isWeapp = (): boolean => process.env.TARO_ENV === 'weapp';
+
+/** CDN URL → 本地临时文件路径(下载成功后写入)。 */
+const localPathCache: Record<string, string> = {};
+
+/** 已发起下载的 URL(下载中/已完成均登记,避免重复请求;失败时移除以允许重试)。 */
+const pendingSet = new Set<string>();
+
+/**
+ * 幂等预下载视频。已缓存或正在下载则直接返回;非 weapp / 低版本无 downloadFile 时静默跳过。
+ * 下载失败不报错、不缓存,转场时自动回退 CDN URL。
+ *
+ * @param url 视频 CDN 地址
+ */
+export const preloadVideo = (url: string): void => {
+  try {
+    if (!isWeapp() || !url) return;
+    if (localPathCache[url] || pendingSet.has(url)) return;
+    // @ts-ignore 微信小程序全局 API,低版本走 typeof 兜底跳过
+    if (typeof wx === 'undefined' || typeof wx.downloadFile !== 'function') return;
+
+    pendingSet.add(url);
+    // @ts-ignore
+    wx.downloadFile({
+      url,
+      success: (res: any) => {
+        if (res && res.statusCode === 200 && res.tempFilePath) {
+          localPathCache[url] = res.tempFilePath;
+        } else {
+          // 非 200:不缓存,允许后续重试
+          pendingSet.delete(url);
+        }
+      },
+      fail: () => {
+        // 下载失败:不缓存、允许后续重试,转场时回退 CDN URL(不影响功能)
+        pendingSet.delete(url);
+      },
+    });
+  } catch (e) {
+    console.error('[preloadVideo] 视频预下载异常:', e);
+    // 兜底:绝不抛出
+  }
+};
+
+/**
+ * 取预下载好的本地视频路径;未就绪则回退原 CDN URL。
+ *
+ * @param url 视频 CDN 地址
+ * @returns 本地临时路径(已就绪)或原 CDN URL
+ */
+export const getPreloadedVideoSrc = (url: string): string => localPathCache[url] || url;
diff --git a/src/utils/preload-assets.ts b/src/utils/preload-assets.ts
new file mode 100644
index 0000000..fbf8a99
--- /dev/null
+++ b/src/utils/preload-assets.ts
@@ -0,0 +1,54 @@
+/**
+ * 通用预加载素材描述。
+ *
+ * 仅支持 `image` 与 `font`——微信 `wx.preloadAssets` 目前只支持这两类,
+ * **不支持视频**(视频无法通过该 API 预热,转场视频仅能预热其封面图)。
+ */
+export type PreloadAsset = { type: 'image' | 'font'; src: string };
+
+/** 判断当前是否为微信小程序环境。 */
+const isWeapp = (): boolean => process.env.TARO_ENV === 'weapp';
+
+/**
+ * 通用预加载方法:图片、字体统一走 `wx.preloadAssets` 为视图层预热媒体资源。
+ *
+ * ## 特性
+ * - 仅在微信小程序环境执行;非 weapp / 低版本(无 `wx.preloadAssets`)静默跳过。
+ * - image / font 合批调用一次 `wx.preloadAssets`,组件仍用同一 CDN URL,命中缓存即秒开。
+ * - **预加载失败不报错**:整体 try/catch + `fail` 空回调兜底。
+ *
+ * 参考:https://developers.weixin.qq.com/miniprogram/dev/api/base/performance/wx.preloadAssets.html
+ * (基础库 2.22.1+ 支持,功能描述:为视图层预加载媒体资源文件,目前支持 font、image)
+ *
+ * @param assets 待预加载素材列表
+ */
+export const preloadAssets = (assets: PreloadAsset[]): void => {
+  try {
+    if (!isWeapp() || !Array.isArray(assets) || assets.length === 0) {
+      return;
+    }
+    // @ts-ignore 微信基础库 2.22.1+ 能力,低版本走 typeof 兜底跳过
+    if (typeof wx === 'undefined' || typeof wx.preloadAssets !== 'function') {
+      return;
+    }
+
+    const data = assets
+      .filter((asset) => asset && asset.src && (asset.type === 'image' || asset.type === 'font'))
+      .map((asset) => ({ type: asset.type, src: asset.src }));
+
+    if (data.length === 0) {
+      return;
+    }
+
+    // @ts-ignore
+    wx.preloadAssets({
+      data,
+      fail: () => { /* 预加载失败不报错,静默忽略 */ },
+    });
+  } catch (e) {
+    console.error('[preloadAssets] 预加载素材异常:', e);
+    // 兜底:绝不抛出
+  }
+};
+
+export default preloadAssets;

--
Gitblit v1.7.1