Revert "feat: 合并feature_v2.4.0 带支付功能"
This reverts commit 1c5bffb67c6deb4c3b957a153e2ea1c80671bb73
| | |
| | | } |
| | | |
| | | .marketing-popup-multi-panel { |
| | | margin-top: auto; |
| | | margin-top: 408px; |
| | | z-index: 100010; |
| | | width: 560px; |
| | | height: auto; |
| | |
| | | .marketing-popup-single-panel { |
| | | position: relative; |
| | | margin-top: 388px; |
| | | margin: auto; |
| | | z-index: 100010; |
| | | width: 540px; |
| | | height: auto; |
| | |
| | | } |
| | | } |
| | | .marketing-popup-coupon-title { |
| | | margin-top: 72px; |
| | | margin-top: 64px; |
| | | font-size: 56px; |
| | | line-height: 68px; |
| | | display: flex; |
| | |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin: 0 auto; |
| | | margin-top: 120px; |
| | | margin-top: 112px; |
| | | font-size: 29.79px; |
| | | font-weight: 500; |
| | | line-height: 44.68px; |
| | |
| | | align-items: center; |
| | | color: #5b290e; |
| | | } |
| | | // .marketing-popup-close-single{ |
| | | // margin-top: 0 !important; |
| | | // } |
| | | // .marketing-popup-close-icon{ |
| | | // position: absolute; |
| | | // bottom: -120rpx; |
| | | // } |
| | | } |
| | | |
| | | .marketing-popup.is-active { |
| | |
| | | } |
| | | |
| | | const timer = setTimeout(() => { |
| | | // props.onClose(); |
| | | props.onClose(); |
| | | }, AUTO_CLOSE_DURATION); |
| | | |
| | | return () => { |
| | |
| | | import { FLOAT_ACTIVITY_DEFAULT_IMAGE } from '../constants'; |
| | | |
| | | type MarketingData = Record<string, any>; |
| | | const DEFAULT_MARKETING_POPUP_TITLE = '恭喜获得优惠券'; |
| | | const DEFAULT_MARKETING_POPUP_BUTTON_TEXT = '我知道了'; |
| | | |
| | | type Deps = { |
| | | /** 首次进入营销弹窗查询。 */ |
| | |
| | | }; |
| | | |
| | | /** |
| | | * 兼容旧 firstEnter 与新 exposureCoupon 两种返回结构,统一为首页营销弹窗可消费的数据。 |
| | | */ |
| | | const normalizeMarketingData = (rawData: any): MarketingData => { |
| | | if (Array.isArray(rawData)) { |
| | | const activities = rawData.filter(Boolean); |
| | | const selectedActivity = activities.find((item: any) => Array.isArray(item?.coupons) && item.coupons.length > 0) |
| | | || activities[0] |
| | | || {}; |
| | | const coupons = (Array.isArray(selectedActivity?.coupons) ? selectedActivity.coupons : []) |
| | | .filter(Boolean) |
| | | .map((item: any, index: number) => ({ |
| | | ...item, |
| | | id: item?.id ?? item?.couponId ?? index, |
| | | title: item?.title ?? item?.couponName ?? '', |
| | | amount: item?.amount ?? item?.money ?? item?.discountAmount ?? 0, |
| | | unit: item?.unit ?? (Number(item?.couponType) === 1 ? '折' : '元'), |
| | | endTime: item?.endTime ?? item?.expirationTime ?? item?.expireText ?? '', |
| | | quantity: item?.quantity ?? item?.count ?? 1, |
| | | conditionalAmount: item?.conditionalAmount ?? item?.fullMoney ?? 0 |
| | | })); |
| | | return { |
| | | ...selectedActivity, |
| | | isFirst: activities.length > 0, |
| | | coupons, |
| | | activityId: selectedActivity?.activityId ?? selectedActivity?.id, |
| | | popupId: selectedActivity?.popupId ?? selectedActivity?.id, |
| | | id: selectedActivity?.id ?? selectedActivity?.activityId, |
| | | miniProgramPopupTitle: selectedActivity?.miniProgramPopupTitle || selectedActivity?.acticityName || DEFAULT_MARKETING_POPUP_TITLE, |
| | | miniProgramBtnText: selectedActivity?.miniProgramBtnText || DEFAULT_MARKETING_POPUP_BUTTON_TEXT |
| | | }; |
| | | } |
| | | |
| | | if (rawData && typeof rawData === 'object') { |
| | | return { |
| | | miniProgramPopupTitle: rawData?.miniProgramPopupTitle || DEFAULT_MARKETING_POPUP_TITLE, |
| | | miniProgramBtnText: rawData?.miniProgramBtnText || DEFAULT_MARKETING_POPUP_BUTTON_TEXT, |
| | | ...rawData |
| | | }; |
| | | } |
| | | |
| | | return { |
| | | isFirst: false, |
| | | coupons: [], |
| | | miniProgramPopupTitle: DEFAULT_MARKETING_POPUP_TITLE, |
| | | miniProgramBtnText: DEFAULT_MARKETING_POPUP_BUTTON_TEXT |
| | | }; |
| | | }; |
| | | |
| | | /** |
| | | * 刷新首页营销弹窗与悬浮活动状态。 |
| | | */ |
| | | const syncMarketingState = async (hasLogin: boolean) => { |
| | |
| | | deps.firstEnter(), |
| | | deps.getFloatActicity() |
| | | ]); |
| | | const firstEnterData = normalizeMarketingData(firstEnterResponse?.data ?? firstEnterResponse ?? {}); |
| | | const firstEnterData = firstEnterResponse?.data ?? firstEnterResponse ?? {}; |
| | | const floatData = floatResponse?.data ?? floatResponse ?? {}; |
| | | const isFloatActivityEnabled = Number(floatData?.enableFloatWindow) === 1; |
| | | |
| | | setShowMarketing(Array.isArray(firstEnterData?.coupons) |
| | | ? firstEnterData.coupons.length > 0 |
| | | : Boolean(firstEnterData?.isFirst)); |
| | | setShowMarketing(Boolean(firstEnterData?.isFirst)); |
| | | setMarketingData(firstEnterData); |
| | | setShowFloatActivity(isFloatActivityEnabled); |
| | | setFloatActivityUrl(String(floatData?.floatWindowJumpUrl || '')); |
| | | |
| | | let parsedCoverUrl = ''; |
| | | try { |
| | | const coverImage = floatData?.coverImage; |
| | | if (coverImage) { |
| | | const coverArray = typeof coverImage === 'string' ? JSON.parse(coverImage) : coverImage; |
| | | parsedCoverUrl = Array.isArray(coverArray) ? coverArray[0]?.url : ''; |
| | | } |
| | | } catch (e) { |
| | | console.error('解析悬浮窗图片失败', e); |
| | | } |
| | | setFloatActivityImg(parsedCoverUrl || FLOAT_ACTIVITY_DEFAULT_IMAGE); |
| | | setFloatActivityImg(String(floatData?.coverImage || '') || FLOAT_ACTIVITY_DEFAULT_IMAGE); |
| | | } catch (error) { |
| | | resetMarketingState(); |
| | | } |
| | |
| | | .weather-info { |
| | | display: flex; |
| | | align-items: center; |
| | | <<<<<<< HEAD |
| | | font-size: 24px; |
| | | ======= |
| | | font-size: 28px; |
| | | >>>>>>> feature_v2.4.0 |
| | | font-weight: 500; |
| | | color: @black-text-04; |
| | | |
| | |
| | | right: 6px; |
| | | bottom: 234px; |
| | | z-index: 10020; |
| | | width: 108px; |
| | | height: 108px; |
| | | width: 96px; |
| | | height: 96px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .activity-float-window-img { |
| | | width: 108px; |
| | | height: 108px; |
| | | width: 96px; |
| | | height: 96px; |
| | | } |
| | | } |
| | | |
| | |
| | | left: 50%; |
| | | top: 100%; |
| | | transform: translate(-50%, -50%); |
| | | <<<<<<< HEAD |
| | | width: 28px; |
| | | ======= |
| | | width: 18px; |
| | | >>>>>>> feature_v2.4.0 |
| | | height: 28px; |
| | | z-index: -1; |
| | | } |
| | |
| | | UnlockVehiclePayload, |
| | | UnlockVehicleResponse, |
| | | OrderPaymentResponse, |
| | | QueryOrderPaymentResultResponse, |
| | | QueryLedgerCreditOrderResultResponse |
| | | QueryOrderPaymentResultResponse |
| | | } from './types/begin-route'; |
| | | |
| | | // 导航-获取路线和费用 |
| | |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/creditPay/createOrder`, |
| | | method: 'POST', |
| | | payload, |
| | | showToast: false |
| | | }, true); |
| | | } |
| | | |
| | | // 查询免密签约状态 |
| | | export const queryCreditPaySign = async (): Promise<any> => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/order/api/v1/mini-program/order/creditPay/sign', |
| | | method: 'POST' |
| | | payload |
| | | }, true); |
| | | } |
| | | |
| | |
| | | payload: { |
| | | orderId: String(orderId) |
| | | } |
| | | }, true); |
| | | } |
| | | |
| | | // 查询信用分授权结果 |
| | | export const queryLedgerCreditOrderResult = async (orderId: OrderId): Promise<QueryLedgerCreditOrderResultResponse> => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/queryLedgerCreditOrderResult`, |
| | | method: 'POST', |
| | | payload: { |
| | | orderId: String(orderId) |
| | | }, |
| | | skipException: true, |
| | | showToast: false |
| | | }, true); |
| | | } |
| | | |
| | |
| | | // 第一次进入应用 |
| | | export const firstEnter = async () => { |
| | | return request({ |
| | | // url: _ROBOTAXI_HOST + '/operation/api/v1/mini-program/acticity/firstEnter', |
| | | url: _ROBOTAXI_HOST + '/operation/api/v1/mini-program/acticity/exposureCoupon', |
| | | url: _ROBOTAXI_HOST + '/operation/api/v1/mini-program/acticity/firstEnter', |
| | | method: 'GET' |
| | | }, true); |
| | | } |
| | |
| | | }; |
| | | |
| | | /** |
| | | * 查询支付分开通情况。 |
| | | */ |
| | | export const getPayNoPwd = async (): Promise<GetPayPwdResponse> => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/order/api/v1/mini-program/order/creditPay/sign', |
| | | method: 'POST', |
| | | }, true); |
| | | }; |
| | | /** |
| | | * 设置支付分开通状态。 |
| | | */ |
| | | export const setPayPwd = async (status: RobotaxiPaySettingStatus): Promise<SetPayPwdResponse> => { |
| | |
| | | |
| | | /** 订单支付结果查询返回值。 */ |
| | | export type QueryOrderPaymentResultResponse = { |
| | | payStatus: number; |
| | | payType: number; |
| | | payTime: string; |
| | | }; |
| | | |
| | | // 信用分授权结果查询响应 |
| | | export type QueryLedgerCreditOrderResultResponse = { |
| | | creditOrderStatus: 'DOING' | 'REVOKED' | 'EXPIRED' | string; |
| | | status: RobotaxiOrderStatus | number; |
| | | orderId: string; |
| | | }; |
| | |
| | | }: Props) { |
| | | const [showDetail, setShowDetail] = useState(false); // 费用明细展开状态 |
| | | const canOpenCouponSelect = tripStatus === TripStatus.TripFinished && typeof onOpenCouponSelect === 'function'; // 仅待支付支持切换优惠券 |
| | | const isCreditPayProcessing = ( |
| | | tripStatus === TripStatus.TripFinished |
| | | && !!orderId |
| | | && String(Taro.getStorageSync('creditPayOrderId') || '') === String(orderId) |
| | | ); // 信用下单结束后,展示免密支付中并阻止重复点击 |
| | | const payButtonText = isCreditPayProcessing ? '免密支付中' : '立即支付'; |
| | | |
| | | // 外部重置版本变化时收起明细 |
| | | useEffect(() => { |
| | |
| | | </View> |
| | | </View> |
| | | { |
| | | (fareBreakdown?.operateFree != 1 && [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus)) && Number(fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney) > 0 && |
| | | (fareBreakdown?.operateFree != 1 && [TripStatus.TripFinished].includes(tripStatus)) && Number(fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney) > 0 && |
| | | <View className="car-card-end-through" onClick={canOpenCouponSelect ? onOpenCouponSelect : undefined}> |
| | | <View className="car-card-end-through-left"> |
| | | <View className="car-card-end-through-content-title">最佳优惠</View> |
| | |
| | | ? 0 : fareBreakdown?.payMoney)}</View><View className="unit">元</View></View> |
| | | : |
| | | <View className="pay-box-free">员工内部测试</View> |
| | | }{/* */} |
| | | <View className="pay-box-btn-wrapper"> |
| | | <View |
| | | onClick={isCreditPayProcessing ? undefined : handlePay} |
| | | className="pay-box-btn" |
| | | > |
| | | {payButtonText} |
| | | </View> |
| | | {isCreditPayProcessing && <View className="pay-box-btn-mask" />} |
| | | </View> |
| | | } */} |
| | | <View onClick={handlePay} className="pay-box-btn">立即支付</View> |
| | | </View> |
| | | } |
| | | </View> |
| | |
| | | overflow: hidden; |
| | | .calling-car-content-box { |
| | | position: relative; |
| | | <<<<<<< HEAD |
| | | padding: 48px 48px 82px 48px; |
| | | ======= |
| | | padding: 48px 48px 52px 48px; |
| | | >>>>>>> feature_v2.4.0 |
| | | background: @gary-theme; |
| | | border-radius: 60px 60px 0 0; |
| | | } |
| | |
| | | font-size: 88px; |
| | | line-height: 108px; |
| | | font-weight: 600; |
| | | <<<<<<< HEAD |
| | | font-family: @font-xpsans-bold; |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | letter-spacing: normal; |
| | | color: #FFFFFF; |
| | | } |
| | |
| | | // 提升特异性:与 .calling-car .fare-box-card { padding-bottom: 0 } 同特异性且在其之前, |
| | | // 用 &.calling-car 组合两个类名避免下方规则覆盖 padding-bottom |
| | | &.calling-car .fare-box-card { |
| | | <<<<<<< HEAD |
| | | padding: 24px 48px 208px 48px; |
| | | ======= |
| | | padding: 48px 48px 208px 48px; |
| | | >>>>>>> feature_v2.4.0 |
| | | background: @gary-theme; |
| | | border-radius: 60px 60px 0 0; |
| | | } |
| | |
| | | } |
| | | .car-card-detail-num { |
| | | font-size: 44px; |
| | | <<<<<<< HEAD |
| | | font-weight: 600; |
| | | ======= |
| | | font-weight: 500; |
| | | >>>>>>> feature_v2.4.0 |
| | | line-height: 56px; |
| | | letter-spacing: normal; |
| | | color: @black-text-main; |
| | |
| | | background: rgba(204, 208, 215, 0.25); |
| | | &:first-child { |
| | | position: relative; |
| | | <<<<<<< HEAD |
| | | border-radius: 32px; |
| | | ======= |
| | | border-radius: 16px; |
| | | >>>>>>> feature_v2.4.0 |
| | | box-sizing: border-box; |
| | | } |
| | | &:last-child { |
| | | position: relative; |
| | | <<<<<<< HEAD |
| | | border-radius: 32px; |
| | | box-sizing: border-box; |
| | | } |
| | |
| | | font-size: 28px; |
| | | font-weight: 500; |
| | | color: @black-text-main; |
| | | ======= |
| | | border-radius: 16px; |
| | | box-sizing: border-box; |
| | | } |
| | | } |
| | | .car-card-arrive-item-status { |
| | | font-size: 20px; |
| | | line-height: normal; |
| | | letter-spacing: normal; |
| | | color: rgba(20, 30, 39, 0.5); |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | .car-card-arrive-item-img { |
| | | width: 64px; |
| | |
| | | margin-bottom: 16px; |
| | | padding: 24px 20px; |
| | | opacity: 1; |
| | | <<<<<<< HEAD |
| | | overflow-y: scroll; |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | } |
| | | .particulars-detail-item { |
| | |
| | | color: @black-text-04; |
| | | font-size: 28px; |
| | | } |
| | | <<<<<<< HEAD |
| | | .car-card-end-cost-money-line { |
| | | display: inline-block; |
| | | margin: 0 20px; |
| | | color: @black-text-04; |
| | | } |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | .car-card-end-cost-content { |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | padding: 22px 0; |
| | | &:first-child { |
| | | position: relative; |
| | | <<<<<<< HEAD |
| | | border-radius: 32px; |
| | | background: rgba(204, 208, 215, 0.25); |
| | | box-sizing: border-box; |
| | |
| | | &:last-child { |
| | | border-radius: 32px; |
| | | background: rgba(204, 208, 215, 0.25); |
| | | ======= |
| | | border-radius: 16px; |
| | | background: #ffffff; |
| | | box-sizing: border-box; |
| | | border: 2px solid rgba(0, 0, 0, 0.15); |
| | | } |
| | | &:last-child { |
| | | border-radius: 16px; |
| | | background: linear-gradient(110deg, #8475ff 0%, #7dd6ff 99%); |
| | | color: #fff; |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | } |
| | | .car-card-reach-btn-item-img { |
| | |
| | | font-size: 24px; |
| | | line-height: 40px; |
| | | letter-spacing: normal; |
| | | <<<<<<< HEAD |
| | | color: @black-text-main; |
| | | } |
| | | ======= |
| | | color: rgba(0, 0, 0, 0.9); |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .unit { |
| | | margin-left: 4px; |
| | | font-size: 24px; |
| | | line-height: 40px; |
| | | letter-spacing: normal; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | } |
| | | } |
| | | |
| | | &-btn-wrapper { |
| | | width: 296px; |
| | | height: 94px; |
| | | position: relative; |
| | | } |
| | | |
| | | &-btn { |
| | | width: 296px; |
| | | height: 94px; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | border-radius: 20px; |
| | | background: linear-gradient(102deg, #8475FF 0%, #7DD6FF 102%); |
| | | font-size: 28px; |
| | | letter-spacing: normal; |
| | | color: #FFFFFF; |
| | | } |
| | | |
| | | &-btn-mask { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | border-radius: 20px; |
| | | background: rgba(255, 255, 255, 0.4); |
| | | } |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | .pay-box-btn { |
| | | width: 654px; |
| | |
| | | flex-shrink: 0; |
| | | opacity: 0.75; |
| | | } |
| | | <<<<<<< HEAD |
| | | ======= |
| | | .close-btn-item { |
| | | position: relative; |
| | | border-radius: 16px; |
| | | background: linear-gradient(110deg, rgba(132, 117, 255, 0.2) 0%, rgba(125, 214, 255, 0.2) 99%) !important; |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | border-radius: 16px; |
| | | padding: 1px; |
| | | background: linear-gradient(180deg, #aaa8ff -10%, rgba(41, 159, 255, 0.0469) 108%, rgba(0, 0, 0, 0) 108%); |
| | | -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); |
| | | -webkit-mask-composite: xor; |
| | | mask-composite: exclude; |
| | | pointer-events: none; |
| | | } |
| | | } |
| | | >>>>>>> feature_v2.4.0 |
| | | .bluetooth-operate-card { |
| | | position: relative; |
| | | z-index: 1; |
| | |
| | | .distance { |
| | | font-size: 24px; |
| | | font-weight: 500; |
| | | <<<<<<< HEAD |
| | | font-family: @font-xpsans-bold; |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | color: #ffffff; |
| | | } |
| | | .time { |
| | |
| | | padding: 0; |
| | | font-size: 24px; |
| | | font-weight: 500; |
| | | <<<<<<< HEAD |
| | | font-family: @font-xpsans-bold; |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | color: #ffffff; |
| | | } |
| | | } |
| | |
| | | font-size: 48px; |
| | | font-weight: normal; |
| | | line-height: 64px; |
| | | <<<<<<< HEAD |
| | | font-family: @font-xpsans-bold; |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | color: @black-text-main; |
| | | } |
| | | |
| | |
| | | |
| | | .cancel-btn-cancel { |
| | | flex: 1; |
| | | <<<<<<< HEAD |
| | | border: 4px solid #141E27; |
| | | ======= |
| | | border: 2px solid #141E27; |
| | | >>>>>>> feature_v2.4.0 |
| | | border-radius: 1000px; |
| | | font-size: 32px; |
| | | line-height: 104px; |
| | |
| | | flex: 1; |
| | | text-align: center; |
| | | border-radius: 1000px; |
| | | <<<<<<< HEAD |
| | | border: 4px solid #141E27; |
| | | ======= |
| | | border: 2px solid #141E27; |
| | | >>>>>>> feature_v2.4.0 |
| | | font-size: 32px; |
| | | line-height: 104px; |
| | | text-align: center; |
| | |
| | | right: 0; |
| | | background: #F1F5FA; |
| | | border-radius: 60px 60px 0 0; |
| | | <<<<<<< HEAD |
| | | padding: 64px 0 88px 0; |
| | | |
| | | .evaluate-rating-switcher, |
| | | .content-box { |
| | | transition: opacity 180ms ease, transform 180ms ease; |
| | | will-change: opacity, transform; |
| | | } |
| | | |
| | | &.is-rating-switching { |
| | | .evaluate-rating-switcher, |
| | | .content-box { |
| | | opacity: 0; |
| | | transform: translateY(12px) scale(0.985); |
| | | pointer-events: none; |
| | | } |
| | | } |
| | | ======= |
| | | padding: 42px 0 88px 0; |
| | | >>>>>>> feature_v2.4.0 |
| | | |
| | | .evaluate-rating-switcher, |
| | | .content-box { |
| | |
| | | } |
| | | |
| | | .evaluate-rating-switcher { |
| | | <<<<<<< HEAD |
| | | |
| | | ======= |
| | | margin-top: 18px; |
| | | >>>>>>> feature_v2.4.0 |
| | | padding: 0 49px; |
| | | display: flex; |
| | | flex-direction: column; |
| | |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | <<<<<<< HEAD |
| | | border-radius: 32px; |
| | | ======= |
| | | border-radius: 16px; |
| | | >>>>>>> feature_v2.4.0 |
| | | background: #E8ECF1; |
| | | |
| | | &-img { |
| | |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | <<<<<<< HEAD |
| | | |
| | | &-btn { |
| | | width: 100%; |
| | |
| | | border-radius: 1000px; |
| | | background: #D6D9DC; |
| | | } |
| | | ======= |
| | | padding: 32px 80px; |
| | | gap: 16px; |
| | | box-sizing: border-box; |
| | | z-index: 2; |
| | | font-size: 30px; |
| | | letter-spacing: normal; |
| | | color: #FFFFFF; |
| | | text-align: center; |
| | | border-radius: 1000px; |
| | | background: #141E27; |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | |
| | | .ok-btn { |
| | |
| | | /* 香氛名称,大小类似温度显示 */ |
| | | .tf-card-fragrance-name { |
| | | font-size: 40px; |
| | | <<<<<<< HEAD |
| | | font-weight: 500; |
| | | ======= |
| | | font-weight: normal; |
| | | >>>>>>> feature_v2.4.0 |
| | | line-height: 64px; |
| | | color: @black-text-main; |
| | | overflow: hidden; |
| | |
| | | background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/fragrance-close-20260703.png'); |
| | | |
| | | // 关闭态卡片文字统一弱化 |
| | | <<<<<<< HEAD |
| | | ======= |
| | | .tf-card-header-title, |
| | | >>>>>>> feature_v2.4.0 |
| | | .tf-card-fragrance-name, |
| | | .tf-card-footer-text { |
| | | color: rgba(20, 30, 39, 0.3); |
| | |
| | | position: relative; |
| | | z-index: 103; |
| | | background: #FFFFFF; |
| | | <<<<<<< HEAD |
| | | border-radius: 60px 60px 0 0; |
| | | ======= |
| | | border-radius: 40px 40px 0 0; |
| | | >>>>>>> feature_v2.4.0 |
| | | width: 100%; |
| | | // 设计稿左右边距 24px |
| | | padding: 0 0 calc(48px + env(safe-area-inset-bottom)); |
| | |
| | | right: 0; |
| | | background: @gary-theme; |
| | | border-radius: 60px 60px 0px 0px; |
| | | <<<<<<< HEAD |
| | | padding: 48px 48px 80px 48px; |
| | | ======= |
| | | padding: 48px 18px 68px 48px; |
| | | >>>>>>> feature_v2.4.0 |
| | | height: 75vh; |
| | | overflow: hidden; |
| | | .passenger-content-bg { |
| | |
| | | position: relative; |
| | | z-index: 1; |
| | | border-radius: 60px 60px 0px 0px; |
| | | <<<<<<< HEAD |
| | | padding: 48px 48px 112px 48px; |
| | | ======= |
| | | padding: 32px 48px 72px 48px; |
| | | >>>>>>> feature_v2.4.0 |
| | | background: @gary-theme; |
| | | } |
| | | .release-be-being-top-bg { |
| | |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | <<<<<<< HEAD |
| | | height: 276px; |
| | | ======= |
| | | height: 282px; |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | .top-box { |
| | | .top-box-title { |
| | |
| | | font-size: 20px; |
| | | line-height: 20px; |
| | | letter-spacing: normal; |
| | | <<<<<<< HEAD |
| | | color: @black-text-main; |
| | | ======= |
| | | color: rgba(0, 0, 0, 0.9); |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | |
| | | .route-select-content-item-time { |
| | |
| | | font-size: 20px; |
| | | line-height: 20px; |
| | | letter-spacing: normal; |
| | | <<<<<<< HEAD |
| | | color: @black-text-main; |
| | | ======= |
| | | color: rgba(0, 0, 0, 0.9); |
| | | >>>>>>> feature_v2.4.0 |
| | | |
| | | .traffic-signal-item { |
| | | display: flex; |
| | |
| | | padding-top: 94px; |
| | | background: @gary-theme; |
| | | border-radius: 60px 60px 0 0; |
| | | <<<<<<< HEAD |
| | | box-sizing: border-box; |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | .select-site-content { |
| | | position: relative; |
| | | z-index: 1; |
| | |
| | | .route-content { |
| | | position: relative; |
| | | margin-top: 16px; |
| | | <<<<<<< HEAD |
| | | padding-bottom: 36px; |
| | | box-sizing: border-box; |
| | | ======= |
| | | margin-bottom: 24px; |
| | | >>>>>>> feature_v2.4.0 |
| | | z-index: 10; |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | |
| | | .discount-amount-text { |
| | | flex-shrink: 0; |
| | | <<<<<<< HEAD |
| | | border-radius: 8px; |
| | | ======= |
| | | border-radius: 200px; |
| | | >>>>>>> feature_v2.4.0 |
| | | background: rgba(255, 255, 255, 0); |
| | | box-sizing: border-box; |
| | | border: 1.4px solid @gold-text; |
| | |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | <<<<<<< HEAD |
| | | padding: 8px 38px 62px 32px; |
| | | ======= |
| | | padding: 24px 38px 62px 32px; |
| | | >>>>>>> feature_v2.4.0 |
| | | background: @gary-theme; |
| | | |
| | | .money-box { |
| | |
| | | <<<<<<< HEAD |
| | | @import '../../../../style/ui-variables.less'; |
| | | |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | .ac-control { |
| | | flex-shrink: 0; |
| | | |
| | |
| | | z-index: 103; |
| | | width: 100%; |
| | | height: 1232px; |
| | | <<<<<<< HEAD |
| | | border-radius: 60px 60px 0 0; |
| | | ======= |
| | | border-radius: 40px 40px 0 0; |
| | | >>>>>>> feature_v2.4.0 |
| | | overflow: hidden; |
| | | background: #FFFFFF; |
| | | box-sizing: border-box; |
| | |
| | | .popup-ac-title { |
| | | display: block; |
| | | text-align: center; |
| | | <<<<<<< HEAD |
| | | font-size: 32px; |
| | | ======= |
| | | font-size: 28px; |
| | | >>>>>>> feature_v2.4.0 |
| | | font-weight: 500; |
| | | line-height: 40px; |
| | | color: rgba(0, 0, 0, 0.9); |
| | |
| | | justify-content: center; |
| | | |
| | | .popup-temp-item-value { |
| | | <<<<<<< HEAD |
| | | font-family: @font-xpsans-bold; |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | font-size: 40px; |
| | | line-height: 44px; |
| | | // 未选中温度:#141E27 透明度 0.2(对齐设计标注) |
| | |
| | | font-size: 64px; |
| | | line-height: 72px; |
| | | font-weight: 600; |
| | | <<<<<<< HEAD |
| | | color: @black-text-main; |
| | | ======= |
| | | color: #141E27; |
| | | >>>>>>> feature_v2.4.0 |
| | | opacity: 1; |
| | | } |
| | | |
| | | .popup-temp-item-unit { |
| | | margin-left: 4px; |
| | | <<<<<<< HEAD |
| | | font-family: @font-xpsans-bold; |
| | | font-size: 28px; |
| | | line-height: 40px; |
| | | font-weight: 500; |
| | | color: @black-text-main; |
| | | ======= |
| | | font-size: 28px; |
| | | line-height: 40px; |
| | | font-weight: 500; |
| | | color: #141E27; |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | } |
| | | |
| | |
| | | .popup-ac-switch-text { |
| | | font-size: 28px; |
| | | line-height: 40px; |
| | | <<<<<<< HEAD |
| | | color: @black-text-main; |
| | | ======= |
| | | color: rgba(0, 0, 0, 0.9); |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | } |
| | | } |
| | |
| | | import Taro from "@tarojs/taro"; |
| | | import { useEffect, useRef, useState } from 'react'; |
| | | import { useEffect, useState } from 'react'; |
| | | import { Image, View } from "@tarojs/components"; |
| | | import './css-folder/release-be-being.less' |
| | | |
| | | type Props = { |
| | | handleOpenCancelPopup?: () => void; |
| | | onFinish?: () => void; |
| | | finishBlocked?: boolean; |
| | | } |
| | | |
| | | export default function ReleaseBeBeing({ handleOpenCancelPopup, onFinish, finishBlocked = false }: Props) { |
| | | export default function ReleaseBeBeing({ handleOpenCancelPopup, onFinish }: Props) { |
| | | const [completedCount, setCompletedCount] = useState(0); // 已完成步骤数 |
| | | const [finishReady, setFinishReady] = useState(false); // 发布动画是否已走完 |
| | | const finishTriggeredRef = useRef(false); // 防止 onFinish 重复触发 |
| | | const startSite = Taro.getStorageSync('startSite') || {}; |
| | | const endSite = Taro.getStorageSync('endSite') || {}; |
| | | const startSiteName = startSite?.name || startSite?.address || '-'; |
| | |
| | | const step1 = setTimeout(() => setCompletedCount(1), 1000); |
| | | const step2 = setTimeout(() => setCompletedCount(2), 2000); |
| | | |
| | | // 3 秒后标记动画完成;是否真正切页由 finishBlocked 控制 |
| | | // 3秒后进入呼叫车中状态 |
| | | const step3 = setTimeout(() => { |
| | | setFinishReady(true); |
| | | onFinish?.(); |
| | | }, 3000); |
| | | |
| | | return () => { |
| | |
| | | clearTimeout(step3); |
| | | }; |
| | | }, []); |
| | | |
| | | |
| | | useEffect(() => { |
| | | // 信用分下单时,需要等待用户从支付分确认页返回后再推进到“呼叫车中”。 |
| | | if (!finishReady || finishBlocked || finishTriggeredRef.current) { |
| | | return; |
| | | } |
| | | finishTriggeredRef.current = true; |
| | | onFinish?.(); |
| | | }, [finishBlocked, finishReady, onFinish]); |
| | | |
| | | const renderSiteItemMarker = (index, count) => { |
| | | /* 根据 completedCount 动态显示完成图标 */ |
| | |
| | | import { useCallback, useEffect, useRef } from 'react'; |
| | | import Taro from '@tarojs/taro'; |
| | | import { queryLedgerCreditOrderResult } from '../../../api/begin-route'; |
| | | |
| | | type Deps = { |
| | | /** 当前正在下单的订单 ID,用于查询授权结果。 */ |
| | | orderId?: string; |
| | | /** 支付分授权成功后的回调。 */ |
| | | onAuthorizedReturn: (queryId: string) => void | Promise<void>; |
| | | /** 支付分未授权后的回调。 */ |
| | | onUnauthorizedReturn: (orderId?: string) => void | Promise<void>; |
| | | /** 支付分页面拉起失败后的回调。 */ |
| | | onLaunchFailed: (orderId?: string) => void | Promise<void>; |
| | | /** 支付分页面返回后的回调。 */ |
| | | onReturn: () => void; |
| | | }; |
| | | |
| | | const PAY_SCORE_REFERRER_APP_ID = 'wxd8f3793ea3b935b8'; |
| | |
| | | /** |
| | | * 拉起微信支付分订单确认页。 |
| | | */ |
| | | const openCreditPayScoreUse = useCallback((packageValue: string, targetOrderId?: string) => { |
| | | const queryOrderId = targetOrderId || deps.orderId; |
| | | const openCreditPayScoreUse = useCallback((packageValue: string) => { |
| | | if (!packageValue) { |
| | | Taro.showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' }); |
| | | void deps.onLaunchFailed(queryOrderId); |
| | | return true; |
| | | return false; |
| | | } |
| | | |
| | | // @ts-ignore |
| | | if (!wx.openBusinessView) { |
| | | Taro.showToast({ title: '请升级微信版本后重试', icon: 'none' }); |
| | | void deps.onLaunchFailed(queryOrderId); |
| | | return true; |
| | | return false; |
| | | } |
| | | |
| | | clearAppShowListener(); |
| | | |
| | | /** 支付分页面返回监听函数。 */ |
| | | const handleAppShow = async (res) => { |
| | | const handleAppShow = (res) => { |
| | | if (res?.scene !== 1038) { |
| | | return; |
| | | } |
| | |
| | | return; |
| | | } |
| | | |
| | | const queryId = String(res?.referrerInfo?.extraData?.query_id || '').trim(); |
| | | console.log('[信用分授权返回][AppShow]', { |
| | | scene: res?.scene, |
| | | referrerInfo: res?.referrerInfo, |
| | | queryId |
| | | }); |
| | | |
| | | clearAppShowListener(); |
| | | if (!queryId) { |
| | | void deps.onUnauthorizedReturn(queryOrderId); |
| | | return; |
| | | } |
| | | |
| | | // 根据 query_id 轮询查询真实的授权结果 |
| | | if (!queryOrderId) { |
| | | console.log('[信用分授权查询失败] 缺失 orderId'); |
| | | void deps.onUnauthorizedReturn(queryOrderId); |
| | | return; |
| | | } |
| | | |
| | | Taro.showLoading({ title: '授权结果查询中', mask: true }); |
| | | |
| | | const maxPollingCount = 120; |
| | | let currentPollingCount = 0; |
| | | |
| | | const pollStatus = async () => { |
| | | try { |
| | | const result = await queryLedgerCreditOrderResult(queryOrderId); |
| | | const status = result?.data?.creditOrderStatus; |
| | | console.log(`[信用分授权查询结果] 第${currentPollingCount + 1}次`, result); |
| | | |
| | | if (status === 'REVOKED' || status === 'EXPIRED') { |
| | | Taro.hideLoading(); |
| | | void deps.onUnauthorizedReturn(queryOrderId); |
| | | return; |
| | | } |
| | | |
| | | if (status === 'DOING' || result?.code === 20710) { |
| | | Taro.hideLoading(); |
| | | Taro.showToast({ title: '授权成功', icon: 'none' }); |
| | | void deps.onAuthorizedReturn(queryId); |
| | | return; |
| | | } |
| | | |
| | | if (status === 'CREATED') { |
| | | // CREATED 状态也当作中间状态继续轮询 |
| | | currentPollingCount++; |
| | | if (currentPollingCount >= maxPollingCount) { |
| | | Taro.hideLoading(); |
| | | console.log('[信用分授权查询超时] CREATED 状态持续2分钟'); |
| | | void deps.onUnauthorizedReturn(queryOrderId); |
| | | return; |
| | | } |
| | | setTimeout(pollStatus, 1000); |
| | | return; |
| | | } |
| | | |
| | | // 其他状态,继续轮询 |
| | | currentPollingCount++; |
| | | if (currentPollingCount >= maxPollingCount) { |
| | | Taro.hideLoading(); |
| | | console.log('[信用分授权查询超时]'); |
| | | void deps.onUnauthorizedReturn(queryOrderId); |
| | | return; |
| | | } |
| | | |
| | | setTimeout(pollStatus, 1000); |
| | | } catch (error: any) { |
| | | const errorCode = String(error?.code || error?.data?.code || ''); |
| | | if (errorCode === '20710') { |
| | | console.log(`[信用分授权查询遇到20710] 第${currentPollingCount + 1}次,当作授权成功处理`); |
| | | Taro.hideLoading(); |
| | | Taro.showToast({ title: '授权成功', icon: 'none' }); |
| | | void deps.onAuthorizedReturn(queryId); |
| | | return; |
| | | } |
| | | |
| | | Taro.hideLoading(); |
| | | console.log('[信用分授权查询失败]', error); |
| | | void deps.onUnauthorizedReturn(queryOrderId); |
| | | } |
| | | }; |
| | | |
| | | await pollStatus(); |
| | | deps.onReturn(); |
| | | }; |
| | | |
| | | appShowListenerRef.current = handleAppShow; |
| | |
| | | extraData: { |
| | | package: packageValue |
| | | }, |
| | | success: (res) => { |
| | | console.log('[信用分授权拉起][openBusinessView]', res); |
| | | }, |
| | | fail: () => { |
| | | clearAppShowListener(); |
| | | console.log('[信用分授权拉起失败][openBusinessView]'); |
| | | Taro.showToast({ title: '拉起支付分订单失败', icon: 'none' }); |
| | | void deps.onLaunchFailed(queryOrderId); |
| | | } |
| | | }); |
| | | |
| | | return true; |
| | | }, [clearAppShowListener, deps.onAuthorizedReturn, deps.onLaunchFailed, deps.onUnauthorizedReturn, deps.orderId]); |
| | | }, [clearAppShowListener, deps.onReturn]); |
| | | |
| | | useEffect(() => clearAppShowListener, [clearAppShowListener]); |
| | | |
| | |
| | | /** 未签约态支付处理。 */ |
| | | handleUnsignedPayment: (token: string) => void | Promise<void>; |
| | | /** 已签约态支付处理。 */ |
| | | handleSignedCreditPayment: (value: SignedOrderPaymentValue, targetOrderId?: string) => boolean | Promise<boolean>; |
| | | /** 结束行程后进入待支付态时,触发信用扣款结果查询。 */ |
| | | handleCreditPayTripFinished?: (targetOrderId: string) => void | Promise<void>; |
| | | handleSignedCreditPayment: (value: SignedOrderPaymentValue) => void | Promise<void>; |
| | | /** 同步上车点。 */ |
| | | setStartSite?: (site: any) => void; |
| | | }; |
| | |
| | | deps.setVehicleInfo({}); |
| | | clearOrderTripStorage(); |
| | | Taro.removeStorageSync('pickupEndTime'); |
| | | Taro.removeStorageSync('creditPayOrderId'); |
| | | Taro.removeStorageSync(WAITING_TIMER_START_STORAGE_KEY); |
| | | Taro.removeStorageSync(WAITING_TIMER_ELAPSED_STORAGE_KEY); |
| | | }; |
| | |
| | | */ |
| | | const handleOpenCancelPopupByTripStatus = (status: TripStatus, displayInfo?: any) => { |
| | | const waitMinutes = Number(displayInfo?.waitMinutes); |
| | | if ([TripStatus.CallingCar, TripStatus.VehicleApproaching].includes(status) && Number.isFinite(waitMinutes)) { |
| | | if (status === TripStatus.VehicleApproaching && Number.isFinite(waitMinutes)) { |
| | | deps.setCancelPopupWaitMinutes(waitMinutes); |
| | | } else { |
| | | deps.setCancelPopupWaitMinutes(null); |
| | |
| | | } else { |
| | | syncTripFinishedStatus(); |
| | | } |
| | | return { |
| | | orderDetail, |
| | | nextTripStatus |
| | | }; |
| | | return orderDetail; |
| | | }; |
| | | |
| | | /** |
| | |
| | | |
| | | try { |
| | | const endOrderResult: any = await endOrder(targetOrderId); |
| | | const { orderDetail, nextTripStatus } = await syncPostEndOrderTripStatus(targetOrderId); |
| | | if (nextTripStatus === TripStatus.TripFinished) { |
| | | // 结束行程后若仍待支付,立即触发信用扣款结果查询,避免依赖页面 effect 才能进入。 |
| | | await deps.handleCreditPayTripFinished?.(targetOrderId); |
| | | } |
| | | const orderDetail = await syncPostEndOrderTripStatus(targetOrderId); |
| | | reportTripPayStartEvent(orderDetail); |
| | | if (endOrderResult) { |
| | | Taro.showToast({ |
| | |
| | | await endOrder(targetOrderId); |
| | | } catch (error) { |
| | | } |
| | | const { nextTripStatus } = await syncPostEndOrderTripStatus(targetOrderId); |
| | | if (nextTripStatus === TripStatus.TripFinished) { |
| | | // 倒计时自动结束行程时也复用同一套信用扣款结果查询逻辑。 |
| | | await deps.handleCreditPayTripFinished?.(targetOrderId); |
| | | } |
| | | await syncPostEndOrderTripStatus(targetOrderId); |
| | | }; |
| | | |
| | | /** |
| | |
| | | mask: false |
| | | }); |
| | | try { |
| | | // await getOrderPayment({ orderId: targetOrderId }); |
| | | // syncPaymentSuccessStatus(); |
| | | await getOrderPayment({ orderId: targetOrderId }); |
| | | syncPaymentSuccessStatus(); |
| | | |
| | | |
| | | const paymentResult = await getOrderPayment({ orderId: targetOrderId }); |
| | | const paymentStatus = Number(paymentResult?.status); |
| | | // const paymentResult = await getOrderPayment({ orderId: targetOrderId }); |
| | | // const paymentStatus = Number(paymentResult?.status); |
| | | |
| | | if (paymentStatus === RobotaxiPaymentEntryStatus.Unsigned) { |
| | | const cashierToken = paymentResult?.value?.token; |
| | | if (!cashierToken) { |
| | | Taro.showToast({ title: '支付凭证缺失,请稍后重试', icon: 'none' }); |
| | | return; |
| | | } |
| | | await deps.handleUnsignedPayment(cashierToken); |
| | | return; |
| | | } |
| | | // if (paymentStatus === RobotaxiPaymentEntryStatus.Unsigned) { |
| | | // const cashierToken = paymentResult?.value?.token; |
| | | // if (!cashierToken) { |
| | | // Taro.showToast({ title: '支付凭证缺失,请稍后重试', icon: 'none' }); |
| | | // return; |
| | | // } |
| | | // await deps.handleUnsignedPayment(cashierToken); |
| | | // return; |
| | | // } |
| | | |
| | | if (paymentStatus === RobotaxiPaymentEntryStatus.Signed) { |
| | | const signedPaymentValue = paymentResult?.value as SignedOrderPaymentValue; |
| | | if (!signedPaymentValue?.packageValue) { |
| | | Taro.showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' }); |
| | | return; |
| | | } |
| | | await deps.handleSignedCreditPayment(signedPaymentValue); |
| | | return; |
| | | } |
| | | // if (paymentStatus === RobotaxiPaymentEntryStatus.Signed) { |
| | | // const signedPaymentValue = paymentResult?.value as SignedOrderPaymentValue; |
| | | // if (!signedPaymentValue?.packageValue) { |
| | | // Taro.showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' }); |
| | | // return; |
| | | // } |
| | | // await deps.handleSignedCreditPayment(signedPaymentValue); |
| | | // return; |
| | | // } |
| | | |
| | | Taro.showToast({ title: '暂不支持当前支付方式', icon: 'none' }); |
| | | // Taro.showToast({ title: '暂不支持当前支付方式', icon: 'none' }); |
| | | |
| | | } catch (error) { |
| | | Taro.showToast({ title: '支付失败,请重试', icon: 'none' }); |
| | | } finally { |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import { useCallback, useRef } from 'react'; |
| | | import { formatDateTime, debounce, reportEvent } from '../../../../utils/Robotaxi-public-method'; |
| | | import { createCreditPayOrder, saveOrder, routeStaleCheck, queryCreditPaySign } from '../../../api/begin-route'; |
| | | import { createCreditPayOrder, saveOrder, routeStaleCheck } from '../../../api/begin-route'; |
| | | import type { SaveOrderPayload } from '../../../api/types/begin-route'; |
| | | import { getAgreement } from '../../../api/home'; |
| | | import { getPayPwd, RobotaxiPaySettingStatus, setPayPwd } from '../../../api/payment'; |
| | | import useCreditPaySign from '../../../hooks/use-credit-pay-sign'; |
| | | import { checkOtp } from '../../../../utils/tools'; |
| | | import { jumpTo } from '../../../../utils/jump'; |
| | |
| | | callImmediately: () => void; |
| | | }; |
| | | |
| | | /** 当前路线是否为载客测试免费。 */ |
| | | const isOperateFreeRoute = (selectedRoute: any) => Number(selectedRoute?.operateFree) === 1; |
| | | |
| | | /** 标准化文本,兼容普通空格与不可见空格差异。 */ |
| | | const normalizeText = (value: any) => String(value || '').replace(/\s+/g, ' ').trim().toLowerCase(); |
| | | |
| | |
| | | skipCreditPayCheck?: boolean; |
| | | /** 是否跳过下单起始埋点。 */ |
| | | skipPayStartReport?: boolean; |
| | | /** 跳过校验后强制指定信用下单模式。 */ |
| | | forcedCreditPaySubmitMode?: 'credit_pay_submit' | 'credit_pay_skip_auth_submit'; |
| | | }; |
| | | |
| | | type CreditPayCheckResult = |
| | | | 'normal_submit' |
| | | | 'credit_pay_submit' |
| | | | 'credit_pay_skip_auth_submit' |
| | | | 'blocked'; |
| | | |
| | | /** 将签约状态转换为后续下单模式。 */ |
| | | const resolveCreditPaySubmitMode = ( |
| | | status: any |
| | | ): Extract<CreditPayCheckResult, 'credit_pay_submit' | 'credit_pay_skip_auth_submit'> | null => { |
| | | if (Number(status) === 2) { |
| | | return 'credit_pay_skip_auth_submit'; |
| | | } |
| | | if (Number(status) === 3) { |
| | | return 'credit_pay_submit'; |
| | | } |
| | | return null; |
| | | }; |
| | | type CreditPayCheckResult = 'normal_submit' | 'credit_pay_submit' | 'blocked'; |
| | | |
| | | /** |
| | | * 管理下单前校验与提交动作。 |
| | |
| | | export default function useOrderSubmit(deps: OrderActionsDeps): UseOrderSubmitResult { |
| | | const waitingCreditPayReturnRef = useRef(false); // 是否正在等待微信支付分签约页返回 |
| | | const submitOrderFlowRef = useRef<(options?: SubmitOrderFlowOptions) => Promise<void>>(); // 始终指向最新的下单流程 |
| | | |
| | | /** |
| | | * 查询长期签约回跳后的最新支付分状态,并决定是否继续信用下单。 |
| | | */ |
| | | const resolveCreditPayModeAfterSignReturn = useCallback(async () => { |
| | | Taro.showLoading({ title: '授权结果查询中', mask: true }); |
| | | let shouldHideLoading = true; |
| | | try { |
| | | const signResult = await queryCreditPaySign(); |
| | | const latestStatus = signResult?.data?.status ?? signResult?.status; |
| | | const resolvedCreditPaySubmitMode = resolveCreditPaySubmitMode(latestStatus); |
| | | if (resolvedCreditPaySubmitMode) { |
| | | Taro.hideLoading(); |
| | | shouldHideLoading = false; |
| | | Taro.showToast({ title: '授权成功', icon: 'none' }); |
| | | return resolvedCreditPaySubmitMode; |
| | | } |
| | | return resolvedCreditPaySubmitMode; |
| | | } catch (error) { |
| | | return null; |
| | | } finally { |
| | | if (shouldHideLoading) { |
| | | Taro.hideLoading(); |
| | | } |
| | | } |
| | | }, []); |
| | | |
| | | /** |
| | | * 结束下单中状态。 |
| | |
| | | } |
| | | }; |
| | | |
| | | const { openCreditPaySign } = useCreditPaySign({ |
| | | onReturn: async () => { |
| | | // 长期签约页回跳后,先查询最新授权结果,再决定是否继续信用下单。 |
| | | waitingCreditPayReturnRef.current = false; |
| | | const nextCreditPaySubmitMode = await resolveCreditPayModeAfterSignReturn(); |
| | | if (!nextCreditPaySubmitMode) { |
| | | stopCallSubmitting(); |
| | | Taro.showToast({ title: '支付分授权未完成,请重试', icon: 'none' }); |
| | | return; |
| | | } |
| | | |
| | | void submitOrderFlowRef.current?.({ |
| | | skipCreditPayCheck: true, |
| | | forcedCreditPaySubmitMode: nextCreditPaySubmitMode |
| | | }); |
| | | }, |
| | | onLaunchFail: () => { |
| | | waitingCreditPayReturnRef.current = false; |
| | | stopCallSubmitting(); |
| | | Taro.showToast({ title: '拉起签约失败', icon: 'none' }); |
| | | /** |
| | | * 继续执行签约页返回后的下单流程。 |
| | | */ |
| | | const continueSubmitAfterCreditPayReturn = useCallback(() => { |
| | | if (!waitingCreditPayReturnRef.current) { |
| | | return; |
| | | } |
| | | waitingCreditPayReturnRef.current = false; |
| | | void submitOrderFlowRef.current?.({ |
| | | skipCreditPayCheck: true, |
| | | skipPayStartReport: true |
| | | }); |
| | | }, []); |
| | | |
| | | /** |
| | | * 拉起签约失败后,降级为继续正常下单。 |
| | | */ |
| | | const continueSubmitAfterCreditPayLaunchFail = useCallback(() => { |
| | | if (!waitingCreditPayReturnRef.current) { |
| | | return; |
| | | } |
| | | waitingCreditPayReturnRef.current = false; |
| | | // Taro.showToast({ title: '拉起免密开通失败,已继续下单', icon: 'none' }); |
| | | void submitOrderFlowRef.current?.({ |
| | | skipCreditPayCheck: true, |
| | | skipPayStartReport: true |
| | | }); |
| | | }, []); |
| | | |
| | | const { openCreditPaySign } = useCreditPaySign({ |
| | | onReturn: continueSubmitAfterCreditPayReturn, |
| | | onLaunchFail: continueSubmitAfterCreditPayLaunchFail |
| | | }); |
| | | |
| | | /** |
| | | * 下单前校验免密状态。 |
| | | */ |
| | | const checkCreditPayReadyBeforeOrder = useCallback(async (selectedRoute: any) => { |
| | | if (isOperateFreeRoute(selectedRoute)) { |
| | | // 载客测试免费单直接走普通下单,不拉起微信信用下单弹窗。 |
| | | return 'normal_submit' as CreditPayCheckResult; |
| | | } |
| | | |
| | | const checkCreditPayReadyBeforeOrder = useCallback(async () => { |
| | | // 免密状态校验,先默认返回正常下单,后续根据校验结果再处理。 |
| | | return 'normal_submit' as CreditPayCheckResult; |
| | | try { |
| | | const res = await queryCreditPaySign(); |
| | | const status = res?.data?.status ?? res?.status; // 兼容不同包裹层级 |
| | | const value = res?.data?.value ?? res?.value; |
| | | |
| | | const resolvedCreditPaySubmitMode = resolveCreditPaySubmitMode(status); |
| | | if (resolvedCreditPaySubmitMode) { |
| | | // status=2: 已确认签约,信用下单后无需再拉起微信支付分授权页。 |
| | | // status=3: 每次下单授权流程,信用下单后仍需拉起微信支付分确认页。 |
| | | return resolvedCreditPaySubmitMode; |
| | | const hasEnabledCreditPay = await getPayPwd(); |
| | | if (hasEnabledCreditPay === true) { |
| | | return 'normal_submit' as CreditPayCheckResult; |
| | | } |
| | | |
| | | if (status === 1) { |
| | | // 需要拉起长期授权 |
| | | const isCanceled = await deps.handleOpenCreditPayPopup(); |
| | | if (isCanceled) { |
| | | // 用户取消开通,中断下单流程 |
| | | stopCallSubmitting(); |
| | | return 'blocked' as CreditPayCheckResult; |
| | | } |
| | | const isCanceled = await deps.handleOpenCreditPayPopup(); |
| | | if (isCanceled) { |
| | | // 用户取消开通,继续下单 |
| | | return 'normal_submit' as CreditPayCheckResult; |
| | | } |
| | | |
| | | // value 为支付中心签约响应对象(CreditPaySignVo),需取其中的 signStr 字段作为预授权 token。 |
| | | const signStr = String(value?.signStr ?? value ?? '').trim(); |
| | | if (Number(deps.distanceConfig?.creditPayNeedConfirm) !== 0) { |
| | | // 默认优先走信用下单,只有后端明确返回 0 时才继续旧的签约流程。 |
| | | return 'credit_pay_submit' as CreditPayCheckResult; |
| | | } |
| | | |
| | | try { |
| | | const response = await setPayPwd(RobotaxiPaySettingStatus.Enable); |
| | | const signStr = String(response?.signStr || '').trim(); |
| | | if (!signStr) { |
| | | // 获取免密签约信息失败,降级为继续下单 |
| | | return 'normal_submit' as CreditPayCheckResult; |
| | |
| | | // 等待微信签约页返回后再恢复下单,避免当前流程继续往下执行。 |
| | | stopCallSubmitting(); |
| | | return 'blocked' as CreditPayCheckResult; |
| | | } catch (e) { |
| | | // 免密状态校验失败,降级为继续下单 |
| | | return 'normal_submit' as CreditPayCheckResult; |
| | | } |
| | | |
| | | // 未知状态降级为继续下单 |
| | | return 'normal_submit' as CreditPayCheckResult; |
| | | |
| | | } catch (error) { |
| | | // 接口失败,降级为继续下单 |
| | | return 'normal_submit' as CreditPayCheckResult; |
| | | } |
| | | }, [openCreditPaySign, deps]); |
| | |
| | | return; |
| | | } |
| | | |
| | | let submitMode: CreditPayCheckResult = options.forcedCreditPaySubmitMode || 'normal_submit'; |
| | | let submitMode: CreditPayCheckResult = 'normal_submit'; |
| | | if (!options.skipCreditPayCheck) { |
| | | submitMode = await checkCreditPayReadyBeforeOrder(selectedRoute); |
| | | submitMode = await checkCreditPayReadyBeforeOrder(); |
| | | if (submitMode === 'blocked') { |
| | | return; |
| | | } |
| | |
| | | await waitForOrderSuccessSubscribePopup(); |
| | | |
| | | try { |
| | | const isCreditPaySubmit = submitMode === 'credit_pay_submit' |
| | | || submitMode === 'credit_pay_skip_auth_submit'; |
| | | |
| | | if (isCreditPaySubmit) { |
| | | Taro.showLoading({ title: '加载中', mask: true }); |
| | | } |
| | | |
| | | const submitResult = isCreditPaySubmit |
| | | const submitResult = submitMode === 'credit_pay_submit' |
| | | ? await submitCreditPayOrder(startSite, openCityId, selectedRoute) |
| | | : await submitOrder(startSite, openCityId); |
| | | |
| | | if (!submitResult?.orderId) { |
| | | Taro.hideLoading(); |
| | | stopCallSubmitting(); |
| | | return; |
| | | } |
| | | deps.setOrderId(submitResult.orderId); |
| | | deps.setTripStatus(TripStatus.Publishing); |
| | | deps.bindSocketListeners(); |
| | | void subscribeAfterOrderSuccess(submitResult.orderId); |
| | | |
| | | if (!isCreditPaySubmit) { |
| | | Taro.removeStorageSync('creditPayOrderId'); |
| | | } |
| | | |
| | | if (isCreditPaySubmit) { |
| | | Taro.setStorageSync('creditPayOrderId', submitResult.orderId); |
| | | if (submitMode === 'credit_pay_skip_auth_submit') { |
| | | Taro.hideLoading(); |
| | | // status=2 代表当前已是免确认模式,信用下单成功后直接进入发单态。 |
| | | deps.setTripStatus(TripStatus.Publishing); |
| | | return; |
| | | } |
| | | |
| | | if (submitMode === 'credit_pay_submit') { |
| | | const creditPayPackageValue = String(submitResult.creditPayPackageValue || '').trim(); |
| | | if (!creditPayPackageValue) { |
| | | Taro.hideLoading(); |
| | | Taro.showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' }); |
| | | return; |
| | | } |
| | | // 复用现有支付分确认页拉起与回跳查询链路。 |
| | | const launched = await deps.handleSignedCreditPayment({ |
| | | void deps.handleSignedCreditPayment({ |
| | | businessOrderId: '', |
| | | authOrderId: '', |
| | | channelOrderId: '', |
| | | payType: 13, |
| | | packageValue: creditPayPackageValue |
| | | }, submitResult.orderId); |
| | | |
| | | Taro.hideLoading(); |
| | | |
| | | // 只有拉起失败才立刻进入发单状态(或者依靠 abort 逻辑终止) |
| | | if (!launched) { |
| | | deps.setTripStatus(TripStatus.Publishing); |
| | | } |
| | | return; |
| | | }); |
| | | } |
| | | deps.setTripStatus(TripStatus.Publishing); |
| | | } catch (error) { |
| | | Taro.hideLoading(); |
| | | const errorMessage = String((error as any)?.msg || (error as any)?.message || '下单失败,请重试'); |
| | | if (submitMode === 'credit_pay_submit' || submitMode === 'credit_pay_skip_auth_submit') { |
| | | Taro.showToast({ title: errorMessage, icon: 'none' }); |
| | | } |
| | | reportPayResultEvent(resolvePayResultStatus(errorMessage)); |
| | | reportPayResultEvent(resolvePayResultStatus((error as any)?.msg || (error as any)?.message || error)); |
| | | stopCallSubmitting(); |
| | | } |
| | | }; |
| | |
| | | type Deps = { |
| | | tripStatus: TripStatus; |
| | | startSite: any; |
| | | returnSource?: string; |
| | | getCheckedEnter: () => boolean; |
| | | setCheckedEnter: (checked: boolean) => void; |
| | | onBeforeBack?: () => void; |
| | |
| | | }; |
| | | |
| | | /** |
| | | * 根据来源返回上一页或首页,并清理必要缓存。 |
| | | * 返回首页并清理必要缓存。 |
| | | */ |
| | | const toBack = () => { |
| | | deps.onBeforeBack?.(); |
| | | Taro.removeStorageSync('selectedCouponId'); |
| | | clearModifyDestinationStorage(); |
| | | clearPassedRouteCache(); |
| | | if (deps.returnSource === 'order') { |
| | | if (deps.tripStatus === TripStatus.PaymentSuccess || deps.tripStatus === TripStatus.PublishRoute) { |
| | | clearOrderTripStorage(); |
| | | Taro.removeStorageSync('endSite'); |
| | | } |
| | | Taro.navigateBack(); |
| | | return; |
| | | } |
| | | if (deps.tripStatus === TripStatus.PaymentSuccess || deps.tripStatus === TripStatus.PublishRoute) { |
| | | if (deps.tripStatus === TripStatus.PublishRoute) { |
| | | retainStartSiteForNextHome(); |
| | |
| | | endSite: any; |
| | | /** 发单 */ |
| | | callImmediately: () => void; |
| | | /** 是否正在等待信用分确认页返回结果 */ |
| | | isWaitingSignedCreditPayResult?: boolean; |
| | | /** 预估态选择上下车点 */ |
| | | handleEstimateSelectSite: (pageType: "start" | "end") => void; |
| | | /** 费用信息 */ |
| | |
| | | handlePickupTimeoutAutoCancel: () => void; |
| | | /** 行程状态设置 */ |
| | | setTripStatus: (value: TripStatus) => void; |
| | | /** 发布中动画结束 */ |
| | | handlePublishingAnimationFinish?: () => void; |
| | | /** 完成倒计时启用 */ |
| | | finishCountdownEnabled: boolean; |
| | | /** 订单 ID */ |
| | |
| | | handleOpenCancelPopup={() => |
| | | deps.handleOpenCancelPopupByTripStatus(TripStatus.Publishing) |
| | | } |
| | | finishBlocked={Boolean(deps.isWaitingSignedCreditPayResult)} |
| | | onFinish={() => { |
| | | if (deps.handlePublishingAnimationFinish) { |
| | | deps.handlePublishingAnimationFinish(); |
| | | return; |
| | | } |
| | | deps.setTripStatus(TripStatus.CallingCar); |
| | | Taro.setStorageSync("tripStatus", TripStatus.CallingCar); |
| | | }} |
| | |
| | | |
| | | export type UseWebSocketEventsDeps = { |
| | | tripStatusRef: React.MutableRefObject<TripStatus>; |
| | | isWaitingSignedCreditPayResultRef?: React.MutableRefObject<boolean>; |
| | | pendingSignedCreditPayTripStatusRef?: React.MutableRefObject<TripStatus | null>; |
| | | isHoldingTripStatusDuringPublishingAnimationRef?: React.MutableRefObject<boolean>; |
| | | pendingTripStatusAfterPublishingAnimationRef?: React.MutableRefObject<TripStatus | null>; |
| | | pickupArrivedReportedOrderIdRef: React.MutableRefObject<string>; |
| | | tripInProgressReportedOrderIdRef: React.MutableRefObject<string>; |
| | | arrivedDestinationReportedOrderIdRef: React.MutableRefObject<string>; |
| | |
| | | if (currentTripStatus === mappedTripStatus) { |
| | | return mappedTripStatus; |
| | | } |
| | | if (deps.isWaitingSignedCreditPayResultRef?.current) { |
| | | deps.pendingSignedCreditPayTripStatusRef && (deps.pendingSignedCreditPayTripStatusRef.current = mappedTripStatus); |
| | | return currentTripStatus; |
| | | } |
| | | if (deps.isHoldingTripStatusDuringPublishingAnimationRef?.current && currentTripStatus === TripStatus.Publishing) { |
| | | deps.pendingTripStatusAfterPublishingAnimationRef && (deps.pendingTripStatusAfterPublishingAnimationRef.current = mappedTripStatus); |
| | | return currentTripStatus; |
| | | } |
| | | deps.setTripStatus(mappedTripStatus); |
| | | deps.tripStatusRef.current = mappedTripStatus; |
| | | Taro.setStorageSync('tripStatus', mappedTripStatus); |
| | |
| | | |
| | | // 气泡底部中间的钻石指示图标 |
| | | .callout-diamond-icon { |
| | | <<<<<<< HEAD |
| | | width: 28px; |
| | | ======= |
| | | width: 18px; |
| | | >>>>>>> feature_v2.4.0 |
| | | height: 28px; |
| | | margin-top: -14px; |
| | | flex-shrink: 0; |
| | |
| | | flex-direction: row; |
| | | align-items: center; |
| | | justify-content: center; |
| | | <<<<<<< HEAD |
| | | background: @black-text-main; |
| | | border-radius: 100px; |
| | | padding: 16px 20px; |
| | | ======= |
| | | background: #141E27; |
| | | border-radius: 100px; |
| | | padding: 16px 16px 16px 20px; |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | |
| | | .site-countdown-icon { |
| | |
| | | } |
| | | |
| | | .site-countdown-time { |
| | | <<<<<<< HEAD |
| | | font-family: @font-xpsans-bold; |
| | | ======= |
| | | >>>>>>> feature_v2.4.0 |
| | | font-size: 24px; |
| | | font-weight: normal; |
| | | line-height: 32px; |
| | |
| | | import useBluetoothCarControl from './hooks/use-bluetooth-car-control'; |
| | | import { toTripStatus, TripStatus } from '../../constants/trip-status'; |
| | | import { MODIFY_DESTINATION_SOCKET_STATUS_KEY } from '../../constants/modify-destination'; |
| | | import { cancelOrder, selectNavigationRoute } from '../../api/begin-route'; |
| | | import { selectNavigationRoute } from '../../api/begin-route'; |
| | | import { getAddressByLocation, getDistance, reportEvent } from '../../../utils/Robotaxi-public-method'; |
| | | import { CurrentUserLocation, RouteDistanceValue } from './hooks/use-route-manager.shared'; |
| | | import { MODIFY_DESTINATION_SESSION_KEY } from '../../constants/modify-destination'; |
| | | import { getOrderId } from '../../utils/order'; |
| | | import { getOrderDetail } from '../../api/order'; |
| | | |
| | | import { _ROBOTAXI_WS_HOST } from '../../../constants/api'; |
| | | import { RobotaxiSignedPayType, SignedOrderPaymentValue } from '../../api/types/begin-route'; |
| | |
| | | const [pageDidShowVersion, setPageDidShowVersion] = useState(0); // 页面展示版本号,用于触发子模块重查 |
| | | const [resetDetailVersion, setResetDetailVersion] = useState(0); // 重置详情版本号,用于触发组件更新 |
| | | const [priceUpdateVersion, setPriceUpdateVersion] = useState(0); // 价格更新版本号 |
| | | const [, setCreditPayFallbackRefreshVersion] = useState(0); // 信用扣款回落普通支付后,触发页面重渲染 |
| | | const [evaluateSubmitVersion, setEvaluateSubmitVersion] = useState(0); // 评价提交版本号 |
| | | const [evaluateCloseVersion, setEvaluateCloseVersion] = useState(0); // 评价关闭版本号 |
| | | const [evaluateOrderRating, setEvaluateOrderRating] = useState(1); // 当前评价弹窗对应评分 |
| | |
| | | const [pickupExceptionInfo, setPickupExceptionInfo] = useState<PickupExceptionInfo | null>(null); // 接驾异常信息 |
| | | const [showPickupExceptionPopup, setShowPickupExceptionPopup] = useState(false); // 接驾异常弹窗展示状态 |
| | | const [showCreditPayPopup, setShowCreditPayPopup] = useState(false); // 免密支付开通弹窗 |
| | | const [isWaitingSignedCreditPayResult, setIsWaitingSignedCreditPayResult] = useState(false); // 信用分确认页已拉起,等待用户返回结果 |
| | | const creditPayResolverRef = useRef<((value: boolean) => void) | null>(null); // 免密弹窗的 Promise resolve 回调 |
| | | const isWaitingSignedCreditPayResultRef = useRef(false); // 信用分确认结果等待态引用,供 socket 状态同步拦截使用 |
| | | const pendingSignedCreditPayTripStatusRef = useRef<TripStatus | null>(null); // 等待信用分结果期间暂存的最新行程状态 |
| | | const pendingPublishingAfterSignedCreditPayRef = useRef(false); // 信用分下单成功后,等待结果返回再进入发布态 |
| | | const isHoldingTripStatusDuringPublishingAnimationRef = useRef(false); // 信用分返回后,发布中 3 秒动画期间暂存 socket 状态 |
| | | const pendingTripStatusAfterPublishingAnimationRef = useRef<TripStatus | null>(null); // 发布中动画结束后待恢复的最新状态 |
| | | const clearActiveOrderRuntimeRef = useRef<(() => void) | null>(null); // 信用分取消发单后复用订单清理逻辑 |
| | | const restartPlanningRouteRef = useRef<(() => void) | null>(null); // 信用分取消发单后复用发单前页面恢复逻辑 |
| | | const stopCallSubmittingRef = useRef<(() => void) | null>(null); // 信用分中止发单后复位“立即呼叫”提交态 |
| | | const [orderRouteMetrics, setOrderRouteMetrics] = useState<OrderRouteMetrics>({ |
| | | distanceMeters: null, |
| | | durationSeconds: null |
| | |
| | | }, []); |
| | | const shouldShowWaitingRadar = isAndroid && [TripStatus.Publishing, TripStatus.CallingCar].includes(tripStatus); // 是否显示等待雷达动画(Android 且处于发布中/呼叫车中) |
| | | const vehicleMarkerId = !isAndroid && tripStatus === TripStatus.InService ? 30 : 3; // 车辆标记 ID,iOS 行驶中阶段使用不同 ID |
| | | const router = Taro.useRouter(); |
| | | const { |
| | | customCalloutIconSrc, |
| | | waitingRadarSrc, |
| | |
| | | } = usePageActions({ |
| | | tripStatus, |
| | | startSite, |
| | | returnSource: router?.params?.from, |
| | | getCheckedEnter: () => isCheckedEnter.current, |
| | | setCheckedEnter: (checked) => { |
| | | isCheckedEnter.current = checked; |
| | |
| | | getCurrentVehicleLocation: () => vehicleLocation, |
| | | updatePickupRouteChangeEligibleOrder |
| | | }); // 页面恢复与初始化编排 |
| | | const router = Taro.useRouter(); |
| | | const tripRuntime = useTripRuntime({ |
| | | waitingTimerStartKey: WAITING_TIMER_START_KEY, |
| | | waitingTimerElapsedKey: WAITING_TIMER_ELAPSED_KEY, |
| | |
| | | }, []); |
| | | |
| | | /** |
| | | * 统一维护“等待信用分结果”状态,同时同步 ref,供异步回调和 socket 读取。 |
| | | */ |
| | | const syncWaitingSignedCreditPayResult = useCallback((value: boolean) => { |
| | | isWaitingSignedCreditPayResultRef.current = value; |
| | | setIsWaitingSignedCreditPayResult(value); |
| | | }, []); |
| | | |
| | | /** |
| | | * 信用分结果返回后,恢复页面状态推进。 |
| | | */ |
| | | const resumeTripStatusAfterSignedCreditPayReturn = useCallback(() => { |
| | | syncWaitingSignedCreditPayResult(false); |
| | | const deferredTripStatus = pendingSignedCreditPayTripStatusRef.current; |
| | | pendingSignedCreditPayTripStatusRef.current = null; |
| | | |
| | | if (pendingPublishingAfterSignedCreditPayRef.current) { |
| | | pendingPublishingAfterSignedCreditPayRef.current = false; |
| | | pendingTripStatusAfterPublishingAnimationRef.current = deferredTripStatus; |
| | | isHoldingTripStatusDuringPublishingAnimationRef.current = true; |
| | | setTripStatus(TripStatus.Publishing); |
| | | tripStatusRef.current = TripStatus.Publishing; |
| | | Taro.setStorageSync('tripStatus', TripStatus.Publishing); |
| | | return; |
| | | } |
| | | |
| | | if (deferredTripStatus !== null) { |
| | | setTripStatus(deferredTripStatus); |
| | | tripStatusRef.current = deferredTripStatus; |
| | | Taro.setStorageSync('tripStatus', deferredTripStatus); |
| | | } |
| | | }, [syncWaitingSignedCreditPayResult]); |
| | | |
| | | /** |
| | | * 发布中 3 秒动画结束后,再恢复信用分结果等待期间暂存的最新状态。 |
| | | */ |
| | | const handlePublishingAnimationFinish = useCallback(() => { |
| | | const deferredTripStatus = pendingTripStatusAfterPublishingAnimationRef.current; |
| | | pendingTripStatusAfterPublishingAnimationRef.current = null; |
| | | isHoldingTripStatusDuringPublishingAnimationRef.current = false; |
| | | const nextTripStatus = deferredTripStatus ?? TripStatus.CallingCar; |
| | | setTripStatus(nextTripStatus); |
| | | tripStatusRef.current = nextTripStatus; |
| | | Taro.setStorageSync('tripStatus', nextTripStatus); |
| | | }, []); |
| | | |
| | | /** |
| | | * 确认开通免密支付。 |
| | | */ |
| | | const handleConfirmCreditPayPopup = useCallback(() => { |
| | |
| | | |
| | | const { bindSocketListeners, ensureSocketListeners } = useWebSocketEvents({ |
| | | tripStatusRef, |
| | | isWaitingSignedCreditPayResultRef, |
| | | pendingSignedCreditPayTripStatusRef, |
| | | isHoldingTripStatusDuringPublishingAnimationRef, |
| | | pendingTripStatusAfterPublishingAnimationRef, |
| | | pickupArrivedReportedOrderIdRef, |
| | | tripInProgressReportedOrderIdRef, |
| | | arrivedDestinationReportedOrderIdRef, |
| | |
| | | onPaySuccess: handlePaymentSuccess |
| | | }); |
| | | |
| | | // 信用分结束行程 loading 状态 |
| | | const creditPayLoadingTimerRef = useRef<any>(null); |
| | | const creditPayFallbackCheckTimerRef = useRef<any>(null); |
| | | const creditPayFallbackActiveOrderIdRef = useRef(''); |
| | | |
| | | const clearCreditPayFallbackRuntime = useCallback((hideLoading = true) => { |
| | | if (creditPayLoadingTimerRef.current) { |
| | | clearTimeout(creditPayLoadingTimerRef.current); |
| | | creditPayLoadingTimerRef.current = null; |
| | | } |
| | | if (creditPayFallbackCheckTimerRef.current) { |
| | | clearInterval(creditPayFallbackCheckTimerRef.current); |
| | | creditPayFallbackCheckTimerRef.current = null; |
| | | } |
| | | creditPayFallbackActiveOrderIdRef.current = ''; |
| | | if (hideLoading) { |
| | | Taro.hideLoading(); |
| | | } |
| | | }, []); |
| | | |
| | | const handleCreditPayTripFinished = useCallback(async (targetOrderId: string) => { |
| | | const resolvedOrderId = String(targetOrderId || '').trim(); |
| | | const creditPayOrderId = String(Taro.getStorageSync('creditPayOrderId') || '').trim(); |
| | | if (!resolvedOrderId || !creditPayOrderId || creditPayOrderId !== resolvedOrderId) { |
| | | return; |
| | | } |
| | | if ( |
| | | creditPayFallbackActiveOrderIdRef.current === resolvedOrderId |
| | | && (creditPayLoadingTimerRef.current || creditPayFallbackCheckTimerRef.current) |
| | | ) { |
| | | return; |
| | | } |
| | | |
| | | clearCreditPayFallbackRuntime(false); |
| | | creditPayFallbackActiveOrderIdRef.current = resolvedOrderId; |
| | | Taro.showLoading({ title: '扣款结果查询中,请勿重复支付', mask: true }); |
| | | |
| | | const checkCreditPayFallback = async () => { |
| | | try { |
| | | const orderDetail: any = await getOrderDetail(resolvedOrderId); |
| | | if (Number(orderDetail?.payType) === 1) { |
| | | Taro.removeStorageSync('creditPayOrderId'); |
| | | clearCreditPayFallbackRuntime(); |
| | | setCreditPayFallbackRefreshVersion((prev) => prev + 1); |
| | | Taro.showToast({ title: '免密扣款失败,请手动支付', icon: 'none' }); |
| | | } |
| | | } catch (error) { |
| | | } |
| | | }; |
| | | |
| | | // 先立即查一次,再轮询订单详情感知 payType 是否从信用支付回落到普通支付。 |
| | | await checkCreditPayFallback(); |
| | | if (creditPayFallbackActiveOrderIdRef.current !== resolvedOrderId) { |
| | | return; |
| | | } |
| | | |
| | | creditPayFallbackCheckTimerRef.current = setInterval(() => { |
| | | void checkCreditPayFallback(); |
| | | }, 1000); |
| | | creditPayLoadingTimerRef.current = setTimeout(() => { |
| | | clearCreditPayFallbackRuntime(); |
| | | Taro.redirectTo({ |
| | | url: '/pages/robotaxi/home/index', |
| | | }); |
| | | }, 10000); |
| | | }, [clearCreditPayFallbackRuntime]); |
| | | |
| | | useEffect(() => { |
| | | if (tripStatus === TripStatus.TripFinished) { |
| | | void handleCreditPayTripFinished(orderId); |
| | | return () => { |
| | | clearCreditPayFallbackRuntime(); |
| | | }; |
| | | } else { |
| | | clearCreditPayFallbackRuntime(); |
| | | } |
| | | return () => { |
| | | clearCreditPayFallbackRuntime(); |
| | | }; |
| | | }, [tripStatus, orderId, handleCreditPayTripFinished, clearCreditPayFallbackRuntime]); |
| | | |
| | | /** |
| | | * 信用分流程未完成时,统一取消订单并终止发单。 |
| | | */ |
| | | const abortSignedCreditPayFlow = useCallback(async (reason: 'unauthorized' | 'launch_failed', fallbackOrderId?: string) => { |
| | | const targetOrderId = getOrderId(orderId) || fallbackOrderId; |
| | | console.log('[信用分流程中止]', { |
| | | reason, |
| | | orderId: targetOrderId |
| | | }); |
| | | |
| | | stopCallSubmittingRef.current?.(); |
| | | syncWaitingSignedCreditPayResult(false); |
| | | pendingPublishingAfterSignedCreditPayRef.current = false; |
| | | pendingSignedCreditPayTripStatusRef.current = null; |
| | | pendingTripStatusAfterPublishingAnimationRef.current = null; |
| | | isHoldingTripStatusDuringPublishingAnimationRef.current = false; |
| | | |
| | | if (!targetOrderId) { |
| | | clearActiveOrderRuntimeRef.current?.(); |
| | | restartPlanningRouteRef.current?.(); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | await cancelOrder(targetOrderId); |
| | | // Taro.showToast({ |
| | | // title: reason === 'launch_failed' ? '拉起信用分失败,已取消发单' : '未完成信用分授权,已取消发单', |
| | | // icon: 'none' |
| | | // }); |
| | | clearActiveOrderRuntimeRef.current?.(); |
| | | if (restartPlanningRouteRef.current) { |
| | | restartPlanningRouteRef.current(); |
| | | return; |
| | | } |
| | | setTripStatus(TripStatus.PublishRoute); |
| | | tripStatusRef.current = TripStatus.PublishRoute; |
| | | Taro.setStorageSync('tripStatus', TripStatus.PublishRoute); |
| | | } catch (error) { |
| | | console.log('[信用分流程中止][取消订单失败]', { |
| | | reason, |
| | | error |
| | | }); |
| | | Taro.showToast({ title: '取消订单失败,请重试', icon: 'none' }); |
| | | } |
| | | }, [orderId, syncWaitingSignedCreditPayResult]); |
| | | |
| | | /** |
| | | * 信用分页面返回但未拿到 query_id,视为未授权。 |
| | | */ |
| | | const handleSignedCreditPayUnauthorizedReturn = useCallback((targetOrderId?: string) => { |
| | | return abortSignedCreditPayFlow('unauthorized', targetOrderId); |
| | | }, [abortSignedCreditPayFlow]); |
| | | |
| | | /** |
| | | * 信用分页面拉起失败,直接终止本次发单。 |
| | | */ |
| | | const handleSignedCreditPayLaunchFailed = useCallback((targetOrderId?: string) => { |
| | | return abortSignedCreditPayFlow('launch_failed', targetOrderId); |
| | | }, [abortSignedCreditPayFlow]); |
| | | |
| | | const { openCreditPayScoreUse } = useCreditPayReturn({ |
| | | orderId, |
| | | // 信用下单链路回跳后,有 query_id 且结果为 DOING 或已确认时,继续正常发单。 |
| | | onAuthorizedReturn: () => resumeTripStatusAfterSignedCreditPayReturn(), |
| | | // 授权结果为失败/撤销等,立即取消订单并终止发单。 |
| | | onUnauthorizedReturn: handleSignedCreditPayUnauthorizedReturn, |
| | | // 拉起失败同样取消订单并终止发单。 |
| | | onLaunchFailed: handleSignedCreditPayLaunchFailed |
| | | // 信用下单链路回跳后仅关闭监听,不再主动查询支付结果。 |
| | | onReturn: () => {} |
| | | }); |
| | | |
| | | useCashierPayListener({ |
| | |
| | | /** |
| | | * 处理已签约态支付分支付。 |
| | | */ |
| | | const handleSignedCreditPayment = useCallback((value: SignedOrderPaymentValue, targetOrderId?: string) => { |
| | | const handleSignedCreditPayment = useCallback((value: SignedOrderPaymentValue) => { |
| | | if (Number(value?.payType) !== RobotaxiSignedPayType.WechatCredit) { |
| | | Taro.showToast({ title: '暂不支持当前支付方式', icon: 'none' }); |
| | | return false; |
| | | return; |
| | | } |
| | | |
| | | pendingPublishingAfterSignedCreditPayRef.current = true; |
| | | pendingSignedCreditPayTripStatusRef.current = null; |
| | | syncWaitingSignedCreditPayResult(true); |
| | | const launched = openCreditPayScoreUse(value.packageValue, targetOrderId); |
| | | if (!launched) { |
| | | pendingPublishingAfterSignedCreditPayRef.current = false; |
| | | syncWaitingSignedCreditPayResult(false); |
| | | } |
| | | return launched; |
| | | }, [openCreditPayScoreUse, syncWaitingSignedCreditPayResult]); |
| | | |
| | | useEffect(() => { |
| | | if (tripStatus !== TripStatus.Publishing) { |
| | | syncWaitingSignedCreditPayResult(false); |
| | | isHoldingTripStatusDuringPublishingAnimationRef.current = false; |
| | | pendingTripStatusAfterPublishingAnimationRef.current = null; |
| | | } |
| | | }, [tripStatus, syncWaitingSignedCreditPayResult]); |
| | | openCreditPayScoreUse(value.packageValue); |
| | | }, [openCreditPayScoreUse]); |
| | | |
| | | const orderActions = useOrderActions({ |
| | | tripStatus, |
| | |
| | | routeRequestRouteAndFare, |
| | | handleUnsignedPayment, |
| | | handleSignedCreditPayment, |
| | | handleCreditPayTripFinished, |
| | | setStartSite |
| | | }); |
| | | |
| | |
| | | handlePickupTimeoutAutoCancel, |
| | | handleConfirmOverRoute, |
| | | handleFinishCountdownTimeout, |
| | | handlePay, |
| | | stopCallSubmitting, |
| | | clearActiveOrderRuntime, |
| | | restartPlanningRoute |
| | | handlePay |
| | | } = orderActions; |
| | | stopCallSubmittingRef.current = stopCallSubmitting; |
| | | clearActiveOrderRuntimeRef.current = clearActiveOrderRuntime; |
| | | restartPlanningRouteRef.current = restartPlanningRoute; |
| | | const { |
| | | authSuccess: bluetoothAuthSuccess, |
| | | startAuth: startBluetoothAuth, |
| | |
| | | useDidShow(() => { |
| | | setPageDidShowVersion((v) => v + 1); |
| | | setDoorUnlockedInCurrentPage(false); |
| | | // 信用分结果页返回后,需要先保留“发布中”动画,不要立刻走页面恢复链路把状态顶到最新。 |
| | | if ( |
| | | isWaitingSignedCreditPayResultRef.current |
| | | || isHoldingTripStatusDuringPublishingAnimationRef.current |
| | | ) { |
| | | updateMapViewportPadding(); |
| | | return; |
| | | } |
| | | handleDidShow(router.params?.skipPageFlag, router.params?.fromModifyDestination); |
| | | updateMapViewportPadding(); |
| | | // 完成态下隐藏小程序再恢复时,受控中心点可能与原生地图实际中心脱钩,主动触发一次 |
| | |
| | | elapsedSeconds, |
| | | endSite: effectiveEndSite, |
| | | callImmediately: handleCallImmediately, |
| | | isWaitingSignedCreditPayResult, |
| | | handleEstimateSelectSite, |
| | | fareEstimate, |
| | | handleOpenCouponSelect, |
| | |
| | | handleOpenCancelPopupWithType, |
| | | handlePickupTimeoutAutoCancel, |
| | | setTripStatus, |
| | | handlePublishingAnimationFinish, |
| | | finishCountdownEnabled, |
| | | orderId, |
| | | vehicleInfo, |
| | |
| | | border-radius: 24px; |
| | | padding: 32rpx 32rpx 36rpx 32rpx; |
| | | box-sizing: border-box; |
| | | max-height: 410rpx; |
| | | height: 410rpx; |
| | | overflow: auto; |
| | | |
| | | .content-title { |
| | |
| | | |
| | | const DEFAULT_MAP_LONGITUDE = 116.397128; |
| | | const DEFAULT_MAP_LATITUDE = 39.916527; |
| | | const MAP_INCLUDE_POINTS_PADDING = [120, 10, 50, 10]; |
| | | const MAP_INCLUDE_POINTS_PADDING = [10, 10, 10, 10]; |
| | | // includePoints 缩放动画的预估时长,动画结束后再显示地图,避免“先展示再放大”的跳动 |
| | | const MAP_FIT_ANIMATION_DURATION = 700; |
| | | const POLYGON_STROKE_COLOR = '#DFB369'; // rgb(223, 179, 105) |
| | |
| | | // scale={30} |
| | | polygons={polygons} |
| | | showLocation={false} |
| | | style={{ width: '100%', height: '70%' }} |
| | | style={{ width: '100%', height: '65%' }} |
| | | onPolygonTap={handlePolygonTap} |
| | | onTap={handleMapTap} |
| | | > |
| | |
| | | </View> |
| | | <View className="content-desc"> |
| | | <View className="desc-item"> |
| | | <Text className="text">各站点运营时间可能存在调整,实际运营情况请以页面实时展示为准</Text> |
| | | <Text className="text">1. 运营时间内可正常使用打车服务,超出运营时间无法使用打车功能</Text> |
| | | </View> |
| | | {/* <View className="desc-item"> |
| | | <View className="desc-item"> |
| | | <Text className="text">2. 各站点运营时间可能存在差异,站点运营情况请依照实际情况</Text> |
| | | </View> |
| | | <View className="desc-item"> |
| | | <Text className="text">3. 有问题可联系区域负责人,联系电话:4008208820</Text> |
| | | </View> */} |
| | | </View> |
| | | </View> |
| | | </View> |
| | | </View> |
| | |
| | | OrderHistoryGroup |
| | | } from "../../../robotaxi/api/order"; |
| | | import { _INVOICE_CHARGE_URL } from "../../../constants/api"; |
| | | import { |
| | | clearOrderTripStorage, |
| | | mapOrderStatusToTripStatus |
| | | } from "../../../utils/Robotaxi-public-method"; |
| | | // import { hasActiveTripOrderInStorage } from "../../../utils/order"; |
| | | import { hasActiveTripOrderInStorage } from '../../utils/order'; |
| | | |
| | | type OrderFilterKey = "all" | "pendingPay" | "completed" | "cancelled"; |
| | | |
| | |
| | | formatOrderStatus(item.status) == "进行中" || |
| | | formatOrderStatus(item.status) == "待支付" |
| | | ) { |
| | | Taro.setStorageSync("orderId", item.orderId || ""); |
| | | Taro.setStorageSync( |
| | | "tripStatus", |
| | | mapOrderStatusToTripStatus(item.status || 1) |
| | | ); |
| | | Taro.navigateTo({ |
| | | url: "/robotaxi/pages/begin-route/index?from=order" |
| | | url: "/robotaxi/pages/begin-route/index" |
| | | }); |
| | | return; |
| | | } |
| | |
| | | }; |
| | | |
| | | const againOrder = (item: any) => { |
| | | if (hasActiveTripOrderInStorage()) { |
| | | Taro.navigateTo({ |
| | | url: "/robotaxi/pages/begin-route/index?from=order" |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | Taro.setStorageSync("startSite", { |
| | | id: item.startPointId || "", |
| | | name: item.startPointName || "", |
| | |
| | | electronicAreaId: item.xpElectronicFenceAreaId || "", |
| | | time: item.endParkingTimeLimit || 0 |
| | | }); |
| | | clearOrderTripStorage(); |
| | | Taro.navigateTo({ |
| | | url: "/robotaxi/pages/begin-route/index?from=order" |
| | | url: "/robotaxi/pages/begin-route/index" |
| | | }); |
| | | }; |
| | | |
| | |
| | | } |
| | | if (status == 10) { |
| | | return "已完成"; |
| | | } |
| | | if (status == 11) { |
| | | return "退款中"; |
| | | } |
| | | if (status == 12) { |
| | | return "已退款"; |
| | | } |
| | | if (status == 13) { |
| | | return "退款失败"; |
| | | } |
| | | }; |
| | | |
| | |
| | | <View |
| | | onClick={e => { |
| | | e.stopPropagation(); |
| | | toOrderDetail(item); |
| | | againOrder(item); |
| | | }} |
| | | > |
| | | <View |
| | |
| | | </View> |
| | | )} |
| | | {/* 发票功能暂未上线,屏蔽入口 */} |
| | | { ["已完成", "待评价"].includes( |
| | | {false && ["已完成", "待评价"].includes( |
| | | formatOrderStatus(item.status) |
| | | ) && |
| | | item.invoiceStatus != 2 && |
| | |
| | | </View> |
| | | </View> |
| | | )} |
| | | {["已完成", "已取消", "待评价", "退款中", "已退款", "退款失败"].includes( |
| | | {["已完成", "已取消", "待评价"].includes( |
| | | formatOrderStatus(item.status) |
| | | ) && |
| | | canAgainOrder(item) && ( |
| | |
| | | |
| | | function PassengerNotice() { |
| | | const [content, setContent] = useState(''); |
| | | const [title, setTitle] = useState('乘车须知'); |
| | | // const [title, setTitle] = useState('乘车须知'); |
| | | const RIDE_NOTICE_TYPE = 5; // 乘车须知协议类型 |
| | | |
| | | const normalizeContent = (rawContent: string) => { |
| | |
| | | getLoginAgreementContentInfo(typeParam || '').then((res: any) => { |
| | | const target = resolveAgreementItem(res); |
| | | if (target) { |
| | | setTitle(String(target.name || '乘车须知')); |
| | | // setTitle(String(target.name || '乘车须知')); |
| | | setContent(normalizeContent(target.content || '')); |
| | | console.log('normalizeContent(target.content ||', normalizeContent(target.content || '')); |
| | | return; |
| | |
| | | |
| | | return ( |
| | | <View className='passenger-notice'> |
| | | <View className="passenger-notice-title">{title}</View> |
| | | {/* <View className="passenger-notice-title">{title}</View> */} |
| | | <View className="passenger-notice-content"> |
| | | <RichText nodes={content} /> |
| | | </View> |
| | |
| | | import { View, Text, Image } from '@tarojs/components'; |
| | | import { useCallback, useState } from 'react'; |
| | | import { getPayPwd, RobotaxiPaySettingStatus, setPayPwd } from '../../api/payment'; |
| | | import { getOrderActive } from '../../api/home'; |
| | | import { withRobotaxiWhitelist } from '../../../utils/whitelist'; |
| | | |
| | | import './index.less'; |
| | |
| | | }, []); |
| | | |
| | | /** |
| | | * 关闭免密支付前,先确认当前没有进行中的订单。 |
| | | */ |
| | | const ensureNoActiveOrderBeforeClose = useCallback(async () => { |
| | | try { |
| | | const activeOrder = await getOrderActive(); |
| | | if (activeOrder) { |
| | | Taro.showToast({ |
| | | title: '有进行中的订单,暂无法关闭服务', |
| | | icon: 'none' |
| | | }); |
| | | return false; |
| | | } |
| | | return true; |
| | | } catch (error) { |
| | | Taro.showToast({ |
| | | title: '查询订单状态失败,请稍后重试', |
| | | icon: 'none' |
| | | }); |
| | | return false; |
| | | } |
| | | }, []); |
| | | |
| | | /** |
| | | * 关闭微信免密支付。 |
| | | */ |
| | | const handleClosePayment = async () => { |
| | |
| | | return; |
| | | } |
| | | |
| | | setIsOperating(true); |
| | | const canClosePayment = await ensureNoActiveOrderBeforeClose(); |
| | | if (!canClosePayment) { |
| | | setIsOperating(false); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | setIsOperating(true); |
| | | await setPayPwd(RobotaxiPaySettingStatus.Disable); |
| | | Taro.showToast({ |
| | | title: '微信免密支付已关闭', |
| | |
| | | |
| | | const toolMenuList: ToolMenuItem[] = [ |
| | | { key: "service", label: "联系客服" }, |
| | | { key: "invoice", label: "开发票" }, // 暂未上线,屏蔽入口 |
| | | // { key: "invoice", label: "开发票" }, // 暂未上线,屏蔽入口 |
| | | { key: "payment", label: "支付设置" }, |
| | | { key: "favorite", label: "收藏站点" } |
| | | ]; |
| | |
| | | left: 50%; |
| | | top: 100%; |
| | | transform: translate(-50%, -50%); |
| | | <<<<<<< HEAD |
| | | width: 28px; |
| | | ======= |
| | | width: 18px; |
| | | >>>>>>> feature_v2.4.0 |
| | | height: 28px; |
| | | z-index: -1; |
| | | } |
| | |
| | | margin-top: 32px; |
| | | font-size: 32px; |
| | | font-weight: 500; |
| | | <<<<<<< HEAD |
| | | color: @gold-text; |
| | | ======= |
| | | color: #141E27; |
| | | >>>>>>> feature_v2.4.0 |
| | | display: flex; |
| | | align-items: center; |
| | | height: 120px; |
| | |
| | | align-items: center; |
| | | border-radius: 1000px; |
| | | box-sizing: border-box; |
| | | <<<<<<< HEAD |
| | | border: 4px solid #141E27; |
| | | font-size: 32px; |
| | | letter-spacing: normal; |
| | | color: #141E27; |
| | | font-weight: 760; |
| | | ======= |
| | | border: 2px solid #141E27; |
| | | font-size: 28px; |
| | | letter-spacing: normal; |
| | | color: #141E27; |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | |
| | | .no-confirm { |
| | |
| | | |
| | | // 浮窗底部的钻石指示图标,对齐 begin-route .callout-diamond-icon |
| | | .callout-diamond-icon { |
| | | <<<<<<< HEAD |
| | | width: 28px; |
| | | ======= |
| | | width: 18px; |
| | | >>>>>>> feature_v2.4.0 |
| | | height: 28px; |
| | | margin-top: -14px; |
| | | flex-shrink: 0; |
| | |
| | | } |
| | | |
| | | .end-icon { |
| | | <<<<<<< HEAD |
| | | background: @gold-light ; |
| | | } |
| | | ======= |
| | | background: @black-text-main; |
| | | } |
| | | >>>>>>> feature_v2.4.0 |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | |
| | | justify-content: center; |
| | | |
| | | .collect-icon { |
| | | <<<<<<< HEAD |
| | | width: 40px; |
| | | height: 40px; |
| | | margin-bottom: 4px; |
| | | ======= |
| | | width: 32px; |
| | | height: 32px; |
| | | margin-bottom: 8px; |
| | | >>>>>>> feature_v2.4.0 |
| | | } |
| | | |
| | | .history-distance { |
| | |
| | | ...normalized.slice(insertIndex), |
| | | ]; |
| | | } |
| | | return [...normalized]; |
| | | return [...normalized, RIDE_NOTICE_ITEM]; |
| | | }; |