import {useEffect, useMemo, useState} from "react"; import { Image, Button, Cell, CellGroup, Input, Space, ActionSheet, Popup, InputNumber, DatePicker, ConfigProvider } from '@nutui/nutui-react-taro' import {Location, ArrowRight} from '@nutui/icons-react-taro' import Taro, {useDidShow} from '@tarojs/taro' import {ShopGoods} from "@/api/shop/shopGoods/model"; import {getShopGoods} from "@/api/shop/shopGoods"; import {View, Text} from '@tarojs/components'; import {listShopUserAddress} from "@/api/shop/shopUserAddress"; import {ShopUserAddress} from "@/api/shop/shopUserAddress/model"; import './index.scss' import Gap from "@/components/Gap"; import {selectPayment} from "@/api/system/payment"; import {Payment} from "@/api/system/payment/model"; import {PaymentHandler, PaymentType, buildSingleGoodsOrder} from "@/utils/payment"; import OrderConfirmSkeleton from "@/components/OrderConfirmSkeleton"; import CouponList from "@/components/CouponList"; import {CouponCardProps} from "@/components/CouponCard"; import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon"; import {getGltTicketTemplateByGoodsId} from "@/api/glt/gltTicketTemplate"; import type {GltTicketTemplate} from "@/api/glt/gltTicketTemplate/model"; import { transformCouponData, calculateCouponDiscount, isCouponUsable, getCouponUnusableReason, sortCoupons, 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"; import { ensureLoggedIn, isLoggedIn } from '@/utils/auth' const OrderConfirm = () => { const [goods, setGoods] = useState(null); const [address, setAddress] = useState() const [payments, setPayments] = useState([]) const [payment, setPayment] = useState() const [isVisible, setIsVisible] = useState(false) const [quantity, setQuantity] = useState(1) const [orderRemark, setOrderRemark] = useState('') const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [payLoading, setPayLoading] = useState(false) // 配送时间(仅水票套票商品需要) // 当日截单时间:超过该时间下单,最早配送日顺延到次日(避免 21:00 下单仍显示“当天配送”) const DELIVERY_CUTOFF_HOUR = 21 const getMinSendDate = () => { const now = dayjs() const cutoff = now.hour(DELIVERY_CUTOFF_HOUR).minute(0).second(0).millisecond(0) const startOfToday = now.startOf('day') // >= 截单时间则最早只能选次日 return now.isSame(cutoff) || now.isAfter(cutoff) ? startOfToday.add(1, 'day') : startOfToday } const [sendTime, setSendTime] = useState(() => getMinSendDate().toDate()) const [sendTimePickerVisible, setSendTimePickerVisible] = useState(false) // 水票套票活动(若存在则按规则限制最小购买量等) const [ticketTemplate, setTicketTemplate] = useState(null) // InputNumber 主题配置 const customTheme = { nutuiInputnumberButtonWidth: '28px', nutuiInputnumberButtonHeight: '28px', nutuiInputnumberInputWidth: '40px', nutuiInputnumberInputHeight: '28px', nutuiInputnumberInputBorderRadius: '4px', nutuiInputnumberButtonBorderRadius: '4px', } // 优惠券相关状态 const [selectedCoupon, setSelectedCoupon] = useState(null) const [couponVisible, setCouponVisible] = useState(false) const [availableCoupons, setAvailableCoupons] = useState([]) const [couponLoading, setCouponLoading] = useState(false) // 门店选择:用于在下单页展示当前“已选门店”,并允许用户切换(写入 SelectedStore Storage) const [storePopupVisible, setStorePopupVisible] = useState(false) const [stores, setStores] = useState([]) const [storeLoading, setStoreLoading] = useState(false) const [selectedStore, setSelectedStore] = useState(getSelectedStoreFromStorage()) const router = Taro.getCurrentInstance().router; const goodsId = router?.params?.goodsId; // 页面级兜底:未登录直接进入下单页时,引导去注册/登录并回跳 useEffect(() => { if (!goodsId) { // 也可能是 orderData 模式;这里只做最小兜底 if (!ensureLoggedIn('/shop/orderConfirm/index')) return return } if (!ensureLoggedIn(`/shop/orderConfirm/index?goodsId=${goodsId}`)) return }, [goodsId]) const isTicketTemplateActive = !!ticketTemplate && ticketTemplate.enabled !== false && ticketTemplate.status !== 1 && ticketTemplate.deleted !== 1 const hasTicketTemplate = !!ticketTemplate // 套票活动最低购买量:优先取模板配置 const ticketMinBuyQty = (() => { const n = Number(ticketTemplate?.minBuyQty) return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1 })() 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) const startSend = Number(ticketTemplate?.startSendQty || 0) if (multiplier > 0) return Math.max(0, buyQty) * multiplier return Math.max(0, startSend) } const loadStores = async () => { if (storeLoading) return try { setStoreLoading(true) const list = await listShopStore() setStores((list || []).filter(s => s?.isDelete !== 1)) } catch (e) { console.error('获取门店列表失败:', e) setStores([]) Taro.showToast({title: '获取门店列表失败', icon: 'none'}) } finally { setStoreLoading(false) } } // @ts-ignore const openStorePopup = async () => { setStorePopupVisible(true) if (!stores.length) { await loadStores() } } // 计算商品总价 const getGoodsTotal = () => { if (!goods) return 0 const price = parseFloat(goods.price || '0') // const total = price * quantity // 🔍 详细日志,用于排查数值精度问题 // console.log('💵 商品总价计算:', { // goodsPrice: goods.price, // goodsPriceType: typeof goods.price, // parsedPrice: price, // quantity: quantity, // total: total, // totalFixed2: total.toFixed(2), // totalString: total.toString() // }) return price * quantity } // 计算优惠券折扣 const getCouponDiscount = () => { if (!selectedCoupon || !goods) return 0 const total = getGoodsTotal() return calculateCouponDiscount(selectedCoupon, total) } // 计算实付金额 const getFinalPrice = () => { const total = getGoodsTotal() const discount = getCouponDiscount() return Math.max(0, total - discount) } const handleSelect = (item: any) => { setPayment(payments.find(payment => payment.name === item.name)) setIsVisible(false) } // 处理数量变化 const handleQuantityChange = (value: string | number) => { const fallback = isTicketTemplateActive ? minBuyQty : 1 const newQuantity = typeof value === 'string' ? parseInt(value, 10) || fallback : value const finalQuantity = Math.max(fallback, Math.min(newQuantity, goods?.stock || 999)) setQuantity(finalQuantity) // 数量变化时,重新排序优惠券并检查当前选中的优惠券是否还可用 if (availableCoupons.length > 0) { const newTotal = parseFloat(goods?.price || '0') * finalQuantity const sortedCoupons = sortCoupons(availableCoupons, newTotal) const usableCoupons = filterUsableCoupons(sortedCoupons, newTotal) setAvailableCoupons(sortedCoupons) // 检查当前选中的优惠券是否还可用 if (selectedCoupon && !isCouponUsable(selectedCoupon, newTotal)) { setSelectedCoupon(null) Taro.showToast({ title: '当前优惠券不满足使用条件,已自动取消', icon: 'none' }) // 🎯 自动推荐新的最优优惠券 if (usableCoupons.length > 0) { const bestCoupon = usableCoupons[0] const discount = calculateCouponDiscount(bestCoupon, newTotal) if (discount > 0) { setSelectedCoupon(bestCoupon) Taro.showToast({ title: `已为您重新推荐最优优惠券,可省¥${discount.toFixed(2)}`, icon: 'success', duration: 3000 }) } } } else if (!selectedCoupon && usableCoupons.length > 0) { // 🔔 如果没有选中优惠券但有可用的,推荐最优的 const bestCoupon = usableCoupons[0] const discount = calculateCouponDiscount(bestCoupon, newTotal) if (discount > 0) { setSelectedCoupon(bestCoupon) Taro.showToast({ title: `已为您推荐最优优惠券,可省¥${discount.toFixed(2)}`, icon: 'success', duration: 3000 }) } } else if (selectedCoupon && usableCoupons.length > 0) { // 🔍 检查是否有更好的优惠券 const bestCoupon = usableCoupons[0] const currentDiscount = calculateCouponDiscount(selectedCoupon, newTotal) const bestDiscount = calculateCouponDiscount(bestCoupon, newTotal) // 如果有更好的优惠券(优惠超过0.01元) if (bestDiscount > currentDiscount + 0.01 && bestCoupon.id !== selectedCoupon.id) { Taro.showModal({ title: '发现更优惠的优惠券', content: `有更好的优惠券可用,额外节省¥${(bestDiscount - currentDiscount).toFixed(2)},是否更换?`, success: (res) => { if (res.confirm) { setSelectedCoupon(bestCoupon) Taro.showToast({ title: '优惠券已更换', icon: 'success' }) } } }) } } } } // 处理优惠券选择 const handleCouponSelect = (coupon: CouponCardProps) => { const total = getGoodsTotal() // 🔍 详细日志记录,用于排查问题 console.log('🎫 手动选择优惠券详细信息:', { coupon: { id: coupon.id, title: coupon.title, type: coupon.type, amount: coupon.amount, minAmount: coupon.minAmount, status: coupon.status }, orderInfo: { goodsPrice: goods?.price, quantity: quantity, total: total, totalFixed: total.toFixed(2) }, validation: { isUsable: isCouponUsable(coupon, total), discount: calculateCouponDiscount(coupon, total), reason: getCouponUnusableReason(coupon, total) } }) // 检查是否可用 if (!isCouponUsable(coupon, total)) { const reason = getCouponUnusableReason(coupon, total) // 🚨 记录手动选择失败的详细信息 console.error('🚨 手动选择优惠券失败:', { reason, coupon, total, minAmount: coupon.minAmount, comparison: { totalVsMinAmount: `${total} < ${coupon.minAmount}`, result: total < (coupon.minAmount || 0) } }) Taro.showToast({ title: reason || '优惠券不可用', icon: 'none' }) return } setSelectedCoupon(coupon) setCouponVisible(false) Taro.showToast({ title: '优惠券选择成功', icon: 'success' }) } // 取消选择优惠券 const handleCouponCancel = () => { setSelectedCoupon(null) Taro.showToast({ title: '已取消使用优惠券', icon: 'success' }) } // 加载用户优惠券 const loadUserCoupons = async (totalOverride?: number) => { try { setCouponLoading(true) // 使用新的API获取可用优惠券 const res = await getMyAvailableCoupons() if (res && res.length > 0) { // 转换数据格式 const transformedCoupons = res.map(transformCouponData) // 按优惠金额排序 const total = totalOverride ?? getGoodsTotal() const sortedCoupons = sortCoupons(transformedCoupons, total) const usableCoupons = filterUsableCoupons(sortedCoupons, total) setAvailableCoupons(sortedCoupons) // 🎯 智能推荐:自动应用最优惠的可用优惠券 if (usableCoupons.length > 0 && !selectedCoupon) { const bestCoupon = usableCoupons[0] // 已经按优惠金额排序,第一个就是最优的 const discount = calculateCouponDiscount(bestCoupon, total) // 🔍 详细日志记录自动推荐的信息 console.log('🤖 自动推荐优惠券详细信息:', { coupon: { id: bestCoupon.id, title: bestCoupon.title, type: bestCoupon.type, amount: bestCoupon.amount, minAmount: bestCoupon.minAmount, status: bestCoupon.status }, orderInfo: { goodsPrice: goods?.price, quantity: quantity, total: total, totalFixed: total.toFixed(2) }, validation: { isUsable: isCouponUsable(bestCoupon, total), discount: discount, reason: getCouponUnusableReason(bestCoupon, total) } }) if (discount > 0) { setSelectedCoupon(bestCoupon) // 显示智能推荐提示 Taro.showToast({ title: `已为您推荐最优优惠券,可省¥${discount.toFixed(2)}`, icon: 'success', duration: 3000 }) } } // 🔔 优惠券提示:如果有可用优惠券,显示提示 if (usableCoupons.length > 0) { console.log(`发现${usableCoupons.length}张可用优惠券,已为您推荐最优惠券`) } console.log('加载优惠券成功:', { originalData: res, transformedData: transformedCoupons, sortedData: sortedCoupons, usableCoupons: usableCoupons, recommendedCoupon: usableCoupons[0] || null }) } else { setAvailableCoupons([]) console.log('暂无可用优惠券') } } catch (error) { console.error('加载优惠券失败:', error) setAvailableCoupons([]) Taro.showToast({ title: '加载优惠券失败', icon: 'none' }) } finally { setCouponLoading(false) } } /** * 统一支付入口 */ const onPay = async (goods: ShopGoods) => { let skipFinallyResetPayLoading = false try { setPayLoading(true) // 基础校验 if (!address) { Taro.showToast({ title: '请选择收货地址', icon: 'error' }) return; } if (!payment) { Taro.showToast({ title: '请选择支付方式', icon: 'error' }) return; } // 水票套票商品:保存配送时间到 ShopOrder.sendStartTime if (hasTicketTemplate && !sendTime) { Taro.showToast({ title: '请选择配送时间', icon: 'none' }) return } if (hasTicketTemplate) { const min = getMinSendDate() if (dayjs(sendTime).isBefore(min, 'day')) { setSendTime(min.toDate()) Taro.showToast({ title: `已过当日${DELIVERY_CUTOFF_HOUR}点截单,最早配送:${min.format('YYYY-MM-DD')}`, icon: 'none' }) return } } // 水票套票活动:最小购买量校验 if (isTicketTemplateActive && quantity < minBuyQty) { Taro.showToast({ title: `最低购买量:${minBuyQty}桶`, icon: 'none' }) return } // 库存校验 if (goods.stock !== undefined && quantity > goods.stock) { Taro.showToast({ title: '商品库存不足', icon: 'error' }) return; } // 优惠券校验 if (selectedCoupon) { const total = getGoodsTotal() if (!isCouponUsable(selectedCoupon, total)) { const reason = getCouponUnusableReason(selectedCoupon, total) Taro.showToast({ title: reason || '优惠券不可用', icon: 'error' }) return; } } else { // 🔔 支付前最后一次检查:提醒用户是否有可用优惠券 const total = getGoodsTotal() const usableCoupons = filterUsableCoupons(availableCoupons, total) if (usableCoupons.length > 0) { const bestCoupon = usableCoupons[0] const discount = calculateCouponDiscount(bestCoupon, total) if (discount > 0) { // 用模态框提醒用户 const confirmResult = await new Promise((resolve) => { Taro.showModal({ title: '发现可用优惠券', content: `您有优惠券可使用,可省¥${discount.toFixed(2)},是否使用?`, success: (res) => resolve(res.confirm), fail: () => resolve(false) }) }) if (confirmResult) { setSelectedCoupon(bestCoupon) // 🔄 使用优惠券后需要重新构建订单数据,这里直接递归调用支付函数 // 但要确保传递最新的优惠券信息 const currentPaymentType = payment.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT; const updatedOrderData = buildSingleGoodsOrder( goods.goodsId!, quantity, address.id, { 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) } ); console.log('🎯 使用推荐优惠券的订单数据:', updatedOrderData); // 执行支付 await PaymentHandler.pay(updatedOrderData, currentPaymentType); return; // 提前返回,避免重复执行支付 } else { // 用户选择不使用优惠券,继续支付 } } } } // 构建订单数据 const orderData = buildSingleGoodsOrder( goods.goodsId!, quantity, address.id, { 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 } ); // 根据支付方式选择支付类型 const paymentType = payment.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT; // 🔍 支付前的详细信息记录 console.log('💰 开始支付 - 详细信息:', { orderData, paymentType, selectedCoupon: selectedCoupon ? { id: selectedCoupon.id, title: selectedCoupon.title, type: selectedCoupon.type, amount: selectedCoupon.amount, minAmount: selectedCoupon.minAmount, discount: getCouponDiscount() } : null, priceCalculation: { goodsPrice: goods?.price, quantity: quantity, goodsTotal: getGoodsTotal(), couponDiscount: getCouponDiscount(), finalPrice: getFinalPrice() }, couponValidation: selectedCoupon ? { isUsable: isCouponUsable(selectedCoupon, getGoodsTotal()), reason: getCouponUnusableReason(selectedCoupon, getGoodsTotal()) } : null }); // 执行支付 - 移除这里的成功提示,让PaymentHandler统一处理 await PaymentHandler.pay(orderData, paymentType); // ✅ 移除双重成功提示 - PaymentHandler会处理成功提示 // Taro.showToast({ // title: '支付成功', // icon: 'success' // }) } catch (error: any) { const message = String(error?.message || '') const isUserCancelPay = message.includes('用户取消支付') || message.includes('取消支付') || message.toLowerCase().includes('requestpayment:fail cancel') || message.toLowerCase().includes('cancel') // 用户取消支付:跳转到待付款列表,方便继续支付 if (isUserCancelPay) { skipFinallyResetPayLoading = true setPayLoading(false) const url = '/user/order/order?statusFilter=0' try { await Taro.redirectTo({ url }) } catch (_e) { try { await Taro.navigateTo({ url }) } catch (_e2) { // ignore } } return } const isOutOfDeliveryRange = message.includes('不在配送范围') || message.includes('配送范围') || message.includes('电子围栏') || message.includes('围栏') // “配送范围”类错误给出更友好的解释,并提供快捷入口去更换收货地址 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 { if (!skipFinallyResetPayLoading) { setPayLoading(false) } } }; // 统一的数据加载函数 const loadAllData = async () => { // 未登录时不发起接口请求;页面会被登录兜底逻辑引导走注册/登录页 if (!isLoggedIn()) return try { setLoading(true) setError('') // 分别加载数据,避免类型推断问题 let goodsRes: ShopGoods | null = null if (goodsId) { goodsRes = await getShopGoods(Number(goodsId)) } const [addressRes, paymentRes] = await Promise.all([ listShopUserAddress({isDefault: true}), selectPayment({}) ]) // 设置商品信息 // 查询当前商品是否存在水票套票活动(失败/无数据时不影响正常下单) let tpl: GltTicketTemplate | null = null if (goodsId) { try { tpl = await getGltTicketTemplateByGoodsId(Number(goodsId)) } catch (e) { tpl = null } } const tplActive = !!tpl && tpl.enabled !== false && tpl.status !== 1 && tpl.deleted !== 1 const tplMinBuyQty = (() => { const n = Number(tpl?.minBuyQty) return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1 })() // 设置商品信息(若存在套票模板,则默认 canBuyNumber 使用模板最小购买量) if (goodsRes) { const patchedGoods: ShopGoods = { ...goodsRes } if (tplActive && ((patchedGoods.canBuyNumber ?? 0) === 0)) { patchedGoods.canBuyNumber = tplMinBuyQty } setGoods(patchedGoods) // 设置默认购买数量:优先使用 canBuyNumber,否则使用 1 const initQty = (patchedGoods.canBuyNumber ?? 0) > 0 ? (patchedGoods.canBuyNumber as number) : 1 setQuantity(initQty) } setTicketTemplate(tpl) // 设置默认收货地址 if (addressRes && addressRes.length > 0) { setAddress(addressRes[0]) } // 设置支付方式 if (paymentRes && paymentRes.length > 0) { setPayments(paymentRes.map((d) => ({ type: d.type, name: d.name }))) setPayment(paymentRes[0]) } // 加载优惠券:使用“初始数量”对应的总价做推荐,避免默认数量变化导致推荐不准 if (goodsRes) { const initQty = (() => { const n = Number(goodsRes?.canBuyNumber) if (Number.isFinite(n) && n > 0) return Math.floor(n) if (tplActive) return tplMinBuyQty return 1 })() const total = parseFloat(goodsRes.price || '0') * initQty await loadUserCoupons(total) } } catch (err) { console.error('加载数据失败:', err) setError('加载数据失败,请重试') } finally { setLoading(false) } } useDidShow(() => { // 返回/切换到该页面时,刷新一下当前已选门店 if (!isLoggedIn()) return setSelectedStore(getSelectedStoreFromStorage()) loadAllData() }) useEffect(() => { // 切换商品时重置配送时间,避免沿用上一次选择 if (!isLoggedIn()) return setSendTime(getMinSendDate().toDate()) setSendTimePickerVisible(false) loadAllData() }, [goodsId]); // 重新加载数据 const handleRetry = () => { loadAllData() } // 错误状态 if (error) { return ( {error} ) } // 加载状态 if (loading || !goods) { return } return (
{ address && ( Taro.navigateTo({url: '/user/address/index'})}> 送至 {address.province} {address.city} {address.region} {address.address} {address.name} {address.phone} ) } {!address && ( Taro.navigateTo({url: '/user/address/index'})}> 添加收货地址 )} {hasTicketTemplate && ( {sendTimeText} )} onClick={() => { // 若页面停留跨过截单时间,打开选择器前再校正一次最早可选日期 const min = getMinSendDate() if (dayjs(sendTime).isBefore(min, 'day')) { setSendTime(min.toDate()) } setSendTimePickerVisible(true) }} /> )} {/**/} {/* */} {/* */} {/* 门店*/} {/* */} {/* )}*/} {/* extra={(*/} {/* */} {/* */} {/* {selectedStore?.name || '请选择门店'}*/} {/* */} {/* */} {/* */} {/* )}*/} {/* onClick={openStorePopup}*/} {/* />*/} {/**/} {goods.name} {/*80g/袋*/} ¥{goods.price} {goods.stock !== undefined && ( 库存 {goods.stock} 件 )} {isTicketTemplateActive && ( 最低购买量:{minBuyQty}桶 赠送水票:{getGiftTicketQty(quantity)}张 )} {payment?.name} )} onClick={() => setIsVisible(true)} /> ¥{getGoodsTotal().toFixed(2)}} /> {selectedCoupon ? `-¥${getCouponDiscount().toFixed(2)}` : '暂未使用'} {(() => { const usableCoupons = filterUsableCoupons(availableCoupons, getGoodsTotal()) if (usableCoupons.length > 0 && !selectedCoupon) { return ( {usableCoupons.length}张可用 ) } else if (usableCoupons.length > 0) { return ( 已选择 ) } else { return } })() } )} onClick={() => setCouponVisible(true)} /> 已优惠 ¥{getCouponDiscount().toFixed(2)} 实付 ¥{getFinalPrice().toFixed(2)} )}/> setOrderRemark(value)} maxLength={100} /> )}/> {ticketTemplate && ( 注意事项: 最低起送量≥{ticketTemplate.startSendQty}桶; 配送范围要在电子围栏内; 上楼费暂不收取,收费另行通知。
)}/> )} {/* 支付方式选择 */} setIsVisible(false)} /> {/* 优惠券选择弹窗 */} setCouponVisible(false)} style={{height: '60vh'}} > 选择优惠券 {couponLoading ? ( 加载优惠券中... ) : ( <> {selectedCoupon && ( 当前使用 {selectedCoupon.title} -¥{calculateCouponDiscount(selectedCoupon, getGoodsTotal()).toFixed(2)} )} {(() => { const total = getGoodsTotal() const usableCoupons = filterUsableCoupons(availableCoupons, total) const unusableCoupons = filterUnusableCoupons(availableCoupons, total) return ( <> {unusableCoupons.length > 0 && ( ({ ...coupon, status: 2 as const }))} layout="vertical" showEmpty={false} /> )} ) })()} )} {/* 门店选择弹窗 */} setStorePopupVisible(false)} > 选择门店 setStorePopupVisible(false)} > 关闭 {storeLoading ? ( 加载中... ) : ( {stores.map((s) => { const isActive = !!selectedStore?.id && selectedStore.id === s.id return ( {s.name || `门店${s.id}`}} description={s.address || ''} onClick={async () => { let storeToSave: ShopStore = s if (s?.id) { try { const full = await getShopStore(s.id) if (full) storeToSave = full } catch (_e) { // keep base item } } setSelectedStore(storeToSave) saveSelectedStoreToStorage(storeToSave) setStorePopupVisible(false) Taro.showToast({title: '门店已切换', icon: 'success'}) }} /> ) })} {!stores.length && ( 暂无门店数据} /> )} )} 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) }} />
实付金额: ¥{getFinalPrice().toFixed(2)} {selectedCoupon && ( 已优惠 ¥{getCouponDiscount().toFixed(2)} )}
); }; export default OrderConfirm;