import React, { useState, useCallback } from 'react'; import { View, Text } from '@tarojs/components'; import Taro, { useDidShow } from '@tarojs/taro'; import { listProducts, exchangeProduct, } from '../../../services/points'; import type { PointsProduct } from '../../../services/points'; import { usePointsStore } from '../../../stores/points'; import Loading from '../../../components/Loading'; import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; const TYPE_INITIAL: Record = { physical: '物', service: '券', privilege: '权', }; const TYPE_LABEL: Record = { physical: '实物商品', service: '服务券', privilege: '权益卡', }; const TYPE_COLOR: Record = { physical: '#5B7A5E', service: '#C4623A', privilege: '#8B3E1F', }; export default function ExchangeConfirm() { const modeClass = useElderClass(); const [product, setProduct] = useState(null); const { account, refresh: refreshPoints } = usePointsStore(); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); useDidShow(() => { Taro.setNavigationBarTitle({ title: '确认兑换' }); loadData(); }); const loadData = useCallback(async () => { const instance = Taro.getCurrentInstance(); const productId = instance.router?.params?.product_id; if (!productId) { Taro.showToast({ title: '参数错误', icon: 'none' }); setTimeout(() => Taro.navigateBack(), 1500); return; } setLoading(true); try { const [productRes] = await Promise.all([ listProducts({ page: 1, page_size: 100 }), refreshPoints(), ]); const found = productRes.data.find((p) => p.id === productId); if (!found) { Taro.showToast({ title: '商品不存在', icon: 'none' }); setTimeout(() => Taro.navigateBack(), 1500); return; } setProduct(found); } catch { Taro.showToast({ title: '加载失败', icon: 'none' }); setTimeout(() => Taro.navigateBack(), 1500); } finally { setLoading(false); } }, [refreshPoints]); const balance = account?.balance ?? 0; const cost = product?.points_cost ?? 0; const insufficient = balance < cost; const handleConfirm = useCallback(async () => { if (!product || submitting) return; if (insufficient) { Taro.showToast({ title: '积分不足', icon: 'none' }); return; } const modalRes = await Taro.showModal({ title: '确认兑换', content: `确定花费 ${cost} 积分兑换「${product.name}」吗?`, }); if (!modalRes.confirm) return; setSubmitting(true); try { const order = await exchangeProduct(product.id); Taro.showToast({ title: '兑换成功', icon: 'success', duration: 2000 }); setTimeout(() => { Taro.showModal({ title: '兑换成功', content: `核销码: ${order.qr_code}\n请凭此码到前台核销`, showCancel: false, confirmText: '查看订单', success: () => { Taro.navigateTo({ url: `/pages/pkg-mall/orders/index`, }); }, }); }, 2000); } catch (err) { const msg = err instanceof Error ? err.message : '兑换失败'; if (msg.includes('余额不足') || msg.includes('insufficient')) { Taro.showToast({ title: '积分不足', icon: 'none' }); } else { Taro.showToast({ title: msg, icon: 'none' }); } } finally { setSubmitting(false); } }, [product, submitting, insufficient, cost]); if (loading) { return ( ); } const productType = product?.product_type || 'physical'; const initial = TYPE_INITIAL[productType] || '礼'; const typeLabel = TYPE_LABEL[productType] || '商品'; const typeColor = TYPE_COLOR[productType] || '#C4623A'; return ( {/* 商品预览卡片 */} {initial} {product?.name || ''} {typeLabel} {/* 兑换明细 */} 兑换明细 所需积分 {cost.toLocaleString()} 当前余额 {balance.toLocaleString()} {insufficient && ( 差额 -{(cost - balance).toLocaleString()} )} 库存 {product && product.stock > 0 ? `剩余 ${product.stock} 件` : '已兑完'} {/* 温馨提示 */} 温馨提示 兑换成功后将生成核销码,请凭核销码到前台核销领取。 积分一经兑换不可退回。 {/* 底部操作 */} 合计 {cost.toLocaleString()} 积分 {submitting ? '兑换中...' : insufficient ? '积分不足' : (product?.stock ?? 0) <= 0 ? '已兑完' : '确认兑换'} ); }