hejianhao
2026-05-11 8f4e9146f8a47fdf58c9401249d9d99e5abadf8a
feat(begin-route): 新增修改目的地失败弹窗并完善校验与错误处理

- 提取退出修改目的地的状态清理逻辑为复用函数,消除重复代码
- 新增统一样式的修改目的地失败弹窗组件
- 添加修改目的地前的距离和时长限制校验
- 重构错误处理逻辑,将异常集中在页面层统一通过弹窗展示
- 整合弹窗组件到页面并统一关闭与确认行为
1个文件已添加
2个文件已修改
146 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/components/modify-destination-fail-popup.tsx 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-modify-destination-flow.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/modify-destination-fail-popup.tsx
New file
@@ -0,0 +1,69 @@
import Taro from '@tarojs/taro';
import { View, Image } from '@tarojs/components';
import { useEffect, useState } from 'react';
import './css-folder/over-route.less';
type Props = {
  show?: boolean;
  content?: string;
  onClose?: () => void;
  onConfirm?: () => void;
};
export default function ModifyDestinationFailPopup({
  show = false,
  content = '',
  onClose,
  onConfirm
}: Props) {
  const [visible, setVisible] = useState(false);
  const [animationData, setAnimationData] = useState<any>();
  // 确认“我知道了”
  const handleConfirm = () => {
    onConfirm?.();
  };
  // 控制弹窗关闭动画
  useEffect(() => {
    if (!show && visible) {
      const animation = Taro.createAnimation({
        duration: 300,
        timingFunction: 'ease-in'
      });
      animation.opacity(0).scale(0.8).step();
      setAnimationData(animation.export());
      setTimeout(() => {
        setVisible(false);
      }, 300);
    }
  }, [show, visible]);
  // 控制弹窗打开动画
  useEffect(() => {
    if (!show) {
      return;
    }
    setVisible(true);
    const animation = Taro.createAnimation({
      duration: 300,
      timingFunction: 'ease-out'
    });
    animation.opacity(1).scale(1).step();
    setAnimationData(animation.export());
  }, [show]);
  if (!visible) return null;
  return (
    <View className='over-popup' catchMove>
      <View className='over-mask' onClick={onClose}></View>
      <View className='over-content' animation={animationData}>
        <Image onClick={onClose} className='close-icon' src={require('../../../assets/close-icon.png')} />
        <View className='over-title'>修改目的地失败</View>
        {content ? <View className='over-txt'>{content}</View> : null}
        <View onClick={handleConfirm} className='reselect-btn'>我知道了</View>
      </View>
    </View>
  );
}
src/robotaxi/pages/begin-route/hooks/use-modify-destination-flow.ts
@@ -163,6 +163,16 @@
    Taro.removeStorageSync(MODIFY_DESTINATION_OVERRIDE_KEY);
  };
  // 退出修改目的地确认态并清理本地会话缓存
  const exitModifyDestinationReview = () => {
    setPreviewEndSite(null);
    setPreviousEndSite(null);
    setPreviousFareEstimate(null);
    setPreviousRoutePoints([]);
    clearModifyDestinationSession();
    clearLocalDestinationOverride();
  };
  const handleConfirmModifyDestination = async (selectedRoute?: any) => {
    if (!isValidSite(previewEndSite)) return;
    if (isConfirmingModifyDestination) return;
@@ -194,12 +204,7 @@
      Taro.setStorageSync('endSite', newEndSite);
      deps.setEndSite(newEndSite);
      setPreviewEndSite(null);
      setPreviousEndSite(null);
      setPreviousFareEstimate(null);
      setPreviousRoutePoints([]);
      clearModifyDestinationSession();
      clearLocalDestinationOverride();
      exitModifyDestinationReview();
      Taro.hideLoading();
      Taro.showToast({
@@ -212,10 +217,7 @@
      }
    } catch (e: any) {
      Taro.hideLoading();
      Taro.showToast({
        title: '修改目的地失败',
        icon: 'none'
      });
      throw e;
    } finally {
      setIsConfirmingModifyDestination(false);
    }
@@ -241,6 +243,7 @@
    isModifyDestinationReviewVisible,
    hasLocalDestinationOverride,
    clearLocalDestinationOverride,
    exitModifyDestinationReview,
    handleStartModifyDestination,
    handleConfirmModifyDestination
  };
src/robotaxi/pages/begin-route/index.tsx
@@ -4,6 +4,8 @@
import './index.less';
import BeginRouteView from './components/begin-route-view';
// 修改目的地失败弹窗
import ModifyDestinationFailPopup from './components/modify-destination-fail-popup';
import CouponSelectPopup from '../../components/coupon-select-popup';
// 弹窗状态管理
import usePopupManager from './hooks/use-popup-manager';
@@ -135,6 +137,8 @@
  const [evaluateSubmitVersion, setEvaluateSubmitVersion] = useState(0); // 评价提交版本号
  const [evaluateCloseVersion, setEvaluateCloseVersion] = useState(0); // 评价关闭版本号
  const [showCouponSelectPopup, setShowCouponSelectPopup] = useState(false); // 优惠券选择弹窗展示状态
  const [showModifyDestinationFailPopup, setShowModifyDestinationFailPopup] = useState(false); // 修改目的地失败弹窗
  const [modifyDestinationFailMessage, setModifyDestinationFailMessage] = useState(''); // 修改目的地失败文案(默认空,避免闪文案)
  const [orderRouteMetrics, setOrderRouteMetrics] = useState<OrderRouteMetrics>({
    distanceMeters: null,
    durationSeconds: null,
@@ -213,6 +217,7 @@
    isModifyDestinationReviewVisible,
    hasLocalDestinationOverride,
    clearLocalDestinationOverride,
    exitModifyDestinationReview,
    handleStartModifyDestination,
    handleConfirmModifyDestination
  } = useModifyDestinationFlow({
@@ -830,6 +835,47 @@
    isCheckedEnter.current = true;
    handleStartModifyDestination(fareEstimate, routePoints);
  };
  // 打开修改目的地失败弹窗,先写入文案再展示,避免文案闪烁
  const openModifyDestinationFailPopup = (message: string) => {
    setModifyDestinationFailMessage(message || '');
    setShowModifyDestinationFailPopup(true);
  };
  // 失败弹窗确认后退出修改目的地确认态,并清理缓存会话
  const handleAcknowledgeModifyDestinationFail = () => {
    setShowModifyDestinationFailPopup(false);
    setModifyDestinationFailMessage('');
    exitModifyDestinationReview();
  };
  // 关闭失败弹窗与确认按钮行为一致,统一走清理逻辑
  const handleCloseModifyDestinationFailPopup = () => {
    handleAcknowledgeModifyDestinationFail();
  };
  // 确认修改前进行阈值校验,命中限制或接口失败都展示失败弹窗
  const handleConfirmModifyDestinationWithGuard = async (selectedRoute?: any) => {
    const currentDistance = Number(orderRouteMetrics?.distanceMeters);
    const currentDurationSeconds = Number(orderRouteMetrics?.durationSeconds);
    const distanceLimit = Number(distanceConfig?.xczSyDistance);
    const durationLimitMinutes = Number(distanceConfig?.xczSyTime);
    const hitDistanceLimit = Number.isFinite(distanceLimit)
      && distanceLimit > 0
      && Number.isFinite(currentDistance)
      && currentDistance < distanceLimit;
    const hitDurationLimit = Number.isFinite(durationLimitMinutes)
      && durationLimitMinutes > 0
      && Number.isFinite(currentDurationSeconds)
      && (currentDurationSeconds / 60) < durationLimitMinutes;
    if (hitDistanceLimit || hitDurationLimit) {
      openModifyDestinationFailPopup('即将到达终点,已无法修改目的地');
      return;
    }
    try {
      await handleConfirmModifyDestination(selectedRoute);
    } catch (error) {
      openModifyDestinationFailPopup('修改目的地失败,请返回重试');
    }
  };
  const handleShareTrip = () => {
    isCheckedEnter.current = true;
  };
@@ -955,7 +1001,7 @@
    previousEndSite,
    previousFareEstimate,
    previewEndSite,
    handleConfirmModifyDestination,
    handleConfirmModifyDestination: handleConfirmModifyDestinationWithGuard,
    bluetoothStatusReady,
    bluetoothCardDescription,
    bluetoothCardActionText,
@@ -1072,6 +1118,12 @@
        onClose={handleCloseCouponSelect}
        onSelect={handleSelectCoupon}
      />
      <ModifyDestinationFailPopup
        show={showModifyDestinationFailPopup}
        content={modifyDestinationFailMessage}
        onClose={handleCloseModifyDestinationFailPopup}
        onConfirm={handleAcknowledgeModifyDestinationFail}
      />
    </>
  )
}