| src/robotaxi/api/begin-route.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/components/calling-car-modules/trunk-confirm-popup/index.less | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/components/calling-car-modules/trunk-confirm-popup/index.tsx | ●●●●● 补丁 | 查看 | 原始文档 | 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> ); }