diff --git a/src/api/shop/shopGoods/model/index.ts b/src/api/shop/shopGoods/model/index.ts index 441aab4..ce4328f 100644 --- a/src/api/shop/shopGoods/model/index.ts +++ b/src/api/shop/shopGoods/model/index.ts @@ -124,6 +124,8 @@ export interface ShopGoods { canUseDate?: string; ensureTag?: string; expiredDay?: number; + // 可购买数量 + canBuyNumber?: number; } export interface BathSet { diff --git a/src/components/CouponCard.scss b/src/components/CouponCard.scss new file mode 100644 index 0000000..6dd4559 --- /dev/null +++ b/src/components/CouponCard.scss @@ -0,0 +1,253 @@ +.coupon-card { + position: relative; + display: flex; + width: 100%; + height: 100px; + margin-bottom: 12px; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + background: #fff; + + &--disabled { + opacity: 0.6; + } + + // 主题颜色 + &--red { + .coupon-card__left { + background: linear-gradient(135deg, #ff6b6b, #ff5252); + } + .coupon-card__btn--receive, + .coupon-card__btn--use { + background: linear-gradient(135deg, #ff6b6b, #ff5252); + color: #fff; + } + } + + &--orange { + .coupon-card__left { + background: linear-gradient(135deg, #ffa726, #ff9800); + } + .coupon-card__btn--receive, + .coupon-card__btn--use { + background: linear-gradient(135deg, #ffa726, #ff9800); + color: #fff; + } + } + + &--blue { + .coupon-card__left { + background: linear-gradient(135deg, #42a5f5, #2196f3); + } + .coupon-card__btn--receive, + .coupon-card__btn--use { + background: linear-gradient(135deg, #42a5f5, #2196f3); + color: #fff; + } + } + + &--purple { + .coupon-card__left { + background: linear-gradient(135deg, #ab47bc, #9c27b0); + } + .coupon-card__btn--receive, + .coupon-card__btn--use { + background: linear-gradient(135deg, #ab47bc, #9c27b0); + color: #fff; + } + } + + &--green { + .coupon-card__left { + background: linear-gradient(135deg, #66bb6a, #4caf50); + } + .coupon-card__btn--receive, + .coupon-card__btn--use { + background: linear-gradient(135deg, #66bb6a, #4caf50); + color: #fff; + } + } + + // 左侧金额区域 + &__left { + flex: 0 0 100px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #fff; + position: relative; + } + + &__amount { + display: flex; + align-items: baseline; + margin-bottom: 4px; + } + + &__currency { + font-size: 14px; + font-weight: 500; + margin-right: 1px; + } + + &__value { + font-size: 28px; + font-weight: bold; + line-height: 1; + } + + &__condition { + font-size: 11px; + opacity: 0.9; + margin-top: 2px; + } + + // 分割线区域 + &__divider { + flex: 0 0 2px; + position: relative; + background: #f0f0f0; + } + + &__divider-line { + width: 100%; + height: 100%; + background: repeating-linear-gradient( + to bottom, + transparent 0px, + transparent 4px, + #ddd 4px, + #ddd 8px + ); + } + + &__circle { + position: absolute; + width: 16px; + height: 16px; + background: #f5f5f5; + border-radius: 50%; + left: 50%; + transform: translateX(-50%); + + &--top { + top: -8px; + } + + &--bottom { + bottom: -8px; + } + } + + // 右侧信息区域 + &__right { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 12px; + } + + &__info { + flex: 1; + } + + &__title { + font-size: 14px; + font-weight: 600; + color: #333; + margin-bottom: 4px; + } + + &__validity { + font-size: 11px; + color: #999; + } + + &__action { + display: flex; + justify-content: flex-end; + align-items: center; + } + + &__btn { + min-width: 50px; + height: 24px; + border-radius: 12px; + font-size: 11px; + border: none; + + &--receive, + &--use { + color: #fff; + } + } + + &__status { + font-size: 12px; + color: #999; + padding: 4px 8px; + } + + // 状态遮罩 + &__mask { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + } + + &__mask-text { + background: rgba(0, 0, 0, 0.6); + color: #fff; + padding: 4px 12px; + border-radius: 12px; + font-size: 14px; + font-weight: 500; + } +} + +// 优惠券列表容器 +.coupon-list { + padding: 16px; + + &__title { + font-size: 18px; + font-weight: 600; + color: #333; + margin-bottom: 16px; + } + + &__empty { + text-align: center; + padding: 40px 20px; + color: #999; + font-size: 14px; + } +} + +// 优惠券横向滚动容器 +.coupon-scroll { + display: flex; + padding: 16px; + gap: 8px; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + + &::-webkit-scrollbar { + display: none; + } + + .coupon-card { + flex: 0 0 240px; + margin-bottom: 0; + } +} diff --git a/src/components/CouponCard.tsx b/src/components/CouponCard.tsx new file mode 100644 index 0000000..6cca271 --- /dev/null +++ b/src/components/CouponCard.tsx @@ -0,0 +1,168 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Button } from '@nutui/nutui-react-taro' +import './CouponCard.scss' + +export interface CouponCardProps { + /** 优惠券金额 */ + amount: number + /** 最低消费金额 */ + minAmount?: number + /** 优惠券类型:1-满减券 2-折扣券 3-免费券 */ + type?: 1 | 2 | 3 + /** 优惠券状态:0-未使用 1-已使用 2-已过期 */ + status?: 0 | 1 | 2 + /** 优惠券标题 */ + title?: string + /** 有效期开始时间 */ + startTime?: string + /** 有效期结束时间 */ + endTime?: string + /** 是否显示领取按钮 */ + showReceiveBtn?: boolean + /** 是否显示使用按钮 */ + showUseBtn?: boolean + /** 领取按钮点击事件 */ + onReceive?: () => void + /** 使用按钮点击事件 */ + onUse?: () => void + /** 优惠券样式主题:red | orange | blue | purple | green */ + theme?: 'red' | 'orange' | 'blue' | 'purple' | 'green' +} + +const CouponCard: React.FC = ({ + amount, + minAmount, + type = 1, + status = 0, + title, + startTime, + endTime, + showReceiveBtn = false, + showUseBtn = false, + onReceive, + onUse, + theme = 'red' +}) => { + // 格式化优惠券金额显示 + const formatAmount = () => { + switch (type) { + case 1: // 满减券 + return `¥${amount}` + case 2: // 折扣券 + return `${amount}折` + case 3: // 免费券 + return '免费' + default: + return `¥${amount}` + } + } + + // 获取优惠券状态文本 + const getStatusText = () => { + switch (status) { + case 0: + return '未使用' + case 1: + return '已使用' + case 2: + return '已过期' + default: + return '未使用' + } + } + + // 获取使用条件文本 + const getConditionText = () => { + if (type === 3) return '无门槛' + if (minAmount && minAmount > 0) { + return `满${minAmount}可用` + } + return '无门槛' + } + + // 格式化日期 + const formatDate = (dateStr?: string) => { + if (!dateStr) return '' + const date = new Date(dateStr) + return `${date.getMonth() + 1}.${date.getDate()}` + } + + // 获取有效期文本 + const getValidityText = () => { + if (startTime && endTime) { + return `${formatDate(startTime)}-${formatDate(endTime)}` + } + return '' + } + + return ( + + {/* 左侧金额区域 */} + + + ¥ + {amount} + + + {getConditionText()} + + + + {/* 中间分割线 */} + + + + + + + {/* 右侧信息区域 */} + + + + {title || (type === 1 ? '满减券' : type === 2 ? '折扣券' : '免费券')} + + + 有效期:{getValidityText()} + + + + {/* 按钮区域 */} + + {showReceiveBtn && status === 0 && ( + + )} + {showUseBtn && status === 0 && ( + + )} + {status !== 0 && ( + + {getStatusText()} + + )} + + + + {/* 状态遮罩 */} + {status !== 0 && ( + + {getStatusText()} + + )} + + ) +} + +export default CouponCard diff --git a/src/components/CouponList.tsx b/src/components/CouponList.tsx new file mode 100644 index 0000000..3e5d02b --- /dev/null +++ b/src/components/CouponList.tsx @@ -0,0 +1,96 @@ +import React from 'react' +import { View, ScrollView } from '@tarojs/components' +import CouponCard, { CouponCardProps } from './CouponCard' +import './CouponCard.scss' + +export interface CouponListProps { + /** 优惠券列表数据 */ + coupons: CouponCardProps[] + /** 列表标题 */ + title?: string + /** 布局方式:vertical-垂直布局 horizontal-水平滚动 */ + layout?: 'vertical' | 'horizontal' + /** 是否显示空状态 */ + showEmpty?: boolean + /** 空状态文案 */ + emptyText?: string + /** 优惠券点击事件 */ + onCouponClick?: (coupon: CouponCardProps, index: number) => void +} + +const CouponList: React.FC = ({ + coupons = [], + title, + layout = 'vertical', + showEmpty = true, + emptyText = '暂无优惠券', + onCouponClick +}) => { + const handleCouponClick = (coupon: CouponCardProps, index: number) => { + onCouponClick?.(coupon, index) + } + + // 垂直布局 + if (layout === 'vertical') { + return ( + + {title && ( + {title} + )} + + {coupons.length === 0 ? ( + showEmpty && ( + + {emptyText} + + ) + ) : ( + coupons.map((coupon, index) => ( + handleCouponClick(coupon, index)} + > + + + )) + )} + + ) + } + + // 水平滚动布局 + return ( + + {title && ( + + {title} + + )} + + {coupons.length === 0 ? ( + showEmpty && ( + + {emptyText} + + ) + ) : ( + + {coupons.map((coupon, index) => ( + handleCouponClick(coupon, index)} + > + + + ))} + + )} + + ) +} + +export default CouponList diff --git a/src/components/OrderConfirmSkeleton.scss b/src/components/OrderConfirmSkeleton.scss new file mode 100644 index 0000000..66b5520 --- /dev/null +++ b/src/components/OrderConfirmSkeleton.scss @@ -0,0 +1,78 @@ +.order-confirm-skeleton { + padding: 0; + background: #f5f5f5; + + .skeleton-section { + background: #fff; + margin-bottom: 8px; + padding: 16px; + } + + .skeleton-address { + display: flex; + align-items: flex-start; + gap: 12px; + + &-content { + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; + } + } + + .skeleton-goods { + display: flex; + align-items: flex-start; + gap: 12px; + + &-content { + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; + } + + &-price { + display: flex; + align-items: center; + gap: 12px; + } + } + + .skeleton-payment { + display: flex; + justify-content: space-between; + align-items: center; + } + + .skeleton-price-item { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + + &:last-child { + margin-bottom: 0; + } + } + + .skeleton-remark { + display: flex; + justify-content: space-between; + align-items: center; + } + + .skeleton-bottom { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: #fff; + padding: 16px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1); + } +} diff --git a/src/components/OrderConfirmSkeleton.tsx b/src/components/OrderConfirmSkeleton.tsx new file mode 100644 index 0000000..a0e41ad --- /dev/null +++ b/src/components/OrderConfirmSkeleton.tsx @@ -0,0 +1,80 @@ +import React from 'react' +import { View } from '@tarojs/components' +import { Skeleton } from '@nutui/nutui-react-taro' +import './OrderConfirmSkeleton.scss' + +const OrderConfirmSkeleton: React.FC = () => { + return ( + + {/* 收货地址骨架 */} + + + + + + + + + + + {/* 商品信息骨架 */} + + + + + + + + + + + + + + + {/* 支付方式骨架 */} + + + + + + + + {/* 价格明细骨架 */} + + + + + + + + + + + + + + + + + + + + {/* 订单备注骨架 */} + + + + + + + + {/* 底部按钮骨架 */} + + + + + + ) +} + +export default OrderConfirmSkeleton diff --git a/src/components/QuantitySelector.scss b/src/components/QuantitySelector.scss new file mode 100644 index 0000000..072f6d6 --- /dev/null +++ b/src/components/QuantitySelector.scss @@ -0,0 +1,121 @@ +.quantity-selector { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + + &__controls { + display: flex; + align-items: center; + border: 1px solid #e5e5e5; + border-radius: 4px; + overflow: hidden; + background: #fff; + } + + &__btn { + display: flex; + align-items: center; + justify-content: center; + border: none; + background: #f8f8f8; + color: #666; + transition: all 0.2s ease; + + &:active { + background: #e5e5e5; + } + + &--disabled { + background: #f5f5f5 !important; + color: #ccc !important; + cursor: not-allowed; + } + } + + &__input { + display: flex; + align-items: center; + justify-content: center; + background: #fff; + border-left: 1px solid #e5e5e5; + border-right: 1px solid #e5e5e5; + } + + &__value { + font-size: 14px; + color: #333; + font-weight: 500; + text-align: center; + } + + &__stock { + margin-top: 2px; + } + + &__stock-text { + font-size: 12px; + color: #999; + } + + // 尺寸变体 + &--small { + .quantity-selector__controls { + height: 24px; + } + + .quantity-selector__btn { + width: 24px; + height: 24px; + } + + .quantity-selector__input { + width: 32px; + height: 24px; + } + + .quantity-selector__value { + font-size: 12px; + } + } + + &--medium { + .quantity-selector__controls { + height: 32px; + } + + .quantity-selector__btn { + width: 32px; + height: 32px; + } + + .quantity-selector__input { + width: 40px; + height: 32px; + } + + .quantity-selector__value { + font-size: 14px; + } + } + + &--large { + .quantity-selector__controls { + height: 40px; + } + + .quantity-selector__btn { + width: 40px; + height: 40px; + } + + .quantity-selector__input { + width: 48px; + height: 40px; + } + + .quantity-selector__value { + font-size: 16px; + } + } +} diff --git a/src/components/QuantitySelector.tsx b/src/components/QuantitySelector.tsx new file mode 100644 index 0000000..065e3c0 --- /dev/null +++ b/src/components/QuantitySelector.tsx @@ -0,0 +1,88 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Button } from '@nutui/nutui-react-taro' +import { Minus, Plus } from '@nutui/icons-react-taro' +import './QuantitySelector.scss' + +export interface QuantitySelectorProps { + /** 当前数量 */ + value: number + /** 最小数量 */ + min?: number + /** 最大数量(库存) */ + max?: number + /** 是否禁用 */ + disabled?: boolean + /** 数量变化回调 */ + onChange?: (value: number) => void + /** 尺寸 */ + size?: 'small' | 'medium' | 'large' + /** 是否显示库存提示 */ + showStock?: boolean + /** 库存数量 */ + stock?: number +} + +const QuantitySelector: React.FC = ({ + value, + min = 1, + max = 999, + disabled = false, + onChange, + size = 'medium', + showStock = false, + stock +}) => { + const handleDecrease = () => { + if (disabled || value <= min) return + const newValue = value - 1 + onChange?.(newValue) + } + + const handleIncrease = () => { + if (disabled || value >= max) return + const newValue = value + 1 + onChange?.(newValue) + } + + const canDecrease = !disabled && value > min + const canIncrease = !disabled && value < max + + return ( + + + + + + {value} + + + + + + {showStock && stock !== undefined && ( + + + 库存 {stock} 件 + + + )} + + ) +} + +export default QuantitySelector diff --git a/src/shop/orderConfirm/index.scss b/src/shop/orderConfirm/index.scss index 54ec1b6..87b5288 100644 --- a/src/shop/orderConfirm/index.scss +++ b/src/shop/orderConfirm/index.scss @@ -1,6 +1,21 @@ .order-confirm-page { padding-bottom: 100px; // 留出底部固定按钮的空间 + .error-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + text-align: center; + + .error-text { + color: #999; + margin-bottom: 20px; + font-size: 14px; + } + } + .fixed-bottom { position: fixed; bottom: 0; @@ -42,3 +57,51 @@ content: ""; } } + +// 优惠券弹窗样式 +.coupon-popup { + height: 100%; + display: flex; + flex-direction: column; + + &__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + border-bottom: 1px solid #f0f0f0; + } + + &__title { + font-size: 16px; + font-weight: 600; + color: #333; + } + + &__content { + flex: 1; + overflow-y: auto; + } + + &__current { + padding: 16px; + background: #f8f9fa; + border-bottom: 1px solid #f0f0f0; + + &-title { + font-size: 14px; + color: #666; + margin-bottom: 8px; + } + + &-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background: #fff; + border-radius: 6px; + font-size: 14px; + } + } +} diff --git a/src/shop/orderConfirm/index.tsx b/src/shop/orderConfirm/index.tsx index 1d826cd..39e6b64 100644 --- a/src/shop/orderConfirm/index.tsx +++ b/src/shop/orderConfirm/index.tsx @@ -1,5 +1,17 @@ import {useEffect, useState} from "react"; -import {Image, Button, Cell, CellGroup, Input, Space, ActionSheet} from '@nutui/nutui-react-taro' +import { + Image, + Button, + Cell, + CellGroup, + Input, + Space, + ActionSheet, + Popup, + Toast, + InputNumber, + 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"; @@ -12,6 +24,10 @@ 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"; + const OrderConfirm = () => { const [goods, setGoods] = useState(null); @@ -19,91 +35,254 @@ const OrderConfirm = () => { 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) + + // 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] = useState([ + { + amount: 5, + minAmount: 20, + type: 1, + status: 0, + title: '满20减5', + startTime: '2024-01-01', + endTime: '2024-12-31', + theme: 'red' + }, + { + amount: 10, + minAmount: 50, + type: 1, + status: 0, + title: '满50减10', + startTime: '2024-01-01', + endTime: '2024-12-31', + theme: 'orange' + }, + { + amount: 20, + minAmount: 100, + type: 1, + status: 0, + title: '满100减20', + startTime: '2024-01-01', + endTime: '2024-12-31', + theme: 'blue' + } + ]) const router = Taro.getCurrentInstance().router; const goodsId = router?.params?.goodsId; - const reload = async () => { - // 默认收货地址 - const address = await listShopUserAddress({isDefault: true}); - if (address.length > 0) { - setAddress(address[0]) + // 计算商品总价 + const getGoodsTotal = () => { + if (!goods) return 0 + return parseFloat(goods.price || '0') * quantity + } + + // 计算优惠券折扣 + const getCouponDiscount = () => { + if (!selectedCoupon || !goods) return 0 + const total = getGoodsTotal() + + // 检查是否满足使用条件 + if (selectedCoupon.minAmount && total < selectedCoupon.minAmount) { + return 0 } - // 支付方式 - const paymentList = await selectPayment({}); - if (paymentList && paymentList.length > 0) { - setPayments(paymentList?.map((d, _) => { - return { - type: d.type, - name: d.name - } - })) - setPayment(paymentList[0]) + + switch (selectedCoupon.type) { + case 1: // 满减券 + return selectedCoupon.amount + case 2: // 折扣券 + return total * (1 - selectedCoupon.amount / 10) + case 3: // 免费券 + return total + default: + return 0 } } + // 计算实付金额 + 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 newQuantity = typeof value === 'string' ? parseInt(value) || 1 : value + setQuantity(Math.max(1, Math.min(newQuantity, goods?.stock || 999))) + } + + // 处理优惠券选择 + const handleCouponSelect = (coupon: CouponCardProps) => { + const total = getGoodsTotal() + + // 检查是否满足使用条件 + if (coupon.minAmount && total < coupon.minAmount) { + Toast.show(`需满${coupon.minAmount}元才能使用此优惠券`) + return + } + + setSelectedCoupon(coupon) + setCouponVisible(false) + Toast.show('优惠券选择成功') + } + + // 取消选择优惠券 + const handleCouponCancel = () => { + setSelectedCoupon(null) + Toast.show('已取消使用优惠券') + } + /** * 统一支付入口 */ const onPay = async (goods: ShopGoods) => { - // 基础校验 - if (!address) { - Taro.showToast({ - title: '请选择收货地址', - icon: 'error' - }); - return; - } + try { + setPayLoading(true) - if (!payment) { - Taro.showToast({ - title: '请选择支付方式', - icon: 'error' - }); - return; - } + // 基础校验 + if (!address) { + Toast.show('请选择收货地址') + return; + } - // 构建订单数据 - const orderData = buildSingleGoodsOrder( - goods.goodsId!, - 1, - address.id, - { - comments: goods.name, - deliveryType: 0, - buyerRemarks: '', + if (!payment) { + Toast.show('请选择支付方式') + return; } - ); - // 根据支付方式选择支付类型 - const paymentType = payment.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT; + // 库存校验 + if (goods.stock !== undefined && quantity > goods.stock) { + Toast.show('商品库存不足') + return; + } + + // 构建订单数据 + const orderData = buildSingleGoodsOrder( + goods.goodsId!, + quantity, + address.id, + { + comments: goods.name, + deliveryType: 0, + buyerRemarks: orderRemark, + couponId: selectedCoupon ? selectedCoupon.amount : undefined, + couponDiscount: getCouponDiscount() + } + ); + + // 根据支付方式选择支付类型 + const paymentType = payment.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT; + + // 执行支付 + await PaymentHandler.pay(orderData, paymentType); - // 执行支付 - await PaymentHandler.pay(orderData, paymentType); + Toast.show('支付成功') + } catch (error) { + console.error('支付失败:', error) + Toast.show('支付失败,请重试') + } finally { + setPayLoading(false) + } }; + // 统一的数据加载函数 + const loadAllData = async () => { + try { + setLoading(true) + setError('') + + // 并行加载商品信息和其他数据 + const promises = [] + + if (goodsId) { + promises.push(getShopGoods(Number(goodsId))) + } + + promises.push(listShopUserAddress({isDefault: true})) + promises.push(selectPayment({})) + + const [goodsRes, addressRes, paymentRes] = await Promise.all(promises) + + // 设置商品信息 + if (goodsRes) { + setGoods(goodsRes) + } + + // 设置默认收货地址 + 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]) + } + } catch (err) { + console.error('加载数据失败:', err) + setError('加载数据失败,请重试') + } finally { + setLoading(false) + } + } + useDidShow(() => { - reload().then() + loadAllData() }) useEffect(() => { - if (goodsId) { - getShopGoods(Number(goodsId)).then(res => { - setGoods(res); - }).catch(error => { - console.error("Failed to fetch goods detail:", error); - }); - } - reload().then() + loadAllData() }, [goodsId]); - if (!goods) { - return
加载中...
; + // 重新加载数据 + const handleRetry = () => { + loadAllData() + } + + // 错误状态 + if (error) { + return ( + + + {error} + + + + ) + } + + // 加载状态 + if (loading || !goods) { + return } return ( @@ -120,7 +299,7 @@ const OrderConfirm = () => { {address.province} {address.city} {address.region} {address.address} - + {address.name} {address.phone} @@ -141,20 +320,37 @@ const OrderConfirm = () => { - - - - {goods.name} - 80g/袋 - - ¥{goods.price} - x 1 - + + + - + + {goods.name} + 80g/袋 + + ¥{goods.price} + + + + + {goods.stock !== undefined && ( + + 库存 {goods.stock} 件 + + )} + + + + @@ -172,30 +368,46 @@ const OrderConfirm = () => { - {'¥' + goods.price}}/> - - -¥0.00 - - - )}/> + ¥{getGoodsTotal().toFixed(2)}} + /> + + + {selectedCoupon ? `-¥${getCouponDiscount().toFixed(2)}` : '暂未使用'} + + + + )} + onClick={() => setCouponVisible(true)} + /> 已优惠 - ¥0.0 - 小计 - ¥{goods.price} + ¥{getCouponDiscount().toFixed(2)} + 实付 + ¥{getFinalPrice().toFixed(2)} )}/> + setOrderRemark(value)} + maxLength={100} + /> )}/> + {/* 支付方式选择 */} { onCancel={() => setIsVisible(false)} /> + {/* 优惠券选择弹窗 */} + setCouponVisible(false)} + style={{height: '60vh'}} + > + + + 选择优惠券 + + + + + {selectedCoupon && ( + + 当前使用 + + {selectedCoupon.title} -¥{selectedCoupon.amount} + + + + )} + + { + const total = getGoodsTotal() + return !coupon.minAmount || total >= coupon.minAmount + })} + layout="vertical" + onCouponClick={handleCouponSelect} + /> + + { + const total = getGoodsTotal() + return coupon.minAmount && total < coupon.minAmount + }).map(coupon => ({...coupon, status: 2}))} + layout="vertical" + showEmpty={false} + /> + + + +
-
- 实付金额: - ¥{goods.price} +
+ + 实付金额: + ¥{getFinalPrice().toFixed(2)} + + {selectedCoupon && ( + + 已优惠 ¥{getCouponDiscount().toFixed(2)} + + )}
- +