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'
|
import tipBlueIcon from '../../../assets/tip-blue-icon.png'
|
import tipDefaultIcon from '../../../assets/tip-default-icon.png'
|
import selectLineIcon from '../../../assets/select-line.png'
|
import trafficSignalIcon from '../../../assets/traffic-signal.png'
|
import RoutePreferencePopup, { RoutePreferenceOption } from './route-preference-popup';
|
import { formatDurationDisplay, formatTripDistance } from '../../../../utils/Robotaxi-public-method';
|
|
type Props = {
|
startSite: any;
|
endSite: any;
|
callImmediately: () => void;
|
fareEstimate?: any;
|
handleOpenPassengerNotice?: () => void;
|
walkingTime?: number;
|
isCallDisabled?: boolean;
|
isRouteSelectExpanded?: boolean;
|
onToggleRouteSelect?: (expanded: boolean) => void;
|
routeOptions?: any[];
|
selectedRouteIndex?: number;
|
handleSelectRoute?: (index: number) => void;
|
}
|
|
export default function ReleaseRoute({ startSite, endSite, callImmediately, fareEstimate = {}, handleOpenPassengerNotice, walkingTime = 0, isCallDisabled = false, isRouteSelectExpanded = false, onToggleRouteSelect, routeOptions = [], selectedRouteIndex = 0, handleSelectRoute }: 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<RoutePreferenceOption>({ label: '智能推荐', value: '智能推荐' });
|
const openCityId = Number(Taro.getStorageSync('selectedCity')?.id || 0);
|
|
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({
|
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) {
|
const hours = Math.floor(safeMinutes / 60);
|
const remainMinutes = safeMinutes % 60;
|
return remainMinutes > 0 ? `${hours}小时${remainMinutes}分钟` : `${hours}小时`;
|
}
|
return `${safeMinutes}分钟`;
|
}
|
|
return (
|
<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.label}</View>
|
<Image src={selectLineIcon} />
|
</View>
|
</View>
|
<View className="route-select-content">
|
{
|
routeOptions.map((item, index) => {
|
const routeData = item.route || {};
|
const time = formatDurationDisplay(routeData.duration || 0);
|
const dist = formatTripDistance(routeData.distance || 0);
|
const isSelected = index === selectedRouteIndex;
|
|
return (
|
<View
|
className={`route-select-content-item ${isSelected ? 'active-item' : ''}`}
|
key={index}
|
onClick={() => handleSelectRoute && handleSelectRoute(index)}
|
>
|
<View className="route-select-content-item-title">{item.label || (index === 0 ? '智能推荐' : `路线${index + 1}`)}</View>
|
<View className="route-select-content-item-time">{time.value}{time.unit}</View>
|
<View className="route-select-content-item-distance">
|
<View>{dist}</View>
|
{typeof routeData.trafficLights !== 'undefined' && (
|
<View className="traffic-signal-item">
|
<Image className="traffic-signal-icon" src={trafficSignalIcon} />
|
<Text>{routeData.trafficLights}个</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 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>
|
</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">
|
<Image className="tip-icon" src={tipDefaultIcon} />
|
<View>乘车须知</View>
|
</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>
|
</View>
|
<View className="confirm-btn-wrapper">
|
<View onClick={() => !isCallDisabled && callImmediately()} className="confirm-btn">立即呼叫</View>
|
{isCallDisabled && <View className="confirm-btn-mask" />}
|
</View>
|
</View>
|
</View>
|
<RoutePreferencePopup
|
show={showPreferencePopup}
|
value={preference.value}
|
openCityId={openCityId}
|
onClose={() => setShowPreferencePopup(false)}
|
onSelect={(item) => setPreference(item)}
|
/>
|
</View>
|
)
|
}
|