feat(robotaxi): 新增优惠券管理、WebSocket集成与行程状态处理
- 新增优惠券管理功能,包括列表、领取、详情查看
- 集成WebSocket连接管理,支持订单状态、车辆位置、路线更新等实时消息
- 实现行程状态与订单状态的映射逻辑,完善各状态下的UI展示
- 新增起终点运营区域校验,确保在同一电子围栏内
- 优化地图标记渲染逻辑,根据行程状态动态显示上车点、车辆位置和目的地
- 新增等待时间、路线时长等接口调用,完善行程预估信息
- 更新隐私协议弹窗,增加两项隐私说明条款
- 修复客服电话接口路径和响应字段映射问题
| | |
| | | const CAR_CONTROL_BIZ_HOST = '"https://apps-biz.deploy-test.xiaopeng.com"'; |
| | | const COMMON_OSS_HOST = '"https://spre.xiaopeng.com"'; // 官渠通用 oss 资源前缀 |
| | | const ADMIN_OSS_HOST = '"https://pre-xps01.xiaopeng.com"'; // 运营后台上传的额文件的 oss 域名 |
| | | const ROBOTAXI_HOST = '"http://192.168.110.85:8000"'; // 打车接口域名 |
| | | const ROBOTAXI_HOST = '"http://192.168.110.80:8000"'; // 打车接口域名 |
| | | const ROBOTAXI_WS_HOST = '"ws://192.168.110.34:8888/websocket"'; |
| | | |
| | | const XP_APPID_MAP = { |
| | | weapp: '"xxxxxxx"', // 用户中心登录appId |
| | |
| | | COMMON_OSS_HOST, |
| | | ADMIN_OSS_HOST, |
| | | ROBOTAXI_HOST, |
| | | ROBOTAXI_WS_HOST, |
| | | }, |
| | | mini: { |
| | | // webpackChain (chain) { |
| | |
| | | export const _LDX_RESERVE_URL = LDX_RESERVE_URL |
| | | //@ts-ignore |
| | | export const _ROBOTAXI_HOST = ROBOTAXI_HOST |
| | | //@ts-ignore |
| | | export const _ROBOTAXI_WS_HOST = ROBOTAXI_WS_HOST |
| | | |
| | | // pic CDN |
| | | export const CDN = 'https://s.xiaopeng.com/xp-fe/mini' |
| | |
| | | |
| | | .link-item { |
| | | display: flex; |
| | | align-items: center; |
| | | align-items: flex-start; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | .item-name { |
| | | flex-shrink: 0; |
| | | flex: 1; |
| | | font-size: 24px; |
| | | font-weight: normal; |
| | | line-height: 40px; |
| | | letter-spacing: normal; |
| | | color: rgba(0, 0, 0, 0.6); |
| | | word-break: break-word; |
| | | white-space: normal; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | |
| | | 0: false, |
| | | 1: false, |
| | | 2: false, |
| | | 3: false, |
| | | }); |
| | | const [isAllAgree, setIsAllAgree] = useState(false); |
| | | |
| | | const AGREE_NAME = ['用户服务协议', '小鹏Robotaxi 平台隐私政策', '关于小鹏Robotaxi平台敏感个人信息处理情况的说明', '关于小鹏Robotaxi平台向第三方提供个人信息情况的说明'] |
| | | |
| | | useEffect(() => { |
| | | if (show && !visible) { |
| | |
| | | 0: !isAllAgree, |
| | | 1: !isAllAgree, |
| | | 2: !isAllAgree, |
| | | 3: !isAllAgree, |
| | | }) |
| | | } |
| | | |
| | |
| | | <View className='link-item' key={key}> |
| | | <Image onClick={checkItem(key)} className='item-radio' src={isAgree[key] ? check : uncheck} |
| | | mode='aspectFill' /> |
| | | <Text className='item-name'>《 {key === '0' ? '用户服务协议' : '小鹏Robotaxi 平台隐私政策'} 》</Text> |
| | | <View className='item-name'>《 {AGREE_NAME[key]} 》</View> |
| | | </View> |
| | | )) |
| | | } |
| | | <View className='link-item'> |
| | | <Image onClick={checkAll} className='item-radio' src={isAllAgree ? check : uncheck} |
| | | mode='aspectFill' /> |
| | | <Text className='item-name'>全部勾选</Text> |
| | | <View className='item-name'>全部勾选</View> |
| | | </View> |
| | | </View> |
| | | |
| | |
| | | } from '../../../utils/Robotaxi-public-method'; |
| | | import { getGeofence, getBannerList } from '../../../robotaxi/api/home'; |
| | | import { getOrderDistance } from '../../../robotaxi/api/select-map-site'; |
| | | import wsManager from '../../../utils/websocket'; |
| | | import { checkOtp } from '../../../utils/tools'; |
| | | |
| | | export default function Home() { |
| | | |
| | |
| | | const DEFAULT_LONGITUDE = 116.397128; // 北京故宫经度 |
| | | |
| | | const [selectedCity, setSelectedCity] = useState(Taro.getStorageSync('selectedCity') || { |
| | | name: '北京市', |
| | | code: '110000', |
| | | name: '', |
| | | code: '', |
| | | }); // 选中的城市 |
| | | |
| | | const ORDER_STATUS = { |
| | |
| | | |
| | | useDidShow(() => { |
| | | |
| | | // 检查登录状态并连接 WebSocket |
| | | checkOtp().then(otp => { |
| | | if (otp) { |
| | | // wsManager.connect('wss://your-websocket-server.com/ws'); |
| | | } |
| | | }); |
| | | |
| | | // 从本地存储中获取选中的城市 |
| | | const selectedCity = Taro.getStorageSync('selectedCity'); |
| | | if (selectedCity) { |
| | | setSelectedCity(selectedCity); |
| | | } else { |
| | | setSelectedCity({ |
| | | name: '北京市', |
| | | code: '110000', |
| | | }) |
| | | Taro.setStorageSync('selectedCity', { |
| | | name: '北京市', |
| | | code: '110000', |
| | | }); |
| | | } |
| | | // 从本地存储中获取行程状态 |
| | | const tripStatus = Taro.getStorageSync('tripStatus'); |
| | |
| | | setIsLocationAuthDenied(true); |
| | | setLatitude(DEFAULT_LATITUDE); |
| | | setLongitude(DEFAULT_LONGITUDE); |
| | | if (!selectedCity || !selectedCity.code) { |
| | | setSelectedCity({ |
| | | name: '北京市', |
| | | code: '110000', |
| | | }) |
| | | Taro.setStorageSync('selectedCity', { |
| | | name: '北京市', |
| | | code: '110000', |
| | | }); |
| | | } |
| | | } else { |
| | | // 检查缓存中是否有 startSite |
| | | const startSite = Taro.getStorageSync('startSite'); |
| | |
| | | |
| | | // 初始化时也调用接口检查运营区状态和更新站点 |
| | | updateMapData(startSite.latitude, startSite.longitude); |
| | | if (!selectedCity || !selectedCity.code) { |
| | | getAddressByLocation(startSite.longitude, startSite.latitude) |
| | | .then((data) => { |
| | | if (data?.regeocodeData?.addressComponent) { |
| | | const { city, province, adcode } = data.regeocodeData.addressComponent; |
| | | const cityName = (city && typeof city === 'string' && city.length > 0) ? city : province; |
| | | const cityCode = convertAdcodeToCityCode(adcode, cityName, province); |
| | | const cityInfo = { |
| | | name: cityName, |
| | | code: cityCode |
| | | }; |
| | | setSelectedCity(cityInfo); |
| | | Taro.setStorageSync('selectedCity', cityInfo); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | }); |
| | | } |
| | | } else { |
| | | getLocationInfo(true); |
| | | } |
| | |
| | | |
| | | if (res && Array.isArray(res)) { |
| | | const newPolygons = formatGeofencePolygons(res); |
| | | console.log(newPolygons); |
| | | |
| | | setPolygons(newPolygons); |
| | | } else { |
| | |
| | | |
| | | try { |
| | | // 更新地图数据 |
| | | const { newMarkers } = await updateMapData(res.latitude, res.longitude); |
| | | const { newMarkers, sites } = await updateMapData(res.latitude, res.longitude); |
| | | |
| | | const { targetMarker, minDistance } = findNearestMarker(res.latitude, res.longitude, newMarkers); |
| | | const { targetMarker, minDistance } = findNearestMarker(res.latitude, res.longitude, sites.length > 0 ? sites : newMarkers); |
| | | |
| | | if (minDistance < adsorptionDistance && targetMarker) { |
| | | setIsAdsorbed(true); |
| | |
| | | |
| | | try { |
| | | // 更新地图数据 |
| | | const { newMarkers } = await updateMapData(lat, lng); |
| | | const { newMarkers, sites } = await updateMapData(lat, lng); |
| | | |
| | | // 自动吸附逻辑:查找最近的推荐上车点 |
| | | const { targetMarker, minDistance } = findNearestMarker(lat, lng, newMarkers); |
| | | const nearestCandidates = sites.length > 0 ? sites : newMarkers; |
| | | const { targetMarker, minDistance } = findNearestMarker(lat, lng, nearestCandidates); |
| | | |
| | | // 只有用户手势拖动触发的移动才进行吸附判断,避免 moveToLocation 触发死循环 |
| | | if ((causedBy === 'gesture' || causedBy === 'drag' || causedBy === 'scale') && targetMarker && minDistance < adsorptionDistance) { |
| | |
| | | } |
| | | |
| | | const toSeleteSitePage = (type: string) => { |
| | | Taro.setStorageSync('startSite', currentSite); |
| | | if (!electronicAreaId) { |
| | | Taro.showToast({ |
| | | title: '请先移动到运营区内', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | if (type == 'end') { |
| | | Taro.setStorageSync('startSite', currentSite); |
| | | } |
| | | Taro.navigateTo({ |
| | | url: `/robotaxi/pages/site-search/index?pageType=${type}&electronicAreaId=${electronicAreaId}&longitude=${longitude}&latitude=${latitude}` |
| | | }) |
| | |
| | | } |
| | | |
| | | const toBeginRoute = () => { |
| | | if (currentSite && currentSite.name) { |
| | | Taro.setStorageSync('startSite', currentSite); |
| | | } |
| | | Taro.setStorageSync('endSite', { |
| | | latitude: 30.571100, |
| | | longitude: 104.065200, |
| | | name: '中国南方电网·3号门' |
| | | }); |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/begin-route/index', |
| | | }) |
| | | } |
| | | |
| | | const toCoupon = () => { |
| | |
| | | { |
| | | (!isOperatingArea || isAdsorbed) && ( |
| | | <View className="toolTip-bg"> |
| | | <Text className="toolTip-bg-text">{!isOperatingArea ? '不在运营区' : '限停3分钟'}</Text> |
| | | <Text className="toolTip-bg-text">{!isOperatingArea ? '不在运营区' : `限停${currentSite.time}分钟`}</Text> |
| | | </View> |
| | | ) |
| | | } |
| | |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | | } |
| | | |
| | | // 保存订单 |
| | | export const saveOrder = async (payload) => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/operation/base/v1/mini-program/order/save', |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | | } |
| | | |
| | | // 获取等待时间 |
| | | export const getWaitTime = async (payload) => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/operation/base/v1/mini-program/pricing/getWaitTime', |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | | } |
| New file |
| | |
| | | import request from '../../utils/request'; |
| | | import { _ROBOTAXI_HOST } from '../../constants/api'; |
| | | |
| | | export interface CouponItem { |
| | | couponId: string; |
| | | title: string; |
| | | discountAmount: number; |
| | | expireTime: string; |
| | | status: number; // 1-未使用 2-已使用 3-已过期 0待领取 |
| | | } |
| | | |
| | | export interface CouponDetail { |
| | | couponId: string; |
| | | title: string; |
| | | expireTime: string; |
| | | validTimeRange: string; |
| | | applicableCities: string[]; |
| | | usageInstructions: string; |
| | | } |
| | | |
| | | export interface GetCouponListPayload { |
| | | pageNum: number; |
| | | pageSize: number; |
| | | status?: number | string; |
| | | } |
| | | |
| | | export interface GetCouponListResponse { |
| | | list: CouponItem[]; |
| | | total: number; |
| | | } |
| | | |
| | | // 优惠券列表 |
| | | export const getCouponList = async (payload: GetCouponListPayload): Promise<GetCouponListResponse> => { |
| | | const res = await request({ |
| | | url: _ROBOTAXI_HOST + '/account/api/v1/coupon/list', |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | | |
| | | // 手动转换下划线字段为驼峰 |
| | | const list = (res.list || []).map((item: any) => ({ |
| | | couponId: item.coupon_id, |
| | | title: item.title, |
| | | discountAmount: item.discount_amount, |
| | | expireTime: item.expire_time, |
| | | status: item.status, |
| | | })); |
| | | |
| | | return { |
| | | total: res.total || 0, |
| | | list |
| | | }; |
| | | } |
| | | |
| | | // 领取优惠券 |
| | | export const receiveCoupon = async (payload: { couponId: string }) => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/account/api/v1/coupon/receive', |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | | } |
| | | |
| | | // 优惠券详情 |
| | | export const getCouponDetail = async (couponId: string): Promise<CouponDetail> => { |
| | | const res = await request({ |
| | | url: _ROBOTAXI_HOST + `/account/api/v1/coupon/detail/${couponId}`, |
| | | method: 'POST' |
| | | }); |
| | | |
| | | // 手动转换下划线字段为驼峰 |
| | | return { |
| | | couponId: res.coupon_id, |
| | | title: res.title, |
| | | expireTime: res.expire_time, |
| | | validTimeRange: res.valid_time_range, |
| | | applicableCities: res.applicable_cities || [], |
| | | usageInstructions: res.usage_instructions |
| | | }; |
| | | } |
| | |
| | | |
| | | export const getServicePhone = async () => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/base/v1/mini-program/support/phone', |
| | | url: _ROBOTAXI_HOST + '/operation/base/v1/mini-program/support/phone', |
| | | method: 'GET' |
| | | }); |
| | | } |
| | |
| | | useEffect(() => { |
| | | if (show && !visible) { |
| | | getServicePhone().then((res) => { |
| | | setPhoneNumber(res?.data?.phone_number); |
| | | setPhoneNumber(res?.msg); |
| | | setTimeout(() => { |
| | | Taro.hideTabBar({ animation: true }); |
| | | setVisible(true); |
| New file |
| | |
| | | socke消息status:0=已取消,1=待接单,2=待出发,3=接驾中,4=待客上车,5=乘客已上车,6=服务中,7=待客下车,8=待付款,9=待评价,10=已完成 |
| | | 页面tripStatus:1:发布行程 2:发布中 3:呼叫车中 4:车辆即将到达 5:车辆到达上车点 6:车辆行驶中 7:到达目的地 8:行程已结束 9:支付成功 |
| | | |
| | | 对应关系(status:tripStatus、tripStatus) |
| | | 0:弹出showCancelPopup,tripStatus:1 |
| | | 1:3,判断是否有首次标识passengerNoticeShowed来弹出PassengerNotice |
| | | 2:3 |
| | | 3:4 |
| | | 4:5,弹出AnnouncementsPopup |
| | | 5:6 |
| | | 6:6 |
| | | 7:7 |
| | | 8:8 |
| | | 9:9 |
| | | 10:9 |
| | | |
| | | |
| | | tripStatus不同状态标点和线路渲染逻辑,路线都是从socket消息来,车辆点位也是 |
| | | 4:渲染上车点、车辆点位、车辆位置公里、路线 |
| | | 5、6:渲染车辆点位、车辆位置公里、下车点、路线 |
| | | 7:渲染车辆点位、下车点 |
| | | 8、9::渲染车辆上车点、下车点、路线 |
| | |
| | | import activeGood from '../../../assets/active-good.png' |
| | | import activeBad from '../../../assets/active-bad.png' |
| | | import activeNormal from '../../../assets/active-normal.png' |
| | | import { getWaitTime } from '../../../api/begin-route'; |
| | | |
| | | type Props = { |
| | | tripStatus?: number; |
| | |
| | | const [tempStatus, setTempStatus] = useState(19); |
| | | const [showDetail, setShowDetail] = useState(false); |
| | | const [activeIndex, setActiveIndex] = useState(0) // 默认选中"很满意" |
| | | |
| | | // 使用 useRef 保存最新的 updateStatus 回调,避免因父组件重绘导致定时器重置 |
| | | const updateStatusRef = useRef(updateStatus); |
| | | useEffect(() => { |
| | | updateStatusRef.current = updateStatus; |
| | | }, [updateStatus]); |
| | | |
| | | // 自动更新状态逻辑 |
| | | useEffect(() => { |
| | | let timer: any = null; |
| | | if (tripStatus >= 3 && tripStatus < 7) { |
| | | timer = setInterval(() => { |
| | | if (updateStatusRef.current) { |
| | | updateStatusRef.current(); |
| | | } |
| | | }, 5000); |
| | | } |
| | | return () => { |
| | | if (timer) clearInterval(timer); |
| | | }; |
| | | }, [tripStatus]); |
| | | const [waitMinutes, setWaitMinutes] = useState<number | null>(null); |
| | | const [routeDuration, setRouteDuration] = useState<number | null>(null); |
| | | const [limitMinutes, setLimitMinutes] = useState<number | null>(null); |
| | | |
| | | const titleMap = { |
| | | 3: <>预计<View className="time">6</View>分钟内有车应答</>, |
| | | 4: <>车辆还有<View className="time">15</View>分钟到达</>, |
| | | 3: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>, |
| | | 4: <>车辆还有<View className="time">{routeDuration}</View>分钟到达</>, |
| | | 5: <>车辆已到达上车点<View className="time">04:49</View></>, |
| | | 6: <>预计<View className="time">45</View>分钟抵达目的地</>, |
| | | 7: <>已到达目的地<View className="time">04:49</View></>, |
| | |
| | | |
| | | const descMap = { |
| | | 3: { text: '正全力为您叫车中,请耐心等待...', className: 'calling-car-content-desc' }, |
| | | 4: { text: '此处限停5分钟,请尽快前往上车点', className: 'calling-car-content-warning' }, |
| | | 4: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: 'calling-car-content-warning' } : null, |
| | | 5: { text: '此处限停,请在倒计时结束前到达上车点', className: 'calling-car-content-desc' }, |
| | | 6: { text: '行程已开始,请全程系好安全带', className: 'calling-car-content-desc' }, |
| | | 7: { text: '倒计时结束时车辆将自动驶离,请带好物品安全下车', className: 'calling-car-content-desc' }, |
| | | 8: { text: '订单待支付,请核对后完成支付', className: 'calling-car-content-desc' } |
| | | } |
| | | |
| | | useEffect(() => { |
| | | let isActive = true; |
| | | if (tripStatus === 3) { |
| | | const s = Taro.getStorageSync('startSite'); |
| | | const payload = { |
| | | latitude: s?.latitude, |
| | | longitude: s?.longitude |
| | | }; |
| | | getWaitTime(payload).then((res) => { |
| | | const minutes = Number(res); |
| | | if (isActive && !Number.isNaN(minutes)) { |
| | | setWaitMinutes(minutes); |
| | | } |
| | | }).catch(() => { }); |
| | | } else if (tripStatus === 4) { |
| | | const duration = Number(Taro.getStorageSync('orderRouteDuration')); |
| | | if (!Number.isNaN(duration)) { |
| | | setRouteDuration(duration); |
| | | } |
| | | const s = Taro.getStorageSync('startSite'); |
| | | const time = Number(s?.time); |
| | | if (!Number.isNaN(time)) { |
| | | setLimitMinutes(time); |
| | | } |
| | | } |
| | | return () => { |
| | | isActive = false; |
| | | }; |
| | | }, [tripStatus]); |
| | | |
| | | // 切换评价选项 |
| | | const changeEvaluate = (index: number) => { |
| | |
| | | 车身编码: 03 |
| | | </View> |
| | | </View> |
| | | <Image className="car-card-detail-img" src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e76e8fd6-1f52-482f-b9c0-6a026f4d2797.png' /> |
| | | <Image className="car-card-detail-img" src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/825d7fa2-6cf6-4a31-aaac-3a4de806e9a3.png' /> |
| | | </View> |
| | | { |
| | | tripStatus == 4 && <View> |
| | |
| | | } |
| | | </View> |
| | | ); |
| | | } |
| | | } |
| | |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: rgba(0, 0, 0, 0.5); |
| | | background: rgba(0, 0, 0, 0); |
| | | } |
| | | |
| | | .announcements-content { |
| | |
| | | import coupon from '../../../robotaxi/assets/coupon.png' |
| | | import suggest from '../../../robotaxi/assets/suggest.png' |
| | | import originIcon from '../../../robotaxi/assets/origin-icon.png' |
| | | import { queryRouteAndFare } from '../../../robotaxi/api/begin-route'; |
| | | import wsManager from '../../../utils/websocket'; |
| | | import { saveOrder, queryRouteAndFare } from '../../../robotaxi/api/begin-route'; |
| | | import { _ROBOTAXI_WS_HOST } from '../../../constants/api'; |
| | | |
| | | export default function SelectMapSite() { |
| | | |
| | |
| | | const [showEvaluatePopup, setShowEvaluatePopup] = useState(false); // 评价弹窗是否显示 |
| | | const [routePoints, setRoutePoints] = useState<any[]>([]); // 路线点集合 |
| | | const [fareEstimate, setFareEstimate] = useState<any>({}); // 预估费用信息 |
| | | const lastRequestParams = useRef<string>(''); // 记录上一次请求参数,防止重复调用 |
| | | const [vehicleLocation, setVehicleLocation] = useState<{ latitude: number, longitude: number } | null>(null); |
| | | const [routeDistance, setRouteDistance] = useState<number | null>(null); |
| | | const [routeDuration, setRouteDuration] = useState<number | null>(null); |
| | | const tripStatusRef = useRef(tripStatus); |
| | | const routeRequestKeyRef = useRef<string>(''); |
| | | const shouldShowRoute = [4, 5, 6, 8, 9].includes(tripStatus); |
| | | const authSuccessBound = useRef(false); // 认证成功是否绑定 |
| | | const messageBound = useRef(false); // 消息是否绑定 |
| | | const orderStatusBound = useRef(false); // 订单状态是否绑定 |
| | | const orderRouteBound = useRef(false); // 订单路线是否绑定 |
| | | const vehicleLocationBound = useRef(false); |
| | | const authSuccessHandlerRef = useRef<(data: any) => void>(); // 认证成功处理函数 |
| | | const messageHandlerRef = useRef<(data: any) => void>(); // 消息处理函数 |
| | | const orderStatusHandlerRef = useRef<(data: any) => void>(); // 订单状态处理函数 |
| | | const orderRouteHandlerRef = useRef<(data: any) => void>(); // 订单路线处理函数 |
| | | const vehicleLocationHandlerRef = useRef<(data: any) => void>(); |
| | | |
| | | // 应用路线点 |
| | | const applyRoutePoints = (points: any[]) => { |
| | | if (Array.isArray(points) && points.length > 0) { |
| | | setRoutePoints(points); |
| | | } else { |
| | | setRoutePoints([]); |
| | | } |
| | | }; |
| | | |
| | | // 获取存储的路线点 |
| | | const getStoredRoutePoints = (status?: number) => { |
| | | const cached = Taro.getStorageSync('orderRoutePoints') || {}; |
| | | const preferredType = typeof status === 'number' && status >= 6 ? 2 : 1; |
| | | const preferred = cached[String(preferredType)]; |
| | | if (Array.isArray(preferred) && preferred.length > 0) { |
| | | return preferred; |
| | | } |
| | | const latestType = Taro.getStorageSync('orderRouteLatestType'); |
| | | const latest = cached[String(latestType)]; |
| | | if (Array.isArray(latest) && latest.length > 0) { |
| | | return latest; |
| | | } |
| | | if (Array.isArray(cached.latest) && cached.latest.length > 0) { |
| | | return cached.latest; |
| | | } |
| | | const keys = Object.keys(cached); |
| | | for (const key of keys) { |
| | | if (Array.isArray(cached[key]) && cached[key].length > 0) { |
| | | return cached[key]; |
| | | } |
| | | } |
| | | return []; |
| | | }; |
| | | |
| | | const requestRouteAndFare = (originLat: number, originLng: number, destLat: number, destLng: number) => { |
| | | const requestKey = `${originLng},${originLat}-${destLng},${destLat}`; |
| | | if (requestKey === routeRequestKeyRef.current) { |
| | | return; |
| | | } |
| | | routeRequestKeyRef.current = requestKey; |
| | | const selectedCity = Taro.getStorageSync('selectedCity'); |
| | | const openCityId = selectedCity ? selectedCity.id : 0; |
| | | const params = { |
| | | destination: { |
| | | latitude: destLat, |
| | | longitude: destLng |
| | | }, |
| | | openCityId: openCityId, |
| | | origin: { |
| | | latitude: originLat, |
| | | longitude: originLng |
| | | }, |
| | | }; |
| | | queryRouteAndFare(params).then((res: any) => { |
| | | if (res?.fareEstimate) { |
| | | setFareEstimate(res.fareEstimate); |
| | | } |
| | | }).catch(() => { |
| | | }); |
| | | }; |
| | | |
| | | // 认证成功处理函数 |
| | | useEffect(() => { |
| | | authSuccessHandlerRef.current = () => { |
| | | setTripStatus(2); |
| | | if (!messageBound.current && messageHandlerRef.current) { |
| | | wsManager.on('message', messageHandlerRef.current); |
| | | messageBound.current = true; |
| | | } |
| | | }; |
| | | messageHandlerRef.current = (data) => { |
| | | console.log('ws message:', data); |
| | | }; |
| | | orderStatusHandlerRef.current = (payload) => { |
| | | const mappedTripStatus = payload?.tripStatus; |
| | | if (typeof mappedTripStatus === 'number') { |
| | | setTripStatus(mappedTripStatus); |
| | | } |
| | | if (payload?.clearRoute) { |
| | | setRoutePoints([]); |
| | | setPolyline([]); |
| | | } |
| | | if (payload?.showCancelPopup) { |
| | | setShowCancelPopup(true); |
| | | } |
| | | if (payload?.showAnnouncementsPopup) { |
| | | setShowAnnouncementsPopup(true); |
| | | } |
| | | if (payload?.showPassengerNotice) { |
| | | if (Taro.getStorageSync('passengerNoticeShowed') !== 'true') { |
| | | setShowPassengerNotice(true); |
| | | Taro.setStorageSync('passengerNoticeShowed', 'true'); |
| | | } |
| | | if (Taro.getStorageSync('passengerNoticeCheckedTime')) { |
| | | const checkedTime = Number(Taro.getStorageSync('passengerNoticeCheckedTime')); |
| | | if (Date.now() - checkedTime > 3 * 30 * 24 * 60 * 60 * 1000) { |
| | | setShowPassengerNotice(true); |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | orderRouteHandlerRef.current = (payload) => { |
| | | const points = payload?.points || []; |
| | | applyRoutePoints(points); |
| | | const distance = Number(payload?.content?.distance); |
| | | const duration = Number(payload?.content?.duration); |
| | | if (!Number.isNaN(distance)) { |
| | | setRouteDistance(distance); |
| | | } |
| | | if (!Number.isNaN(duration)) { |
| | | setRouteDuration(duration); |
| | | } |
| | | }; |
| | | vehicleLocationHandlerRef.current = (payload) => { |
| | | const latitude = payload?.latitude; |
| | | const longitude = payload?.longitude; |
| | | if (typeof latitude === 'number' && typeof longitude === 'number') { |
| | | if ([4, 5, 6, 7].includes(tripStatusRef.current)) { |
| | | setVehicleLocation({ latitude, longitude }); |
| | | } |
| | | } |
| | | }; |
| | | return () => { |
| | | if (authSuccessBound.current && authSuccessHandlerRef.current) { |
| | | wsManager.off('auth_success', authSuccessHandlerRef.current); |
| | | authSuccessBound.current = false; |
| | | } |
| | | if (messageBound.current && messageHandlerRef.current) { |
| | | wsManager.off('message', messageHandlerRef.current); |
| | | messageBound.current = false; |
| | | } |
| | | if (orderStatusBound.current && orderStatusHandlerRef.current) { |
| | | wsManager.off('order_status', orderStatusHandlerRef.current); |
| | | orderStatusBound.current = false; |
| | | } |
| | | if (orderRouteBound.current && orderRouteHandlerRef.current) { |
| | | wsManager.off('order_route', orderRouteHandlerRef.current); |
| | | orderRouteBound.current = false; |
| | | } |
| | | if (vehicleLocationBound.current && vehicleLocationHandlerRef.current) { |
| | | wsManager.off('vehicle_location', vehicleLocationHandlerRef.current); |
| | | vehicleLocationBound.current = false; |
| | | } |
| | | }; |
| | | }, []); |
| | | |
| | | |
| | | // 监听行程状态,处理等待计时 |
| | | useEffect(() => { |
| | | if (tripStatus === 4) { |
| | | if (Taro.getStorageSync('passengerNoticeShowed') !== 'true') { |
| | | setShowPassengerNotice(true); |
| | | Taro.setStorageSync('passengerNoticeShowed', 'true'); |
| | | } |
| | | if (Taro.getStorageSync('passengerNoticeCheckedTime')) { |
| | | const checkedTime = Number(Taro.getStorageSync('passengerNoticeCheckedTime')); |
| | | if (Date.now() - checkedTime > 3 * 30 * 24 * 60 * 60 * 1000) { |
| | | setShowPassengerNotice(true); |
| | | } |
| | | } |
| | | } |
| | | if (tripStatus === 5) { |
| | | setShowAnnouncementsPopup(true) |
| | | } |
| | | Taro.setStorageSync('tripStatus', tripStatus); |
| | | tripStatusRef.current = tripStatus; |
| | | let timer: any = null; |
| | | if (tripStatus === 2 || tripStatus === 3) { |
| | | // 状态为2或3时,将地图中心定位到上车点 |
| | |
| | | }; |
| | | }, [tripStatus]); |
| | | |
| | | useEffect(() => { |
| | | const storedPoints = getStoredRoutePoints(tripStatus); |
| | | applyRoutePoints(storedPoints); |
| | | }, [tripStatus]); |
| | | |
| | | useEffect(() => { |
| | | if (shouldShowRoute && routePoints.length > 0) { |
| | | setPolyline([{ |
| | | points: routePoints, |
| | | color: '#6D7FD9', |
| | | width: 10, |
| | | arrowLine: true, |
| | | }]); |
| | | } else { |
| | | setPolyline([]); |
| | | } |
| | | }, [tripStatus, routePoints]); |
| | | |
| | | // 页面初始化 |
| | | useDidShow(() => { |
| | | const s = Taro.getStorageSync('startSite'); |
| | |
| | | } else { |
| | | getLocationInfo(); |
| | | } |
| | | const storedPoints = getStoredRoutePoints(tripStatus); |
| | | applyRoutePoints(storedPoints); |
| | | const storedDistance = Number(Taro.getStorageSync('orderRouteDistance')); |
| | | if (!Number.isNaN(storedDistance)) { |
| | | setRouteDistance(storedDistance); |
| | | } |
| | | const storedDuration = Number(Taro.getStorageSync('orderRouteDuration')); |
| | | if (!Number.isNaN(storedDuration)) { |
| | | setRouteDuration(storedDuration); |
| | | } |
| | | const originLng = s?.longitude || longitude; |
| | | const originLat = s?.latitude || latitude; |
| | | if (originLng && originLat && e?.longitude && e?.latitude) { |
| | | requestRouteAndFare(originLat, originLng, e.latitude, e.longitude); |
| | | } |
| | | }); |
| | | |
| | | // 规划路线 |
| | | useEffect(() => { |
| | | const originLng = startSite?.longitude || longitude; |
| | | const originLat = startSite?.latitude || latitude; |
| | | |
| | | if (!originLng || !originLat || !endSite.longitude || !endSite.latitude) return; |
| | | |
| | | const requestKey = `${originLng},${originLat}-${endSite.longitude},${endSite.latitude}`; |
| | | |
| | | if (requestKey === lastRequestParams.current) { |
| | | return; |
| | | if (originLng && originLat && endSite?.longitude && endSite?.latitude) { |
| | | requestRouteAndFare(originLat, originLng, endSite.latitude, endSite.longitude); |
| | | } |
| | | lastRequestParams.current = requestKey; |
| | | |
| | | const selectedCity = Taro.getStorageSync('selectedCity'); |
| | | const openCityId = selectedCity ? selectedCity.id : 0; |
| | | |
| | | const params = { |
| | | destination: { |
| | | latitude: endSite.latitude, |
| | | longitude: endSite.longitude |
| | | }, |
| | | openCityId: openCityId, |
| | | origin: { |
| | | latitude: originLat, |
| | | longitude: originLng |
| | | }, |
| | | }; |
| | | |
| | | queryRouteAndFare(params).then((res: any) => { |
| | | if (res && res.route && res.route.polyline) { |
| | | const points = res.route.polyline; |
| | | setRoutePoints(points); |
| | | setFareEstimate(res.fareEstimate || {}); |
| | | |
| | | setPolyline([{ |
| | | points: points, |
| | | color: '#6D7FD9', |
| | | width: 10, |
| | | arrowLine: true, |
| | | }]); |
| | | } |
| | | }).catch(err => { |
| | | console.error('规划失败', err); |
| | | }); |
| | | }, [endSite.longitude, endSite.latitude, startSite?.longitude, startSite?.latitude, longitude, latitude]) |
| | | }, [startSite?.longitude, startSite?.latitude, endSite?.longitude, endSite?.latitude, longitude, latitude]); |
| | | |
| | | // 生成标记点 |
| | | useEffect(() => { |
| | | if (routePoints.length > 0) { |
| | | const startPoint = routePoints[0]; |
| | | const endPoint = routePoints[routePoints.length - 1]; |
| | | |
| | | // 检查 storage 中的 startSite 是否存在 |
| | | const s = Taro.getStorageSync('startSite'); |
| | | if ([1, 2, 3, 4, 7, 8].includes(tripStatus)) { |
| | | const isCar = [4, 7].includes(tripStatus); |
| | | |
| | | const startMarker: any = { |
| | | id: Date.now() + 1, |
| | | latitude: startPoint.latitude, |
| | | longitude: startPoint.longitude, |
| | | iconPath: isCar |
| | | ? 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e51e92e6-700d-4e83-b3e0-14e6f18e0b9a.png' |
| | | : 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/51e7cb75-72af-4994-9d6e-d999efb2fbc7.png', |
| | | width: isCar ? 40 : 22, |
| | | height: isCar ? 20 : 40, |
| | | anchor: { x: 0.5, y: 1 }, |
| | | zIndex: 2, |
| | | }; |
| | | |
| | | if (tripStatus === 4) { |
| | | startMarker.name = '车辆位置'; |
| | | startMarker.customCallout = { anchorY: 0, anchorX: 0, display: 'ALWAYS' }; |
| | | } else if (tripStatus !== 7) { |
| | | startMarker.name = s?.name || '当前位置'; |
| | | startMarker.customCallout = { anchorY: 8, anchorX: 0, display: 'ALWAYS' }; |
| | | } |
| | | |
| | | setMarkers([ |
| | | // 起点指针 |
| | | startMarker, |
| | | // 终点指针 |
| | | { |
| | | id: Date.now() + 2, |
| | | latitude: endPoint.latitude, |
| | | longitude: endPoint.longitude, |
| | | iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/6a56b1eb-ec37-4839-8733-262942af070d.png', |
| | | width: 22, |
| | | height: 40, |
| | | anchor: { x: 0.5, y: 1 }, |
| | | zIndex: 2, |
| | | name: tripStatus === 7 ? '已到达' : endSite.name, |
| | | customCallout: { |
| | | anchorY: 8, |
| | | anchorX: 0, |
| | | display: 'ALWAYS' |
| | | } |
| | | }, |
| | | ]) |
| | | const s = Taro.getStorageSync('startSite'); |
| | | const pickupMarker = s?.latitude && s?.longitude ? { |
| | | id: Date.now() + 1, |
| | | latitude: s.latitude, |
| | | longitude: s.longitude, |
| | | iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/51e7cb75-72af-4994-9d6e-d999efb2fbc7.png', |
| | | width: 22, |
| | | height: 40, |
| | | anchor: { x: 0.5, y: 1 }, |
| | | zIndex: 2, |
| | | name: s?.name || '上车点', |
| | | customCallout: { |
| | | anchorY: 8, |
| | | anchorX: 0, |
| | | display: 'ALWAYS' |
| | | } |
| | | } |
| | | }, [tripStatus, routePoints, startSite?.name, endSite.name]) |
| | | } : null; |
| | | const dropoffMarker = endSite?.latitude && endSite?.longitude ? { |
| | | id: Date.now() + 2, |
| | | latitude: endSite.latitude, |
| | | longitude: endSite.longitude, |
| | | iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/6a56b1eb-ec37-4839-8733-262942af070d.png', |
| | | width: 22, |
| | | height: 40, |
| | | anchor: { x: 0.5, y: 1 }, |
| | | zIndex: 2, |
| | | name: endSite.name || '目的地', |
| | | customCallout: { |
| | | anchorY: 8, |
| | | anchorX: 0, |
| | | display: 'ALWAYS' |
| | | } |
| | | } : null; |
| | | const carMarker = vehicleLocation ? { |
| | | id: Date.now() + 3, |
| | | latitude: vehicleLocation.latitude, |
| | | longitude: vehicleLocation.longitude, |
| | | iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e51e92e6-700d-4e83-b3e0-14e6f18e0b9a.png', |
| | | width: 40, |
| | | height: 20, |
| | | anchor: { x: 0.5, y: 1 }, |
| | | zIndex: 3, |
| | | name: '车辆位置', |
| | | customCallout: { anchorY: 0, anchorX: 0, display: 'ALWAYS' } |
| | | } : null; |
| | | |
| | | // 调整视角 |
| | | const includePickup = [1, 2, 3, 4, 5, 8, 9].includes(tripStatus); |
| | | const includeDropoff = [1, 6, 7, 8, 9].includes(tripStatus); |
| | | const includeCar = [4, 5, 6, 7].includes(tripStatus); |
| | | const nextMarkers: any[] = []; |
| | | if (includePickup && pickupMarker) nextMarkers.push(pickupMarker); |
| | | if (includeCar && carMarker) nextMarkers.push(carMarker); |
| | | if (includeDropoff && dropoffMarker) nextMarkers.push(dropoffMarker); |
| | | setMarkers(nextMarkers); |
| | | }, [tripStatus, startSite?.name, startSite?.latitude, startSite?.longitude, endSite.name, endSite?.latitude, endSite?.longitude, vehicleLocation]) |
| | | |
| | | useEffect(() => { |
| | | if (routePoints.length > 0) { |
| | | if (shouldShowRoute && routePoints.length > 0) { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | if (tripStatus === 1 || tripStatus === 8 || tripStatus === 7 || tripStatus === 9) { |
| | | // 状态为1(发布行程)或8(行程已结束)时,展示整条路线 |
| | | if ([8, 9].includes(tripStatus)) { |
| | | mapCtx.includePoints({ |
| | | points: routePoints, |
| | | padding: [50, 50, 500, 50] |
| | |
| | | return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; |
| | | } |
| | | |
| | | const formatDateTime = (date: Date) => { |
| | | const y = date.getFullYear(); |
| | | const mo = String(date.getMonth() + 1).padStart(2, '0'); |
| | | const d = String(date.getDate()).padStart(2, '0'); |
| | | const h = String(date.getHours()).padStart(2, '0'); |
| | | const mi = String(date.getMinutes()).padStart(2, '0'); |
| | | const s = String(date.getSeconds()).padStart(2, '0'); |
| | | return `${y}-${mo}-${d} ${h}:${mi}:${s}`; |
| | | }; |
| | | |
| | | // 获取用户位置信息 |
| | | const getLocationInfo = () => { |
| | | Taro.getLocation({ type: 'gcj02' }) |
| | |
| | | mapCtx.moveToLocation(); |
| | | } |
| | | |
| | | const handleCloseCustomerService = () => { |
| | | setShowCustomerService(false); |
| | | }; |
| | | |
| | | const openService = () => { |
| | | setShowCustomerService(true); |
| | | } |
| | | const setPopupVisible = (setter: (value: boolean) => void, visible: boolean) => () => setter(visible); |
| | | const handleCloseCustomerService = setPopupVisible(setShowCustomerService, false); |
| | | const openService = setPopupVisible(setShowCustomerService, true); |
| | | |
| | | |
| | | const toBack = () => { |
| | |
| | | } |
| | | |
| | | // 立即呼叫 |
| | | const callImmediately = () => { |
| | | const callImmediately = async () => { |
| | | if (!endSite.name) { |
| | | Taro.showToast({ |
| | | title: '请选择站点', |
| | |
| | | }) |
| | | return |
| | | } |
| | | setTripStatus(2) |
| | | // 校验上车点是否存在 |
| | | const s = Taro.getStorageSync('startSite'); |
| | | if (s && s.latitude && s.longitude) { |
| | | // 移动地图中心到上车点 |
| | | setLatitude(s.latitude); |
| | | setLongitude(s.longitude); |
| | | if (!s?.latitude || !s?.longitude || !endSite?.latitude || !endSite?.longitude) { |
| | | Taro.showToast({ |
| | | title: '请先选择起终点', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | const selectedCity = Taro.getStorageSync('selectedCity'); |
| | | const openCityId = selectedCity?.id || 0; |
| | | const waitingTime = Number(fareEstimate?.waitingTime || 0); |
| | | const estimatedTime = formatDateTime(new Date(Date.now() + waitingTime * 60 * 1000)); |
| | | const payload = { |
| | | endPointId: endSite.id ?? endSite.pointId ?? endSite.point_id ?? 0, |
| | | openCityId: openCityId, |
| | | placementAddress: s?.address || s?.name || '', |
| | | placementLat: s?.latitude, |
| | | placementLon: s?.longitude, |
| | | startPointId: s?.id ?? s?.pointId ?? s?.point_id ?? 0, |
| | | xpElectronicFenceAreaId: s?.electronicAreaId || endSite?.electronicAreaId || '', |
| | | estimatedTime: estimatedTime, |
| | | }; |
| | | try { |
| | | await saveOrder(payload); |
| | | } catch (error) { |
| | | Taro.showToast({ |
| | | title: '下单失败,请重试', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | if (!authSuccessBound.current && authSuccessHandlerRef.current) { |
| | | wsManager.on('auth_success', authSuccessHandlerRef.current); |
| | | authSuccessBound.current = true; |
| | | } |
| | | if (!orderStatusBound.current && orderStatusHandlerRef.current) { |
| | | wsManager.on('order_status', orderStatusHandlerRef.current); |
| | | orderStatusBound.current = true; |
| | | } |
| | | if (!orderRouteBound.current && orderRouteHandlerRef.current) { |
| | | wsManager.on('order_route', orderRouteHandlerRef.current); |
| | | orderRouteBound.current = true; |
| | | } |
| | | if (!vehicleLocationBound.current && vehicleLocationHandlerRef.current) { |
| | | wsManager.on('vehicle_location', vehicleLocationHandlerRef.current); |
| | | vehicleLocationBound.current = true; |
| | | } |
| | | wsManager.connect(_ROBOTAXI_WS_HOST); |
| | | } |
| | | |
| | | // 确认取消 |
| | |
| | | } |
| | | |
| | | // 关闭取消弹窗 |
| | | const handleCloseCancelPopup = () => { |
| | | setShowCancelPopup(false); |
| | | } |
| | | |
| | | // 打开取消弹窗 |
| | | const handleOpenCancelPopup = () => { |
| | | setShowCancelPopup(true); |
| | | } |
| | | |
| | | // 关闭安全注意事项弹窗 |
| | | const handleCloseAnnouncementsPopup = () => { |
| | | setShowAnnouncementsPopup(false); |
| | | } |
| | | |
| | | // 打开结束行程弹窗 |
| | | const handleOpenOverRoutePopup = () => { |
| | | setShowOverRoutePopup(true); |
| | | } |
| | | |
| | | // 关闭结束行程弹窗 |
| | | const handleCloseOverRoutePopup = () => { |
| | | setShowOverRoutePopup(false); |
| | | } |
| | | const handleCloseCancelPopup = setPopupVisible(setShowCancelPopup, false); |
| | | const handleOpenCancelPopup = setPopupVisible(setShowCancelPopup, true); |
| | | const handleCloseAnnouncementsPopup = setPopupVisible(setShowAnnouncementsPopup, false); |
| | | const handleOpenOverRoutePopup = setPopupVisible(setShowOverRoutePopup, true); |
| | | const handleCloseOverRoutePopup = setPopupVisible(setShowOverRoutePopup, false); |
| | | |
| | | // 确认结束行程 |
| | | const handleConfirmOverRoute = () => { |
| | |
| | | } |
| | | |
| | | // 关闭乘车需知弹窗 |
| | | const handleClosePassengerNotice = () => { |
| | | setShowPassengerNotice(false); |
| | | } |
| | | const handleClosePassengerNotice = setPopupVisible(setShowPassengerNotice, false); |
| | | |
| | | // 打开评价弹窗 |
| | | const handleOpenEvaluatePopup = () => { |
| | | setShowEvaluatePopup(true); |
| | | } |
| | | |
| | | // 关闭评价弹窗 |
| | | const handleCloseEvaluatePopup = () => { |
| | | setShowEvaluatePopup(false); |
| | | } |
| | | const handleOpenEvaluatePopup = setPopupVisible(setShowEvaluatePopup, true); |
| | | const handleCloseEvaluatePopup = setPopupVisible(setShowEvaluatePopup, false); |
| | | |
| | | // 确认评价 |
| | | const handleConfirmEvaluate = () => { |
| | | setShowEvaluatePopup(false); |
| | | } |
| | | const handleConfirmEvaluate = handleCloseEvaluatePopup; |
| | | |
| | | const toSelectSite = (id: number) => { |
| | | if (id === 1) { |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/site-search/index?pageType=start' |
| | | }) |
| | | } else { |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/site-search/index?pageType=end' |
| | | }) |
| | | } |
| | | const toSelectSite = (pageType: 'start' | 'end') => { |
| | | Taro.navigateTo({ |
| | | url: `/robotaxi/pages/site-search/index?pageType=${pageType}` |
| | | }) |
| | | } |
| | | |
| | | // 点击气泡 |
| | | const handleCalloutTap = (e) => { |
| | | if (tripStatus === 1) { |
| | | // 仅在发布行程时,点击标记用于选择起点或终点 |
| | | const markerId = e.detail.markerId; |
| | | toSelectSite(markerId); |
| | | toSelectSite(markerId === 1 ? 'start' : 'end'); |
| | | } |
| | | } |
| | | |
| | |
| | | </CoverView> |
| | | } |
| | | { |
| | | item.name === '车辆位置' && |
| | | (item.name === '车辆位置' && [4, 5, 6].includes(tripStatus)) && |
| | | <CoverView className="custom-callout-content"> |
| | | <CoverView className="car-time"> |
| | | <CoverView className="car-time-num">3</CoverView> |
| | | <CoverView className="car-time-num">{routeDistance ?? 0}</CoverView> |
| | | <CoverView>公里</CoverView> |
| | | <CoverView className="car-time-num">2</CoverView> |
| | | <CoverView className="car-time-num">{routeDuration ?? 0}</CoverView> |
| | | <CoverView>分钟</CoverView> |
| | | </CoverView> |
| | | </CoverView> |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import { Component, useState, useEffect } from 'react'; |
| | | import Taro, { useReachBottom } from '@tarojs/taro'; |
| | | import { useState, useEffect } from 'react'; |
| | | import { View, Text, Image } from '@tarojs/components'; |
| | | import './index.less'; |
| | | import icon from '../../assets/Subtract.png' |
| | | import close from '../../assets/close.png' |
| | | import { getCouponList, receiveCoupon, getCouponDetail, CouponItem, CouponDetail } from '../../../robotaxi/api/coupon'; |
| | | |
| | | export default function CouponPage() { |
| | | const [couponStatus, setCouponStatus] = useState([ |
| | | { label: '全部', value: '1' }, |
| | | { label: '未使用', value: '2' }, |
| | | { label: '已使用', value: '3' }, |
| | | { label: '已失效', value: '4' }]) |
| | | const [activeStatus, setActiveStatus] = useState('1') |
| | | const [couponList, setCouponList] = useState([ |
| | | { |
| | | id: '1', |
| | | name: '优惠券名称', |
| | | time: '2026-03-04 12:12:01', |
| | | price: '20' |
| | | }, { |
| | | id: '2', |
| | | name: '优惠券名称', |
| | | time: '2026-03-04 12:12:01', |
| | | price: '20' |
| | | }, { |
| | | id: '3', |
| | | name: '优惠券名称', |
| | | time: '2026-03-04 12:12:01', |
| | | price: '20' |
| | | }, { |
| | | id: '4', |
| | | name: '优惠券名称', |
| | | time: '2026-03-04 12:12:01', |
| | | price: '20' |
| | | }, { |
| | | id: '5', |
| | | name: '优惠券名称', |
| | | time: '2026-03-04 12:12:01', |
| | | price: '20' |
| | | }, { |
| | | id: '6', |
| | | name: '优惠券名称', |
| | | time: '2026-03-04 12:12:01', |
| | | price: '20' |
| | | }, { |
| | | id: '7', |
| | | name: '优惠券名称', |
| | | time: '2026-03-04 12:12:01', |
| | | price: '20' |
| | | }]) |
| | | const [animationData, setAnimationData] = useState(null) |
| | | const [couponStatus] = useState([ |
| | | { label: '全部', value: '' }, |
| | | { label: '待领取', value: 0 }, |
| | | { label: '未使用', value: 1 }, |
| | | { label: '已使用', value: 2 }, |
| | | { label: '已失效', value: 3 }]) |
| | | const [activeStatus, setActiveStatus] = useState<string | number>('') |
| | | const [couponList, setCouponList] = useState<CouponItem[]>([]) |
| | | const [animationData, setAnimationData] = useState<any>(null) |
| | | const [visible, setVisible] = useState(false) |
| | | const [pageNum, setPageNum] = useState(1) |
| | | const [hasMore, setHasMore] = useState(true) |
| | | const [currentCoupon, setCurrentCoupon] = useState<CouponDetail | null>(null) |
| | | const pageSize = 10 |
| | | |
| | | const getList = async (page: number, status: string | number, isRefresh = false) => { |
| | | try { |
| | | const res = await getCouponList({ pageNum: page, pageSize, status }); |
| | | const newList = res.list || []; |
| | | if (isRefresh) { |
| | | setCouponList(newList); |
| | | } else { |
| | | setCouponList(prev => [...prev, ...newList]); |
| | | } |
| | | // 如果返回数量少于 pageSize,说明没有更多数据了 |
| | | setHasMore(newList.length >= pageSize); |
| | | } catch (e) { |
| | | console.error(e); |
| | | // 出错时如果是刷新,清空列表 |
| | | if (isRefresh) setCouponList([]); |
| | | } |
| | | } |
| | | |
| | | useEffect(() => { |
| | | // 切换 tab 时重置并加载第一页 |
| | | setPageNum(1); |
| | | setHasMore(true); |
| | | getList(1, activeStatus, true); |
| | | }, [activeStatus]) |
| | | |
| | | useReachBottom(() => { |
| | | if (hasMore) { |
| | | const nextPage = pageNum + 1; |
| | | setPageNum(nextPage); |
| | | getList(nextPage, activeStatus); |
| | | } |
| | | }) |
| | | |
| | | const changeStatus = (value) => { |
| | | const changeStatus = (value: string | number) => { |
| | | setActiveStatus(value) |
| | | } |
| | | const openPopup = () => { |
| | | const animation = Taro.createAnimation({ |
| | | duration: 300, |
| | | timingFunction: 'ease-out' |
| | | }); |
| | | animation.translateY(0).step(); |
| | | setAnimationData(animation.export()) |
| | | setVisible(true) |
| | | |
| | | const openPopup = async (item: CouponItem) => { |
| | | try { |
| | | Taro.showLoading({ title: '加载中...' }); |
| | | const detail = await getCouponDetail(item.couponId); |
| | | setCurrentCoupon(detail); |
| | | Taro.hideLoading(); |
| | | |
| | | const animation = Taro.createAnimation({ |
| | | duration: 300, |
| | | timingFunction: 'ease-out' |
| | | }); |
| | | animation.translateY(0).step(); |
| | | setAnimationData(animation.export()) |
| | | setVisible(true) |
| | | } catch (error) { |
| | | Taro.hideLoading(); |
| | | Taro.showToast({ title: '获取详情失败', icon: 'none' }); |
| | | } |
| | | } |
| | | |
| | | const closePopup = () => { |
| | | const animation = Taro.createAnimation({ |
| | | duration: 300, |
| | |
| | | animation.translateY(500).step(); |
| | | setAnimationData(animation.export()) |
| | | setVisible(false) |
| | | } |
| | | |
| | | const handleReceive = async (e, item: CouponItem) => { |
| | | e.stopPropagation(); |
| | | try { |
| | | await receiveCoupon({ couponId: item.couponId }); |
| | | Taro.showToast({ title: '领取成功', icon: 'success' }); |
| | | // 领取成功后刷新列表 |
| | | getList(1, activeStatus, true); |
| | | setPageNum(1); |
| | | } catch (error) { |
| | | Taro.showToast({ title: '领取失败', icon: 'none' }); |
| | | } |
| | | } |
| | | |
| | | return ( |
| | |
| | | </View> |
| | | <View className='coupon-card'> |
| | | {couponList.map((item, index) => { |
| | | return <View className='coupon-item' key={item.id || index}> |
| | | return <View className='coupon-item' key={item.couponId || index}> |
| | | |
| | | <View className='coupon-price'> |
| | | <Text className='price'>{item.price}</Text> |
| | | <Text className='price'>{item.discountAmount}</Text> |
| | | <Text className='unit'>元</Text> |
| | | </View> |
| | | <View className='coupon-info'> |
| | | <Text className='name'>{item.name}</Text> |
| | | <Text className='time'>{item.time}到期</Text> |
| | | <Text className='name'>{item.title}</Text> |
| | | <Text className='time'>{item.expireTime}到期</Text> |
| | | <View className='more' onClick={() => openPopup(item)}> |
| | | <Text className='more-detail'>查看详情</Text> |
| | | <Image className='more-img' src={icon}></Image> |
| | | </View> |
| | | </View> |
| | | <View className='receive'>领取</View> |
| | | {/* 状态 1-未使用 2-已使用 3-已过期 0待领取 */} |
| | | {item.status === 0 ? ( |
| | | <View className='receive' onClick={(e) => handleReceive(e, item)}>领取</View> |
| | | ) : item.status === 1 ? ( |
| | | <View className='receive' style={{backgroundColor: '#ccc'}}>未使用</View> |
| | | ) : item.status === 2 ? ( |
| | | <View className='receive' style={{backgroundColor: '#f5f5f5', color: '#999'}}>已使用</View> |
| | | ) : ( |
| | | <View className='receive' style={{backgroundColor: '#f5f5f5', color: '#999'}}>已过期</View> |
| | | )} |
| | | </View> |
| | | })} |
| | | </View> |
| | | |
| | | {/* 优惠券说明 */} |
| | | {visible && <View className='privacy-popup' catchMove> |
| | | <View className='privacy-mask'></View> |
| | | {visible && currentCoupon && <View className='privacy-popup' catchMove> |
| | | <View className='privacy-mask' onClick={closePopup}></View> |
| | | <View className='privacy-content' animation={animationData}> |
| | | {/* 头部:标题 + 关闭*/} |
| | | <View className='privacy-header'> |
| | |
| | | {/* 内容文案 */} |
| | | <View className='remark-card'> |
| | | <Text className='remark-label'>有效时间</Text> |
| | | <Text className='remark-value'>2025-12-16 失效</Text> |
| | | <Text className='remark-value'>{currentCoupon.expireTime} 失效</Text> |
| | | </View> |
| | | <View className='remark-card'> |
| | | <Text className='remark-label'>使用时段</Text> |
| | | <Text className='remark-value'>运营时间内可用</Text> |
| | | <Text className='remark-value'>{currentCoupon.validTimeRange}</Text> |
| | | </View> |
| | | <View className='remark-card'> |
| | | <Text className='remark-label'>使用地区</Text> |
| | | <Text className='remark-value'> 广州、武汉</Text> |
| | | <Text className='remark-value'> {(currentCoupon.applicableCities || []).join('、')}</Text> |
| | | </View> |
| | | <View className='remark-card'> |
| | | <Text className='remark-label'>使用说明</Text> |
| | | <Text className='remark-value'>小鹏Robotaxi用户专享,领取后7天有效 适用于小鹏Robotaxi设定运营区内使用 如有疑问请联系:400-xxx-xxxxxx</Text> |
| | | <Text className='remark-value'>{currentCoupon.usageInstructions}</Text> |
| | | </View> |
| | | |
| | | </View> |
| | |
| | | return |
| | | } |
| | | if (pageType === 'start') { |
| | | const endSiteCache = Taro.getStorageSync('endSite'); |
| | | if (endSiteCache?.electronicAreaId && selectedSite?.electronicAreaId && endSiteCache.electronicAreaId !== selectedSite.electronicAreaId) { |
| | | Taro.showToast({ |
| | | title: '起终点需在同一运营区域', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | // 校验当前位置与选择的上车点距离 |
| | | if (longitude && latitude) { |
| | | const distance = getDistance(latitude, longitude, selectedSite.latitude, selectedSite.longitude); |
| | |
| | | } |
| | | } |
| | | |
| | | Taro.setStorageSync('startSite', { |
| | | latitude: selectedSite.latitude, |
| | | longitude: selectedSite.longitude, |
| | | name: selectedSite.name, |
| | | }) |
| | | Taro.setStorageSync('startSite', selectedSite) |
| | | Taro.navigateBack({ |
| | | delta: 2, |
| | | }) |
| | | } |
| | | if (pageType === 'end') { |
| | | // 校验上车点与目的地的距离 |
| | | const startSiteCache = Taro.getStorageSync('startSite'); |
| | | if (startSiteCache?.electronicAreaId && selectedSite?.electronicAreaId && startSiteCache.electronicAreaId !== selectedSite.electronicAreaId) { |
| | | Taro.showToast({ |
| | | title: '起终点需在同一运营区域', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | // 校验上车点与目的地的距离 |
| | | if (startSiteCache && startSiteCache.longitude && startSiteCache.latitude) { |
| | | const distance = getDistance(startSiteCache.latitude, startSiteCache.longitude, selectedSite.latitude, selectedSite.longitude); |
| | | if (distance < distanceConfig.minDistance) { |
| | |
| | | } |
| | | } |
| | | |
| | | Taro.setStorageSync('endSite', { |
| | | latitude: selectedSite.latitude, |
| | | longitude: selectedSite.longitude, |
| | | name: selectedSite.name, |
| | | }) |
| | | Taro.setStorageSync('endSite', selectedSite) |
| | | Taro.reLaunch({ |
| | | url: '/robotaxi/pages/begin-route/index', |
| | | }) |
| | |
| | | |
| | | const onChangeSelectSite = (item) => { |
| | | if (pageType == 'start') { |
| | | const endSiteCache = Taro.getStorageSync('endSite'); |
| | | if (endSiteCache?.electronicAreaId && item?.electronicAreaId && endSiteCache.electronicAreaId !== item.electronicAreaId) { |
| | | Taro.showToast({ |
| | | title: '起终点需在同一运营区域', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | Taro.setStorageSync('startSite', item); |
| | | Taro.navigateBack() |
| | | } else if (pageType == 'end') { |
| | | const startSiteCache = Taro.getStorageSync('startSite'); |
| | | if (startSiteCache?.electronicAreaId && item?.electronicAreaId && startSiteCache.electronicAreaId !== item.electronicAreaId) { |
| | | Taro.showToast({ |
| | | title: '起终点需在同一运营区域', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | Taro.setStorageSync('endSite', item) |
| | | Taro.reLaunch({ |
| | | url: '/robotaxi/pages/begin-route/index', |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import { getStorage } from './tools'; |
| | | import { _UID } from '../constants/api'; |
| | | |
| | | type MessageCallback = (data: any) => void; |
| | | |
| | |
| | | private heartbeatTimer: any = null; |
| | | private heartbeatInterval: number = 30000; // 30秒心跳 |
| | | private listeners: Map<string, MessageCallback[]> = new Map(); |
| | | private globalEventsBound: boolean = false; |
| | | |
| | | private constructor() {} |
| | | private constructor() { } |
| | | |
| | | public static getInstance(): WebSocketManager { |
| | | if (!WebSocketManager.instance) { |
| | |
| | | * 初始化连接 |
| | | * @param url WebSocket地址 |
| | | */ |
| | | public connect(url: string) { |
| | | public async connect(url: string) { |
| | | if (this.socketTask || this.isConnected) { |
| | | console.log('WebSocket 已连接或正在连接中'); |
| | | return; |
| | |
| | | this.url = url; |
| | | console.log(`正在连接 WebSocket: ${url}`); |
| | | |
| | | Taro.connectSocket({ |
| | | url: this.url, |
| | | success: (task) => { |
| | | this.socketTask = task; |
| | | this.initEventListeners(); |
| | | const uid = await getStorage(_UID) || '-1'; |
| | | const task = Taro.connectSocket({ |
| | | header: { |
| | | 'XP-Uid': uid.toString(), |
| | | }, |
| | | url: this.url, |
| | | success: () => { }, |
| | | fail: (err) => { |
| | | console.error('WebSocket 连接失败:', err); |
| | | this.handleReconnect(); |
| | | } |
| | | }); |
| | | const maybeTask: any = task as any; |
| | | if (maybeTask && typeof maybeTask.onOpen === 'function') { |
| | | this.socketTask = maybeTask; |
| | | this.initEventListeners(); |
| | | return; |
| | | } |
| | | if (maybeTask && typeof maybeTask.then === 'function') { |
| | | maybeTask |
| | | .then((resolvedTask) => { |
| | | this.socketTask = resolvedTask; |
| | | this.initEventListeners(); |
| | | }) |
| | | .catch((err) => { |
| | | console.error('WebSocket 连接失败:', err); |
| | | this.handleReconnect(); |
| | | }); |
| | | return; |
| | | } |
| | | this.socketTask = null; |
| | | this.initEventListeners(); |
| | | } |
| | | |
| | | /** |
| | | * 初始化事件监听 |
| | | */ |
| | | private initEventListeners() { |
| | | if (!this.socketTask) return; |
| | | if (this.socketTask && typeof this.socketTask.onOpen === 'function') { |
| | | this.socketTask.onOpen(async () => { |
| | | console.log('WebSocket 连接已打开'); |
| | | this.isConnected = true; |
| | | this.reconnectAttempts = 0; |
| | | this.startHeartbeat(); |
| | | const uid = await getStorage(_UID) || '-1'; |
| | | this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() }); |
| | | this.notifyListeners('open', { status: 'connected' }); |
| | | }); |
| | | |
| | | this.socketTask.onOpen(() => { |
| | | this.socketTask.onMessage((res) => { |
| | | try { |
| | | const data = JSON.parse(res.data as string); |
| | | const content = JSON.parse(data.content || '{}'); |
| | | this.handleIncomingMessage(data, content); |
| | | if (data.type) { |
| | | this.notifyListeners(data.type, data); |
| | | } |
| | | this.notifyListeners('message', data); |
| | | } catch (e) { |
| | | console.warn('解析 WebSocket 消息失败:', res.data); |
| | | this.notifyListeners('message', res.data); |
| | | } |
| | | }); |
| | | |
| | | this.socketTask.onClose((res) => { |
| | | console.log('WebSocket 连接已关闭', res); |
| | | this.isConnected = false; |
| | | this.stopHeartbeat(); |
| | | this.socketTask = null; |
| | | |
| | | if (res.code !== 1000) { |
| | | this.handleReconnect(); |
| | | } |
| | | this.notifyListeners('close', res); |
| | | }); |
| | | |
| | | this.socketTask.onError((err) => { |
| | | console.error('WebSocket 错误:', err); |
| | | this.isConnected = false; |
| | | this.notifyListeners('error', err); |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | if (this.globalEventsBound) return; |
| | | this.globalEventsBound = true; |
| | | |
| | | Taro.onSocketOpen(async () => { |
| | | console.log('WebSocket 连接已打开'); |
| | | this.isConnected = true; |
| | | this.reconnectAttempts = 0; |
| | | this.startHeartbeat(); |
| | | const uid = await getStorage(_UID) || '-1'; |
| | | this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() }); |
| | | this.notifyListeners('open', { status: 'connected' }); |
| | | }); |
| | | |
| | | this.socketTask.onMessage((res) => { |
| | | Taro.onSocketMessage((res) => { |
| | | try { |
| | | const data = JSON.parse(res.data as string); |
| | | // 假设后端消息格式包含 type 字段来区分消息类型 |
| | | const content = JSON.parse(data.content || '{}'); |
| | | this.handleIncomingMessage(data, content); |
| | | if (data.type) { |
| | | this.notifyListeners(data.type, data); |
| | | } |
| | | // 同时触发通用的 message 事件 |
| | | this.notifyListeners('message', data); |
| | | } catch (e) { |
| | | console.warn('解析 WebSocket 消息失败:', res.data); |
| | |
| | | } |
| | | }); |
| | | |
| | | this.socketTask.onClose((res) => { |
| | | Taro.onSocketClose((res) => { |
| | | console.log('WebSocket 连接已关闭', res); |
| | | this.isConnected = false; |
| | | this.stopHeartbeat(); |
| | | this.socketTask = null; |
| | | |
| | | // 如果不是手动关闭(code 1000),则尝试重连 |
| | | |
| | | if (res.code !== 1000) { |
| | | this.handleReconnect(); |
| | | } |
| | | this.notifyListeners('close', res); |
| | | }); |
| | | |
| | | this.socketTask.onError((err) => { |
| | | Taro.onSocketError((err) => { |
| | | console.error('WebSocket 错误:', err); |
| | | this.isConnected = false; |
| | | this.notifyListeners('error', err); |
| | |
| | | * @param data 消息内容 |
| | | */ |
| | | public send(data: any) { |
| | | if (!this.isConnected || !this.socketTask) { |
| | | if (!this.isConnected) { |
| | | console.warn('WebSocket 未连接,消息未发送:', data); |
| | | return; |
| | | } |
| | | |
| | | const message = typeof data === 'string' ? data : JSON.stringify(data); |
| | | this.socketTask.send({ |
| | | data: message, |
| | | fail: (err) => { |
| | | console.error('发送消息失败:', err); |
| | | } |
| | | }); |
| | | if (this.socketTask) { |
| | | this.socketTask.send({ |
| | | data: message, |
| | | fail: (err) => { |
| | | console.error('发送消息失败:', err); |
| | | } |
| | | }); |
| | | } else { |
| | | Taro.sendSocketMessage({ |
| | | data: message, |
| | | fail: (err) => { |
| | | console.error('发送消息失败:', err); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | clearTimeout(this.reconnectTimer); |
| | | this.reconnectTimer = null; |
| | | } |
| | | |
| | | |
| | | if (this.socketTask) { |
| | | this.socketTask.close({ |
| | | code: 1000, |
| | |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // 处理传入消息 |
| | | private handleIncomingMessage(data: any, content: any) { |
| | | if (data?.type === 'trip_status' || data?.type === 'trip_status_update') { |
| | | const status = content?.tripStatus ?? content?.trip_status ?? data?.tripStatus ?? data?.trip_status; |
| | | if (typeof status === 'number') { |
| | | Taro.setStorageSync('tripStatus', status); |
| | | this.notifyListeners('trip_status_change', { status, data, content }); |
| | | } |
| | | } |
| | | if (data?.type === 'ORDER_STATUS') { |
| | | const status = content?.type ?? content?.status ?? data?.status; |
| | | if (typeof status === 'number') { |
| | | const mapped = this.mapOrderStatusToTripStatus(status); |
| | | if (typeof mapped.tripStatus === 'number') { |
| | | Taro.setStorageSync('tripStatus', mapped.tripStatus); |
| | | } |
| | | this.notifyListeners('order_status', { status, ...mapped, data, content }); |
| | | } |
| | | } |
| | | if (data?.type === 'VEHICLE_LOCATION') { |
| | | const longitude = content?.lon ?? content?.lng ?? content?.longitude; |
| | | const latitude = content?.lat ?? content?.latitude; |
| | | if (typeof longitude === 'number' && typeof latitude === 'number') { |
| | | this.notifyListeners('vehicle_location', { longitude, latitude, data, content }); |
| | | } |
| | | } |
| | | if (data?.type === 'ORDER_ROUTE') { |
| | | const routeType = content?.type ?? content?.routeType ?? content?.route_type; |
| | | const duration = Number(content?.duration); |
| | | const distance = Number(content?.distance); |
| | | const points = this.normalizeRoutePoints(content); |
| | | if (!Number.isNaN(duration)) { |
| | | Taro.setStorageSync('orderRouteDuration', duration); |
| | | } |
| | | if (!Number.isNaN(distance)) { |
| | | Taro.setStorageSync('orderRouteDistance', distance); |
| | | } |
| | | if (points.length > 0) { |
| | | const cached = Taro.getStorageSync('orderRoutePoints') || {}; |
| | | if (typeof routeType === 'number') { |
| | | cached[String(routeType)] = points; |
| | | Taro.setStorageSync('orderRouteLatestType', routeType); |
| | | } else { |
| | | cached.latest = points; |
| | | } |
| | | Taro.setStorageSync('orderRoutePoints', cached); |
| | | this.notifyListeners('order_route', { routeType, points, data, content }); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 归一化路线点 |
| | | private normalizeRoutePoints(content: any) { |
| | | const route = JSON.parse(content?.route ?? '{}'); |
| | | const rawPoints = route?.polyline ?? []; |
| | | if (!Array.isArray(rawPoints)) return []; |
| | | return rawPoints.map((item: any) => { |
| | | const latitude = item?.latitude; |
| | | const longitude = item?.longitude; |
| | | if (typeof latitude === 'number' && typeof longitude === 'number') { |
| | | return { latitude, longitude }; |
| | | } |
| | | return null; |
| | | }).filter(Boolean); |
| | | } |
| | | |
| | | // 映射订单状态到行程状态 |
| | | private mapOrderStatusToTripStatus(status: number) { |
| | | switch (status) { |
| | | case 0: |
| | | return { tripStatus: 1, showCancelPopup: true }; |
| | | case 1: |
| | | return { tripStatus: 3, showPassengerNotice: true }; |
| | | case 2: |
| | | return { tripStatus: 3 }; |
| | | case 3: |
| | | return { tripStatus: 4 }; |
| | | case 4: |
| | | return { tripStatus: 5, showAnnouncementsPopup: true }; |
| | | case 5: |
| | | case 6: |
| | | return { tripStatus: 6 }; |
| | | case 7: |
| | | return { tripStatus: 7 }; |
| | | case 8: |
| | | return { tripStatus: 8 }; |
| | | case 9: |
| | | case 10: |
| | | return { tripStatus: 9 }; |
| | | default: |
| | | return { tripStatus: undefined }; |
| | | } |
| | | } |
| | | } |
| | | |
| | | export default WebSocketManager.getInstance(); |