ai-coding
2026-07-28 f9eef8dc9a60893b1dfec5f9577353a7dc41550d
feat(trunk): 新增后备箱开启二次确认弹窗,关闭温控相关接口错误提示

Co-Authored-By: 甄雪君 <zhenxj@xiaopeng.com>
2个文件已添加
2个文件已修改
198 ■■■■■ 已修改文件
src/robotaxi/api/begin-route.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/trunk-confirm-popup/index.less 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/trunk-confirm-popup/index.tsx 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/begin-route.ts
@@ -145,7 +145,8 @@
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/ac/set-temperature',
        method: 'POST',
        payload
        payload,
        showToast: false
    }, true);
}
@@ -198,7 +199,8 @@
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/airConditionerUnlock',
        method: 'POST',
        payload
        payload,
        showToast: false
    }, true);
}
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -6,6 +6,7 @@
import { TripStatus } from '../../../../constants/trip-status';
import { showToast } from '../../../../components/toast';
import useMinimumSpinLoading from './use-minimum-spin-loading';
import TrunkConfirmPopup from './trunk-confirm-popup';
const LOCK_CLOSE_ICON_URL = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/lock-close-20260726.png';
const LOCK_OPEN_ICON_URL = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/lock-open-20260726.png';
@@ -32,6 +33,7 @@
}: Props) {
    const [trunkStatus, setTrunkStatus] = useState(orderTrunkStatus); // 后备箱开关状态
    const [lockStatus, setLockStatus] = useState(false); // 车门开关状态
    const [trunkConfirmVisible, setTrunkConfirmVisible] = useState(false); // 后备箱开启二次确认弹窗
    const trunkPollTimerRef = useRef<any>(null); // 后备箱状态轮询定时器
    const overRoutePopupDebounceTimerRef = useRef<any>(null); // 结束行程弹窗防抖定时器
    const OVER_ROUTE_POPUP_DEBOUNCE_MS = 1000; // 结束行程弹窗点击防抖时长
@@ -245,6 +247,29 @@
        });
    };
    // 点击后备箱:当前为关闭态(将执行开启)时弹二次确认,开启态(将执行关闭)直接执行
    const handleTrunkCardClick = () => {
        if (loadingControlType) {
            return;
        }
        if (!trunkStatus) {
            setTrunkConfirmVisible(true);
            return;
        }
        handleUnlock('trunk', () => setTrunkStatus(!trunkStatus));
    };
    // 二次确认弹窗:确认开启
    const handleTrunkConfirm = () => {
        setTrunkConfirmVisible(false);
        handleUnlock('trunk', () => setTrunkStatus(!trunkStatus));
    };
    // 二次确认弹窗:取消
    const handleTrunkCancel = () => {
        setTrunkConfirmVisible(false);
    };
    /** 渲染带 loading 效果的控制按钮图标。 */
    const renderControlIcon = (controlType: 'door' | 'trunk', defaultIconSrc: string, className: string) => {
        const isLoading = loadingControlType === controlType;
@@ -264,8 +289,9 @@
    if (tripStatus === TripStatus.VehicleArrivedPickup) {
        return (
            <>
            <View className="car-card-arrive">
                <View className={`car-card-arrive-item ${trunkStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={() => handleUnlock('trunk', () => setTrunkStatus(!trunkStatus))}>
                <View className={`car-card-arrive-item ${trunkStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={handleTrunkCardClick}>
                    <View className="car-card-arrive-item-text">后备箱</View>
                    <View className="car-card-arrive-item-status">{trunkStatus ? '点击关闭' : '点击打开'}</View>
                    {renderControlIcon('trunk', trunkStatus ? TRUNK_OPEN_ICON_URL : TRUNK_CLOSE_ICON_URL, 'car-card-arrive-item-img')}
@@ -276,17 +302,20 @@
                    {renderControlIcon('door', lockStatus ? LOCK_OPEN_ICON_URL : LOCK_CLOSE_ICON_URL, 'car-card-arrive-item-img')}
                </View>
            </View>
            <TrunkConfirmPopup show={trunkConfirmVisible} onCancel={handleTrunkCancel} onConfirm={handleTrunkConfirm} />
            </>
        );
    }
    if (tripStatus === TripStatus.ArrivedDestination) {
      // !! 6.30修改,车端会给我们传信号,说已到达目的地,小程序就会从前往目的地这个页面流转到行程结束待支付;意思是相当于这个不会展示
        return (
            <>
            <View className="car-card-reach">
                <View className="car-card-reach-text">点击按钮开车门</View>
                <Image className='car-card-reach-img' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/door-anim.webp' />
                <View className="car-card-reach-btn">
                    <View className={`car-card-reach-btn-item ${trunkStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={() => handleUnlock('trunk', () => setTrunkStatus(!trunkStatus))}>
                    <View className={`car-card-reach-btn-item ${trunkStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={handleTrunkCardClick}>
                        {renderControlIcon('trunk', trunkStatus ? TRUNK_OPEN_ICON_URL : TRUNK_CLOSE_ICON_URL, 'car-card-reach-btn-item-img')}
                        <View className="car-card-reach-btn-item-text">后备箱</View>
                    </View>
@@ -296,6 +325,8 @@
                    </View>
                </View>
            </View>
            <TrunkConfirmPopup show={trunkConfirmVisible} onCancel={handleTrunkCancel} onConfirm={handleTrunkConfirm} />
            </>
        );
    }
src/robotaxi/pages/begin-route/components/calling-car-modules/trunk-confirm-popup/index.less
New file
@@ -0,0 +1,75 @@
.trunk-confirm-popup {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 99999;
    display: flex;
    align-items: center;
    justify-content: center;
    .trunk-confirm-mask {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.8);
    }
    .trunk-confirm-content {
        position: relative;
        width: 600rpx;
        box-sizing: border-box;
        display: flex;
        flex-direction: column;
        align-items: center;
        padding: 72rpx 40rpx 48rpx 40rpx;
        gap: 64rpx;
        border-radius: 48rpx;
        background: #FFFFFF;
        text-align: center;
    }
    .trunk-confirm-title {
        font-family: PingFang SC;
        font-size: 36rpx;
        font-weight: 500;
        line-height: 52rpx;
        color: rgba(0, 0, 0, 0.9);
    }
    .trunk-confirm-btns {
        display: flex;
        flex-direction: row;
        align-items: center;
        width: 100%;
        gap: 20rpx;
    }
    .trunk-confirm-btn {
        flex: 1;
        height: 110rpx;
        box-sizing: border-box;
        display: flex;
        justify-content: center;
        align-items: center;
        border-radius: 110rpx;
        font-family: PingFang SC;
        font-size: 36rpx;
        font-weight: 500;
        line-height: 52rpx;
    }
    .trunk-confirm-btn-cancel {
        background: #FFFFFF;
        border: 4rpx solid #141E27;
        color: #141E27;
    }
    .trunk-confirm-btn-primary {
        background: #141E27;
        color: #FFFFFF;
    }
}
src/robotaxi/pages/begin-route/components/calling-car-modules/trunk-confirm-popup/index.tsx
New file
@@ -0,0 +1,82 @@
import Taro from '@tarojs/taro';
import { View } from '@tarojs/components';
import { useEffect, useRef, useState } from 'react';
import './index.less';
type Props = {
    /** 是否展示弹窗 */
    show?: boolean;
    /** 点击「取消」回调 */
    onCancel?: () => void;
    /** 点击「确认」回调 */
    onConfirm?: () => void;
};
const ANIMATION_DURATION_MS = 300; // 弹窗开合动画时长
/**
 * 后备箱开启二次确认弹窗(居中,取消/确认双按钮)。
 * 容器/遮罩/标题沿用项目居中弹窗规范(over-popup),新增横向双按钮 + 实心主按钮。
 */
export default function TrunkConfirmPopup({ show = false, onCancel, onConfirm }: Props) {
    const [visible, setVisible] = useState(false); // 关闭动画保活标记
    const [animationData, setAnimationData] = useState<any>();
    const closeTimerRef = useRef<any>(null);
    // 控制弹窗开合:打开时立即渲染并入场,关闭时保留退场动画
    useEffect(() => {
        if (closeTimerRef.current) {
            clearTimeout(closeTimerRef.current);
            closeTimerRef.current = null;
        }
        if (show) {
            setVisible(true);
            const animation = Taro.createAnimation({ duration: ANIMATION_DURATION_MS, timingFunction: 'ease-out' });
            animation.opacity(1).scale(1).step();
            setAnimationData(animation.export());
            return;
        }
        if (!visible) {
            return;
        }
        const animation = Taro.createAnimation({ duration: ANIMATION_DURATION_MS, timingFunction: 'ease-in' });
        animation.opacity(0).scale(0.8).step();
        setAnimationData(animation.export());
        closeTimerRef.current = setTimeout(() => {
            setVisible(false);
            closeTimerRef.current = null;
        }, ANIMATION_DURATION_MS);
    }, [show, visible]);
    // 卸载时清理定时器
    useEffect(() => {
        return () => {
            if (closeTimerRef.current) {
                clearTimeout(closeTimerRef.current);
            }
        };
    }, []);
    if (!visible) {
        return null;
    }
    return (
        <View className='trunk-confirm-popup' catchMove>
            <View className='trunk-confirm-mask'></View>
            <View className='trunk-confirm-content' animation={animationData}>
                <View className='trunk-confirm-title'>
                    <View>请确认后备箱周边安全</View>
                    <View>确认开启吗?</View>
                </View>
                <View className='trunk-confirm-btns'>
                    <View className='trunk-confirm-btn trunk-confirm-btn-cancel' onClick={onCancel}>取消</View>
                    <View className='trunk-confirm-btn trunk-confirm-btn-primary' onClick={onConfirm}>确认</View>
                </View>
            </View>
        </View>
    );
}