import React, { useState, useEffect } from 'react' import Taro, { useRouter } from '@tarojs/taro' import { View, Text, Image } from '@tarojs/components' import { Cell, CellGroup, Radio, RadioGroup, TextArea, Button, Loading, InputNumber } from '@nutui/nutui-react-taro' import { applyAfterSale } from '@/api/afterSale' import { getShopOrder, updateShopOrder } from '@/api/shop/shopOrder' import { listShopOrderGoods } from '@/api/shop/shopOrderGoods' import './index.scss' // 订单商品信息 interface OrderGoods { goodsId: string goodsName: string goodsImage: string goodsPrice: number goodsNum: number skuInfo?: string canRefundNum: number // 可退款数量 } // 退款原因选项 const REFUND_REASONS = [ '不想要了', '商品质量问题', '商品与描述不符', '收到商品破损', '发错商品', '商品缺件', '其他原因' ] // 退款申请信息 interface RefundApplication { refundType: 'full' | 'partial' // 退款类型:全额退款 | 部分退款 refundReason: string // 退款原因 refundDescription: string // 退款说明 refundAmount: number // 退款金额 refundGoods: Array<{ goodsId: string refundNum: number }> // 退款商品 evidenceImages: string[] // 凭证图片 contactPhone?: string // 联系电话 isUrgent: boolean // 是否加急处理 } const RefundPage: React.FC = () => { const router = useRouter() const { orderId, orderNo } = router.params const [loading, setLoading] = useState(true) const [submitting, setSubmitting] = useState(false) const [markedClientRefund, setMarkedClientRefund] = useState(false) const [orderGoods, setOrderGoods] = useState([]) const [orderAmount, setOrderAmount] = useState(0) const [refundApp, setRefundApp] = useState({ refundType: 'full', refundReason: '', refundDescription: '', refundAmount: 0, refundGoods: [], evidenceImages: [], contactPhone: '', isUrgent: false }) const toMoneyNumber = (value: unknown, defaultValue: number = 0): number => { if (typeof value === 'number') return Number.isFinite(value) ? value : defaultValue if (typeof value === 'string') { // Be tolerant of API strings like "¥12.34" or "1,234.56". const cleaned = value.trim().replace(/,/g, '').replace(/[^\d.-]/g, '') const n = Number.parseFloat(cleaned) return Number.isFinite(n) ? n : defaultValue } return defaultValue } const formatMoney = (value: unknown): string => { const n = toMoneyNumber(value, 0) return n.toFixed(2) } const markOrderClientRefund = async () => { if (markedClientRefund) return if (!orderId) return const orderIdNum = Number.parseInt(String(orderId), 10) if (!Number.isFinite(orderIdNum)) return try { await updateShopOrder({ orderId: orderIdNum, orderStatus: 7 // 客户端申请退款 }) setMarkedClientRefund(true) } catch (e) { console.error('更新订单状态为客户端申请退款失败:', e) // 不阻塞用户填写表单;提交时仍会再次尝试更新一次 } } useEffect(() => { if (orderId) { loadOrderInfo() } }, [orderId]) // 加载订单信息 const loadOrderInfo = async () => { try { setLoading(true) if (!orderId) { throw new Error('缺少订单ID') } const orderIdNum = Number.parseInt(String(orderId), 10) if (!Number.isFinite(orderIdNum)) { throw new Error('订单ID不合法') } // 以订单实付金额为准(避免商品单价合计与优惠/运费等不一致) const order = await getShopOrder(orderIdNum) const payAmount = toMoneyNumber(order?.payPrice ?? order?.totalPrice, 0) // 商品信息加载失败时,不阻塞退款申请(全额退款不依赖商品明细) let mappedGoods: OrderGoods[] = [] try { const goods = (await listShopOrderGoods({ orderId: orderIdNum })) || [] mappedGoods = goods.map((g, idx) => { const goodsNum = Number(g.totalNum ?? 0) || 0 return { goodsId: String(g.goodsId ?? idx), goodsName: g.goodsName || '订单商品', goodsImage: g.image || '/default-goods.png', goodsPrice: toMoneyNumber(g.price, 0), goodsNum, canRefundNum: goodsNum, skuInfo: g.spec } }) } catch (e) { console.warn('加载订单商品失败(不阻塞退款申请):', e) } setOrderGoods(mappedGoods) setOrderAmount(payAmount) // 初始化退款申请信息:默认全额退款 setRefundApp(prev => ({ ...prev, refundType: 'full', refundAmount: payAmount, refundGoods: mappedGoods.map(g => ({ goodsId: g.goodsId, refundNum: g.goodsNum })), evidenceImages: [] })) } catch (error) { console.error('加载订单信息失败:', error) Taro.showToast({ title: '加载失败,请重试', icon: 'none' }) } finally { setLoading(false) } } // 更新退款申请信息 const updateRefundApp = (field: keyof RefundApplication, value: any) => { setRefundApp(prev => ({ ...prev, [field]: value })) } // 切换退款类型 // const handleRefundTypeChange = (type: 'full' | 'partial') => { // updateRefundApp('refundType', type) // // if (type === 'full') { // // 全额退款 // updateRefundApp('refundAmount', orderAmount) // updateRefundApp('refundGoods', orderGoods.map(goods => ({ // goodsId: goods.goodsId, // refundNum: goods.goodsNum // }))) // } else { // // 部分退款 // updateRefundApp('refundAmount', 0) // updateRefundApp('refundGoods', orderGoods.map(goods => ({ // goodsId: goods.goodsId, // refundNum: 0 // }))) // } // } // 更新商品退款数量 const updateGoodsRefundNum = (goodsId: string, refundNum: number) => { const newRefundGoods = refundApp.refundGoods.map(item => item.goodsId === goodsId ? { ...item, refundNum } : item ) updateRefundApp('refundGoods', newRefundGoods) // 重新计算退款金额 const newRefundAmount = newRefundGoods.reduce((sum, item) => { const goods = orderGoods.find(g => g.goodsId === item.goodsId) return sum + (goods ? goods.goodsPrice * item.refundNum : 0) }, 0) updateRefundApp('refundAmount', newRefundAmount) } // 提交退款申请 const submitRefund = async () => { try { // 验证必填信息 if (!refundApp.refundReason) { Taro.showToast({ title: '请选择退款原因', icon: 'none' }) return } if (refundApp.refundAmount <= 0) { Taro.showToast({ title: '退款金额必须大于0', icon: 'none' }) return } if (refundApp.refundType === 'partial') { const hasRefundGoods = refundApp.refundGoods.some(item => item.refundNum > 0) if (!hasRefundGoods) { Taro.showToast({ title: '请选择要退款的商品', icon: 'none' }) return } } setSubmitting(true) // 构造请求参数 const params = { orderId: orderId || '', type: 'refund' as const, reason: refundApp.refundReason, description: refundApp.refundDescription, amount: refundApp.refundAmount, contactPhone: refundApp.contactPhone, evidenceImages: refundApp.evidenceImages, ...(refundApp.refundGoods.some(item => item.refundNum > 0) ? { goodsItems: refundApp.refundGoods .filter(item => item.refundNum > 0) .map(item => ({ goodsId: item.goodsId, quantity: item.refundNum })) } : {}) } // 调用API提交退款申请 const result = await applyAfterSale(params) if (result.success) { // 更新订单状态为"客户端申请退款" if (orderId) { try { await updateShopOrder({ orderId: parseInt(orderId), orderStatus: 7 // 客户端申请退款 }) setMarkedClientRefund(true) } catch (updateError) { console.error('更新订单状态失败:', updateError) // 即使更新订单状态失败,也继续执行后续操作 } } Taro.showToast({ title: '退款申请提交成功', icon: 'success' }) // 延迟返回上一页 setTimeout(() => { Taro.navigateBack() }, 1500) } else { throw new Error(result.message || '提交失败') } } catch (error) { console.error('提交退款申请失败:', error) Taro.showToast({ title: error instanceof Error ? error.message : '提交失败,请重试', icon: 'none' }) } finally { setSubmitting(false) } } if (loading) { return ( 正在加载订单信息... ) } return ( {/* 订单信息 */} 订单号:{orderNo} 订单金额:¥{formatMoney(orderAmount)} {/* 退款类型选择 */} {/**/} {/* handleRefundTypeChange(value as 'full' | 'partial')}*/} {/* >*/} {/* */} {/* 全额退款*/} {/* */} {/* */} {/* 部分退款*/} {/* */} {/* */} {/**/} {/* 商品列表 */} {refundApp.refundType === 'partial' && ( 选择退款商品 {orderGoods.map(goods => { const refundGoods = refundApp.refundGoods.find(item => item.goodsId === goods.goodsId) const refundNum = refundGoods?.refundNum || 0 return ( {goods.goodsName} {goods.skuInfo && ( {goods.skuInfo} )} ¥{goods.goodsPrice} 退款数量 updateGoodsRefundNum(goods.goodsId, Number(value) || 0)} /> 最多{goods.canRefundNum}件 ) })} )} {/* 退款金额 */} ¥{formatMoney(refundApp.refundAmount)} {/* 退款原因 */} { updateRefundApp('refundReason', value) void markOrderClientRefund() }} > {REFUND_REASONS.map(reason => ( {reason} ))} {/* 退款说明 */} 退款说明