import { useState, useCallback } from 'react'; import { View, Text } from '@tarojs/components'; import Taro from '@tarojs/taro'; import { usePageData } from '@/hooks/usePageData'; import { getProduct, listProducts, exchangeProduct, } from '../../../services/points'; import type { PointsProduct } from '../../../services/points'; import { usePointsStore } from '../../../stores/points'; import { useAuthStore } from '../../../stores/auth'; import Loading from '../../../components/Loading'; import { useElderClass } from '../../../hooks/useElderClass'; import { useSafeTimeout } from '@/hooks/useSafeTimeout'; import PageShell from '@/components/ui/PageShell'; import './index.scss'; const TYPE_CHAR: Record = { physical: '物', service: '券', privilege: '权', }; const TYPE_CLASS: Record = { physical: 'physical', service: 'service', privilege: 'privilege', }; export default function ExchangeConfirm() { const modeClass = useElderClass(); const [product, setProduct] = useState(null); const account = usePointsStore((s) => s.account); const refreshPoints = usePointsStore((s) => s.refresh); const currentPatient = useAuthStore((s) => s.currentPatient); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const { safeSetTimeout } = useSafeTimeout(); const loadData = useCallback(async () => { const instance = Taro.getCurrentInstance(); const productId = instance.router?.params?.product_id; if (!productId) { Taro.showToast({ title: '参数错误', icon: 'none' }); safeSetTimeout(() => Taro.navigateBack(), 1500); return; } setLoading(true); try { // 先尝试单商品接口,降级到列表查找 let found: PointsProduct | null = null; try { found = await getProduct(productId); } catch (err) { console.warn('[exchange] 单商品接口失败,降级列表查找:', err); const productRes = await listProducts({ page: 1, page_size: 100 }); found = productRes.data.find((p) => p.id === productId) || null; } if (!found) { Taro.showToast({ title: '商品不存在', icon: 'none' }); safeSetTimeout(() => Taro.navigateBack(), 1500); return; } setProduct(found); await refreshPoints(); } catch (err) { console.warn('[exchange] 加载兑换页面失败:', err); Taro.showToast({ title: '加载失败', icon: 'none' }); safeSetTimeout(() => Taro.navigateBack(), 1500); } finally { setLoading(false); } }, [refreshPoints]); usePageData( useCallback(async () => { Taro.setNavigationBarTitle({ title: '确认兑换' }); await loadData(); }, [loadData]), { throttleMs: 10000, enablePullDown: false }, ); const balance = account?.balance ?? 0; const cost = product?.points_cost ?? 0; const insufficient = balance < cost; const remaining = balance - cost; const productType = product?.product_type || 'physical'; const isService = productType === 'service'; const typeChar = TYPE_CHAR[productType] || '礼'; const typeCls = TYPE_CLASS[productType] || 'physical'; 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 }); safeSetTimeout(() => { if (isService && order.qr_code) { Taro.showModal({ title: '兑换成功', content: `核销码: ${order.qr_code}\n请凭此码到前台核销`, showCancel: false, confirmText: '查看订单', success: () => { Taro.redirectTo({ url: '/pages/pkg-mall/orders/index' }); }, }); } else { Taro.redirectTo({ 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, isService]); if (loading) { return ( ); } return ( {/* 商品预览卡片 */} {typeChar} {product?.name || ''} {cost.toLocaleString()} 积分 ×1 {/* 收货信息(实体商品) */} {!isService && currentPatient && ( 收货信息 修改地址 › {currentPatient.name} 请前往个人中心完善收货地址 )} {/* 兑换明细 */} 兑换明细 商品积分 {cost.toLocaleString()} {isService ? '核销方式' : '运费'} {isService ? '到院核销' : '¥0.00'} 应扣积分 {cost.toLocaleString()} 剩余积分 = 0 ? 'sufficient' : 'insufficient'}`}> {remaining.toLocaleString()} {/* 确认兑换按钮 */} {submitting ? '兑换中...' : insufficient ? '积分不足' : (product?.stock ?? 0) <= 0 ? '已兑完' : '确认兑换'} ); }