diff --git a/src/api/shop/shopOrder/model/index.ts b/src/api/shop/shopOrder/model/index.ts index c28e906..ee1320a 100644 --- a/src/api/shop/shopOrder/model/index.ts +++ b/src/api/shop/shopOrder/model/index.ts @@ -193,6 +193,8 @@ export interface OrderCreateRequest { couponId?: number; // 备注 comments?: string; + // 配送开始时间(用于预约/配送时间) + sendStartTime?: string; // 配送方式 0快递 1自提 deliveryType?: number; // 自提店铺ID @@ -233,6 +235,8 @@ export interface OrderCreateRequest { couponId?: number; // 备注 comments?: string; + // 配送开始时间(用于预约/配送时间) + sendStartTime?: string; // 配送方式 0快递 1自提 deliveryType?: number; // 自提店铺ID diff --git a/src/shop/orderConfirm/index.tsx b/src/shop/orderConfirm/index.tsx index 1facf57..6ac79c9 100644 --- a/src/shop/orderConfirm/index.tsx +++ b/src/shop/orderConfirm/index.tsx @@ -1,4 +1,4 @@ -import {useEffect, useState} from "react"; +import {useEffect, useMemo, useState} from "react"; import { Image, Button, @@ -9,6 +9,7 @@ import { ActionSheet, Popup, InputNumber, + DatePicker, ConfigProvider } from '@nutui/nutui-react-taro' import {Location, ArrowRight} from '@nutui/icons-react-taro' @@ -38,6 +39,7 @@ import { filterUsableCoupons, filterUnusableCoupons } from "@/utils/couponUtils"; +import dayjs from 'dayjs' import type {ShopStore} from "@/api/shop/shopStore/model"; import {getShopStore, listShopStore} from "@/api/shop/shopStore"; import {getSelectedStoreFromStorage, saveSelectedStoreToStorage} from "@/utils/storeSelection"; @@ -54,6 +56,9 @@ const OrderConfirm = () => { const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [payLoading, setPayLoading] = useState(false) + // 配送时间(仅水票套票商品需要) + const [sendTime, setSendTime] = useState(() => dayjs().startOf('day').toDate()) + const [sendTimePickerVisible, setSendTimePickerVisible] = useState(false) // 水票套票活动(若存在则按规则限制最小购买量等) const [ticketTemplate, setTicketTemplate] = useState(null) @@ -88,6 +93,7 @@ const OrderConfirm = () => { ticketTemplate.enabled !== false && ticketTemplate.status !== 1 && ticketTemplate.deleted !== 1 + const hasTicketTemplate = !!ticketTemplate // 套票活动最低购买量:优先取模板配置 const ticketMinBuyQty = (() => { @@ -96,6 +102,10 @@ const OrderConfirm = () => { })() const minBuyQty = isTicketTemplateActive ? ticketMinBuyQty : 1 + const sendTimeText = useMemo(() => { + return dayjs(sendTime).format('YYYY-MM-DD') + }, [sendTime]) + const getGiftTicketQty = (buyQty: number) => { if (!isTicketTemplateActive) return 0 const multiplier = Number(ticketTemplate?.giftMultiplier || 0) @@ -420,6 +430,12 @@ const OrderConfirm = () => { return; } + // 水票套票商品:保存配送时间到 ShopOrder.sendStartTime + if (hasTicketTemplate && !sendTime) { + Taro.showToast({ title: '请选择配送时间', icon: 'none' }) + return + } + // 水票套票活动:最小购买量校验 if (isTicketTemplateActive && quantity < minBuyQty) { Taro.showToast({ @@ -482,6 +498,9 @@ const OrderConfirm = () => { comments: goods.name, deliveryType: 0, buyerRemarks: orderRemark, + sendStartTime: hasTicketTemplate + ? dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss') + : undefined, couponId: parseInt(String(bestCoupon.id), 10) } ); @@ -507,6 +526,9 @@ const OrderConfirm = () => { comments: '桂乐淘', deliveryType: 0, buyerRemarks: orderRemark, + sendStartTime: hasTicketTemplate + ? dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss') + : undefined, // 🔧 确保 couponId 是正确的数字类型,且不传递 undefined couponId: selectedCoupon ? parseInt(String(selectedCoupon.id), 10) : undefined } @@ -549,31 +571,35 @@ const OrderConfirm = () => { // icon: 'success' // }) } catch (error: any) { - // return navTo('/user/order/order?statusFilter=0', true) - // console.error('支付失败:', error) + const message = String(error?.message || '') + const isOutOfDeliveryRange = + message.includes('不在配送范围') || + message.includes('配送范围') || + message.includes('电子围栏') || + message.includes('围栏') - // 只处理PaymentHandler未处理的错误 - // if (!error.handled) { - // let errorMessage = '支付失败,请重试'; - // - // // 根据错误类型提供具体提示 - // if (error.message?.includes('余额不足')) { - // errorMessage = '账户余额不足,请充值后重试'; - // } else if (error.message?.includes('优惠券')) { - // errorMessage = '优惠券使用失败,请重新选择'; - // } else if (error.message?.includes('库存')) { - // errorMessage = '商品库存不足,请减少购买数量'; - // } else if (error.message?.includes('地址')) { - // errorMessage = '收货地址信息有误,请重新选择'; - // } else if (error.message) { - // errorMessage = error.message; - // } - // Taro.showToast({ - // title: errorMessage, - // icon: 'error' - // }) - // console.log('跳去未付款的订单列表页面') - // } + // “配送范围”类错误给出更友好的解释,并提供快捷入口去更换收货地址 + if (isOutOfDeliveryRange) { + try { + const res = await Taro.showModal({ + title: '暂不支持配送', + content: '当前收货地址超出配送范围。您可以更换收货地址后再下单,或联系门店确认配送范围。', + confirmText: '更换地址', + cancelText: '我知道了' + }) + if (res?.confirm) { + Taro.navigateTo({ url: '/user/address/index' }) + } + } catch (_e) { + // ignore + } + return + } + + // 兜底:仅在 PaymentHandler 未弹过提示时再提示一次 + if (!error?.handled) { + Taro.showToast({ title: message || '支付失败,请重试', icon: 'none' }) + } } finally { setPayLoading(false) } @@ -673,6 +699,9 @@ const OrderConfirm = () => { }) useEffect(() => { + // 切换商品时重置配送时间,避免沿用上一次选择 + setSendTime(dayjs().startOf('day').toDate()) + setSendTimePickerVisible(false) loadAllData() }, [goodsId]); @@ -731,6 +760,21 @@ const OrderConfirm = () => { )} + {hasTicketTemplate && ( + + + {sendTimeText} + + + )} + onClick={() => setSendTimePickerVisible(true)} + /> + + )} + {/**/} {/* { - 注意事项: - 1.最低起送量≥20桶; - 2.配送范围要在电子围栏内; - 3.上楼费暂不收取,收费另行通知。 + 注意事项: + 最低起送量≥{ticketTemplate.minBuyQty}桶; + 配送范围要在电子围栏内; + 上楼费暂不收取,收费另行通知。 )}/> @@ -1023,6 +1067,23 @@ const OrderConfirm = () => { + setSendTimePickerVisible(false)} + onCancel={() => setSendTimePickerVisible(false)} + onConfirm={(_options, selectedValue) => { + const [y, m, d] = (selectedValue || []).map(v => Number(v)) + const next = new Date(y, (m || 1) - 1, d || 1, 0, 0, 0) + setSendTime(next) + setSendTimePickerVisible(false) + }} + /> +
diff --git a/src/user/ticket/use.tsx b/src/user/ticket/use.tsx index f26f4d2..2c093ae 100644 --- a/src/user/ticket/use.tsx +++ b/src/user/ticket/use.tsx @@ -6,7 +6,6 @@ import { Cell, CellGroup, ConfigProvider, - DatePicker, Input, InputNumber, Popup, @@ -41,8 +40,7 @@ const OrderConfirm = () => { const [quantity, setQuantity] = useState(MIN_START_QTY) const [orderRemark, setOrderRemark] = useState('') // Delivery date only (no hour/min selection). - const [sendTime, setSendTime] = useState(() => dayjs().startOf('day').toDate()) - const [sendTimePickerVisible, setSendTimePickerVisible] = useState(false) + const [sendTime] = useState(() => dayjs().startOf('day').toDate()) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [submitLoading, setSubmitLoading] = useState(false) @@ -705,10 +703,8 @@ const OrderConfirm = () => { extra={( {sendTimeText} - )} - onClick={() => setSendTimePickerVisible(true)} /> @@ -883,23 +879,6 @@ const OrderConfirm = () => { - setSendTimePickerVisible(false)} - onCancel={() => setSendTimePickerVisible(false)} - onConfirm={(_options, selectedValue) => { - const [y, m, d] = (selectedValue || []).map(v => Number(v)) - const next = new Date(y, (m || 1) - 1, d || 1, 0, 0, 0) - setSendTime(next) - setSendTimePickerVisible(false) - }} - /> -
diff --git a/src/utils/payment.ts b/src/utils/payment.ts index 63c51fb..9b81d29 100644 --- a/src/utils/payment.ts +++ b/src/utils/payment.ts @@ -372,6 +372,17 @@ export class PaymentHandler { const message = error.message; + // 配送范围/电子围栏相关错误(优先于“地址信息有误”的兜底) + if ( + message.includes('不在配送范围') || + message.includes('配送范围') || + message.includes('电子围栏') || + message.includes('围栏') + ) { + // Toast 文案尽量短(小程序 showToast 标题长度有限),更详细的引导可在业务页面用 Modal 呈现。 + return '暂不支持配送'; + } + // 余额相关错误 if (message.includes('余额不足') || message.includes('balance')) { return '账户余额不足,请充值后重试'; @@ -453,6 +464,7 @@ export function buildSingleGoodsOrder( skuId?: number; specInfo?: string; buyerRemarks?: string; + sendStartTime?: string; } ): OrderCreateRequest { return { @@ -467,6 +479,7 @@ export function buildSingleGoodsOrder( addressId, payType: PaymentType.WECHAT, // 默认微信支付,会被PaymentHandler覆盖 comments: options?.buyerRemarks || options?.comments || '', + sendStartTime: options?.sendStartTime, deliveryType: options?.deliveryType || 0, couponId: options?.couponId, selfTakeMerchantId: options?.selfTakeMerchantId