| | |
| | | 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.80:8000"'; // 打车接口域名 |
| | | const ROBOTAXI_WS_HOST = '"ws://192.168.110.34:8888/websocket"'; |
| | | |
| | | // const ROBOTAXI_HOST = '"http://192.168.110.80:8000"'; // 打车接口域名 |
| | | // const ROBOTAXI_WS_HOST = '"ws://192.168.110.34:8888/websocket"'; |
| | | const ROBOTAXI_HOST = '"http://221.182.45.100:8000"'; |
| | | const ROBOTAXI_WS_HOST = '"ws://221.182.45.100:8888/websocket"'; |
| | | |
| | | const XP_APPID_MAP = { |
| | | weapp: '"xxxxxxx"', // 用户中心登录appId |
| | |
| | | 'pages/expense-detail/index', |
| | | 'pages/expense-description/index', |
| | | 'pages/passenger-notice/index', |
| | | 'pages/pricing-rules/index', |
| | | ], |
| | | } |
| | | ]; |
| | |
| | | Taro.setStorageSync('startSite', currentSite); |
| | | } |
| | | Taro.navigateTo({ |
| | | url: `/robotaxi/pages/site-search/index?pageType=${type}&electronicAreaId=${electronicAreaId}&longitude=${longitude}&latitude=${latitude}` |
| | | url: `/robotaxi/pages/site-search/index?pageType=${type}&electronicAreaId=${electronicAreaId}&longitude=${currentSite.longitude}&latitude=${currentSite.latitude}` |
| | | }) |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | const toCoupon = () => { |
| | | if (Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true') { |
| | | setShowPrivacy(true); |
| | | return; |
| | | } |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/coupon/index', |
| | | }) |
| | | } |
| | | |
| | | const toOrder = () => { |
| | | if (Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true') { |
| | | setShowPrivacy(true); |
| | | return; |
| | | } |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/order/index', |
| | | }) |
| | |
| | | // 优惠券列表 |
| | | export const getCouponList = async (payload: GetCouponListPayload): Promise<GetCouponListResponse> => { |
| | | const res = await request({ |
| | | url: _ROBOTAXI_HOST + '/account/api/v1/coupon/list', |
| | | url: _ROBOTAXI_HOST + '/account/api/v1/mini-program/coupon/list', |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | |
| | | // 领取优惠券 |
| | | export const receiveCoupon = async (payload: { couponId: string }) => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/account/api/v1/coupon/receive', |
| | | url: _ROBOTAXI_HOST + '/account/api/v1/mini-program/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}`, |
| | | url: _ROBOTAXI_HOST + `/account/api/v1/mini-program/coupon/detail/${couponId}`, |
| | | method: 'POST' |
| | | }); |
| | | |
| New file |
| | |
| | | import request from '../../utils/request'; |
| | | import { _ROBOTAXI_HOST } from '../../constants/api'; |
| | | |
| | | export interface OrderHistoryOrder { |
| | | amount: number; |
| | | destinationAddress: string; |
| | | orderId: string; |
| | | orderTime: string; |
| | | originAddress: string; |
| | | time?: string; |
| | | } |
| | | |
| | | export interface OrderHistoryGroup { |
| | | dateLabel: string; |
| | | orders: OrderHistoryOrder[]; |
| | | } |
| | | |
| | | export interface GetOrderHistoryPayload { |
| | | pageNum: number; |
| | | pageSize: number; |
| | | status: number | string; |
| | | } |
| | | |
| | | export interface GetOrderHistoryResponse { |
| | | list: OrderHistoryGroup[]; |
| | | total: number; |
| | | } |
| | | |
| | | export const getOrderHistoryList = async (payload: GetOrderHistoryPayload): Promise<GetOrderHistoryResponse> => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/order/api/v1/mini-program/order/history/list', |
| | | method: 'POST', |
| | | payload |
| | | }); |
| | | } |
| | | |
| | | export interface OrderDetail { |
| | | acceptanceTime: string; |
| | | appUserId: number; |
| | | arrivalTime: string; |
| | | boardingAddress: string; |
| | | boardingLat: number; |
| | | boardingLon: number; |
| | | boardingTime: string; |
| | | cityCode: string; |
| | | cityId: number; |
| | | cityName: string; |
| | | couponId: number; |
| | | couponMoney: number; |
| | | createBy: string; |
| | | createTime: string; |
| | | deleted: boolean; |
| | | duration: number; |
| | | durationMoney: number; |
| | | endAddress: string; |
| | | endLat: number; |
| | | endLon: number; |
| | | endPointId: number; |
| | | endPointName: string; |
| | | estimatedTime: string; |
| | | feedbackContent: string; |
| | | feedbackPictures: string; |
| | | feedbackTypeName: string; |
| | | fleetId: number; |
| | | getoffAddress: string; |
| | | getoffLat: number; |
| | | getoffLon: number; |
| | | getoffTime: string; |
| | | id: number; |
| | | longDistance: number; |
| | | longDistancemoney: number; |
| | | mileageKilometers: number; |
| | | mileageMoney: number; |
| | | orderMoney: number; |
| | | orderNumber: string; |
| | | orderRating: number; |
| | | orderSource: number; |
| | | orderTime: number; |
| | | payMoney: number; |
| | | payType: number; |
| | | placementAddress: string; |
| | | placementLat: number; |
| | | placementLon: number; |
| | | placementValue: number; |
| | | plateNumber: string; |
| | | prepayMoney: number; |
| | | redPacketId: number; |
| | | redPacketMoney: number; |
| | | rideTime: string; |
| | | startAddress: string; |
| | | startLat: number; |
| | | startLon: number; |
| | | startMileage: number; |
| | | startMoney: number; |
| | | startPointId: number; |
| | | startPointName: string; |
| | | status: number; |
| | | timeSlot: string; |
| | | totalMileage: number; |
| | | trajectories: any[]; |
| | | transactionId: string; |
| | | tripBegin: string; |
| | | tripEnd: string; |
| | | updateBy: string; |
| | | updateTime: string; |
| | | vehicleCode: string; |
| | | vehicleColor: string; |
| | | vehicleId: number; |
| | | vehiclePictures: string; |
| | | wait: number; |
| | | waitMoney: number; |
| | | xpElectronicFenceAreaId: number; |
| | | } |
| | | |
| | | export const getOrderDetail = async (orderId: string | number): Promise<OrderDetail> => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/orderDetail/${orderId}`, |
| | | method: 'PUT' |
| | | }); |
| | | } |
| | | |
| | | export interface FareItem { |
| | | duration: number; |
| | | mileage: number; |
| | | money: number; |
| | | time: string; |
| | | type: number; // type=1 普通时段 type=2 夜间行驶时段 3高峰时段 4节假日时段 |
| | | } |
| | | |
| | | export interface FareBreakdownResponse { |
| | | couponMoney: number; |
| | | duration: FareItem[]; |
| | | kilometer: number; |
| | | longDistance: FareItem[]; |
| | | mileage: FareItem[]; |
| | | orderMoney: number; |
| | | redPacketMoney: number; |
| | | start: FareItem[]; |
| | | time: number; |
| | | payMoney?: number; // 实际支付金额,虽然detail.txt没写,但页面展示需要,可能包含在返回中或需计算 |
| | | } |
| | | |
| | | export const getFareBreakdown = async (orderId: string | number): Promise<FareBreakdownResponse> => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/fare-breakdown/${orderId}`, |
| | | method: 'PUT' |
| | | }); |
| | | } |
| | | |
| | | export interface FAQItem { |
| | | id: number; |
| | | questions: string; |
| | | answer: string; |
| | | createTime: string; |
| | | updateTime: string; |
| | | } |
| | | |
| | | export const getFAQList = async (): Promise<FAQItem[]> => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/operation/base/v1/mini-program/frequently-asked-questions/getList', |
| | | method: 'GET' |
| | | }); |
| | | } |
| | |
| | | url: '/robotaxi/pages/site-search/index?type=' + type |
| | | }) |
| | | } |
| | | const toPriceDescription = () => { |
| | | Taro.navigateTo({ |
| | | url: `/robotaxi/pages/expense-detail/index`, |
| | | }) |
| | | } |
| | | |
| | | return ( |
| | | <View className='release-route'> |
| | |
| | | <View className="route-content-top"> |
| | | <View className="route-content-txt">预估等待{fareEstimate.waitingTime || 0}分钟</View> |
| | | <View className="route-content-top-money"> |
| | | <Image className="tip-icon" src={tipDefaultIcon} /> |
| | | <Image className="tip-icon" src={tipDefaultIcon} onClick={() => toPriceDescription()} /> |
| | | 预估 |
| | | <Text className="money-num">{fareEstimate.finalFare || 0}</Text> |
| | | 元 |
| | |
| | | import suggest from '../../../robotaxi/assets/suggest.png' |
| | | import originIcon from '../../../robotaxi/assets/origin-icon.png' |
| | | import wsManager from '../../../utils/websocket'; |
| | | // 2-13修改的备注 start: 引入步行路径方法与箭头资源 |
| | | import { getWalkingRoute } from '../../../utils/Robotaxi-public-method'; |
| | | import lineRight from '../../../robotaxi/assets/line-right.png'; |
| | | // 2-13修改的备注 end |
| | | import { |
| | | saveOrder, |
| | | queryRouteAndFare, |
| | |
| | | const [cancelType, setCancelType] = useState(1); // 取消类型 |
| | | const [showEvaluatePopup, setShowEvaluatePopup] = useState(false); // 评价弹窗是否显示 |
| | | const [routePoints, setRoutePoints] = useState<any[]>([]); // 路线点集合 |
| | | // 2-13修改的备注 start: 新增步行路径点 |
| | | const [walkingPoints, setWalkingPoints] = useState<any[]>([]); |
| | | // 2-13修改的备注 end |
| | | const [fareEstimate, setFareEstimate] = useState<any>({}); // 预估费用信息 |
| | | const [vehicleLocation, setVehicleLocation] = useState<{ latitude: number, longitude: number } | null>(null); |
| | | const [routeDistance, setRouteDistance] = useState<number | null>(null); // 路线距离 |
| | |
| | | |
| | | const tripStatusRef = useRef(tripStatus); // 行程状态引用 |
| | | const routeRequestKeyRef = useRef<string>(''); // 路线请求键 |
| | | const shouldShowRoute = [4, 5, 6, 8, 9].includes(tripStatus); |
| | | // 2-13修改的备注 start: 在发布行程状态也允许展示路线 |
| | | const shouldShowRoute = [1, 4, 5, 6, 8, 9].includes(tripStatus); |
| | | // 2-13修改的备注 end |
| | | const authSuccessBound = useRef(false); // 认证成功是否绑定 |
| | | const messageBound = useRef(false); // 消息是否绑定 |
| | | const orderStatusBound = useRef(false); // 订单状态是否绑定 |
| | |
| | | const orderStatusHandlerRef = useRef<(data: any) => void>(); // 订单状态处理函数 |
| | | const orderRouteHandlerRef = useRef<(data: any) => void>(); // 订单路线处理函数 |
| | | const vehicleLocationHandlerRef = useRef<(data: any) => void>(); // 车辆位置处理函数 |
| | | // 2-13修改的备注 start: 记录已为某订单拉取过车辆信息,避免重复 |
| | | const lastFetchedVehicleOrderIdRef = useRef<string | null>(null); |
| | | // 2-13修改的备注 end |
| | | |
| | | // 应用路线点 |
| | | const applyRoutePoints = (points: any[]) => { |
| | |
| | | if (res?.fareEstimate) { |
| | | setFareEstimate(res.fareEstimate); |
| | | } |
| | | if(res?.xpPricingCard){ |
| | | Taro.setStorageSync('xpPricingCard', res.xpPricingCard); |
| | | } |
| | | // 2-13修改的备注 start: 解析预估阶段返回的路线并展示 |
| | | const route = res?.route; |
| | | const distance = Number(route?.distance); |
| | | const duration = Number(route?.duration); |
| | | const points = (Array.isArray(route?.polyline) ? route?.polyline : []); |
| | | if (!Number.isNaN(distance)) { |
| | | setRouteDistance(distance); |
| | | } |
| | | if (!Number.isNaN(duration)) { |
| | | setRouteDuration(duration); |
| | | } |
| | | if (Array.isArray(points) && points.length > 0) { |
| | | applyRoutePoints(points); |
| | | } else { |
| | | applyRoutePoints([]); |
| | | } |
| | | // 2-13修改的备注 end |
| | | }).catch(() => { |
| | | }); |
| | | }; |
| | | |
| | | // 2-13修改的备注 start: 计算“当前位置→上车点”的步行路径 |
| | | const updateWalkingRouteForStart = async () => { |
| | | try { |
| | | const loc = await Taro.getLocation({ type: 'gcj02' }); |
| | | const originLat = loc.latitude; |
| | | const originLng = loc.longitude; |
| | | const targetLat = startSite?.latitude; |
| | | const targetLng = startSite?.longitude; |
| | | if (!originLat || !originLng || !targetLat || !targetLng) { |
| | | setWalkingPoints([]); |
| | | return; |
| | | } |
| | | const origin = `${originLng},${originLat}`; |
| | | const destination = `${targetLng},${targetLat}`; |
| | | const wPoints = await getWalkingRoute(origin, destination); |
| | | if (Array.isArray(wPoints) && wPoints.length > 0) { |
| | | setWalkingPoints(wPoints); |
| | | } else { |
| | | setWalkingPoints([]); |
| | | } |
| | | } catch (e) { |
| | | setWalkingPoints([]); |
| | | } |
| | | }; |
| | | // 2-13修改的备注 end |
| | | |
| | | // 认证成功处理函数 |
| | | useEffect(() => { |
| | |
| | | messageHandlerRef.current = (data) => { |
| | | console.log('ws message:', data); |
| | | }; |
| | | // 2-13修改的备注 start: 优化订单状态回调中的车辆信息拉取逻辑 |
| | | orderStatusHandlerRef.current = (payload) => { |
| | | const mappedTripStatus = payload?.tripStatus; |
| | | console.log('payload?.tripStatus',payload?.tripStatus) |
| | | if (typeof mappedTripStatus === 'number') { |
| | | setTripStatus(mappedTripStatus); |
| | | } |
| | | // 车辆信息拉取:当收到有效的订单号时,在首次达到呼叫及后续阶段拉取车辆信息 |
| | | const incomingOrderId = payload?.content?.orderId || Taro.getStorageSync('orderId'); |
| | | const vehicleInfoEmpty = |
| | | !vehicleInfo || |
| | | (!vehicleInfo.plateNumber && !vehicleInfo.vehicleCode && !vehicleInfo.vehicleColor && !vehicleInfo.vehiclePictures); |
| | | const shouldFetchVehicle = |
| | | !!incomingOrderId && |
| | | (lastFetchedVehicleOrderIdRef.current !== incomingOrderId) && |
| | | (vehicleInfoEmpty || orderId !== incomingOrderId); |
| | | if (incomingOrderId && orderId !== incomingOrderId) { |
| | | setOrderId(incomingOrderId); |
| | | } |
| | | if (shouldFetchVehicle) { |
| | | lastFetchedVehicleOrderIdRef.current = incomingOrderId; |
| | | queryVehicleByOrderId({ orderId: incomingOrderId }) |
| | | .then((res: any) => { |
| | | setVehicleInfo(res); |
| | | }) |
| | | .catch(() => { }); |
| | | } |
| | | if (payload?.clearRoute) { |
| | | setRoutePoints([]); |
| | |
| | | } |
| | | } |
| | | }; |
| | | // 2-13修改的备注 end |
| | | orderRouteHandlerRef.current = (payload) => { |
| | | const points = payload?.points || []; |
| | | applyRoutePoints(points); |
| | |
| | | applyRoutePoints(storedPoints); |
| | | }, [tripStatus]); |
| | | |
| | | // 2-13修改的备注 start: 在tripStatus=1时也绘制预估路线与步行路线 |
| | | useEffect(() => { |
| | | if (shouldShowRoute && routePoints.length > 0) { |
| | | setPolyline([{ |
| | | points: routePoints, |
| | | color: '#6D7FD9', |
| | | width: 10, |
| | | arrowLine: true, |
| | | }]); |
| | | if ((shouldShowRoute) && (routePoints.length > 0 || walkingPoints.length > 0)) { |
| | | const lines: any[] = []; |
| | | if (routePoints.length > 0) { |
| | | lines.push({ |
| | | points: routePoints, |
| | | color: '#6D7FD9', |
| | | width: 10, |
| | | arrowLine: true, |
| | | }); |
| | | } |
| | | if (walkingPoints.length > 0) { |
| | | lines.push({ |
| | | points: walkingPoints, |
| | | color: '#6D7FD9', |
| | | width: 6, |
| | | dottedLine: true, |
| | | arrowLine: true, |
| | | arrowIconPath: lineRight, |
| | | borderColor: '#FFFFFF', |
| | | }); |
| | | } |
| | | setPolyline(lines); |
| | | } else { |
| | | setPolyline([]); |
| | | } |
| | | }, [tripStatus, routePoints]); |
| | | }, [tripStatus, routePoints, walkingPoints]); |
| | | // 2-13修改的备注 end |
| | | |
| | | // 页面初始化 |
| | | useDidShow(() => { |
| | |
| | | if (originLng && originLat && e?.longitude && e?.latitude) { |
| | | requestRouteAndFare(originLat, originLng, e.latitude, e.longitude); |
| | | } |
| | | // 2-13修改的备注 start: 初始阶段尝试计算步行路线 |
| | | if (tripStatus === 1 && s?.latitude && s?.longitude) { |
| | | updateWalkingRouteForStart(); |
| | | } |
| | | // 2-13修改的备注 end |
| | | }); |
| | | |
| | | useEffect(() => { |
| | |
| | | if (originLng && originLat && endSite?.longitude && endSite?.latitude) { |
| | | requestRouteAndFare(originLat, originLng, endSite.latitude, endSite.longitude); |
| | | } |
| | | // 2-13修改的备注 start: 选择或更新上车点后重新计算步行路线 |
| | | if (tripStatus === 1 && startSite?.latitude && startSite?.longitude) { |
| | | updateWalkingRouteForStart(); |
| | | } |
| | | // 2-13修改的备注 end |
| | | }, [startSite?.longitude, startSite?.latitude, endSite?.longitude, endSite?.latitude, longitude, latitude]); |
| | | |
| | | // 生成标记点 |
| | |
| | | setMarkers(nextMarkers); |
| | | }, [tripStatus, startSite?.name, startSite?.latitude, startSite?.longitude, endSite.name, endSite?.latitude, endSite?.longitude, vehicleLocation]) |
| | | |
| | | // 2-13修改的备注 start: 调整视图以包含所有路线点(含步行) |
| | | useEffect(() => { |
| | | if (shouldShowRoute && routePoints.length > 0) { |
| | | if (shouldShowRoute && (routePoints.length > 0 || walkingPoints.length > 0)) { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | if ([8, 9].includes(tripStatus)) { |
| | | mapCtx.includePoints({ |
| | | points: routePoints, |
| | | padding: [50, 50, 500, 50] |
| | | }) |
| | | } else { |
| | | mapCtx.moveToLocation({ |
| | | longitude: routePoints[0].longitude, |
| | | latitude: routePoints[0].latitude |
| | | }) |
| | | } |
| | | const allPoints = [ |
| | | ...routePoints, |
| | | ...walkingPoints, |
| | | ]; |
| | | mapCtx.includePoints({ |
| | | points: allPoints, |
| | | padding: [50, 50, 500, 50] |
| | | }); |
| | | } |
| | | }, [tripStatus, routePoints]) |
| | | }, [tripStatus, routePoints, walkingPoints]); |
| | | // 2-13修改的备注 end |
| | | |
| | | const formatTime = (sec: number) => { |
| | | const m = Math.floor(sec / 60); |
| | |
| | | } |
| | | |
| | | const toCoupon = () => { |
| | | if (Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true') { |
| | | Taro.showToast({ |
| | | title: '请先同意协议', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/coupon/index', |
| | | }) |
| | | } |
| | | |
| | | const toOrder = () => { |
| | | if (Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true') { |
| | | Taro.showToast({ |
| | | title: '请先同意协议', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/order/index', |
| | | }) |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import { Component, useState, useEffect } from 'react'; |
| | | import { View, Text, Image, Input } from '@tarojs/components'; |
| | | import Taro, { useRouter } from '@tarojs/taro'; |
| | | import { useState, useEffect } from 'react'; |
| | | import { View, Text, Image } from '@tarojs/components'; |
| | | import { getFareBreakdown, FareBreakdownResponse, getFAQList, FAQItem } from '../../api/order'; |
| | | import EvaluatePopup from '../begin-route/components/evaluate-popup'; |
| | | import './index.less'; |
| | | import Subtract from '../../assets/Subtract.png'; |
| | | import more from '../../assets/more.png'; |
| | | |
| | | // FAQ数据 |
| | | const faqData = [ |
| | | { |
| | | id: 1, |
| | | question: '为什么我的优惠券用不了?', |
| | | answer: '惊喜特价不可叠加使用优惠券' |
| | | }, |
| | | { |
| | | id: 2, |
| | | question: '如何申请发票?', |
| | | answer: '您可以在订单详情页面点击"申请发票"按钮,填写相关信息后提交申请' |
| | | }, |
| | | { |
| | | id: 3, |
| | | question: '取消订单会扣费吗?', |
| | | answer: '根据取消时间不同,可能会产生不同的取消费用,具体请查看取消规则' |
| | | }, |
| | | { |
| | | id: 4, |
| | | question: '如何联系客服?', |
| | | answer: '您可以在APP首页点击"客服"按钮,或拨打客服热线400-xxx-xxxx' |
| | | }, |
| | | { |
| | | id: 5, |
| | | question: '支付失败怎么办?', |
| | | answer: '请检查您的支付方式是否正常,或尝试更换其他支付方式' |
| | | }, |
| | | { |
| | | id: 6, |
| | | question: '行程中可以修改目的地吗?', |
| | | answer: '可以的,您可以在行程中联系客服或在APP中修改目的地' |
| | | }, |
| | | { |
| | | id: 7, |
| | | question: '如何评价订单?', |
| | | answer: '订单完成后,您可以在订单详情页面进行评价' |
| | | } |
| | | ]; |
| | | |
| | | export default function OrderPage() { |
| | | const router = useRouter(); |
| | | const { orderId } = router.params; |
| | | const [showAllFaq, setShowAllFaq] = useState(false); // 是否显示全部FAQ |
| | | const [expandedItems, setExpandedItems] = useState<number[]>([]); // 展开的FAQ项ID列表 |
| | | const [fareData, setFareData] = useState<FareBreakdownResponse | null>(null); |
| | | const [faqData, setFaqData] = useState<FAQItem[]>([]); |
| | | const [showEvaluatePopup, setShowEvaluatePopup] = useState(false); |
| | | |
| | | useEffect(() => { |
| | | if (orderId) { |
| | | fetchData(orderId); |
| | | } |
| | | fetchFAQ(); |
| | | }, [orderId]); |
| | | |
| | | const fetchData = async (id: string) => { |
| | | try { |
| | | const res = await getFareBreakdown(id); |
| | | setFareData(res); |
| | | } catch (error) { |
| | | console.error('获取费用明细失败', error); |
| | | Taro.showToast({ title: '获取费用明细失败', icon: 'none' }); |
| | | } |
| | | }; |
| | | |
| | | const fetchFAQ = async () => { |
| | | try { |
| | | const res = await getFAQList(); |
| | | setFaqData(res || []); |
| | | } catch (error) { |
| | | console.error('获取常见问题失败', error); |
| | | } |
| | | }; |
| | | |
| | | // 切换FAQ项的展开/收起状态 |
| | | const toggleFaqItem = (id: number) => { |
| | |
| | | // 根据showAllFaq状态决定显示的FAQ数量 |
| | | const displayedFaq = showAllFaq ? faqData : faqData.slice(0, 5); |
| | | |
| | | const getTimeLabel = (type: number) => { |
| | | const labels = { |
| | | 1: '普通时段', |
| | | 2: '夜间行驶时段', |
| | | 3: '高峰时段', |
| | | 4: '节假日时段' |
| | | }; |
| | | return labels[type] || '其他时段'; |
| | | }; |
| | | |
| | | const sumMoney = (items: any[]) => { |
| | | if (!Array.isArray(items)) return '0.00'; |
| | | return items.reduce((sum, item) => sum + (item.money || 0), 0).toFixed(2); |
| | | }; |
| | | |
| | | const formatMoney = (amount: number | undefined) => { |
| | | return (amount || 0).toFixed(2); |
| | | }; |
| | | |
| | | if (!fareData) return null; |
| | | |
| | | return ( |
| | | <View className='expense-detail-page'> |
| | | {/* 账单信息 */} |
| | | <View className='order-info'> |
| | | <View className='flex-between-card'> |
| | | <View className='title'>账单信息</View> |
| | | <View className='look-rule'> |
| | | <View className='look-rule' onClick={() => Taro.navigateTo({ url: '/robotaxi/pages/expense-rule/index' })}> |
| | | <Text className='look-rule-txt'>查看计价规则</Text> |
| | | <Image className='arrow-icon' src={Subtract}></Image> |
| | | </View> |
| | |
| | | <View className='price-card'> |
| | | <View className='flex-between-card mb-16'> |
| | | <View className='price-label'>订单总额</View> |
| | | <View className='price-value'>36.6元</View> |
| | | <View className='price-value'>{formatMoney(fareData.orderMoney)}元</View> |
| | | </View> |
| | | |
| | | {/* 起步费 */} |
| | | <View className='flex-between-card'> |
| | | <View className='price-label'>起步价</View> |
| | | <View className='price-value'>12.5元</View> |
| | | <View className='price-value'>{sumMoney(fareData.start)}元</View> |
| | | </View> |
| | | <View className='flex-between-card mb-16'> |
| | | <View className='price-notice-label'>17:00-19:00(3.2公里,12.0分钟)</View> |
| | | <View className='price-notice-value'>12.5元</View> |
| | | </View> |
| | | {fareData.start?.map((item, index) => ( |
| | | <View className='flex-between-card mb-16' key={index}> |
| | | <View className='price-notice-label'> |
| | | {item.startTime}-{item.endTime}({item.distance}公里,{item.duration}分钟) |
| | | </View> |
| | | <View className='price-notice-value'>{formatMoney(item.money)}元</View> |
| | | </View> |
| | | ))} |
| | | |
| | | {/* 里程费 */} |
| | | <View className='flex-between-card'> |
| | | <View className='price-label'>里程费</View> |
| | | <View className='price-value'>8.6元</View> |
| | | <View className='price-value'>{sumMoney(fareData.mileage)}元</View> |
| | | </View> |
| | | <View className='flex-between-card mb-16'> |
| | | <View className='price-notice-label'>普通时段(7.2公里)</View> |
| | | <View className='price-notice-value'>8.6元</View> |
| | | </View> |
| | | {fareData.mileage?.map((item, index) => ( |
| | | <View className='flex-between-card mb-16' key={index}> |
| | | <View className='price-notice-label'>{getTimeLabel(item.type)}({item.distance}公里)</View> |
| | | <View className='price-notice-value'>{formatMoney(item.money)}元</View> |
| | | </View> |
| | | ))} |
| | | |
| | | {/* 时长费 */} |
| | | <View className='flex-between-card'> |
| | | <View className='price-label'>时长费(20分钟)</View> |
| | | <View className='price-value'>8.6元</View> |
| | | <View className='price-label'>时长费</View> |
| | | <View className='price-value'>{sumMoney(fareData.duration)}元</View> |
| | | </View> |
| | | <View className='flex-between-card mb-16'> |
| | | <View className='price-notice-label'>普通时段(20分钟)</View> |
| | | <View className='price-notice-value'>4.0元</View> |
| | | </View> |
| | | {fareData.duration?.map((item, index) => ( |
| | | <View className='flex-between-card mb-16' key={index}> |
| | | <View className='price-notice-label'>{getTimeLabel(item.type)}({item.duration}分钟)</View> |
| | | <View className='price-notice-value'>{formatMoney(item.money)}元</View> |
| | | </View> |
| | | ))} |
| | | |
| | | {/* 优惠券 */} |
| | | <View className='flex-between-card'> |
| | | <View className='price-label'>优惠券</View> |
| | | <View className='price-value color-red'>-2.3元</View> |
| | | <View className='price-value color-red'>-{formatMoney(fareData.couponMoney)}元</View> |
| | | </View> |
| | | </View> |
| | | |
| | | {/* 总价格 */} |
| | | <View className='flex-card total'> |
| | | <Text className='total-text'>总费用</Text> |
| | | <Text className='num'>34.0</Text> |
| | | <Text className='num'>{formatMoney(fareData.payMoney)}</Text> |
| | | <Text className='unit'>元</Text> |
| | | </View> |
| | | </View> |
| | | {/* 常见问题 */} |
| | | <View className='faq'> |
| | | <View className='title'>常见问题</View> |
| | | |
| | | {displayedFaq.map((item) => ( |
| | | <View className='faq-item' key={item.id}> |
| | | <View className='question' onClick={() => toggleFaqItem(item.id)}> |
| | | <Text className='question-text'>{item.question}</Text> |
| | | {/* 常见问题 */} |
| | | {faqData.length > 0 && ( |
| | | <View className='faq'> |
| | | <View className='title'>常见问题</View> |
| | | |
| | | {displayedFaq.map((item) => ( |
| | | <View className='faq-item' key={item.id}> |
| | | <View className='question' onClick={() => toggleFaqItem(item.id)}> |
| | | <Text className='question-text'>{item.questions}</Text> |
| | | <Image |
| | | className={`arrow-icon ${expandedItems.includes(item.id) ? 'rotated' : ''}`} |
| | | src={more} |
| | | ></Image> |
| | | </View> |
| | | {expandedItems.includes(item.id) && ( |
| | | <View className='answer'>{item.answer}</View> |
| | | )} |
| | | </View> |
| | | ))} |
| | | |
| | | {/* 展开更多/收起 */} |
| | | {faqData.length > 5 && ( |
| | | <View className='expand-more' onClick={toggleShowAll}> |
| | | <Text className='expand-more-text'>{showAllFaq ? '收起' : '展开更多'}</Text> |
| | | <Image |
| | | className={`arrow-icon ${expandedItems.includes(item.id) ? 'rotated' : ''}`} |
| | | className={`arrow-icon ${showAllFaq ? 'rotated' : ''}`} |
| | | src={more} |
| | | ></Image> |
| | | </View> |
| | | {expandedItems.includes(item.id) && ( |
| | | <View className='answer'>{item.answer}</View> |
| | | )} |
| | | </View> |
| | | ))} |
| | | |
| | | {/* 展开更多/收起 */} |
| | | {faqData.length > 5 && ( |
| | | <View className='expand-more' onClick={toggleShowAll}> |
| | | <Text className='expand-more-text'>{showAllFaq ? '收起' : '展开更多'}</Text> |
| | | <Image |
| | | className={`arrow-icon ${showAllFaq ? 'rotated' : ''}`} |
| | | src={more} |
| | | ></Image> |
| | | </View> |
| | | )} |
| | | </View> |
| | | )} |
| | | </View> |
| | | )} |
| | | {/* 去投诉 */} |
| | | <View className='complaint'> |
| | | <View className='complaint' onClick={() => setShowEvaluatePopup(true)}> |
| | | <Text className='complaint-text'>不认可车费,去投诉</Text> |
| | | </View> |
| | | |
| | | |
| | | |
| | | {/* 评价弹窗 */} |
| | | <EvaluatePopup |
| | | show={showEvaluatePopup} |
| | | orderId={orderId} |
| | | orderRating={1} |
| | | onClose={() => setShowEvaluatePopup(false)} |
| | | onConfirm={() => { |
| | | setShowEvaluatePopup(false); |
| | | Taro.showToast({ title: '投诉已提交', icon: 'success' }); |
| | | }} |
| | | /> |
| | | </View> |
| | | ) |
| | | } |
| | |
| | | import Subtract from '../../assets/Subtract.png'; |
| | | |
| | | export default function OrderPage() { |
| | | const [pricingCard, setPricingCard] = useState<any>(null); |
| | | const [startSite, setStartSite] = useState<any>(null); |
| | | const [endSite, setEndSite] = useState<any>(null); |
| | | |
| | | useEffect(() => { |
| | | const card = Taro.getStorageSync('xpPricingCard'); |
| | | const start = Taro.getStorageSync('startSite'); |
| | | const end = Taro.getStorageSync('endSite'); |
| | | |
| | | if (card) setPricingCard(card); |
| | | if (start) setStartSite(start); |
| | | if (end) setEndSite(end); |
| | | }, []); |
| | | |
| | | if (!pricingCard) return null; |
| | | |
| | | return ( |
| | | <View className='expense-detail-page'> |
| | | <View className='car-code'>粤PXV7029</View> |
| | | {/* <View className='car-code'>粤PXV7029</View> */} |
| | | <View className='relative'> |
| | | <View className='flex-card start'> |
| | | <View className='start-icon'></View> |
| | | <View className='start-address'>广州岭南职业技术学院-南1门(公交站)</View> |
| | | <View className='start-address'>{startSite?.name || '未知起点'}</View> |
| | | </View> |
| | | <View className='line'></View> |
| | | <View className='flex-card'> |
| | | <View className='end-icon'></View> |
| | | <View className='start-address'>小鹏科技园2号楼</View> |
| | | <View className='start-address'>{endSite?.name || '未知终点'}</View> |
| | | </View> |
| | | </View> |
| | | {/* 订单价格 */} |
| | | <View className='price-card'> |
| | | <View className='flex-between-card mb-16'> |
| | | <View className='price-label'>订单总额</View> |
| | | <View className='price-value'>36.6元</View> |
| | | <View className='price-value'>{pricingCard.totalPrice}元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='price-label'>起步价</View> |
| | | <View className='price-value'>12.5元</View> |
| | | <View className='price-value'>{pricingCard.startPrice}元</View> |
| | | </View> |
| | | <View className='flex-between-card mb-16'> |
| | | <View className='price-notice-label'>17:00-19:00(3.2公里,12.0分钟)</View> |
| | | <View className='price-notice-value'>12.5元</View> |
| | | <View className='price-notice-label'>{pricingCard.time}({pricingCard.distance}公里,{pricingCard.duration}分钟)</View> |
| | | <View className='price-notice-value'>{pricingCard.startPrice}元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='price-label'>里程费</View> |
| | | <View className='price-value'>8.6元</View> |
| | | <View className='price-value'>{pricingCard.distancePrice}元</View> |
| | | </View> |
| | | <View className='flex-between-card mb-16'> |
| | | <View className='price-notice-label'>普通时段(7.2公里)</View> |
| | | <View className='price-notice-value'>8.6元</View> |
| | | <View className='price-notice-label'>普通时段({pricingCard.distancePriceNormal}公里)</View> |
| | | <View className='price-notice-value'>{pricingCard.distancePrice}元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='price-label'>时长费(20分钟)</View> |
| | | <View className='price-value'>8.6元</View> |
| | | <View className='price-label'>时长费({pricingCard.durationPriceNormal}分钟)</View> |
| | | <View className='price-value'>{pricingCard.durationPrice}元</View> |
| | | </View> |
| | | <View className='flex-between-card mb-16'> |
| | | <View className='price-notice-label'>普通时段(20分钟)</View> |
| | | <View className='price-notice-value'>4.0元</View> |
| | | <View className='price-notice-label'>普通时段({pricingCard.durationPriceNormal}分钟)</View> |
| | | <View className='price-notice-value'>{pricingCard.durationPrice}元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='price-label'>优惠券</View> |
| | | <View className='price-value color-red'>-2.3元</View> |
| | | </View> |
| | | {pricingCard.coupon && ( |
| | | <View className='flex-between-card'> |
| | | <View className='price-label'>优惠券</View> |
| | | <View className='price-value color-red'>-{pricingCard.coupon}元</View> |
| | | </View> |
| | | )} |
| | | </View> |
| | | {/* 总价格 */} |
| | | <View className='flex-card total'> |
| | | <Text className='total-text'>总费用</Text> |
| | | <Text className='num'>34.0</Text> |
| | | <Text className='num'>{pricingCard.totalPrice}</Text> |
| | | <Text className='unit'>元</Text> |
| | | </View> |
| | | {/* 质疑事项 */} |
| New file |
| | |
| | | { |
| | | "code": 0, |
| | | "data": [ |
| | | { |
| | | "answer": "string", answer |
| | | "createBy": "string", |
| | | "createTime": "2026-02-13T10:10:04.780Z", |
| | | "deleted": true, |
| | | "id": 0, |
| | | "questions": "string", questions |
| | | "updateBy": "string", |
| | | "updateTime": "2026-02-13T10:10:04.780Z" |
| | | } |
| | | ], |
| | | "msg": "string" |
| | | } |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import { Component, useState, useEffect } from 'react'; |
| | | import Taro, { useRouter } from '@tarojs/taro'; |
| | | import { useState, useEffect } from 'react'; |
| | | import { View, Text, Image, Map } from '@tarojs/components'; |
| | | import './index.less'; |
| | | import good from '../../../assets/good.png' |
| | |
| | | import up from '../../../assets/up.png' |
| | | import down from '../../../assets/down.png' |
| | | import lineRight from '../../../assets/line-right.png' |
| | | import { AMAP_KEY } from '../../../constants/api'; |
| | | import amapFile from '../../../libs/amap-wx.130' |
| | | import { getOrderDetail, OrderDetail } from '../../../api/order'; |
| | | |
| | | // Marker样式配置 |
| | | const MARKER_CONFIG = { |
| | |
| | | height: 36, // marker高度(包含底部小圆点) |
| | | anchorX: 0.55, // 锚点X(0.5表示居中) |
| | | anchorY: 0.8, // 锚点Y(1表示底部对齐坐标点) |
| | | // 起点样式 |
| | | start: { |
| | | bgColor: '#5B8CFF', // 蓝色背景 |
| | | textColor: '#FFFFFF', // 白色文字 |
| | | text: '下', // 显示文字 |
| | | dotColor: '#3D6FE6' // 底部小圆点颜色 |
| | | }, |
| | | // 终点样式 |
| | | end: { |
| | | bgColor: '#7FD15C', // 绿色背景 |
| | | textColor: '#FFFFFF', // 白色文字 |
| | | text: '下', // 显示文字 |
| | | dotColor: '#5FB83D' // 底部小圆点颜色 |
| | | } |
| | | } |
| | | |
| | | export default function orderDetailPage() { |
| | | const [activeIndex, setActiveIndex] = useState(3) // 默认选中"非常满意" |
| | | const router = useRouter(); |
| | | const { orderId } = router.params; |
| | | |
| | | // 虚拟订单数据 |
| | | const mockOrderData = { |
| | | carNumber: '粤B·XP8888', |
| | | carColor: '白色', |
| | | carCode: '07', |
| | | price: '28.5', |
| | | oldPrice: '35.0', |
| | | distance: '5.2', |
| | | duration: '18', |
| | | startAddress: '天府广场地铁站A口', |
| | | endAddress: '成都东站', |
| | | startPoint: { longitude: 104.065735, latitude: 30.659462 }, // 成都天府广场 |
| | | endPoint: { longitude: 104.141728, latitude: 30.641982 } // 成都东站 |
| | | } |
| | | const [activeIndex, setActiveIndex] = useState(3) // 默认选中"非常满意" |
| | | const [orderData, setOrderData] = useState<OrderDetail | null>(null); |
| | | |
| | | // 地图相关状态 |
| | | const [longitude, setLongitude] = useState(104.103731) // 地图中心点经度 |
| | |
| | | const [polyline, setPolyline] = useState<any[]>([]) // 行驶路径 |
| | | |
| | | useEffect(() => { |
| | | const { startPoint, endPoint, startAddress, endAddress } = mockOrderData |
| | | if (orderId) { |
| | | fetchOrderDetail(orderId); |
| | | } |
| | | }, [orderId]); |
| | | |
| | | const fetchOrderDetail = async (id: string) => { |
| | | try { |
| | | const res = await getOrderDetail(id); |
| | | setOrderData(res); |
| | | initMap(res); |
| | | if (res.orderRating) { |
| | | setActiveIndex(res.orderRating); |
| | | } |
| | | } catch (error) { |
| | | console.error('获取订单详情失败', error); |
| | | Taro.showToast({ title: '获取详情失败', icon: 'none' }); |
| | | } |
| | | }; |
| | | |
| | | const initMap = (data: OrderDetail) => { |
| | | const { startLat, startLon, endLat, endLon, trajectories } = data; |
| | | |
| | | // 设置地图中心点(起点和终点的中间位置) |
| | | const centerLng = (startPoint.longitude + endPoint.longitude) / 2 |
| | | const centerLat = (startPoint.latitude + endPoint.latitude) / 2 |
| | | setLongitude(centerLng) |
| | | setLatitude(centerLat) |
| | | const centerLng = (startLon + endLon) / 2; |
| | | const centerLat = (startLat + endLat) / 2; |
| | | setLongitude(centerLng); |
| | | setLatitude(centerLat); |
| | | |
| | | // 异步创建自定义marker图标 |
| | | const initMarkers = async () => { |
| | | const startIcon = up |
| | | const endIcon = down |
| | | // 设置起点和终点标记 |
| | | const markerList = [ |
| | | { |
| | | id: 1, |
| | | longitude: startLon, |
| | | latitude: startLat, |
| | | width: MARKER_CONFIG.width, |
| | | height: MARKER_CONFIG.height, |
| | | iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/51e7cb75-72af-4994-9d6e-d999efb2fbc7.png', |
| | | anchor: { x: MARKER_CONFIG.anchorX, y: MARKER_CONFIG.anchorY }, |
| | | }, |
| | | { |
| | | id: 2, |
| | | longitude: endLon, |
| | | latitude: endLat, |
| | | width: MARKER_CONFIG.width, |
| | | height: MARKER_CONFIG.height, |
| | | iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/6a56b1eb-ec37-4839-8733-262942af070d.png', |
| | | anchor: { x: MARKER_CONFIG.anchorX, y: MARKER_CONFIG.anchorY } |
| | | } |
| | | ]; |
| | | setMarkers(markerList); |
| | | |
| | | // 设置起点和终点标记 |
| | | const markerList = [ |
| | | { |
| | | id: 1, |
| | | longitude: startPoint.longitude, |
| | | latitude: startPoint.latitude, |
| | | width: MARKER_CONFIG.width, |
| | | height: MARKER_CONFIG.height, |
| | | iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/51e7cb75-72af-4994-9d6e-d999efb2fbc7.png', |
| | | anchor: { x: MARKER_CONFIG.anchorX, y: MARKER_CONFIG.anchorY }, |
| | | }, |
| | | { |
| | | id: 2, |
| | | longitude: endPoint.longitude, |
| | | latitude: endPoint.latitude, |
| | | width: MARKER_CONFIG.width, |
| | | height: MARKER_CONFIG.height, |
| | | iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/6a56b1eb-ec37-4839-8733-262942af070d.png', |
| | | anchor: { x: MARKER_CONFIG.anchorX, y: MARKER_CONFIG.anchorY } |
| | | } |
| | | ] |
| | | setMarkers(markerList) |
| | | } |
| | | |
| | | initMarkers() |
| | | |
| | | // 模拟真实的行驶路径(模拟从天府广场到成都东站的路线) |
| | | const routePath = [ |
| | | { longitude: 104.065735, latitude: 30.659462 }, // 起点:天府广场 |
| | | { longitude: 104.073856, latitude: 30.657891 }, // 途经点1 |
| | | { longitude: 104.082145, latitude: 30.655234 }, // 途经点2 |
| | | { longitude: 104.095678, latitude: 30.651456 }, // 途经点3 |
| | | { longitude: 104.108234, latitude: 30.648123 }, // 途经点4 |
| | | { longitude: 104.121567, latitude: 30.645789 }, // 途经点5 |
| | | { longitude: 104.132890, latitude: 30.643456 }, // 途经点6 |
| | | { longitude: 104.141728, latitude: 30.641982 } // 终点:成都东站 |
| | | ] |
| | | // 处理行驶路径 |
| | | const routePath = trajectories?.length > 0 |
| | | ? trajectories.map(item => ({ longitude: item.longitude || item.lon, latitude: item.latitude || item.lat })) |
| | | : [ |
| | | { longitude: startLon, latitude: startLat }, |
| | | { longitude: endLon, latitude: endLat } |
| | | ]; |
| | | |
| | | const polylineData = [{ |
| | | points: routePath, |
| | | color: '#6D7FD9', |
| | | width: 10, |
| | | arrowLine: true, // 显示箭头 |
| | | arrowLine: true, |
| | | arrowIconPath: lineRight, |
| | | borderColor: '#FFFFFF', |
| | | // borderWidth: 2 |
| | | }] |
| | | setPolyline(polylineData) |
| | | }, []) |
| | | }]; |
| | | setPolyline(polylineData); |
| | | }; |
| | | |
| | | useEffect(() => { |
| | | if (!polyline.length || !polyline[0]?.points?.length) return; |
| | |
| | | |
| | | const todescription = () => { |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/expense-description/index', |
| | | url: `/robotaxi/pages/expense-description/index?orderId=${orderId}`, |
| | | }) |
| | | } |
| | | |
| | | if (!orderData) return null; |
| | | |
| | | return ( |
| | | <View className='order-detail-page'> |
| | |
| | | <View className='car-info'> |
| | | <View className='info-top'> |
| | | <View> |
| | | <View className='car-name'>{mockOrderData.carNumber}</View> |
| | | <View className='car-name'>{orderData.plateNumber}</View> |
| | | <View className='info'> |
| | | <Text className='car-color'>{mockOrderData.carColor}</Text> |
| | | <Text className='car-color'>{orderData.vehicleColor}</Text> |
| | | <Text className='info-line'></Text> |
| | | <Text className='car-code'>车身编码: </Text> |
| | | <Text className='code'>{mockOrderData.carCode}</Text> |
| | | <Text className='code'>{orderData.vehicleCode}</Text> |
| | | </View> |
| | | </View> |
| | | <Image src={car} className='car-img' /> |
| | | <Image src={orderData.vehiclePictures || car} className='car-img' /> |
| | | </View> |
| | | <View className='evaluate-card'> |
| | | <View className='card-title'>您对本次行程满意吗?</View> |
| | | <View className='card-content'> |
| | | <View className={`evaluate-item ${activeIndex == 1 ? 'active' : ''}`} onClick={() => { setActiveIndex(1) }}> |
| | | <View className={`evaluate-item ${activeIndex == 1 ? 'active' : ''}`}> |
| | | <Image src={activeIndex == 1 ? activeBad : bad} className='evaluate-img' /> |
| | | <Text className={`evaluate-text ${activeIndex == 1 ? 'active-text' : ''}`}>非常不满意</Text> |
| | | </View> |
| | | <View className={`evaluate-item ${activeIndex == 2 ? 'active' : ''}`} onClick={() => { setActiveIndex(2) }}> |
| | | <View className={`evaluate-item ${activeIndex == 2 ? 'active' : ''}`}> |
| | | <Image src={activeIndex == 2 ? activeNormal : normal} className='evaluate-img' /> |
| | | <Text className={`evaluate-text ${activeIndex == 2 ? 'active-text' : ''}`}>一般般</Text> |
| | | </View> |
| | | <View className={`evaluate-item ${activeIndex == 3 ? 'active' : ''}`} onClick={() => { setActiveIndex(3) }}> |
| | | <View className={`evaluate-item ${activeIndex == 3 ? 'active' : ''}`}> |
| | | <Image src={activeIndex == 3 ? activeGood : good} className='evaluate-img' /> |
| | | <Text className={`evaluate-text ${activeIndex == 3 ? 'active-text' : ''}`}>非常满意</Text> |
| | | </View> |
| | |
| | | <View className='trip-info'> |
| | | <View className='trip-top'> |
| | | <View className='price-card'> |
| | | <Text className='price'>{mockOrderData.price}</Text> |
| | | <Text className='price'>{orderData.payMoney || 0}</Text> |
| | | <Text className='unit'>元</Text> |
| | | <Text className='old-price'>{mockOrderData.oldPrice}元</Text> |
| | | {orderData.orderMoney > orderData.payMoney && ( |
| | | <Text className='old-price'>{orderData.orderMoney || 0}元</Text> |
| | | )} |
| | | </View> |
| | | <View onClick={todescription} className='price-remark'> |
| | | <Text className='remark-text'>费用说明</Text> |
| | |
| | | </View> |
| | | {/* 行程信息 */} |
| | | <View className='trip-line'> |
| | | <View className='line-name'>全程{mockOrderData.distance}公里|{mockOrderData.duration}分钟</View> |
| | | <View className='line-name'>全程{orderData.totalMileage || 0}公里|{orderData.orderTime || 0}分钟</View> |
| | | <View className='line-address'> |
| | | <View className='line-icon first-icon'></View> |
| | | <Text className='line-text'>{mockOrderData.startAddress}</Text> |
| | | <Text className='line-text'>{orderData.startAddress}</Text> |
| | | </View> |
| | | <View className='line-address'> |
| | | <View className='line-icon'></View> |
| | | <Text className='line-text'>{mockOrderData.endAddress}</Text> |
| | | <Text className='line-text'>{orderData.endAddress}</Text> |
| | | </View> |
| | | </View> |
| | | </View> |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import Taro, { useReachBottom } from '@tarojs/taro'; |
| | | import { Component, useState, useEffect } from 'react'; |
| | | import { View, Text, Image, Input } from '@tarojs/components'; |
| | | import './index.less'; |
| | | import { getOrderHistoryList, OrderHistoryGroup, OrderHistoryOrder } from '../../../robotaxi/api/order'; |
| | | |
| | | export default function OrderPage() { |
| | | const [orderList, setOrderList] = useState([ |
| | | { |
| | | id: '1', |
| | | time: '2026年02月03日 14:30', |
| | | start: '深圳湾科技生态园', |
| | | end: "南山区科技园地铁站" |
| | | }, |
| | | { |
| | | id: '2', |
| | | time: '2026年02月02日 09:15', |
| | | start: '腾讯滨海大厦', |
| | | end: "深圳北站" |
| | | }, |
| | | { |
| | | id: '3', |
| | | time: '2026年02月01日 18:45', |
| | | start: '华强北商业街', |
| | | end: "福田口岸" |
| | | }, |
| | | { |
| | | id: '4', |
| | | time: '2026年01月31日 16:20', |
| | | start: '宝安国际机场T3航站楼', |
| | | end: "前海自贸区管理局" |
| | | }, |
| | | { |
| | | id: '5', |
| | | time: '2026年01月30日 11:00', |
| | | start: '深圳市民中心', |
| | | end: "欢乐海岸购物中心" |
| | | }, |
| | | { |
| | | id: '6', |
| | | time: '2026年01月29日 20:30', |
| | | start: '世界之窗', |
| | | end: "南山文体中心" |
| | | }, |
| | | { |
| | | id: '7', |
| | | time: '2026年01月28日 08:45', |
| | | start: '龙华富士康科技园', |
| | | end: "坂田华为基地" |
| | | }, |
| | | { |
| | | id: '8', |
| | | time: '2026年01月27日 13:15', |
| | | start: '罗湖口岸', |
| | | end: "东门老街" |
| | | }, |
| | | { |
| | | id: '9', |
| | | time: '2026年01月26日 17:50', |
| | | start: '蛇口邮轮中心', |
| | | end: "海上世界文化艺术中心" |
| | | }, |
| | | { |
| | | id: '10', |
| | | time: '2026年01月25日 10:30', |
| | | start: '龙岗大运中心', |
| | | end: "坪山高铁站" |
| | | }, |
| | | { |
| | | id: '11', |
| | | time: '2026年01月24日 15:20', |
| | | start: '科技楼2号', |
| | | end: "科技楼2号" |
| | | }, |
| | | { |
| | | id: '12', |
| | | time: '2026年01月23日 19:00', |
| | | start: '福田CBD金融中心', |
| | | end: "车公庙地铁站" |
| | | const [groups, setGroups] = useState<OrderHistoryGroup[]>([]) |
| | | const [pageNum, setPageNum] = useState(1) |
| | | const [hasMore, setHasMore] = useState(true) |
| | | const pageSize = 10 |
| | | |
| | | const formatTime = (t?: string, fallback?: string) => { |
| | | if (!t) return fallback || '-'; |
| | | const d = new Date(t); |
| | | const y = d.getFullYear(); |
| | | const m = String(d.getMonth() + 1).padStart(2, '0'); |
| | | const day = String(d.getDate()).padStart(2, '0'); |
| | | const hh = String(d.getHours()).padStart(2, '0'); |
| | | const mm = String(d.getMinutes()).padStart(2, '0'); |
| | | return `${y}年${m}月${day}日 ${hh}:${mm}`; |
| | | } |
| | | |
| | | const getList = async (page: number, isRefresh = false) => { |
| | | try { |
| | | const res = await getOrderHistoryList({ pageNum: page, pageSize, status: 9 }); |
| | | const newList = (res && (res as any).list) || []; |
| | | if (isRefresh) { |
| | | setGroups(newList); |
| | | } else { |
| | | setGroups(prev => [...prev, ...newList]); |
| | | } |
| | | const count = newList.reduce((acc, g) => acc + (g.orders?.length || 0), 0); |
| | | setHasMore(count >= pageSize); |
| | | } catch (e) { |
| | | if (isRefresh) setGroups([]); |
| | | } |
| | | ]) |
| | | } |
| | | |
| | | useEffect(() => { |
| | | setPageNum(1); |
| | | setHasMore(true); |
| | | getList(1, true); |
| | | }, []) |
| | | |
| | | const toOrderDetail = (item) => { |
| | | Taro.navigateTo({ |
| | | url: '/robotaxi/pages/order/detail/index' |
| | | url: `/robotaxi/pages/order/detail/index?orderId=${item.orderId || ''}` |
| | | }) |
| | | } |
| | | |
| | | |
| | | const againOrder = () => { |
| | | const againOrder = (item?: OrderHistoryOrder) => { |
| | | Taro.navigateTo({ |
| | | url: '/pages/robotaxi/home/index' |
| | | }) |
| | | } |
| | | |
| | | useReachBottom(() => { |
| | | if (hasMore) { |
| | | const nextPage = pageNum + 1; |
| | | setPageNum(nextPage); |
| | | getList(nextPage); |
| | | } |
| | | }) |
| | | |
| | | return ( |
| | | <View className='order-page'> |
| | | { |
| | | orderList.map((item, index) => { |
| | | return <View className='order-item' key={item.id || index} onClick={() => toOrderDetail(item)}> |
| | | <View className='order-time'>{item.time || '-'}</View> |
| | | <View className='order-start'>起点:{item.start || '-'}</View> |
| | | <View className='order-start'>终点:{item.end || '-'}</View> |
| | | <View onClick={againOrder} className='order-again'> |
| | | <Text className='order-again-text'>再来一单</Text> |
| | | {groups.map((group, gi) => { |
| | | return <View key={group.dateLabel || gi}> |
| | | {(group.orders || []).map((item, index) => { |
| | | return <View className='order-item' key={item.orderId || index} onClick={() => toOrderDetail(item)}> |
| | | <View className='order-time'>{`${item.time} ${item.orderTime}` || formatTime(item.time)}</View> |
| | | <View className='order-start'>起点:{item.originAddress || '-'}</View> |
| | | <View className='order-start'>终点:{item.destinationAddress || '-'}</View> |
| | | <View onClick={(e) => { e.stopPropagation(); againOrder(item) }} className='order-again'> |
| | | <Text className='order-again-text'>再来一单</Text> |
| | | </View> |
| | | </View> |
| | | </View> |
| | | }) |
| | | } |
| | | })} |
| | | </View> |
| | | })} |
| | | |
| | | </View> |
| | | ) |
| New file |
| | |
| | | export default { |
| | | navigationBarTitleText: '计价规则', |
| | | }; |
| New file |
| | |
| | | .pricing-rules-page { |
| | | min-height: 100vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | padding: 72px 64px; |
| | | |
| | | .title { |
| | | font-family: PingFang SC; |
| | | font-size: 28px; |
| | | font-weight: 500; |
| | | line-height: 44px; |
| | | letter-spacing: normal; |
| | | /* 文字色/黑色/重要字 90% */ |
| | | color: rgba(0, 0, 0, 0.9); |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .dis { |
| | | font-family: PingFang SC; |
| | | font-size: 24px; |
| | | font-weight: normal; |
| | | line-height: 40px; |
| | | letter-spacing: normal; |
| | | /* 文字色/黑色/次要文字 60% */ |
| | | color: rgba(0, 0, 0, 0.6); |
| | | } |
| | | |
| | | .value-text { |
| | | font-family: PingFang SC; |
| | | font-size: 24px; |
| | | font-weight: normal; |
| | | line-height: 40px; |
| | | letter-spacing: normal; |
| | | /* 文字色/黑色/次要文字 60% */ |
| | | color: rgba(0, 0, 0, 0.6); |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .item-title { |
| | | font-family: PingFang SC; |
| | | font-size: 28px; |
| | | font-weight: 500; |
| | | line-height: 44px; |
| | | letter-spacing: normal; |
| | | /* 文字色/黑色/重要字 90% */ |
| | | color: rgba(0, 0, 0, 0.9); |
| | | margin: 36px 0 16px 0; |
| | | } |
| | | |
| | | .item-value { |
| | | font-family: PingFang SC; |
| | | font-size: 24px; |
| | | font-weight: normal; |
| | | line-height: 40px; |
| | | letter-spacing: normal; |
| | | /* 文字色/黑色/次要文字 60% */ |
| | | color: rgba(0, 0, 0, 0.6); |
| | | } |
| | | |
| | | } |
| | | |
| | | .relative { |
| | | position: relative; |
| | | } |
| | | |
| | | .flex-card { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .flex-between-card { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .color-red { |
| | | color: #FF4D4F !important; |
| | | } |
| New file |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import { Component, useState, useEffect } from 'react'; |
| | | import { View, Text, Image, Input } from '@tarojs/components'; |
| | | import './index.less'; |
| | | import Subtract from '../../assets/Subtract.png'; |
| | | |
| | | export default function PricingRulesPage() { |
| | | |
| | | return ( |
| | | <View className='pricing-rules-page'> |
| | | <View className='title'>起步价(3公里,8分钟)</View> |
| | | <View className='dis'> |
| | | 包含里程3公里,包含时长8分钟,包含里程或包含时长(低速时长)任何一项超出时,将在起步价基础上累加计费。 |
| | | </View> |
| | | |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>4.0元 </View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>00:00-07:00</View> |
| | | <View className='item-value'>4.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>07:00-18:00</View> |
| | | <View className='item-value'>4.0元</View> |
| | | </View> |
| | | {/* 里程费 */} |
| | | <View className='item-title'>里程费</View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | {/* 时长费 */} |
| | | <View className='item-title'>时长费</View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | |
| | | {/* 远途费 */} |
| | | <View className='item-title'>远途费</View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | <View className='flex-between-card'> |
| | | <View className='item-value'>普通时段</View> |
| | | <View className='item-value'>1.0元</View> |
| | | </View> |
| | | </View> |
| | | ) |
| | | } |