From 13c960a2b6bb54797bdbd03aa3184af507d94a9b Mon Sep 17 00:00:00 2001
From: 卢春龙 <lucl@xiaopeng.com>
Date: 星期一, 20 四月 2026 10:42:28 +0800
Subject: [PATCH] Merge branch 'feature_pr_fix' into 'develop'

---
 src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts                          |   10 +
 src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts                              |    2 
 src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx  |    4 
 src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx |   12 +
 src/robotaxi/pages/site-search/index.tsx                                              |    5 
 src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx   |    3 
 src/robotaxi/pages/begin-route/components/evaluate-popup.tsx                          |   22 +
 src/robotaxi/pages/begin-route/components/release-route.tsx                           |    3 
 src/robotaxi/pages/begin-route/components/begin-route-view.tsx                        |    1 
 src/robotaxi/pages/begin-route/components/announcements-popup.tsx                     |    9 
 src/robotaxi/pages/begin-route/components/passenger-notice.tsx                        |   20 +-
 src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts   |   10 +
 src/robotaxi/pages/site-search/hooks/use-site-search.ts                               |  111 ++++++++++---
 src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx                           |    6 
 src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx  |   28 ++-
 src/robotaxi/pages/begin-route/components/over-route.tsx                              |   11 
 src/robotaxi/pages/begin-route/index.tsx                                              |   72 ++++----
 src/robotaxi/pages/begin-route/components/release-be-being.tsx                        |    5 
 src/robotaxi/pages/begin-route/components/calling-car.tsx                             |    2 
 src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx     |   11 
 src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts                              |   70 ++++++++
 src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx    |    7 
 src/robotaxi/pages/begin-route/components/cancel-popup.tsx                            |    7 
 src/robotaxi/pages/begin-route/hooks/use-map-markers.ts                               |   13 +
 src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts                         |    8 
 25 files changed, 331 insertions(+), 121 deletions(-)

diff --git a/src/robotaxi/pages/begin-route/components/announcements-popup.tsx b/src/robotaxi/pages/begin-route/components/announcements-popup.tsx
index 0f40b8d..c4a87ca 100644
--- a/src/robotaxi/pages/begin-route/components/announcements-popup.tsx
+++ b/src/robotaxi/pages/begin-route/components/announcements-popup.tsx
@@ -9,14 +9,15 @@
 }
 
 export default function AnnouncementsPopup({ show = false, onClose }: Props) {
-    const [visible, setVisible] = useState(false);
-    const [animationData, setAnimationData] = useState<any>();
-    const [doorImgError, setDoorImgError] = useState(false);
-    const [seatbeltImgError, setSeatbeltImgError] = useState(false);
+    const [visible, setVisible] = useState(false); // 弹窗显示状态
+    const [animationData, setAnimationData] = useState<any>(); // 弹窗开合动画
+    const [doorImgError, setDoorImgError] = useState(false); // 开门动画图加载失败状态
+    const [seatbeltImgError, setSeatbeltImgError] = useState(false); // 系安全带动画图加载失败状态
 
     const doorImage = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/door-anim.webp'
     const seatbeltImage = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/seatbelt-anim.webp'
 
+    // 控制弹窗开合动画并重置图片兜底状态
     useEffect(() => {
         if (show && !visible) {
             setVisible(true);
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 af2dd01..05765bb 100644
--- a/src/robotaxi/pages/begin-route/components/begin-route-view.tsx
+++ b/src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -115,6 +115,7 @@
     handleConfirmEvaluate
   } = props;
 
+  // 统一渲染地图与业务弹窗容器
   return (
     <View className='home-container'>
       <Map
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 5168982..0fcba6e 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
@@ -18,15 +18,17 @@
     handleOpenOverRoutePopup,
     handleOpenAnnouncementsPopup
 }: Props) {
-    const [trunkStatus, setTrunkStatus] = useState(orderTrunkStatus);
-    const [lockStatus, setLockStatus] = useState(false);
-    const trunkPollTimerRef = useRef<any>(null);
-    const hasShownAnnouncementsRef = useRef(false);
+    const [trunkStatus, setTrunkStatus] = useState(orderTrunkStatus); // 后备箱开关状态
+    const [lockStatus, setLockStatus] = useState(false); // 车门解锁状态
+    const trunkPollTimerRef = useRef<any>(null); // 后备箱状态轮询定时器
+    const hasShownAnnouncementsRef = useRef(false); // 安全须知是否已展示
 
+    // 外部后备箱状态变化时同步本地状态
     useEffect(() => {
         setTrunkStatus(orderTrunkStatus);
     }, [orderTrunkStatus]);
 
+    // 组件卸载时清理轮询定时器
     useEffect(() => {
         return () => {
             if (trunkPollTimerRef.current) {
@@ -35,6 +37,7 @@
         };
     }, []);
 
+    // 获取解锁请求所需定位参数,失败时回退到缓存上车点
     const getUnlockLocationPayload = async () => {
         try {
             Taro.showLoading({ title: '正在获取位置...', mask: true });
@@ -57,11 +60,17 @@
         return {};
     };
 
+    // 解锁车门/后备箱,后备箱场景会轮询确认结果
     const handleUnlock = async (doorType: 'door' | 'trunk', onSuccess?: () => void) => {
         const targetOrderId = orderId || Taro.getStorageSync('orderId');
         if (!targetOrderId) return;
         
         const locationPayload = await getUnlockLocationPayload();
+        const isCloseAction = doorType === 'trunk' ? !!trunkStatus : false;
+        const actionLoadingText = doorType === 'trunk'
+            ? (isCloseAction ? '正在关闭后备箱' : '正在开启后备箱')
+            : '正在解锁车门';
+        Taro.showLoading({ title: actionLoadingText, mask: true });
         unlockVehicle({
             orderId: targetOrderId,
             doorType,
@@ -69,16 +78,13 @@
         }).then((res: any) => {
             console.log('解锁结果:', res);
             
-            if (!res) {
-                Taro.showToast({ title: '解锁失败,请重试或联系客服', icon: 'none' });
+            if (res?.code !== 200 || !res?.data) {
+                Taro.hideLoading();
+                Taro.showToast({ title: res?.msg || '解锁失败,请重试或联系客服', icon: 'none' });
                 return;
             }
 
             if (doorType === 'trunk') {
-                const isCloseAction = !!trunkStatus;
-                const loadingText = isCloseAction ? '正在关闭后备箱' : '正在开启后备箱';
-                Taro.showLoading({ title: loadingText, mask: true });
-
                 let pollCount = 0;
                 const maxPolls = 5;
                 const pollInterval = 2000;
@@ -149,6 +155,7 @@
             }
             
             if (doorType === 'door') {
+                Taro.hideLoading();
                 Taro.showToast({ title: '右侧车门已解锁', icon: 'none' });
                 if (!hasShownAnnouncementsRef.current) {
                     handleOpenAnnouncementsPopup?.();
@@ -156,6 +163,7 @@
                 }
             }
         }).catch(() => {
+            Taro.hideLoading();
             Taro.showToast({ title: '解锁失败,请重试或联系客服', icon: 'none' });
         });
     };
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 a5d3779..f87a94f 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
@@ -24,10 +24,11 @@
     evaluateSubmitVersion,
     evaluateCloseVersion
 }: Props) {
-    const [activeIndex, setActiveIndex] = useState(0);
-    const [isEvaluated, setIsEvaluated] = useState(false);
-    const skipEvaluateResetRef = useRef(false);
+    const [activeIndex, setActiveIndex] = useState(0); // 当前选中的满意度
+    const [isEvaluated, setIsEvaluated] = useState(false); // 是否已完成评价
+    const skipEvaluateResetRef = useRef(false); // 跳过关闭重置的一次性标记
 
+    // 外部提交成功后同步评价状态
     useEffect(() => {
         if (typeof evaluateSubmitVersion === 'number' && evaluateSubmitVersion > 0) {
             skipEvaluateResetRef.current = true;
@@ -36,6 +37,7 @@
         }
     }, [evaluateSubmitVersion]);
 
+    // 关闭评价弹窗时重置高亮状态
     useEffect(() => {
         if (typeof evaluateCloseVersion === 'number' && evaluateCloseVersion > 0) {
             if (skipEvaluateResetRef.current) {
@@ -46,6 +48,7 @@
         }
     }, [evaluateCloseVersion]);
 
+    // 点击评价项:不满意走弹窗,其余直接提交
     const changeEvaluate = (index: number) => {
         if (isEvaluated) return;
         setActiveIndex(index);
@@ -95,4 +98,4 @@
             </View>
         </View>
     );
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx b/src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
index 80ebf0f..5625922 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
@@ -20,20 +20,23 @@
     onToggleDetailFade,
     handlePay
 }: Props) {
-    const [showDetail, setShowDetail] = useState(false);
+    const [showDetail, setShowDetail] = useState(false); // 费用明细展开状态
 
+    // 外部重置版本变化时收起明细
     useEffect(() => {
         if (typeof resetDetailVersion === 'number') {
             setShowDetail(false);
         }
     }, [resetDetailVersion]);
 
+    // 跳转费用说明页
     const toMoneyDetail = () => {
         Taro.navigateTo({
             url: `/robotaxi/pages/expense-description/index?orderId=${orderId}`
         });
     };
 
+    // 切换明细面板并同步地图淡化状态
     const handleToggleDetail = () => {
         const next = !showDetail;
         setShowDetail(next);
@@ -170,4 +173,4 @@
             }
         </View>
     );
-}
\ No newline at end of file
+}
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 13c6abd..3db4f97 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
@@ -9,13 +9,14 @@
 };
 
 export default function TemperatureControl({ tripStatus, orderId }: Props) {
-    const [tempStatus, setTempStatus] = useState<number | null>();
-    const tempTimerRef = useRef<any>(null);
-    const lastConfirmedTempRef = useRef<number>();
+    const [tempStatus, setTempStatus] = useState<number | null>(); // 当前显示温度
+    const tempTimerRef = useRef<any>(null); // 温度设置防抖定时器
+    const lastConfirmedTempRef = useRef<number>(); // 最近一次设置成功温度
 
     const TEMP_MIN = 22;
     const TEMP_MAX = 28;
 
+    // 状态 4 时拉取车辆当前温度
     useEffect(() => {
         if (tripStatus === 4) {
             const targetOrderId = orderId || Taro.getStorageSync('orderId');
@@ -31,6 +32,7 @@
         }
     }, [tripStatus, orderId]);
 
+    // 初始化确认温度并在卸载时清理定时器
     useEffect(() => {
         lastConfirmedTempRef.current = tempStatus as number;
         return () => {
@@ -40,6 +42,7 @@
         };
     }, []);
 
+    // 防抖提交目标温度到后端
     const scheduleTemperatureUpdate = (targetTemperature: number) => {
         if (tempTimerRef.current) {
             clearTimeout(tempTimerRef.current);
@@ -76,6 +79,7 @@
         }, 5000);
     };
 
+    // 处理温度增减并做边界校验
     const handleTempChange = (delta: number) => {
         setTempStatus((prev) => {
             if (typeof prev !== 'number') {
@@ -120,4 +124,4 @@
             </View>
         </View>
     );
-}
\ No newline at end of file
+}
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 2e95613..710c268 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
@@ -27,6 +27,7 @@
     limitMinutes,
     handleOpenCancelPopup
 }: Props) {
+    // 标题行文案映射
     const titleMap = {
         3: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>,
         4: <>车辆还有<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}到达</>,
@@ -39,6 +40,7 @@
         9: '支付成功'
     }
 
+    // 说明文案映射
     const descMap = {
         3: { text: '正全力为您叫车中,请耐心等待...', className: 'calling-car-content-desc' },
         4: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: isArriveSoon ? 'calling-car-content-desc-red' : 'calling-car-content-warning' } : null,
@@ -66,4 +68,4 @@
             }
         </View>
     );
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts b/src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
index eec4cd5..a5844c0 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
+++ b/src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
@@ -36,6 +36,7 @@
     const pickupTimerRef = useRef<any>(null);
     const finishTimerRef = useRef<any>(null);
     const countdownFinishedRef = useRef(false);
+    const pickupTripStatusRef = useRef(tripStatus);
     const pickupEndTimeRef = useRef<number | null>(null);
     const finishEndTimeRef = useRef<number | null>(null);
     const lastFareOrderIdRef = useRef<string | null>(null);
@@ -108,12 +109,21 @@
     }, [tripStatus]);
 
     useEffect(() => {
+        pickupTripStatusRef.current = tripStatus;
+    }, [tripStatus]);
+
+    useEffect(() => {
         if (tripStatus !== 5) {
             clearPickupTimer();
             return;
         }
 
         const runPickupTick = () => {
+            // 防止状态已切换后,旧定时回调仍触发取消逻辑
+            if (pickupTripStatusRef.current !== 5) {
+                clearPickupTimer();
+                return;
+            }
             const remaining = getRemainingSeconds(pickupEndTimeRef.current);
             if (remaining !== null && remaining <= 0) {
                 if (!countdownFinishedRef.current) {
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 b58e100..43407b4 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
@@ -12,6 +12,7 @@
 };
 
 export default function VehicleInfoCard({ vehicleInfo }: Props) {
+    // 展示车辆基础信息与车辆图片
     return (
         <View className="car-card-detail">
             <View>
@@ -31,4 +32,4 @@
             }
         </View>
     );
-}
\ No newline at end of file
+}
diff --git a/src/robotaxi/pages/begin-route/components/calling-car.tsx b/src/robotaxi/pages/begin-route/components/calling-car.tsx
index 9f37838..adaa499 100644
--- a/src/robotaxi/pages/begin-route/components/calling-car.tsx
+++ b/src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -76,6 +76,7 @@
     orderTrunkStatus,
 }: Props) {
 
+    // 汇总行程中的倒计时与费用数据
     const {
         waitMinutes,
         limitMinutes,
@@ -99,6 +100,7 @@
     /** 上车倒计时即将结束(<60秒) */
     const isPickupCountdownSoon = tripStatus === 5 && (typeof pickupCountdownSeconds === 'number' && pickupCountdownSeconds < 60);
 
+    // 按状态组合渲染行程卡片模块
     return (
         <View className='calling-car'>
             <Image className="calling-car-bg" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg.png' />
diff --git a/src/robotaxi/pages/begin-route/components/cancel-popup.tsx b/src/robotaxi/pages/begin-route/components/cancel-popup.tsx
index 0c03ed5..93387dd 100644
--- a/src/robotaxi/pages/begin-route/components/cancel-popup.tsx
+++ b/src/robotaxi/pages/begin-route/components/cancel-popup.tsx
@@ -22,8 +22,8 @@
 }
 
 export default function CancelPopup({ show = false, cancelType = 1, startSite, endSite, waitMinutes, tripStatus, routeDurationSeconds, discountAmount, onClose, onConfirm }: Props) {
-    const [visible, setVisible] = useState(false);
-    const [animationData, setAnimationData] = useState<any>();
+    const [visible, setVisible] = useState(false); // 弹窗显示状态
+    const [animationData, setAnimationData] = useState<any>(); // 弹窗开合动画
     const cachedStartSite = startSite || Taro.getStorageSync('startSite') || {};
     const cachedEndSite = endSite || Taro.getStorageSync('endSite') || {};
     const startSiteName = cachedStartSite?.name || cachedStartSite?.address || '-';
@@ -32,6 +32,7 @@
     const durationDisplay = formatDurationDisplay(routeDurationSeconds);
     const discountAmountValue = typeof discountAmount === 'number' && !Number.isNaN(discountAmount) ? discountAmount : 0;
 
+    // 不同取消阶段对应的文案配置
     const STATUS_MAP = {
         1: {
             title: `预计${waitMinutesValue}分钟内有车,确定取消吗?`,
@@ -61,6 +62,7 @@
         },
     }
 
+    // 控制弹窗开合动画
     useEffect(() => {
         if (show && !visible) {
             setVisible(true);
@@ -84,6 +86,7 @@
         }
     }, [show]);
 
+    // 关闭弹窗时按类型执行对应流程
     const closePopup = () => {
         if (cancelType == 5) {
             onConfirm?.();
diff --git a/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx b/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
index c277d6d..98a96ab 100644
--- a/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
+++ b/src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
@@ -15,18 +15,19 @@
 }
 
 export default function EvaluatePopup({ show = false, onClose, onConfirm, orderId, orderRating, submitMode = 'comment' }: Props) {
-    const [visible, setVisible] = useState(false);
-    const [animationData, setAnimationData] = useState<any>();
-    const [selectedTypeId, setSelectedTypeId] = useState<number | null>(null);
-    const [feedbackContent, setFeedbackContent] = useState('');
-    const [typeOptions, setTypeOptions] = useState<any[]>([]);
-    const [isSubmitting, setIsSubmitting] = useState(false);
+    const [visible, setVisible] = useState(false); // 弹窗显示状态
+    const [animationData, setAnimationData] = useState<any>(); // 弹窗进出场动画
+    const [selectedTypeId, setSelectedTypeId] = useState<number | null>(null); // 当前选择的问题类型
+    const [feedbackContent, setFeedbackContent] = useState(''); // 反馈文本内容
+    const [typeOptions, setTypeOptions] = useState<any[]>([]); // 问题类型选项
+    const [isSubmitting, setIsSubmitting] = useState(false); // 提交中状态
     const [pictures, setPictures] = useState<Array<{
         localUrl: string;
         server?: { id: string; name: string; url: string } | null;
         status: 'uploading' | 'success' | 'failed';
-    }>>([]);
+    }>>([]); // 图片上传队列
 
+    // 控制弹窗开合动画
     useEffect(() => {
         if (show && !visible) {
             setVisible(true);
@@ -50,6 +51,7 @@
         }
     }, [show]);
 
+    // 提交反馈(评价/投诉)
     const submit = () => {
         if (isSubmitting) {
             return;
@@ -114,6 +116,7 @@
         });
     }
 
+    // 压缩图片到可上传大小
     const ensureSizeUnderLimit = async (path: string): Promise<string> => {
         try {
             const fs = Taro.getFileSystemManager();
@@ -149,6 +152,7 @@
         }
     };
 
+    // 上传单张图片并写回状态
     const uploadSingle = async (index: number, filePath: string) => {
         setPictures(prev => {
             const next = [...prev];
@@ -192,6 +196,7 @@
         }
     };
 
+    // 选择图片并触发上传
     const handleChooseImages = async () => {
         const remain = Math.max(0, 3 - pictures.length);
         if (remain <= 0) {
@@ -218,12 +223,14 @@
         }
     };
 
+    // 重试上传失败图片
     const handleRetry = async (index: number) => {
         const item = pictures[index];
         if (!item) return;
         uploadSingle(index, item.localUrl);
     };
 
+    // 删除已选图片
     const handleDelete = (index: number) => {
         setPictures(prev => {
             const next = [...prev];
@@ -232,6 +239,7 @@
         });
     };
 
+    // 拉取问题类型并重置本次填写内容
     useEffect(() => {
         if (!show) {
             return;
diff --git a/src/robotaxi/pages/begin-route/components/over-route.tsx b/src/robotaxi/pages/begin-route/components/over-route.tsx
index fb94ebc..8de4105 100644
--- a/src/robotaxi/pages/begin-route/components/over-route.tsx
+++ b/src/robotaxi/pages/begin-route/components/over-route.tsx
@@ -14,11 +14,12 @@
 }
 
 export default function OverRoute({ show = false, onClose, onConfirm }: Props) {
-    const [visible, setVisible] = useState(false);
-    const [animationData, setAnimationData] = useState<any>();
-    const [finishMinutes, setFinishMinutes] = useState<number | null>(null);
-    const [isSubmitting, setIsSubmitting] = useState(false);
+    const [visible, setVisible] = useState(false); // 弹窗显示状态
+    const [animationData, setAnimationData] = useState<any>(); // 弹窗动画数据
+    const [finishMinutes, setFinishMinutes] = useState<number | null>(null); // 自动驶离倒计时(分钟)
+    const [isSubmitting, setIsSubmitting] = useState(false); // 结束行程提交状态
 
+    // 确认结束行程并调用结束接口
     const handleConfirm = () => {
         if (isSubmitting) {
             return;
@@ -51,6 +52,7 @@
         });
     };
 
+    // 控制弹窗关闭动画
     useEffect(() => {
         if (!show && visible) {
             const animation = Taro.createAnimation({
@@ -66,6 +68,7 @@
         }
     }, [show]);
 
+    // 打开弹窗时先拉取结束后离车时间
     useEffect(() => {
         if (!show) {
             return;
diff --git a/src/robotaxi/pages/begin-route/components/passenger-notice.tsx b/src/robotaxi/pages/begin-route/components/passenger-notice.tsx
index 30a1935..521b2d5 100644
--- a/src/robotaxi/pages/begin-route/components/passenger-notice.tsx
+++ b/src/robotaxi/pages/begin-route/components/passenger-notice.tsx
@@ -11,15 +11,16 @@
 }
 
 export default function PassengerNotice({ show = false, onClose, onCheckedChange }: Props) {
-    const [visible, setVisible] = useState(false);
-    const [animationData, setAnimationData] = useState<any>();
-    const [checked, setChecked] = useState(true);
-    const [noticeContent, setNoticeContent] = useState('');
-    const [thumbHeight, setThumbHeight] = useState(0);
-    const [thumbTop, setThumbTop] = useState(0);
-    const [containerHeight, setContainerHeight] = useState(0);
-    const [showScrollbar, setShowScrollbar] = useState(false);
+    const [visible, setVisible] = useState(false); // 弹窗显示状态
+    const [animationData, setAnimationData] = useState<any>(); // 弹窗动画数据
+    const [checked, setChecked] = useState(true); // 近三个月不再显示勾选状态
+    const [noticeContent, setNoticeContent] = useState(''); // 须知富文本内容
+    const [thumbHeight, setThumbHeight] = useState(0); // 自定义滚动条高度
+    const [thumbTop, setThumbTop] = useState(0); // 自定义滚动条位置
+    const [containerHeight, setContainerHeight] = useState(0); // 文本容器高度
+    const [showScrollbar, setShowScrollbar] = useState(false); // 是否展示自定义滚动条
 
+    // 内容滚动时同步自定义滚动条位置
     const onScroll = (e) => {
         const { scrollHeight, scrollTop } = e.detail;
         if (containerHeight > 0 && scrollHeight > containerHeight) {
@@ -28,6 +29,7 @@
         }
     };
 
+    // 文案渲染完成后计算滚动条尺寸
     useEffect(() => {
         if (noticeContent && visible) {
             // Give it a little time to render
@@ -54,6 +56,7 @@
         }
     }, [noticeContent, visible]);
 
+    // 控制弹窗开合动画
     useEffect(() => {
         if (show && !visible) {
             setVisible(true);
@@ -77,6 +80,7 @@
         }
     }, [show]);
 
+    // 每次打开时拉取最新须知内容
     useEffect(() => {
         if (!show) {
             return;
diff --git a/src/robotaxi/pages/begin-route/components/release-be-being.tsx b/src/robotaxi/pages/begin-route/components/release-be-being.tsx
index 2f35ca7..b7a5604 100644
--- a/src/robotaxi/pages/begin-route/components/release-be-being.tsx
+++ b/src/robotaxi/pages/begin-route/components/release-be-being.tsx
@@ -9,13 +9,14 @@
 }
 
 export default function ReleaseBeBeing({ handleOpenCancelPopup, onFinish }: Props) {
-    const [progress, setProgress] = useState(0);
-    const [completedCount, setCompletedCount] = useState(0);
+    const [progress, setProgress] = useState(0); // 发布进度条
+    const [completedCount, setCompletedCount] = useState(0); // 已完成步骤数
     const startSite = Taro.getStorageSync('startSite') || {};
     const endSite = Taro.getStorageSync('endSite') || {};
     const startSiteName = startSite?.name || startSite?.address || '-';
     const endSiteName = endSite?.name || endSite?.address || '-';
 
+    // 模拟发布流程进度和状态推进
     useEffect(() => {
         // 使用 setTimeout 触发 CSS transition 动画
         const timer = setTimeout(() => {
diff --git a/src/robotaxi/pages/begin-route/components/release-route.tsx b/src/robotaxi/pages/begin-route/components/release-route.tsx
index 546ae34..7fe0581 100644
--- a/src/robotaxi/pages/begin-route/components/release-route.tsx
+++ b/src/robotaxi/pages/begin-route/components/release-route.tsx
@@ -17,16 +17,19 @@
 
 export default function ReleaseRoute({ startSite, endSite, callImmediately, fareEstimate = {}, handleOpenPassengerNotice, walkingTime = 0, isCallDisabled = false }: Props) {
 
+    // 跳转选点页(起点/终点)
     const toSeleteSitePage = (type: string) => {
         Taro.navigateTo({
             url: `/robotaxi/pages/select-map-site/index?pageType=${type}&skipSource=1`,
         })
     }
+    // 跳转费用说明页
     const toPriceDescription = () => {
         Taro.navigateTo({
             url: `/robotaxi/pages/expense-detail/index`,
         })
     }
+    // 格式化步行时长文案
     const formatWalkingTime = (minutes: number) => {
         const safeMinutes = Math.max(0, Math.floor(Number(minutes) || 0));
         if (safeMinutes >= 60) {
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 c5f18c6..c880af5 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
@@ -5,6 +5,7 @@
   shouldShowWaitingRadar: boolean;
   isAndroid: boolean;
   tripStatus: number;
+  mapScale: number;
   startSite: any;
   endSite: any;
   vehicleLocation: { latitude: number; longitude: number } | null;
@@ -151,6 +152,15 @@
     updateArriveCalloutPosition();
   }, [deps.isAndroid, deps.tripStatus, deps.endSite?.latitude, deps.endSite?.longitude]);
 
+  useEffect(() => {
+    if (!deps.isAndroid || deps.tripStatus !== 7) 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]);
+
   return {
     waitingRadarStyle,
     waitingRadarVisible,
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 a6bc18e..ff7e315 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -10,6 +10,8 @@
   vehicleLocation: { latitude: number; longitude: number } | null;
   vehicleDirection: number | null;
   vehicleMarkerId: number;
+  arrivedMarkerReady: boolean;
+  arrivedTooltipReady: boolean;
 };
 
 const defaultDeps: Deps = {
@@ -20,7 +22,9 @@
   endSite: null,
   vehicleLocation: null,
   vehicleDirection: null,
-  vehicleMarkerId: 3
+  vehicleMarkerId: 3,
+  arrivedMarkerReady: false,
+  arrivedTooltipReady: false
 };
 
 export default function useMapMarkers(rawDeps?: Partial<Deps>) {
@@ -137,10 +141,12 @@
     const useAndroidNativeTrip1Callout = deps.isAndroid && [1, 5, 8, 9, 10].includes(deps.tripStatus);
     const pickupMarker = createPickupMarker(pickupSite, useAndroidNativeTrip1Callout);
     const dropoffMarker = createDropoffMarker(dropoffSite, useAndroidNativeTrip1Callout);
+    const shouldWaitArrivedDirection = deps.tripStatus === 7 && !deps.arrivedMarkerReady;
+    const effectiveShowCar = deps.statusRule.showCar && !shouldWaitArrivedDirection;
     const carMarker = createCarMarker();
     const nextMarkers: any[] = [];
     if (deps.statusRule.showPickup && pickupMarker) nextMarkers.push(pickupMarker);
-    if (deps.statusRule.showCar && carMarker) nextMarkers.push(carMarker);
+    if (effectiveShowCar && carMarker) nextMarkers.push(carMarker);
     if (deps.statusRule.showDropoff && dropoffMarker) nextMarkers.push(dropoffMarker);
     const nextMarkersSignature = nextMarkers
       .map((m: any) => [
@@ -154,8 +160,7 @@
       .join(';');
     const hasDropoffMarker = markersRef.current.some((m: any) => m.id === 2);
     const shouldHoldMarkerByTranslate = [4, 6].includes(deps.tripStatus) && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker);
-    const shouldHoldMarkerWhenArrived = deps.tripStatus === 7 && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker);
-    const shouldSkipUpdate = (shouldHoldMarkerByTranslate && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current)) || shouldHoldMarkerWhenArrived;
+    const shouldSkipUpdate = shouldHoldMarkerByTranslate && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current);
     applyMarkerState(nextMarkers, nextMarkersSignature, shouldSkipUpdate, carMarker);
   }, [deps.tripStatus, deps.startSite?.name, deps.startSite?.latitude, deps.startSite?.longitude, deps.endSite?.name, deps.endSite?.latitude, deps.endSite?.longitude, deps.vehicleLocation, deps.vehicleDirection, deps.vehicleMarkerId, deps.isAndroid, deps.statusRule.showPickup, deps.statusRule.showCar, deps.statusRule.showDropoff]);
 
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 315d301..b278b54 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
@@ -49,7 +49,10 @@
 
     const mapRectRef = useRef<any>(null);
     const viewportKeyRef = useRef<string>('');
+    const orderDetailViewportLockedRef = useRef(false);
     const tripStatus6ViewportLockedRef = useRef(false);
+    const orderDetailEnterSignatureRef = useRef('');
+    const orderDetailRouteUpdatedAfterEnterRef = useRef(false);
 
     const isAndroid = useMemo(() => {
         try {
@@ -183,6 +186,22 @@
         return { points, keyParts };
     };
 
+    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('|');
+    };
+
     const updateViewportByPayload = (viewportType: string, points: any[]) => {
         const mapCtx = Taro.createMapContext('map');
         const bottomPadding = computeBottomPadding(points, viewportType);
@@ -263,10 +282,44 @@
         const { points, keyParts } = buildViewportPayload(viewportType, pickupSite, dropoffSite);
         if (points.length === 0) return;
         const nextKey = keyParts.join('|');
-        if (nextKey === viewportKeyRef.current) return;
+        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]);
+    }, [deps.tripStatus, deps.statusRule?.viewport, deps.endSite?.latitude, deps.endSite?.longitude, mapScale]);
+
+    useEffect(() => {
+        if (![8, 9].includes(deps.tripStatus)) {
+            orderDetailViewportLockedRef.current = false;
+            orderDetailRouteUpdatedAfterEnterRef.current = false;
+            orderDetailEnterSignatureRef.current = '';
+            return;
+        }
+        const { pickupSite, dropoffSite } = resolveViewportSites();
+        orderDetailEnterSignatureRef.current = buildOrderDetailRouteSignature(pickupSite, dropoffSite);
+        orderDetailRouteUpdatedAfterEnterRef.current = false;
+        orderDetailViewportLockedRef.current = false;
+        viewportKeyRef.current = '';
+        setMapScale(14);
+    }, [deps.tripStatus]);
+
+    useEffect(() => {
+        if (![8, 9].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;
+        orderDetailRouteUpdatedAfterEnterRef.current = true;
+        orderDetailViewportLockedRef.current = false;
+        viewportKeyRef.current = '';
+    }, [deps.tripStatus, deps.routePoints, deps.startSite?.latitude, deps.startSite?.longitude, deps.endSite?.latitude, deps.endSite?.longitude]);
 
     useEffect(() => {
         const { pickupSite, dropoffSite } = resolveViewportSites();
@@ -274,6 +327,14 @@
         if (viewportType === 'dropoff') return;
         const { points, keyParts } = buildViewportPayload(viewportType, pickupSite, dropoffSite);
         if (points.length === 0) return;
+        if (
+            [8, 9].includes(deps.tripStatus) &&
+            viewportType === 'order_detail' &&
+            orderDetailViewportLockedRef.current &&
+            orderDetailRouteUpdatedAfterEnterRef.current
+        ) {
+            return;
+        }
         if (deps.tripStatus === 6 && viewportType === 'route' && tripStatus6ViewportLockedRef.current) return;
         const nextKey = keyParts.join('|');
         if (nextKey === viewportKeyRef.current) return;
@@ -281,6 +342,10 @@
         updateViewportByPayload(viewportType, points);
         if (deps.tripStatus === 6 && viewportType === 'route') {
             tripStatus6ViewportLockedRef.current = true;
+        }
+        if ([8, 9].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]);
 
@@ -309,6 +374,7 @@
         setShowLocationFlag,
         mapRectRef,
         viewportKeyRef,
+        orderDetailViewportLockedRef,
         tripStatus6ViewportLockedRef,
         isAndroid,
         resolveSafeBottomInset,
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 7095372..8285eed 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
+++ b/src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
@@ -231,7 +231,7 @@
   useEffect(() => {
     const wasHold = [4, 6].includes(deps.lastTripStatusRef.current);
     const isHold = [4, 6].includes(deps.tripStatus);
-    if (deps.tripStatus != 4) Taro.removeStorageSync('pickupEndTime');
+    if (deps.tripStatus != 5) Taro.removeStorageSync('pickupEndTime');
     if (deps.tripStatus != 7) Taro.removeStorageSync('finishEndTime');
     if (!wasHold && isHold) {
       deps.orderRouteInitializedRef.current = false;
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 4295305..94b5350 100644
--- a/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
+++ b/src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -30,6 +30,8 @@
   arriveCalloutVisible: boolean;
   /** 到达气泡样式 */
   arriveCalloutStyle: any;
+  /** 到达态气泡渲染就绪(角度与缩放均就绪) */
+  arrivedTooltipReady: boolean;
   /** 路线距离 */
   routeDistance: { value: number; unit: 'km' | 'm' } | null;
   /** 路线时长 */
@@ -130,7 +132,7 @@
           </CoverView>
         </CoverView>
       )}
-      {(item.id === 2 && [7].includes(deps.tripStatus) && !deps.isAndroid && deps.mapScale >= 20) && (
+      {(item.id === 2 && [7].includes(deps.tripStatus) && !deps.isAndroid && deps.arrivedTooltipReady) && (
         <CoverView className='custom-callout-content'>
           已到达
         </CoverView>
@@ -170,7 +172,7 @@
           </View>
         </View>
       )}
-      {deps.isAndroid && deps.arriveCalloutVisible && (
+      {deps.isAndroid && deps.arriveCalloutVisible && deps.arrivedTooltipReady && (
         <View className="car-callout-android" style={deps.arriveCalloutStyle}>
           <View className="custom-callout-content">
             已到达
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 b73181e..384199c 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
@@ -202,12 +202,10 @@
     const currentTripStatus = Number(deps.tripStatusRef.current);
     if (currentTripStatus === 7) {
         const lastPoint = deps.lastVehiclePointRef.current;
-        const lastUpdateAt = Number(deps.lastVehicleUpdateAtRef.current || 0);
-        const now = Date.now();
         const latDiff = Math.abs(Number(lastPoint?.latitude || 0) - targetPoint.latitude);
         const lngDiff = Math.abs(Number(lastPoint?.longitude || 0) - targetPoint.longitude);
         const almostSamePoint = latDiff < 0.000003 && lngDiff < 0.000003;
-        if (almostSamePoint || (now - lastUpdateAt < 1200)) {
+        if (almostSamePoint) {
             return;
         }
     }
@@ -268,6 +266,10 @@
     const forceFullRedraw = payload?.forceFullRedraw === true;
     const currentTripStatus = Number(deps.tripStatusRef.current);
     updateRouteSummary(payload, deps);
+    // 行程结束态仅更新概要信息,不再消费路线点,避免视口反复重算造成跳动
+    if ([8, 9].includes(currentTripStatus)) {
+        return;
+    }
     if ([4, 6].includes(currentTripStatus)) {
         deps.setVehicleMoveAlongPoints(servedPoints);
     }
diff --git a/src/robotaxi/pages/begin-route/index.tsx b/src/robotaxi/pages/begin-route/index.tsx
index 88447bc..95989f3 100644
--- a/src/robotaxi/pages/begin-route/index.tsx
+++ b/src/robotaxi/pages/begin-route/index.tsx
@@ -110,7 +110,7 @@
     4: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
     5: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
     6: { showPickup: false, showDropoff: true, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
-    7: { showPickup: false, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'dropoff', translateCar: false },
+    7: { showPickup: false, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
     8: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
     9: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
     10: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
@@ -144,38 +144,6 @@
     lastVehiclePointRef,
     lastVehicleUpdateAtRef
   } = vehicleState; // 车辆状态解构
-  const markerState = useMapMarkers({
-    tripStatus,
-    statusRule,
-    isAndroid,
-    startSite,
-    endSite,
-    vehicleLocation,
-    vehicleDirection,
-    vehicleMarkerId
-  }); // 地图标记状态容器
-  const {
-    markers,
-    tripStatus6MarkerRefreshedRef
-  } = markerState; // 标记状态解构
-  const {
-    waitingRadarStyle,
-    waitingRadarVisible,
-    carCalloutStyle,
-    carCalloutVisible,
-    arriveCalloutStyle,
-    arriveCalloutVisible,
-    handleRegionChange: handleAndroidOverlayRegionChange
-  } = useAndroidOverlays({
-    shouldShowWaitingRadar,
-    isAndroid,
-    tripStatus,
-    startSite,
-    endSite,
-    vehicleLocation,
-    longitude,
-    latitude
-  }); // Android 覆盖物状态编排
   const routeState = useRouteManager({
     isAndroid,
     tripStatus,
@@ -239,6 +207,43 @@
     toOrigin,
     getLocationInfo
   } = viewportState; // 视口状态解构
+  const arrivedMarkerReady = tripStatus !== 7 || typeof vehicleDirection === 'number';
+  const arrivedTooltipReady = tripStatus !== 7 || (arrivedMarkerReady && mapScale >= 20);
+  const markerState = useMapMarkers({
+    tripStatus,
+    statusRule,
+    isAndroid,
+    startSite,
+    endSite,
+    vehicleLocation,
+    vehicleDirection,
+    vehicleMarkerId,
+    arrivedMarkerReady,
+    arrivedTooltipReady
+  }); // 地图标记状态容器
+  const {
+    markers,
+    tripStatus6MarkerRefreshedRef
+  } = markerState; // 标记状态解构
+  const {
+    waitingRadarStyle,
+    waitingRadarVisible,
+    carCalloutStyle,
+    carCalloutVisible,
+    arriveCalloutStyle,
+    arriveCalloutVisible,
+    handleRegionChange: handleAndroidOverlayRegionChange
+  } = useAndroidOverlays({
+    shouldShowWaitingRadar,
+    isAndroid,
+    tripStatus,
+    mapScale,
+    startSite,
+    endSite,
+    vehicleLocation,
+    longitude,
+    latitude
+  }); // Android 覆盖物状态编排
   const isCheckedEnter = useRef(false); // 是否已检查页面进入状态
   const { checkDistanceAndCityReady } = useOrderDistanceConfig({
     endSite,
@@ -458,6 +463,7 @@
     carCalloutStyle,
     arriveCalloutVisible,
     arriveCalloutStyle,
+    arrivedTooltipReady,
     routeDistance,
     routeDuration,
     customCalloutIconSrc,
diff --git a/src/robotaxi/pages/site-search/hooks/use-site-search.ts b/src/robotaxi/pages/site-search/hooks/use-site-search.ts
index df70be5..8f8b302 100644
--- a/src/robotaxi/pages/site-search/hooks/use-site-search.ts
+++ b/src/robotaxi/pages/site-search/hooks/use-site-search.ts
@@ -1,7 +1,6 @@
 import Taro from '@tarojs/taro';
 import { useEffect, useMemo, useRef, useState } from 'react';
 import { getStartPoints, getEndPoints, queryPointByName } from '../../../api/site-search';
-import { debounce } from '../../../../utils/Robotaxi-public-method';
 import { checkOtp } from '../../../../utils/tools';
 import { getNearestSite } from '../../../api/home';
 import { _AMAP_KEY } from '../../../../constants/api';
@@ -30,6 +29,12 @@
   const [nearbySiteList, setNearbySiteList] = useState<any[]>([]);
   // 页面类型(start/end)
   const [pageType, setPageType] = useState('');
+  // 当前关键词是否仍在搜索中(含高德补充结果)
+  const [isSearching, setIsSearching] = useState(false);
+  // 最近一次完成搜索的关键词
+  const [lastCompletedKeyword, setLastCompletedKeyword] = useState('');
+  const searchSeqRef = useRef(0);
+  const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
 
   // 拉取附近站点
   const fetchNearbySites = async (lat: number, lon: number, electronicAreaId?: string) => {
@@ -50,9 +55,17 @@
 
   // 站点搜索请求与结果处理
   const searchSite = async (val?: string) => {
-    setNonOperationalSite([]);
     const queryVal = val !== undefined ? val : deps.searchValue;
-    if (!queryVal) return;
+    const normalizedQuery = String(queryVal || '').trim();
+    searchSeqRef.current += 1;
+    const requestSeq = searchSeqRef.current;
+    setNonOperationalSite([]);
+    if (!normalizedQuery) {
+      setIsSearching(false);
+      setLastCompletedKeyword('');
+      return;
+    }
+    setIsSearching(true);
     const { latitude, longitude, electronicAreaId } = deps.router.params;
     const selectedCity = Taro.getStorageSync('selectedCity');
     const payload = {
@@ -60,42 +73,63 @@
       electronicAreaId: electronicAreaId ? Number(electronicAreaId) : 0,
       lat: Number(latitude),
       lon: Number(longitude),
-      pointPoi: queryVal
+      pointPoi: normalizedQuery
     };
     try {
       const res = await queryPointByName(payload);
+      if (requestSeq !== searchSeqRef.current) return;
       const { availablePoints } = res;
       if (Array.isArray(availablePoints)) {
         const list = availablePoints.map(normalizeSiteItem);
-        const sorted = sortSitesByNameMatchAndDistance(list, queryVal);
+        const sorted = sortSitesByNameMatchAndDistance(list, normalizedQuery);
         setSiteList(sorted.slice(0, deps.maxExpandedCount));
       } else {
         setSiteList([]);
       }
       const myAmapFun = new amapFile.AMapWX({ key: _AMAP_KEY });
-      myAmapFun.getPoiAround({
-        querykeywords: queryVal,
-        location: `${longitude},${latitude}`,
-        success: (data: any) => {
-          if (data && data.poisData) {
-            const list = data.poisData.map((item: any) => {
-              const locationArr = item.location.split(',');
-              return {
-                ...item,
-                longitude: Number(locationArr[0]),
-                latitude: Number(locationArr[1]),
-                ...normalizeSiteItem(item)
-              };
-            });
-            const sorted = sortSitesByNameMatchAndDistance(list, queryVal);
-            setNonOperationalSite(sorted.slice(0, deps.maxShowCount));
+      await new Promise<void>((resolve) => {
+        myAmapFun.getPoiAround({
+          querykeywords: normalizedQuery,
+          location: `${longitude},${latitude}`,
+          success: (data: any) => {
+            if (requestSeq !== searchSeqRef.current) {
+              resolve();
+              return;
+            }
+            if (data && data.poisData) {
+              const list = data.poisData.map((item: any) => {
+                const locationArr = item.location.split(',');
+                return {
+                  ...item,
+                  longitude: Number(locationArr[0]),
+                  latitude: Number(locationArr[1]),
+                  ...normalizeSiteItem(item)
+                };
+              });
+              const sorted = sortSitesByNameMatchAndDistance(list, normalizedQuery);
+              setNonOperationalSite(sorted.slice(0, deps.maxShowCount));
+            } else {
+              setNonOperationalSite([]);
+            }
+            resolve();
+          },
+          fail: () => {
+            if (requestSeq === searchSeqRef.current) {
+              setNonOperationalSite([]);
+            }
+            resolve();
           }
-        },
-        fail: () => { }
+        });
       });
     } catch {
+      if (requestSeq !== searchSeqRef.current) return;
       setSiteList([]);
       setNonOperationalSite([]);
+    } finally {
+      if (requestSeq === searchSeqRef.current) {
+        setIsSearching(false);
+        setLastCompletedKeyword(normalizedQuery);
+      }
     }
   };
 
@@ -103,15 +137,38 @@
   searchSiteRef.current = searchSite;
 
   // 搜索输入防抖
-  const debouncedSearch = useMemo(() => debounce((val) => {
-    searchSiteRef.current(val);
-  }, 300), []);
+  const debouncedSearch = useMemo(() => {
+    return (val: string) => {
+      if (debounceTimerRef.current) {
+        clearTimeout(debounceTimerRef.current);
+      }
+      debounceTimerRef.current = setTimeout(() => {
+        searchSiteRef.current(val);
+      }, 300);
+    };
+  }, []);
 
   // 清空搜索结果列表
   const clearSearchResults = () => {
+    if (debounceTimerRef.current) {
+      clearTimeout(debounceTimerRef.current);
+      debounceTimerRef.current = null;
+    }
+    // 推进请求序号,使清空前已发出的请求结果失效
+    searchSeqRef.current += 1;
     setSiteList([]);
     setNonOperationalSite([]);
+    setIsSearching(false);
+    setLastCompletedKeyword('');
   };
+
+  useEffect(() => {
+    return () => {
+      if (debounceTimerRef.current) {
+        clearTimeout(debounceTimerRef.current);
+      }
+    };
+  }, []);
 
   // 初始化:拉取附近站点、历史记录、页面类型
   useEffect(() => {
@@ -145,6 +202,8 @@
     historySite,
     nonOperationalSite,
     nearbySiteList,
+    isSearching,
+    lastCompletedKeyword,
     searchSite,
     debouncedSearch,
     clearSearchResults
diff --git a/src/robotaxi/pages/site-search/index.tsx b/src/robotaxi/pages/site-search/index.tsx
index 795c5a4..58507ca 100644
--- a/src/robotaxi/pages/site-search/index.tsx
+++ b/src/robotaxi/pages/site-search/index.tsx
@@ -37,6 +37,8 @@
     historySite,
     nonOperationalSite,
     nearbySiteList,
+    isSearching,
+    lastCompletedKeyword,
     searchSite,
     debouncedSearch,
     clearSearchResults
@@ -224,6 +226,7 @@
   const recommendList = searchValue ? sortSitesByNameMatchAndDistance(siteList, searchValue) : filteredNearbyList;
   const visibleHistory = showAllHistory ? historySite.slice(0, MAX_EXPANDED_COUNT) : historySite.slice(0, MAX_SHOW_COUNT);
   const visibleRecommend = showAllRecommend ? recommendList.slice(0, MAX_EXPANDED_COUNT) : recommendList.slice(0, MAX_SHOW_COUNT);
+  const canShowSearchFallback = !searchValue || (!isSearching && lastCompletedKeyword === searchValue.trim());
 
   // 使用数据截断展示,不再通过高度裁剪内容
 
@@ -297,7 +300,7 @@
         </View>
       }
       {
-        (!siteList.length && (!searchValue ? !nearbySiteList.length : true) && !nonOperationalSite.length && (!searchValue ? !historySite.length : true)) &&
+        canShowSearchFallback && (!siteList.length && (!searchValue ? !nearbySiteList.length : true) && !nonOperationalSite.length && (!searchValue ? !historySite.length : true)) &&
         <View className='history-card'>
           <View className='card-header'>暂无搜索结果,为您推荐附近站点</View>
           <View className='card-content list-content'>

--
Gitblit v1.7.1