hejianhao
2026-04-20 2f865b9cb1f2a6c46b0bbc64f8f138132539b2f8
Merge branch 'feature_pr_fix' into feature_v1.0
27个文件已修改
454 ■■■■■ 已修改文件
config/prod.js 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/announcements-popup.tsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/begin-route-view.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/vehicle-info-card.tsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/cancel-popup.tsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/evaluate-popup.tsx 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/over-route.tsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/passenger-notice.tsx 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/release-be-being.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/release-route.tsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/hooks/use-site-search.ts 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/index.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/prod.js
@@ -17,7 +17,7 @@
const ADMIN_OSS_HOST = '"https://xps01.xiaopeng.com"'; // 运营后台上传的额文件的 oss 域名
const ROBOTAXI_HOST = '"https://rt-api.xiaopeng.com"';
const ROBOTAXI_WS_HOST = '"wss://rt-api.xiaopeng.com/websocket"';
const ROBOTAXI_WS_HOST = '"wss://rt-ws.xiaopeng.com/websocket"';
const AMAP_KEY = '"8157fdfa13878d3f7bf69c560c8a846a"';
const XP_APPID_MAP = {
@@ -33,7 +33,7 @@
const GIO_CONFIG = {
  accountId: '"84eafaa69d23412f"', // 埋点平台id
  dataSourceId: {
    weapp: '"b9aa24dc0aab9edb"', // 埋点数据源id
    weapp: '"b63706df57a52ecf"', // 埋点数据源id
  },
  host: '"https://collect.xiaopeng.com"',
}
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);
src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -132,6 +132,8 @@
    handleCloseEvaluatePopup,
    handleConfirmEvaluate
  } = props;
  // 统一渲染地图与业务弹窗容器
  return (
    <View className='home-container'>
      <Map
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' });
        });
    };
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>
    );
}
}
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>
    );
}
}
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>
    );
}
}
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
@@ -87,6 +87,7 @@
        };
    }, [tripStatus, orderId]);
    // 标题行文案映射
    const titleMap = {
        3: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>,
        4: <>车辆还有<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}到达</>,
@@ -99,6 +100,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,
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) {
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>
    );
}
}
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -86,6 +86,7 @@
    orderTrunkStatus,
}: Props) {
    // 汇总行程中的倒计时与费用数据
    const {
        waitMinutes,
        limitMinutes,
@@ -109,6 +110,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' />
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?.();
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;
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;
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;
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(() => {
src/robotaxi/pages/begin-route/components/release-route.tsx
@@ -131,16 +131,19 @@
        }
    };
    // 跳转选点页(起点/终点)
    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) {
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,
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -11,6 +11,8 @@
  vehicleLocation: { latitude: number; longitude: number } | null;
  vehicleDirection: number | null;
  vehicleMarkerId: number;
  arrivedMarkerReady: boolean;
  arrivedTooltipReady: boolean;
};
const defaultDeps: Deps = {
@@ -22,7 +24,9 @@
  endSite: null,
  vehicleLocation: null,
  vehicleDirection: null,
  vehicleMarkerId: 3
  vehicleMarkerId: 3,
  arrivedMarkerReady: false,
  arrivedTooltipReady: false
};
export default function useMapMarkers(rawDeps?: Partial<Deps>) {
@@ -139,10 +143,12 @@
    const useAndroidNativeTrip1Callout = deps.isAndroid && ([1, 5, 8, 9, 10].includes(deps.tripStatus) || deps.showTrip1StyleEndpoint);
    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) => [
@@ -156,8 +162,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]);
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,
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts
@@ -635,6 +635,7 @@
    walkingTime,
    setWalkingTime,
    routeOptions,
    setRouteOptions,
    selectedRouteIndex,
    handleSelectRoute,
    handlePolylineTap,
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;
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -31,6 +31,8 @@
  arriveCalloutVisible: boolean;
  /** 到达气泡样式 */
  arriveCalloutStyle: any;
  /** 到达态气泡渲染就绪(角度与缩放均就绪) */
  arrivedTooltipReady: boolean;
  /** 路线距离 */
  routeDistance: { value: number; unit: 'km' | 'm' } | null;
  /** 路线时长 */
@@ -173,7 +175,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>
@@ -216,7 +218,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">
            已到达
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);
    }
src/robotaxi/pages/begin-route/index.tsx
@@ -117,7 +117,7 @@
    4: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: true, viewport: 'route', translateCar: true },
    5: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: true, 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 },
@@ -212,39 +212,6 @@
    lastVehiclePointRef,
    lastVehicleUpdateAtRef
  } = vehicleState; // 车辆状态解构
  const markerState = useMapMarkers({
    tripStatus,
    statusRule,
    isAndroid,
    showTrip1StyleEndpoint,
    startSite,
    endSite: effectiveEndSite,
    vehicleLocation,
    vehicleDirection,
    vehicleMarkerId
  }); // 地图标记状态容器
  const {
    markers,
    tripStatus6MarkerRefreshedRef
  } = markerState; // 标记状态解构
  const {
    waitingRadarStyle,
    waitingRadarVisible,
    carCalloutStyle,
    carCalloutVisible,
    arriveCalloutStyle,
    arriveCalloutVisible,
    handleRegionChange: handleAndroidOverlayRegionChange
  } = useAndroidOverlays({
    shouldShowWaitingRadar,
    isAndroid,
    tripStatus,
    startSite,
    endSite: effectiveEndSite,
    vehicleLocation,
    longitude,
    latitude
  }); // Android 覆盖物状态编排
  const routeState = useRouteManager({
    isAndroid,
    tripStatus,
@@ -269,11 +236,13 @@
    walkingTime,
    setWalkingTime,
    routeOptions,
    setRouteOptions,
    selectedRouteIndex,
    handleSelectRoute,
    handlePolylineTap,
    handleMapTap,
    fareEstimate,
    setFareEstimate,
    routeAndFareError,
    routeDistance,
    setRouteDistance,
@@ -330,10 +299,6 @@
      customCallout: { anchorY: 0, anchorX: 0, display: 'ALWAYS' }
    };
  }, [allowSelectableRoutes, routeDistance, routeDuration, routePoints]);
  const markersWithRouteSummary = useMemo(() => {
    if (!routeSummaryMarker) return markers;
    return [...markers, routeSummaryMarker];
  }, [markers, routeSummaryMarker]);
  const viewportState = useMapViewport({
    tripStatus,
    statusRule,
@@ -359,6 +324,48 @@
    toOrigin,
    getLocationInfo
  } = viewportState; // 视口状态解构
  const arrivedMarkerReady = tripStatus !== 7 || typeof vehicleDirection === 'number';
  const arrivedTooltipReady = tripStatus !== 7 || (arrivedMarkerReady && mapScale >= 20);
  const markerState = useMapMarkers({
    tripStatus,
    statusRule,
    isAndroid,
    showTrip1StyleEndpoint,
    startSite,
    endSite: effectiveEndSite,
    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: effectiveEndSite,
    vehicleLocation,
    longitude,
    latitude
  }); // Android 覆盖物状态编排
  const markersWithRouteSummary = useMemo(() => {
    if (!routeSummaryMarker) return markers;
    return [...markers, routeSummaryMarker];
  }, [markers, routeSummaryMarker]);
  const { checkDistanceAndCityReady } = useOrderDistanceConfig({
    endSite,
    setTipType,
@@ -386,21 +393,6 @@
    getCurrentLongitude: () => longitude,
    getCurrentLatitude: () => latitude
  }); // 页面恢复与初始化编排
  const {
    openService,
    toCoupon,
    toOrder,
    toBack,
    handleCalloutTap,
    toSelectSite
  } = usePageActions({
    tripStatus,
    startSite,
    getCheckedEnter: () => isCheckedEnter.current,
    setCheckedEnter: (checked) => {
      isCheckedEnter.current = checked;
    }
  }); // 页面动作编排
  const router = Taro.useRouter();
  const tripRuntime = useTripRuntime({
    waitingTimerStartKey: WAITING_TIMER_START_KEY,
@@ -655,6 +647,7 @@
    carCalloutStyle,
    arriveCalloutVisible,
    arriveCalloutStyle,
    arrivedTooltipReady,
    routeDistance,
    routeDuration,
    customCalloutIconSrc,
src/robotaxi/pages/site-search/hooks/use-site-search.ts
@@ -30,6 +30,11 @@
  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 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);
      }
    }
  };
@@ -111,6 +145,8 @@
  const clearSearchResults = () => {
    setSiteList([]);
    setNonOperationalSite([]);
    setIsSearching(false);
    setLastCompletedKeyword('');
  };
  // 重新拉取初始化数据(历史记录和附近站点)
@@ -150,6 +186,8 @@
    historySite,
    nonOperationalSite,
    nearbySiteList,
    isSearching,
    lastCompletedKeyword,
    searchSite,
    debouncedSearch,
    clearSearchResults,
src/robotaxi/pages/site-search/index.tsx
@@ -46,6 +46,8 @@
    historySite,
    nonOperationalSite,
    nearbySiteList,
    isSearching,
    lastCompletedKeyword,
    searchSite,
    debouncedSearch,
    clearSearchResults,
@@ -251,6 +253,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());
  // 使用数据截断展示,不再通过高度裁剪内容
@@ -335,7 +338,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'>