| | |
| | | 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' |
| | |
| | | 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({ |
| | |
| | | } |
| | | |
| | | 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> |
| | | ) |
| | | } |