import React, { useState, useEffect, useMemo } from 'react' import { View, Text, Image, ScrollView, Input } from '@tarojs/components' import Taro, { useRouter, useDidShow } from '@tarojs/taro' import { useCartContext } from '@/contexts/CartContext' import { useAddress } from '@/hooks/useAddress' import { useCoupon } from '@/hooks/useCoupon' import { useScrollHeight } from '@/hooks/useScrollHeight' import { getMyAvailableCoupons } from '@/api/shop/shopUserCoupon' import AddressCard from '@/components/business/AddressCard' import CouponCard from '@/components/business/CouponCard' import { createOrder, type WxPayResult } from '@/api/shop/shopOrder' import type { ShopOrder } from '@/api/shop/shopOrder/model' import { listShopGoods } from '@/api/shop/shopGoods' import type { ShopGoods } from '@/api/shop/shopGoods/model' import type { ShopUserCoupon } from '@/api/shop/shopUserCoupon/model' import type { OrderGoodsItem, OrderCreateRequest } from '@/api/shop/shopOrder/model' // 满减门槛配置 const THRESHOLDS = [ { target: 99, discount: 10 }, { target: 199, discount: 20 }, { target: 299, discount: 30 }, { target: 499, discount: 50 }, ] // 解析可能为JSON字符串的规格值 const parseSpecValue = (value: string | undefined): string => { if (!value) return '' const trimmed = value.trim() if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) { try { return JSON.parse(trimmed) } catch { return trimmed.slice(1, -1) } } return trimmed } // 格式化规格信息,展示规格名称和值 const formatSpecInfo = (item: any): { specList: Array<{ name: string; value: string }>; specText: string } => { const specList: Array<{ name: string; value: string }> = [] // 防御性检查:确保 goodsSpecs 是数组 const goodsSpecs = item.product?.goodsSpecs if (Array.isArray(goodsSpecs) && goodsSpecs.length > 0) { const specGroups = new Map() goodsSpecs.forEach((spec: any) => { const specId = spec?.specId || 0 if (!specGroups.has(specId)) { specGroups.set(specId, { name: spec?.specName || `规格${specGroups.size + 1}`, values: [] }) } const group = specGroups.get(specId) if (group) { group.values.push(parseSpecValue(spec?.specValue)) } }) // 2. 如果选择了 SKU,根据 sku.sku 匹配规格值 const skuStr = item.sku?.sku if (typeof skuStr === 'string' && skuStr) { const selectedValues = skuStr.split('|').map((v: string) => parseSpecValue(v.trim())).sort() specGroups.forEach((group) => { // 防御性检查:确保 values 是数组 if (!Array.isArray(group.values)) return // 查找匹配的规格值 const matchedValue = group.values.find(v => selectedValues.includes(v)) if (matchedValue) { specList.push({ name: group.name, value: matchedValue }) } }) } // 如果没有 SKU 但有 skuSpec 字符串 if (specList.length === 0 && item.skuSpec) { const values = String(item.skuSpec).split(',').map(v => v.trim()).filter(Boolean) let idx = 0 specGroups.forEach((group) => { if (values[idx]) { specList.push({ name: group.name, value: values[idx] }) } idx++ }) } } // 3. 如果没有完整的规格定义,但有 skuSpec 字符串 if (specList.length === 0 && item.skuSpec) { const parts = String(item.skuSpec).split(/[,,]/).map(v => v.trim()).filter(Boolean) if (parts.length > 0) { specList.push({ name: '规格', value: parts.join(', ') }) } } return { specList, specText: specList.map(s => `${s.name}: ${s.value}`).join(' | ') || item.skuSpec || '' } } definePageConfig({ navigationBarTitleText: '确认订单', }) const CheckoutPage: React.FC = () => { const router = useRouter() const fromBuyNow = router.params.from === 'buyNow' const { selectedItems, refresh: refreshCart, removeSelected } = useCartContext() const { defaultAddress, loadAddresses } = useAddress() const maxPopupHeight = useScrollHeight(0) // 底部提交栏高度约 60px,动态计算 ScrollView 可用高度 const scrollHeight = useScrollHeight(44) const [coupons, setCoupons] = useState([]) const [selectedCoupon, setSelectedCoupon] = useState(null) const [couponVisible, setCouponVisible] = useState(false) const [remarks, setRemarks] = useState('') const [submitting, setSubmitting] = useState(false) const [rushBuyProducts, setRushBuyProducts] = useState([]) // 支付方式相关状态 // payType: 1=微信支付, 0=货到付款(默认) const [payType, setPayType] = useState(0) // 从本地存储获取立即购买的数据 const buyNowItems = useMemo(() => { if (fromBuyNow) { const data = Taro.getStorageSync('buy_now') if (data) { try { const parsed = JSON.parse(data) return Array.isArray(parsed) ? parsed : [] } catch { /* ignore */ } } } return null }, [fromBuyNow]) // 使用的商品列表 const items = buyNowItems || selectedItems // 计算金额 const goodsPrice = useMemo(() => { if (!items || items.length === 0) return 0 return items.reduce((sum, item) => { return sum + Number(item.skuPrice || item.sku?.price || item.product?.salePrice || item.product?.price || 0) * (item.quantity || item.num || 1) }, 0) }, [buyNowItems, selectedItems]) const couponDiscount = selectedCoupon?.reducePrice ? Number(selectedCoupon.reducePrice) : 0 const totalPrice = Math.max(0, goodsPrice - couponDiscount).toFixed(2) // 计算凑单信息 const rushBuyInfo = useMemo(() => { if (goodsPrice === 0) return null // 找出最近的满减门槛 for (const threshold of THRESHOLDS) { if (goodsPrice < threshold.target) { const needAmount = threshold.target - goodsPrice // 只在需要凑的金额在 5-80 元之间时显示推荐 if (needAmount <= 80) { return { target: threshold.target, discount: threshold.discount, needAmount: needAmount.toFixed(2) } } } } return null }, [goodsPrice]) // 加载优惠券和凑单商品 useEffect(() => { loadCoupons() loadRushBuyProducts() }, []) // 每次页面显示时刷新地址(从地址列表选择后返回) useDidShow(() => { loadAddresses() }) const loadCoupons = async () => { try { const data = await getMyAvailableCoupons() setCoupons(data || []) } catch (e) { console.error('加载优惠券失败:', e) // 不设置任何内容,让页面继续显示 } } // 加载凑单推荐商品 const loadRushBuyProducts = async () => { try { // 获取热销/推荐商品 const data = await listShopGoods({ page: 1, limit: 10, isShow: 1 }) // 防御性检查:确保返回的是数组 const safeData = Array.isArray(data) ? data : [] if (safeData.length > 0) { // 过滤掉已在购物车的商品 const currentItems = buyNowItems || selectedItems const cartGoodsIds = Array.isArray(currentItems) ? currentItems.map(item => item.goodsId) : [] const filtered = safeData.filter(p => !cartGoodsIds.includes(p.goodsId)) setRushBuyProducts(filtered.slice(0, 4)) } } catch (e) { console.error('加载凑单商品失败:', e) // 不设置任何内容,让页面继续显示 } } // 加载凑单推荐商品 const handleSelectAddress = () => { Taro.navigateTo({ url: '/pages/user/address-list?from=checkout', events: { refresh: () => loadAddresses() } }) } // 选择优惠券 const handleSelectCoupon = () => { setCouponVisible(true) } const handleCouponConfirm = (coupon: ShopUserCoupon | null) => { setSelectedCoupon(coupon) setCouponVisible(false) } // 提交订单 const handleSubmit = async () => { if (!defaultAddress) { Taro.showToast({ title: '请选择收货地址', icon: 'none' }) return } if (items.length === 0) { Taro.showToast({ title: '请选择商品', icon: 'none' }) return } setSubmitting(true) try { // 构建商品列表 const goodsItems: OrderGoodsItem[] = items.map(item => ({ goodsId: item.goodsId, skuId: item.skuId && item.skuId > 0 ? item.skuId : undefined, quantity: item.quantity || item.num || 1, specInfo: item.skuSpec || item.sku?.sku || item.product?.specName, })) // 创建订单 const orderParams: OrderCreateRequest = { goodsItems, addressId: defaultAddress.id, payType, couponId: selectedCoupon?.id ? Number(selectedCoupon.id) : undefined, comments: remarks, deliveryType: 0, // 快递配送 } const res = await createOrder(orderParams) // 清理购物车数据(无论哪种支付方式都需清理) if (fromBuyNow) { Taro.removeStorageSync('buy_now') } else { await removeSelected() } // 根据支付方式处理 if (payType === 1) { // 微信支付 - res 是 WxPayResult await handleWxPay(res as WxPayResult) } // 货到付款(payType=0)无需在线支付,直接下单成功 Taro.showToast({ title: payType === 0 ? '下单成功' : '支付成功', icon: 'success' }) setTimeout(() => { Taro.switchTab({ url: '/pages/order/list' }) }, 1500) } catch (err: any) { Taro.showToast({ title: err.message || '提交失败', icon: 'none' }) } finally { setSubmitting(false) } } // 微信支付 const handleWxPay = async (payData: WxPayResult & { paid?: string }) => { // 后端返回已支付(回调丢失自动修复场景) if (payData.paid === 'true') { return // 直接视为支付成功 } return new Promise((resolve, reject) => { Taro.requestPayment({ timeStamp: payData.timeStamp, nonceStr: payData.nonceStr, package: payData.package, signType: payData.signType, paySign: payData.paySign, success: () => resolve(), fail: (err) => reject(new Error('支付取消')) }) }) } // 安全的数字格式化 const formatPrice = (price: number | string): string => { const num = typeof price === 'string' ? parseFloat(price) : price if (isNaN(num)) return '0.00' return num.toFixed(2) } return ( {/* 地址 */} {defaultAddress ? ( ) : ( 📍 请选择收货地址 )} {/* 商品列表 */} 商品 ({(items || []).length}) {items && items.length > 0 ? ( items.map((item, idx) => { const { specList } = formatSpecInfo(item) return ( {item.goodsName || item.product?.name || item.product?.goodsName} {/* 多规格属性展示 */} {specList.length > 0 ? ( {specList.map((spec, specIdx) => ( {spec.name}: {spec.value} ))} ) : item.skuSpec ? ( {item.skuSpec} ) : null} x{item.quantity || item.num || 1} ¥{item.skuPrice || item.sku?.salePrice || item.sku?.price || item.product?.salePrice || item.product?.price || '0'} ) }) ) : ( 购物车为空,请先添加商品 Taro.switchTab({ url: '/pages/shop/index' })} > 去购物 )} {/* 优惠券 */} 优惠券 {selectedCoupon ? ( -{selectedCoupon.reducePrice}元 ) : coupons.length > 0 ? ( 有{coupons.length}张可用 ) : ( 暂无可用 )} {/* 备注 */} 订单备注 选填 setRemarks(e.detail.value)} maxlength={200} /> {/* 金额明细 */} 商品合计 ¥{formatPrice(goodsPrice)} 运费 免运费 {couponDiscount > 0 && ( 优惠券 -¥{formatPrice(couponDiscount)} )} 实付金额 ¥{totalPrice} {/* 支付方式选择 */} 支付方式 {/* 微信支付 */} {/* setPayType(1)}*/} {/*>*/} {/* */} {/* */} {/* */} {/* */} {/* 微信支付*/} {/* 推荐使用*/} {/* */} {/* */} {/* {payType === 1 && (*/} {/* */} {/* )}*/} {/* */} {/**/} {/* 货到付款 */} setPayType(0)} > 货到付款 商品送达后付款 {payType === 0 && ( )} {/* 凑单推荐 */} {rushBuyInfo && rushBuyProducts.length > 0 && ( 🎯 再买 ¥{rushBuyInfo.needAmount} 减 ¥{rushBuyInfo.discount} 凑单更优惠 {rushBuyProducts.map(product => ( { Taro.navigateTo({ url: `/pages/shop/product-detail?id=${product.goodsId}&from=rushbuy` }) }} > {product.name} ¥{product.salePrice || product.price} +加购 ))} )} {/* 底部留白(固定栏高度 + 安全区) */} {/* 底部提交栏 - 固定吸底 */} 合计: ¥{totalPrice} {submitting ? '提交中...' : '提交订单'} {/* 优惠券选择弹窗 */} {couponVisible && ( 选择优惠券 setCouponVisible(false)}>✕ {/* 不使用优惠券 */} handleCouponConfirm(null)} > 不使用优惠券 {/* 可用优惠券列表 */} {coupons.map(coupon => ( handleCouponConfirm(coupon)} /> ))} {coupons.length === 0 && ( 暂无可用优惠券 )} )} ) } export default CheckoutPage