feat(begin-route, order-detail): 配置化优化评价弹窗,新增分享行程按钮配置
1. 重构评价相关API,新增evaluateDimension参数获取对应反馈类型
2. 新增全局状态管理当前选中的评价评分
3. 优化评价弹窗组件,根据评分动态切换图标、标题和输入占位符
4. 新增后端配置控制评价弹窗触发逻辑与分享行程按钮展示
5. 同步更新订单详情页评价流程,与行程页保持一致
6. 临时注释use-order-payment中的支付接口调用
| | |
| | | } |
| | | |
| | | // 获取订单评价类型 |
| | | export const getOrderCommentType = async (): Promise<OrderCommentTypeItem[]> => { |
| | | export const getOrderCommentType = async (evaluateDimension: number): Promise<OrderCommentTypeItem[]> => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + `/operation/base/v1/mini-program/feedback-type/getList`, |
| | | // url: _ROBOTAXI_HOST + `/operation/base/v1/mini-program/feedback-type/getList`, |
| | | url: _ROBOTAXI_HOST + `/operation/base/v1/mini-program/evaluateTag/list?evaluateDimension=${evaluateDimension}`, |
| | | method: 'GET' |
| | | }, true); |
| | | } |
| | |
| | | showEvaluatePopup: boolean; |
| | | /** 订单 ID。 */ |
| | | orderId: string; |
| | | /** 当前评价弹窗对应的评分。 */ |
| | | evaluateOrderRating: number; |
| | | /** 关闭评价弹窗。 */ |
| | | handleCloseEvaluatePopup: () => void; |
| | | /** 确认评价。 */ |
| | |
| | | changeCheckedEnter, |
| | | showEvaluatePopup, |
| | | orderId, |
| | | evaluateOrderRating, |
| | | handleCloseEvaluatePopup, |
| | | handleConfirmEvaluate, |
| | | showAuthBluetoothPopup = false, |
| | |
| | | <EvaluatePopup |
| | | show={showEvaluatePopup} |
| | | orderId={orderId} |
| | | orderRating={1} |
| | | orderRating={evaluateOrderRating} |
| | | onClose={handleCloseEvaluatePopup} |
| | | onConfirm={handleConfirmEvaluate} |
| | | /> |
| | |
| | | type Props = { |
| | | tripStatus: TripStatus; |
| | | orderId?: string; |
| | | handleOpenEvaluatePopup?: () => void; |
| | | handleOpenEvaluatePopup?: (orderRating: number) => void; |
| | | evaluateSubmitVersion?: number; |
| | | evaluateCloseVersion?: number; |
| | | evaluatePopupStatus?: number; |
| | | }; |
| | | |
| | | export default function EvaluationCard({ |
| | |
| | | orderId, |
| | | handleOpenEvaluatePopup, |
| | | evaluateSubmitVersion, |
| | | evaluateCloseVersion |
| | | evaluateCloseVersion, |
| | | evaluatePopupStatus = 0 |
| | | }: Props) { |
| | | const good = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/good.png'; |
| | | const normal = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/normal.png'; |
| | |
| | | } |
| | | }, [evaluateCloseVersion]); |
| | | |
| | | // 点击评价项:不满意走弹窗,其余直接提交 |
| | | // 点击评价项:配置开启时任意评价都走弹窗,否则直接提交 |
| | | const changeEvaluate = (index: number) => { |
| | | if (isEvaluated) return; |
| | | setActiveIndex(index); |
| | | if (index === 1) { |
| | | handleOpenEvaluatePopup?.(); |
| | | if (evaluatePopupStatus === 1) { |
| | | handleOpenEvaluatePopup?.(index); |
| | | return; |
| | | } |
| | | const targetOrderId = getOrderId(orderId); |
| | |
| | | handleModifyDestination?: () => void; |
| | | handleShareTrip?: () => void; |
| | | showModifyDestination?: boolean; |
| | | showShareTrip?: boolean; |
| | | }; |
| | | |
| | | export default function TripActionBar({ |
| | |
| | | handleCancelTrip, |
| | | handleModifyDestination, |
| | | handleShareTrip, |
| | | showModifyDestination = true |
| | | showModifyDestination = true, |
| | | showShareTrip = true |
| | | }: Props) { |
| | | // 按行程状态控制按钮集合,避免在 JSX 中堆叠过多条件判断 |
| | | const actionList = [ |
| | |
| | | key: "share", |
| | | label: "分享行程", |
| | | icon: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/share-trip.png', |
| | | visible: [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(tripStatus), |
| | | visible: [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(tripStatus) && showShareTrip, |
| | | onClick: handleShareTrip |
| | | } |
| | | ].filter(item => item.visible); |
| | |
| | | evaluateSubmitVersion?: number; |
| | | /** 外部触发“评价弹窗关闭”版本号 */ |
| | | evaluateCloseVersion?: number; |
| | | /** 评价弹窗开关配置,0 不弹窗,1 点击任意评价都弹窗 */ |
| | | evaluatePopupStatus?: number; |
| | | /** 打开结束行程弹窗 */ |
| | | handleOpenOverRoutePopup?: () => void; |
| | | /** 费用明细展开/收起时同步地图视觉状态 */ |
| | |
| | | /** 立即支付回调 */ |
| | | handlePay?: () => void; |
| | | /** 打开详细评价弹窗 */ |
| | | handleOpenEvaluatePopup?: () => void; |
| | | handleOpenEvaluatePopup?: (orderRating: number) => void; |
| | | /** 取消行程 */ |
| | | handleCancelTrip?: () => void; |
| | | /** 修改目的地 */ |
| | | handleModifyDestination?: () => void; |
| | | /** 是否展示修改目的地 */ |
| | | showModifyDestination?: boolean; |
| | | /** 是否展示分享行程 */ |
| | | showShareTrip?: boolean; |
| | | /** 分享行程 */ |
| | | handleShareTrip?: () => void; |
| | | /** 预估等待分钟数 */ |
| | |
| | | priceUpdateVersion, |
| | | evaluateSubmitVersion, |
| | | evaluateCloseVersion, |
| | | evaluatePopupStatus = 0, |
| | | handleOpenOverRoutePopup, |
| | | onToggleDetailFade, |
| | | handlePay, |
| | |
| | | handleCancelTrip, |
| | | handleModifyDestination, |
| | | showModifyDestination = true, |
| | | showShareTrip = true, |
| | | handleShareTrip, |
| | | estimatedWaitMinutes, |
| | | routeDurationSeconds, |
| | |
| | | handleCancelTrip={handleCancelTrip} |
| | | handleModifyDestination={handleModifyDestination} |
| | | showModifyDestination={showModifyDestination} |
| | | showShareTrip={showShareTrip} |
| | | handleShareTrip={handleShareTrip} |
| | | /> |
| | | </View> |
| | |
| | | handleOpenEvaluatePopup={handleOpenEvaluatePopup} |
| | | evaluateSubmitVersion={evaluateSubmitVersion} |
| | | evaluateCloseVersion={evaluateCloseVersion} |
| | | evaluatePopupStatus={evaluatePopupStatus} |
| | | /> |
| | | </View> |
| | | ); |
| | |
| | | |
| | | export default function EvaluatePopup({ show = false, onClose, onConfirm, orderId, orderRating, submitMode = 'comment' }: Props) { |
| | | const activeBad = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/active-bad.png'; |
| | | const activeNormal = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/active-normal.png'; |
| | | const activeGood = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/active-good.png'; |
| | | const [visible, setVisible] = useState(false); // 弹窗显示状态 |
| | | const [animationData, setAnimationData] = useState<any>(); // 弹窗进出场动画 |
| | | const [selectedTypeId, setSelectedTypeId] = useState<number | null>(null); // 当前选择的问题类型 |
| | |
| | | compress: true, |
| | | uploadUrl: _ROBOTAXI_HOST + '/operation/base/v1/mini-program/oss/upload' |
| | | }); // 图片上传状态与操作 |
| | | |
| | | // 根据外部点击的评分,动态展示评价弹窗的 icon 和文案 |
| | | const currentOrderRating = Number(orderRating) || 1; |
| | | const popupDisplayConfig = { |
| | | 1: { |
| | | icon: activeBad, |
| | | title: '“不满意,我要吐槽”', |
| | | placeholder: '请详描述您遇到的问题(可添加相关截图辅助说明)' |
| | | }, |
| | | 2: { |
| | | icon: activeNormal, |
| | | title: '“一般般,还需努力”', |
| | | placeholder: '告诉我们哪些方面还可以继续改进' |
| | | }, |
| | | 3: { |
| | | icon: activeGood, |
| | | title: '“很满意,表扬一下”', |
| | | placeholder: '告诉我哪里做的不错,我们继续保持!' |
| | | } |
| | | }[currentOrderRating] || { |
| | | icon: activeBad, |
| | | title: '“不满意,我要吐槽”', |
| | | placeholder: '请详描述您遇到的问题(可添加相关截图辅助说明)' |
| | | }; |
| | | const evaluateDimensionMap: Record<number, number> = { |
| | | 3: 1, |
| | | 2: 2, |
| | | 1: 3 |
| | | }; |
| | | const evaluateDimension = evaluateDimensionMap[currentOrderRating] || 3; // 接口维度:1 满意,2 一般,3 不满意 |
| | | |
| | | // 控制弹窗开合动画 |
| | | useEffect(() => { |
| | |
| | | setSelectedTypeId(null); |
| | | setFeedbackContent(''); |
| | | resetImages(); |
| | | getOrderCommentType().then((res: any) => { |
| | | getOrderCommentType(evaluateDimension).then((res: any) => { |
| | | const list = res; |
| | | if (Array.isArray(list)) { |
| | | setTypeOptions(list); |
| | |
| | | }).catch(() => { |
| | | setTypeOptions([]); |
| | | }); |
| | | }, [resetImages, show]); |
| | | }, [evaluateDimension, resetImages, show]); |
| | | |
| | | if (!visible) return null; |
| | | |
| | |
| | | <Image onClick={onClose} src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/close-icon.png" /> |
| | | </View> |
| | | <View className='evaluate-content-title'> |
| | | <Image src={activeBad} /> |
| | | <View>“不满意,我要吐槽”</View> |
| | | <Image src={popupDisplayConfig.icon} /> |
| | | <View>{popupDisplayConfig.title}</View> |
| | | </View> |
| | | <View className='content-box'> |
| | | <View className='evaluate-problem-type'> |
| | | <View className='evaluate-problem-type-title'>遇到的问题类型:</View> |
| | | <View className='evaluate-problem-type-title'>反馈的问题类型:</View> |
| | | <View className='evaluate-problem-type-content'> |
| | | {typeOptions.map((item) => ( |
| | | <View key={item?.id} className={`evaluate-problem-type-content-item ${selectedTypeId === item?.id && 'active-tag'}`} onClick={() => setSelectedTypeId(item?.id)}>{item?.feedbackType}</View> |
| | |
| | | className='evaluate-problem-desc' |
| | | maxlength={500} |
| | | placeholderClass='placeholder-class' |
| | | placeholder='请描述您的建议或问题(可添加相关截图)' |
| | | placeholder={popupDisplayConfig.placeholder} |
| | | value={feedbackContent} |
| | | onInput={(e) => setFeedbackContent(e.detail.value)} /> |
| | | <View className='evaluate-problem-desc-count'>{feedbackContent.length}/500</View> |
| | |
| | | minDistance?: number; |
| | | adsorptionDistance?: number; |
| | | queuePollingTime?: number; |
| | | tripShareStatus?: number; |
| | | evaluatePopupStatus?: number; |
| | | walkingNavigationDistance?: number; |
| | | maxStandingTiming?: number; |
| | | xczSyDistance?: number; |
| | |
| | | minDistance: Number(res.minDistance), |
| | | adsorptionDistance: Number(res.adsorptionDistance), |
| | | queuePollingTime: Number(res.queuePollingTime), |
| | | tripShareStatus: Number(res.tripShareStatus), |
| | | evaluatePopupStatus: Number(res.evaluatePopupStatus), |
| | | walkingNavigationDistance: Number(res.walkingNavigationDistance), |
| | | maxStandingTiming: Number(res.maxStandingTiming), |
| | | xczSyDistance: Number(res.xczSyDistance), |
| | |
| | | } |
| | | paySubmittingRef.current = true; |
| | | try { |
| | | await getOrderPayment({ orderId: targetOrderId }); |
| | | // await getOrderPayment({ orderId: targetOrderId }); |
| | | syncPaymentSuccessStatus(); |
| | | |
| | | |
| | |
| | | evaluateSubmitVersion: number; |
| | | /** 评价关闭版本 */ |
| | | evaluateCloseVersion: number; |
| | | /** 评价弹窗开关配置 */ |
| | | evaluatePopupStatus: number; |
| | | /** 打开结束弹窗 */ |
| | | handleOpenOverRoutePopup: () => void; |
| | | /** 支付 */ |
| | | handlePay: () => void; |
| | | /** 打开评价 */ |
| | | handleOpenEvaluatePopup: () => void; |
| | | handleOpenEvaluatePopup: (orderRating: number) => void; |
| | | /** 详情展开回调 */ |
| | | handleToggleDetailFade: (expanded: boolean, hasFare: boolean) => void; |
| | | /** 修改目的地 */ |
| | | handleModifyDestination: () => void; |
| | | /** 是否展示修改目的地 */ |
| | | showModifyDestination: boolean; |
| | | /** 是否展示分享行程 */ |
| | | showShareTrip: boolean; |
| | | /** 分享行程 */ |
| | | handleShareTrip: () => void; |
| | | /** 路线选择展开状态 */ |
| | |
| | | handleCancelTrip={() => deps.handleOpenCancelPopupByTripStatus(deps.tripStatus, true)} |
| | | handleModifyDestination={deps.handleModifyDestination} |
| | | showModifyDestination={deps.showModifyDestination} |
| | | showShareTrip={deps.showShareTrip} |
| | | handleShareTrip={deps.handleShareTrip} |
| | | handleCancelTimeout={deps.handlePickupTimeoutAutoCancel} |
| | | handleFinishTimeout={deps.handleFinishCountdownTimeout} |
| | |
| | | priceUpdateVersion={deps.priceUpdateVersion} |
| | | evaluateSubmitVersion={deps.evaluateSubmitVersion} |
| | | evaluateCloseVersion={deps.evaluateCloseVersion} |
| | | evaluatePopupStatus={deps.evaluatePopupStatus} |
| | | handleOpenOverRoutePopup={deps.handleOpenOverRoutePopup} |
| | | handlePay={deps.handlePay} |
| | | onOpenCouponSelect={deps.handleOpenCouponSelect} |
| | |
| | | handleOpenPassengerNotice, |
| | | showEvaluatePopup, |
| | | setShowEvaluatePopup, |
| | | handleOpenEvaluatePopup, |
| | | handleOpenEvaluatePopup: _handleOpenEvaluatePopup, |
| | | showTipPopup, |
| | | setShowTipPopup, |
| | | handleCloseTipPopup, |
| | |
| | | const [priceUpdateVersion, setPriceUpdateVersion] = useState(0); // 价格更新版本号 |
| | | const [evaluateSubmitVersion, setEvaluateSubmitVersion] = useState(0); // 评价提交版本号 |
| | | const [evaluateCloseVersion, setEvaluateCloseVersion] = useState(0); // 评价关闭版本号 |
| | | const [evaluateOrderRating, setEvaluateOrderRating] = useState(1); // 当前评价弹窗对应评分 |
| | | const [showCouponSelectPopup, setShowCouponSelectPopup] = useState(false); // 优惠券选择弹窗展示状态 |
| | | const [showModifyDestinationFailPopup, setShowModifyDestinationFailPopup] = useState(false); // 修改目的地失败弹窗 |
| | | const [modifyDestinationFailMessage, setModifyDestinationFailMessage] = useState(''); // 修改目的地失败文案(默认空,避免闪文案) |
| | |
| | | const queuePollingTimeSeconds = Number.isFinite(Number(distanceConfig?.queuePollingTime)) && Number(distanceConfig?.queuePollingTime) > 0 |
| | | ? Number(distanceConfig?.queuePollingTime) |
| | | : DEFAULT_QUEUE_POLLING_TIME; // 排队位次轮询频率(秒) |
| | | const showShareTrip = Number(distanceConfig?.tripShareStatus) === 1; // 分享行程入口展示开关 |
| | | const evaluatePopupStatus = Number(distanceConfig?.evaluatePopupStatus) === 1 ? 1 : 0; // 评价弹窗开关 |
| | | const walkingNavigationDistance = Number.isFinite(Number(distanceConfig?.walkingNavigationDistance)) |
| | | ? Number(distanceConfig?.walkingNavigationDistance) |
| | | : 0; // 步行导航按钮展示阈值(米) |
| | |
| | | vehicleLocation |
| | | ]); |
| | | |
| | | // 打开评价弹窗 |
| | | // 打开评价弹窗,并记录当前点击的评分 |
| | | const handleOpenEvaluatePopup = (orderRating: number) => { |
| | | setEvaluateOrderRating(orderRating); |
| | | setShowEvaluatePopup(true); |
| | | }; |
| | | |
| | | const handleCloseEvaluatePopup = () => { |
| | | setEvaluateCloseVersion(v => v + 1); |
| | | setShowEvaluatePopup(false); |
| | |
| | | priceUpdateVersion, |
| | | evaluateSubmitVersion, |
| | | evaluateCloseVersion, |
| | | evaluatePopupStatus, |
| | | handleOpenOverRoutePopup, |
| | | handlePay, |
| | | handleOpenEvaluatePopup, |
| | | handleToggleDetailFade, |
| | | handleModifyDestination, |
| | | showModifyDestination, |
| | | showShareTrip, |
| | | handleShareTrip, |
| | | trunkStatus, |
| | | routeOptions, |
| | |
| | | changeCheckedEnter, |
| | | showEvaluatePopup, |
| | | orderId, |
| | | evaluateOrderRating, |
| | | handleCloseEvaluatePopup, |
| | | handleConfirmEvaluate |
| | | }; |
| | |
| | | import { getOrderDetail } from '../../../api/order'; |
| | | import EvaluatePopup from '../../begin-route/components/evaluate-popup'; |
| | | import { submitOrderComment } from '../../../api/begin-route'; |
| | | import { getOrderDistance } from '../../../api/select-map-site'; |
| | | import { formatTripDistance, formatTripDurationFromSeconds, safeParse } from '../../../../utils/Robotaxi-public-method'; |
| | | import { getOrderId } from '../../../utils/order'; |
| | | import { withRobotaxiWhitelist } from '../../../../utils/whitelist'; |
| | |
| | | const [activeIndex, setActiveIndex] = useState<number | null>(null) // 默认选中"非常满意" |
| | | const [orderData, setOrderData] = useState<any>(null); |
| | | const [showEvaluatePopup, setShowEvaluatePopup] = useState(false); |
| | | const [selectedOrderRating, setSelectedOrderRating] = useState(1); // 当前准备提交的评价分值 |
| | | const [evaluatePopupStatus, setEvaluatePopupStatus] = useState(0); // 0 不弹评价弹窗,1 点击任意评价都弹窗 |
| | | const showEvaluatePopupRef = useRef(false); |
| | | |
| | | // 地图相关状态 |
| | |
| | | fetchOrderDetail(orderId); |
| | | } |
| | | }, [orderId]); |
| | | |
| | | useEffect(() => { |
| | | const fetchEvaluatePopupConfig = async () => { |
| | | try { |
| | | const res: any = await getOrderDistance(); |
| | | // setEvaluatePopupStatus(Number(res?.evaluatePopupStatus) === 1 ? 1 : 0); |
| | | setEvaluatePopupStatus(1); |
| | | } catch (error) { |
| | | setEvaluatePopupStatus(0); |
| | | } |
| | | }; |
| | | fetchEvaluatePopupConfig(); |
| | | }, []); |
| | | |
| | | useEffect(() => { |
| | | showEvaluatePopupRef.current = showEvaluatePopup; |
| | |
| | | setActiveIndex(null); |
| | | } |
| | | const handleConfirmEvaluate = () => { |
| | | setActiveIndex(1); |
| | | setActiveIndex(selectedOrderRating); |
| | | setShowEvaluatePopup(false); |
| | | } |
| | | |
| | | const changeEvaluate = (index: number) => { |
| | | if (activeIndex !== null) return; |
| | | if (index === 1) { |
| | | handleOpenEvaluatePopup(); |
| | | return; |
| | | } |
| | | const submitSimpleEvaluate = (index: number) => { |
| | | const targetOrderId = getOrderId(orderId); |
| | | if (!targetOrderId) { |
| | | Taro.showToast({ title: '订单信息缺失,无法提交评价', icon: 'none' }); |
| | | return; |
| | | } |
| | | const successToastMap: Record<number, string> = { |
| | | 1: '收到您的评价,我们将努力提升体验', |
| | | 2: '收到您的评价,我们将努力提升体验', |
| | | 3: '感谢您的反馈,欢迎下次乘坐' |
| | | }; |
| | | submitOrderComment({ orderRating: index, orderId: targetOrderId }) |
| | | .then(() => { |
| | | setActiveIndex(index); |
| | | Taro.showToast({ |
| | | title: ['收到您的评价,我们将努力提升体验', '感谢您的反馈,欢迎下次乘坐'][index - 2], |
| | | title: successToastMap[index] || '评价提交成功', |
| | | icon: 'none' |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | Taro.showToast({ title: '评价提交失败,请稍后重试', icon: 'none' }); |
| | | }); |
| | | } |
| | | |
| | | const changeEvaluate = (index: number) => { |
| | | if (activeIndex !== null) return; |
| | | if (evaluatePopupStatus === 1) { |
| | | // 配置开启时,任意评价项都走弹窗反馈链路。 |
| | | setSelectedOrderRating(index); |
| | | handleOpenEvaluatePopup(); |
| | | return; |
| | | } |
| | | submitSimpleEvaluate(index); |
| | | } |
| | | |
| | | if (!orderData) return null; |
| | |
| | | <EvaluatePopup |
| | | show={showEvaluatePopup} |
| | | orderId={orderId} |
| | | orderRating={1} |
| | | orderRating={selectedOrderRating} |
| | | onClose={handleCloseEvaluatePopup} |
| | | onConfirm={handleConfirmEvaluate} |
| | | /> |