hejianhao
2026-04-14 c3875b2ae3436c8da9ac2e4ecb8a3f431429b47a
feat(robotaxi): 新增路线偏好设置与可展开式路线选择组件

- 添加路线偏好设置弹窗,支持智能推荐、时间优先、低价优先三种偏好选择
- 实现可展开/收起的路线选择区域,支持手势拖拽交互
- 新增 pack-up.png 等图标资源用于展开/收起状态切换
- 优化路线选择区域的样式,包括圆角、阴影和布局调整
- 在开始行程页面集成路线选择展开状态管理,优化用户体验
5个文件已添加
8个文件已修改
593 ■■■■■ 已修改文件
src/pages/robotaxi/home/index.less 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/assets/pack-up.png 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/assets/personal-center.png 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/assets/select-type-line.png 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/begin-route-view.tsx 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/release-route.less 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/release-route.tsx 295 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/route-preference-popup/index.less 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/route-preference-popup/index.tsx 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.less 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/index.less 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.less
@@ -150,7 +150,8 @@
  align-items: center;
  flex-direction: column;
  gap: 8px;
  border-radius: 138px;
  border-radius: 24px;
  box-shadow: 0px 4px 16px 0px rgba(84, 92, 143, 0.1);
  background: rgba(255, 255, 255, 1);
  overflow: hidden;
  padding: 8px 12px;
@@ -636,4 +637,4 @@
    background-clip: text;
    text-fill-color: transparent;
  }
}
}
src/robotaxi/assets/pack-up.png
src/robotaxi/assets/personal-center.png
src/robotaxi/assets/select-type-line.png
src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -34,6 +34,9 @@
  toOrigin: () => void;
  originIcon: string;
  selectRouteIcon: string;
  packUpIcon: string;
  onSelectRouteClick: () => void;
  isRouteSelectExpanded: boolean;
  walkIcon: string;
  renderTripCard: () => JSX.Element | null;
  showCustomerService: boolean;
@@ -89,6 +92,9 @@
    toOrigin,
    originIcon,
    selectRouteIcon,
    packUpIcon,
    onSelectRouteClick,
    isRouteSelectExpanded,
    walkIcon,
    renderTripCard,
    showCustomerService,
@@ -149,7 +155,17 @@
            {shouldShowOrigin && (
              <View className='origin-box' style={{ opacity: originBoxHidden ? 0 : originOpacity, transition: 'opacity 0.2s ease-out', pointerEvents: originBoxHidden ? 'none' : 'auto' }}>
                <Image onClick={toOrigin} className='origin-icon' src={originIcon} />
                <Image className='select-route-icon' src={selectRouteIcon} />
                {
                  tripStatus == 1 &&
                  <View>
                    {
                      isRouteSelectExpanded ?
                        <Image onClick={onSelectRouteClick} className='select-route-icon' src={packUpIcon} />
                        :
                        <Image onClick={onSelectRouteClick} className='select-route-icon' src={selectRouteIcon} />
                    }
                  </View>
                }
              </View>
            )}
            <View catchMove onClick={(e) => e.stopPropagation()}>
src/robotaxi/pages/begin-route/components/css-folder/release-route.less
@@ -1,4 +1,22 @@
.release-route {
.release-route-wrapper {
    position: relative;
    border-radius: 40px 40px 0 0;
    background: transparent;
    z-index: 10;
    width: 100vw;
    overflow: hidden;
}
.release-route-content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
}
.release-route,
.release-route-wrapper,
.release-route-content {
    .route-select {
        border-radius: 24px;
@@ -11,23 +29,33 @@
        .route-select-type {
            display: flex;
            align-items: center;
            font-size: 24px;
            font-weight: 500;
            line-height: 44px;
            letter-spacing: normal;
            color: rgba(0, 0, 0, 0.9);
            .route-select-type-recommend {
                line-height: 40px;
            .route-select-title {
                font-size: 24px;
                font-weight: 500;
                line-height: 44px;
                letter-spacing: normal;
                color: #6D7FD9;
                color: rgba(0, 0, 0, 0.9);
            }
            image {
                flex-shrink: 0;
                margin-left: 9.25px;
                height: 11.97px;
                width: 17.5px;
            .route-select-preference {
                display: flex;
                align-items: center;
                .route-select-preference-text {
                    font-size: 24px;
                    font-weight: 500;
                    line-height: 40px;
                    letter-spacing: normal;
                    color: #6D7FD9;
                }
                image {
                    flex-shrink: 0;
                    margin-left: 9.25px;
                    height: 11.97px;
                    width: 17.5px;
                }
            }
        }
@@ -48,7 +76,7 @@
                    font-size: 20px;
                    line-height: 20px;
                    letter-spacing: normal;
                    color: #6D7FD9;
                    color: rgba(0, 0, 0, 0.4);
                }
                .route-select-content-item-time {
@@ -88,6 +116,10 @@
                background: linear-gradient(97deg, rgba(132, 117, 255, 0.2) 0%, rgba(125, 214, 255, 0.2) 103%) !important;
                box-sizing: border-box;
                position: relative;
                .route-select-content-item-title {
                    color: #6D7FD9;
                }
                &::after {
                    content: "";
@@ -265,6 +297,7 @@
        bottom: 0;
        z-index: 11;
        width: 100%;
        box-sizing: border-box;
        padding: 24px 38px 62px 32px;
        background: #FEFEFF;
        display: flex;
src/robotaxi/pages/begin-route/components/release-route.tsx
@@ -1,4 +1,5 @@
import Taro, { useDidShow } from "@tarojs/taro"
import Taro from "@tarojs/taro"
import { useEffect, useState, useRef } from "react";
import { View, Text, Image } from '@tarojs/components';
import rightIcon from '../../../assets/right-icon.png'
import './css-folder/release-route.less'
@@ -6,18 +7,128 @@
import tipDefaultIcon from '../../../assets/tip-default-icon.png'
import selectLineIcon from '../../../assets/select-line.png'
import trafficSignalIcon from '../../../assets/traffic-signal.png'
import RoutePreferencePopup from './route-preference-popup';
type Props = {
    startSite: Site;
    endSite: Site;
    startSite: any;
    endSite: any;
    callImmediately: () => void;
    fareEstimate?: any;
    handleOpenPassengerNotice?: () => void;
    walkingTime?: number;
    isCallDisabled?: boolean;
    isRouteSelectExpanded?: boolean;
    onToggleRouteSelect?: (expanded: boolean) => void;
}
export default function ReleaseRoute({ startSite, endSite, callImmediately, fareEstimate = {}, handleOpenPassengerNotice, walkingTime = 0, isCallDisabled = false }: Props) {
export default function ReleaseRoute({ startSite, endSite, callImmediately, fareEstimate = {}, handleOpenPassengerNotice, walkingTime = 0, isCallDisabled = false, isRouteSelectExpanded = false, onToggleRouteSelect }: Props) {
    const [boxHeight, setBoxHeight] = useState<number | null>(null);
    const startY = useRef(0);
    const startHeight = useRef(0);
    const fullHeight = useRef(0);
    const minHeight = useRef(0);
    const isDragging = useRef(false);
    const dragFrame = useRef<any>(null);
    const pendingHeight = useRef<number | null>(null);
    const [showPreferencePopup, setShowPreferencePopup] = useState(false);
    const [preference, setPreference] = useState('智能推荐');
    useEffect(() => {
        if (!isRouteSelectExpanded && boxHeight !== null && minHeight.current > 0) {
            setBoxHeight(minHeight.current);
            setTimeout(() => {
                onToggleRouteSelect && onToggleRouteSelect(false);
            }, 300);
        }
    }, [isRouteSelectExpanded]);
    useEffect(() => {
        setTimeout(() => {
            const query = Taro.createSelectorQuery();
            query.select('.route-select').boundingClientRect();
            query.select('.select-site').boundingClientRect();
            query.exec(res => {
                if (res[1]) {
                    // 当路线选择没有渲染时,res[0] 为 null,预估高度给 230
                    const routeSelectHeight = res[0] ? res[0].height : 230;
                    const siteHeight = res[1].height;
                    // 最小高度:仅为选点区域的高度
                    const baseHeight = siteHeight;
                    // 最大高度:选点区域加上上方路线选择的高度
                    const contentHeight = baseHeight + routeSelectHeight;
                    minHeight.current = baseHeight;
                    fullHeight.current = contentHeight;
                    // 只在初次加载未展开时设置基础高度,不要在这里主动将其撑开
                    // 否则在展开(isRouteSelectExpanded变为true)后,这里又会跑一次把它弹高
                    if (boxHeight === null && !isRouteSelectExpanded) {
                        setBoxHeight(baseHeight);
                    }
                }
            });
        }, 100);
    }, [isRouteSelectExpanded, fareEstimate, walkingTime]);
    const handleTouchStart = (e: any) => {
        // 如果没有展开,禁止滑动
        if (!isRouteSelectExpanded) return;
        isDragging.current = true;
        startY.current = e.touches[0].clientY;
        startHeight.current = typeof boxHeight === 'number' ? boxHeight : fullHeight.current;
        if (dragFrame.current) {
            clearTimeout(dragFrame.current);
            dragFrame.current = null;
        }
        pendingHeight.current = null;
    };
    const handleTouchMove = (e: any) => {
        if (!isRouteSelectExpanded) return;
        if (!isDragging.current) return;
        const deltaY = e.touches[0].clientY - startY.current;
        // 注意:因为现在内容是从上方出现的,所以往下拖拽(deltaY > 0)应该代表收起盒子(高度变小)
        // 往上拖拽(deltaY < 0)应该代表拉出盒子(高度变大)
        let newHeight = startHeight.current - deltaY;
        if (newHeight < minHeight.current) newHeight = minHeight.current;
        if (newHeight > fullHeight.current) newHeight = fullHeight.current;
        pendingHeight.current = newHeight;
        if (dragFrame.current) return;
        dragFrame.current = setTimeout(() => {
            if (pendingHeight.current !== null) {
                setBoxHeight(pendingHeight.current);
            }
            pendingHeight.current = null;
            dragFrame.current = null;
        }, 16);
    };
    const handleTouchEnd = () => {
        if (!isRouteSelectExpanded) return;
        if (!isDragging.current) return;
        isDragging.current = false;
        if (dragFrame.current) {
            clearTimeout(dragFrame.current);
            dragFrame.current = null;
        }
        if (pendingHeight.current !== null) {
            setBoxHeight(pendingHeight.current);
            pendingHeight.current = null;
        }
        const movableRange = fullHeight.current - minHeight.current;
        if (typeof boxHeight === 'number' && boxHeight > minHeight.current + movableRange / 5) {
            setBoxHeight(fullHeight.current);
            onToggleRouteSelect && onToggleRouteSelect(true);
        } else {
            setBoxHeight(minHeight.current);
        }
    };
    const toSeleteSitePage = (type: string) => {
        Taro.navigateTo({
@@ -40,92 +151,126 @@
    }
    return (
        <View className='release-route'>
            <View className="route-select">
                <View className="route-select-type">
                    <View>路线选择:</View>
                    <View className="route-select-type-recommend">智能推荐</View>
                    <Image src={selectLineIcon} />
                </View>
                <View className="route-select-content">
                    {
                        [1, 2, 3].map(item => (
                            <View className={`route-select-content-item ${item === 1 ? 'active-item' : ''}`} key={item}>
                                <View className="route-select-content-item-title">智能推荐</View>
                                <View className="route-select-content-item-time">10分钟</View>
                                <View className="route-select-content-item-distance">
                                    <View>10.6公里</View>
                                    <View className="traffic-signal-item">
                                        <Image className="traffic-signal-icon" src={trafficSignalIcon} />
                                        <Text>6个</Text>
        <View className="release-route-container">
            <View
                className='release-route-wrapper'
                style={{ height: boxHeight ? `${boxHeight}px` : 'auto' }}
                catchMove={isRouteSelectExpanded}
                onTouchStart={(e) => {
                    // 如果没有展开,禁止滑动
                    if (!isRouteSelectExpanded) return;
                    e.stopPropagation();
                    handleTouchStart(e);
                }}
                onTouchMove={(e) => {
                    if (!isRouteSelectExpanded) return;
                    e.stopPropagation();
                    handleTouchMove(e);
                }}
                onTouchEnd={(e) => {
                    if (!isRouteSelectExpanded) return;
                    e.stopPropagation();
                    handleTouchEnd();
                }}
            >
                <View className='release-route-content'>
                    {isRouteSelectExpanded && (
                        <View className="route-select">
                            <View className="route-select-type">
                                <View className="route-select-title">路线选择:</View>
                                <View className="route-select-preference" onClick={() => setShowPreferencePopup(true)}>
                                    <View className="route-select-preference-text">{preference}</View>
                                    <Image src={selectLineIcon} />
                                </View>
                            </View>
                            <View className="route-select-content">
                                {
                                    [1, 2, 3].map(item => (
                                        <View className={`route-select-content-item ${item === 1 ? 'active-item' : ''}`} key={item}>
                                            <View className="route-select-content-item-title">智能推荐</View>
                                            <View className="route-select-content-item-time">10分钟</View>
                                            <View className="route-select-content-item-distance">
                                                <View>10.6公里</View>
                                                <View className="traffic-signal-item">
                                                    <Image className="traffic-signal-icon" src={trafficSignalIcon} />
                                                    <Text>6个</Text>
                                                </View>
                                            </View>
                                        </View>
                                    ))
                                }
                            </View>
                        </View>
                    )}
                    <View className="select-site">
                        <Image className="site-top-bg" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg.png' />
                        <View className="site-boarding-point">
                            {/* 起点 */}
                            <View>
                                <View onClick={() => toSeleteSitePage('start')} className="now-site">
                                    <View className="now-site-dot"></View>
                                    <View className="now-site-address">
                                        <View className="now-site-address-content">
                                            从<Text className="now-site-address-text">{startSite.name}</Text>上车
                                        </View>
                                        <Image className="now-site-icon" src={rightIcon} />
                                    </View>
                                </View>
                                {walkingTime > 0 && <View className="now-site-walk">步行{formatWalkingTime(walkingTime)}</View>}
                            </View>
                        ))
                    }
                </View>
            </View>
            <View className="select-site">
                <Image className="site-top-bg" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg.png' />
                <View className="site-boarding-point">
                    {/* 起点 */}
                    <View>
                        <View onClick={() => toSeleteSitePage('start')} className="now-site">
                            <View className="now-site-dot"></View>
                            <View className="now-site-address">
                                <View className="now-site-address-content">
                                    从<Text className="now-site-address-text">{startSite.name}</Text>上车
                            {/* 终点 */}
                            <View onClick={() => toSeleteSitePage('end')} className="to-site">
                                <View className="to-site-dot"></View>
                                <View className="to-site-address">
                                    <View className="to-site-text">{endSite.name}</View>
                                </View>
                                <Image className="now-site-icon" src={rightIcon} />
                            </View>
                        </View>
                        {walkingTime > 0 && <View className="now-site-walk">步行{formatWalkingTime(walkingTime)}</View>}
                    </View>
                    {/* 终点 */}
                    <View onClick={() => toSeleteSitePage('end')} className="to-site">
                        <View className="to-site-dot"></View>
                        <View className="to-site-address">
                            <View className="to-site-text">{endSite.name}</View>
                        <View className="route-content">
                            <View className="route-content-top">
                                <View className="route-content-txt">预估等待{fareEstimate.waitingTime || '-'}分钟 | </View>
                                <View className="route-content-txt">限乘{fareEstimate.maxPeopleNumber || '-'}人</View>
                            </View>
                            <View className="route-content-bottom">
                                <View onClick={handleOpenPassengerNotice} className="route-content-bottom-left">
                                    <Image className="tip-icon" src={tipDefaultIcon} />
                                    <View>乘车须知</View>
                                </View>
                            </View>
                        </View>
                    </View>
                </View>
                <View className="route-content">
                    <View className="route-content-top">
                        <View className="route-content-txt">预估等待{fareEstimate.waitingTime || '-'}分钟 | </View>
                        <View className="route-content-txt">限乘{fareEstimate.maxPeopleNumber || '-'}人</View>
                    </View>
                    <View className="route-content-bottom">
                        <View onClick={handleOpenPassengerNotice} className="route-content-bottom-left">
                <View className="confirm-btn-box">
                    <View className="money-box">
                        <View className="route-content-top-money" onClick={() => toPriceDescription()} >
                            <Image className="tip-icon" src={tipDefaultIcon} />
                            <View>乘车须知</View>
                            预估
                            <Text className="money-num">{fareEstimate.finalFareStr || '-'}</Text>
                            元
                        </View>
                        <View className="route-content-bottom-right">
                            {
                                fareEstimate.discountAmount > 0 &&
                                <View className="discount-amount">已优惠{fareEstimate.discountAmount}元</View>
                            }
                            {
                                fareEstimate.originalFareStr > 0 &&
                                <View className="original-fare">{fareEstimate.originalFareStr || '-'}元</View>
                            }
                        </View>
                    </View>
                </View>
            </View>
            <View className="confirm-btn-box">
                <View className="money-box">
                    <View className="route-content-top-money" onClick={() => toPriceDescription()} >
                        <Image className="tip-icon" src={tipDefaultIcon} />
                        预估
                        <Text className="money-num">{fareEstimate.finalFareStr || '-'}</Text>
                        元
                    </View>
                    <View className="route-content-bottom-right">
                        {
                            fareEstimate.discountAmount > 0 &&
                            <View className="discount-amount">已优惠{fareEstimate.discountAmount}元</View>
                        }
                        {
                            fareEstimate.originalFareStr > 0 &&
                            <View className="original-fare">{fareEstimate.originalFareStr || '-'}元</View>
                        }
                    <View className="confirm-btn-wrapper">
                        <View onClick={() => !isCallDisabled && callImmediately()} className="confirm-btn">立即呼叫</View>
                        {isCallDisabled && <View className="confirm-btn-mask" />}
                    </View>
                </View>
                <View className="confirm-btn-wrapper">
                    <View onClick={() => !isCallDisabled && callImmediately()} className="confirm-btn">立即呼叫</View>
                    {isCallDisabled && <View className="confirm-btn-mask" />}
                </View>
            </View>
            <RoutePreferencePopup
                show={showPreferencePopup}
                value={preference}
                onClose={() => setShowPreferencePopup(false)}
                onSelect={(val) => setPreference(val)}
            />
        </View>
    )
}
src/robotaxi/pages/begin-route/components/route-preference-popup/index.less
New file
@@ -0,0 +1,98 @@
.route-preference-popup {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    pointer-events: none;
    &.show {
        pointer-events: auto;
    }
    .popup-mask {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.4);
        z-index: 1;
    }
    .popup-content {
        position: relative;
        z-index: 2;
        background: #FFFFFF;
        border-radius: 40px 40px 0 0;
        width: 100%;
        display: flex;
        flex-direction: column;
        transform: translateY(100%);
    }
    .popup-header {
        height: 112px;
        padding-top: 48px;
        display: flex;
        justify-content: center;
        position: relative;
        flex-shrink: 0;
        .popup-title {
            font-size: 32px;
            font-weight: 500;
            line-height: 32px;
            color: rgba(0, 0, 0, 0.9);
        }
        .close-icon {
            position: absolute;
            top: 48px;
            right: 32px;
            width: 40px;
            height: 40px;
        }
    }
    .popup-list-wrap {
        position: relative;
        overflow: hidden;
        margin-top: 46px;
        padding-left: 40px;
        padding-right: 44px;
        padding-bottom: constant(safe-area-inset-bottom);
        padding-bottom: env(safe-area-inset-bottom);
        .popup-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            height: 88px;
            &:last-child {
                border-bottom: none;
            }
            .popup-name {
                font-size: 32px;
                color: rgba(0, 0, 0, 0.9);
            }
            .select-icon {
                width: 28px;
                height: 18.67px;
            }
            &.active {
                .popup-name {
                    color: #6D7FD9;
                }
            }
        }
    }
}
src/robotaxi/pages/begin-route/components/route-preference-popup/index.tsx
New file
@@ -0,0 +1,83 @@
import Taro from '@tarojs/taro';
import { View, Text, Image, ScrollView } from '@tarojs/components';
import { useEffect, useState } from 'react';
import './index.less';
import closeIcon from '../../../../assets/close-icon.png';
import selectTypeLineIcon from '../../../../assets/select-type-line.png';
type Props = {
  show: boolean;
  value: string;
  onClose: () => void;
  onSelect: (item: string) => void;
};
const OPTIONS = [
  { label: '智能推荐', value: '智能推荐' },
  { label: '时间优先', value: '时间优先' },
  { label: '低价优先', value: '低价优先' }
];
export default function RoutePreferencePopup({ show, value, onClose, onSelect }: Props) {
  const [visible, setVisible] = useState(false);
  const [animationData, setAnimationData] = useState<any>();
  useEffect(() => {
    if (show && !visible) {
      setVisible(true);
      const animation = Taro.createAnimation({
        duration: 300,
        timingFunction: 'ease-out'
      });
      animation.translateY(0).step();
      setAnimationData(animation.export());
    } else if (!show && visible) {
      const animation = Taro.createAnimation({
        duration: 300,
        timingFunction: 'ease-in'
      });
      animation.translateY('100%').step();
      setAnimationData(animation.export());
      setTimeout(() => {
        setVisible(false);
      }, 300);
    }
  }, [show]);
  if (!visible && !show) return null;
  return (
    <View className={`route-preference-popup ${show ? 'show' : ''}`} catchMove>
      <View className='popup-mask' onClick={onClose}></View>
      <View className='popup-content' animation={animationData}>
        <View className='popup-header'>
          <Text className='popup-title'>路线偏好设置</Text>
          <Image className='close-icon' src={closeIcon} onClick={onClose} />
        </View>
        <View className='popup-list-wrap'>
            <View className='popup-list-inner'>
              {OPTIONS.map((item, index) => {
                const isSelected = item.value === value;
                return (
                  <View
                    key={item.value || index}
                    className={`popup-item ${isSelected ? 'active' : ''}`}
                    onClick={() => {
                      onSelect(item.value);
                      onClose();
                    }}
                  >
                    <View className='popup-name'>{item.label}</View>
                    {isSelected && (
                      <Image className='select-icon' src={selectTypeLineIcon} />
                    )}
                  </View>
                );
              })}
            </View>
        </View>
      </View>
    </View>
  );
}
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -80,6 +80,10 @@
  handleOpenEvaluatePopup: () => void;
  /** 详情展开回调 */
  handleToggleDetailFade: (expanded: boolean, hasFare: boolean) => void;
  /** 路线选择展开状态 */
  isRouteSelectExpanded: boolean;
  /** 路线选择状态切换 */
  onToggleRouteSelect: (expanded: boolean) => void;
  /** 后备箱状态 */
  trunkStatus: boolean;
};
@@ -192,6 +196,8 @@
          handleOpenPassengerNotice={deps.handleOpenPassengerNotice}
          walkingTime={deps.walkingTime}
          isCallDisabled={deps.isCallDisabled}
          isRouteSelectExpanded={deps.isRouteSelectExpanded}
          onToggleRouteSelect={deps.onToggleRouteSelect}
        />
      );
    }
src/robotaxi/pages/begin-route/index.less
@@ -26,7 +26,8 @@
    align-items: center;
    flex-direction: column;
    gap: 8px;
    border-radius: 138px;
    border-radius: 24px;
    box-shadow: 0px 4px 16px 0px rgba(84, 92, 143, 0.1);
    background: rgba(255, 255, 255, 1);
    overflow: hidden;
    padding: 8px 12px;
@@ -232,4 +233,4 @@
.pr-0 {
    padding-right: 0;
}
}
src/robotaxi/pages/begin-route/index.tsx
@@ -37,6 +37,7 @@
import suggest from '../../../robotaxi/assets/suggest.png'
import originIcon from '../../../robotaxi/assets/origin-icon.png'
import selectRouteIcon from '../../../robotaxi/assets/select-route.png'
import packUpIcon from '../../../robotaxi/assets/pack-up.png'
import walkIcon from '../../../robotaxi/assets/walk-icon.png'
import { _ROBOTAXI_WS_HOST } from '../../../constants/api';
@@ -91,6 +92,7 @@
  const [customCalloutIconSrc, setCustomCalloutIconSrc] = useState(customCalloutIconUrl); // 自定义气泡图标源(本地缓存路径)
  const [waitingCalloutBgSrc, setWaitingCalloutBgSrc] = useState(waitingCalloutBgUrl); // 等待气泡背景图源
  const [waitingRadarSrc, setWaitingRadarSrc] = useState(waitingRadarUrl); // 等待雷达动画图源
  const [isRouteSelectExpanded, setIsRouteSelectExpanded] = useState(false); // 路线选择展开状态
  const [resetDetailVersion, setResetDetailVersion] = useState(0); // 重置详情版本号,用于触发组件更新
  const [priceUpdateVersion, setPriceUpdateVersion] = useState(0); // 价格更新版本号
  const [evaluateSubmitVersion, setEvaluateSubmitVersion] = useState(0); // 评价提交版本号
@@ -442,10 +444,20 @@
    }
    updateMapViewportPadding();
  };
  useEffect(() => {
    if (isRouteSelectExpanded) {
      setOriginOpacity(0);
    } else {
      setOriginOpacity(1);
    }
  }, [isRouteSelectExpanded]);
  const { renderMapOverlays, renderTripCard } = useViewDelegates({
    isAndroid,
    tripStatus,
    markers,
    isRouteSelectExpanded,
    onToggleRouteSelect: setIsRouteSelectExpanded,
    shouldShowWaitingRadar,
    waitingRadarVisible,
    waitingRadarStyle,
@@ -523,6 +535,9 @@
      originOpacity={originOpacity}
      toOrigin={toOrigin}
      selectRouteIcon={selectRouteIcon}
      packUpIcon={packUpIcon}
      onSelectRouteClick={() => setIsRouteSelectExpanded(!isRouteSelectExpanded)}
      isRouteSelectExpanded={isRouteSelectExpanded}
      walkIcon={walkIcon}
      originIcon={originIcon}
      renderTripCard={renderTripCard}
src/robotaxi/pages/select-map-site/index.less
@@ -26,7 +26,8 @@
    align-items: center;
    flex-direction: column;
    gap: 8px;
    border-radius: 138px;
    border-radius: 24px;
    box-shadow: 0px 4px 16px 0px rgba(84, 92, 143, 0.1);
    background: rgba(255, 255, 255, 1);
    overflow: hidden;
    padding: 8px 12px;
@@ -309,4 +310,4 @@
            background: rgba(0, 0, 0, 0.15);
        }
    }
}
}