refactor(order): 优化退款流程和订单状态管理

- 移除Uploader组件并隐藏上传凭证功能
- 添加getShopOrder和listShopOrderGoods API导入
- 新增toMoneyNumber和formatMoney工具函数处理金额格式
- 实现markOrderClientRefund函数标记客户端退款状态
- 使用真实订单数据替代模拟数据加载订单商品信息
- 修复订单金额显示格式化问题
- 优化订单状态判断逻辑和颜色配置
- 简化申请退款流程,移除预更新订单状态步骤
- 修复商品数量变更处理逻辑
- 更新订单状态码:退款申请中改为客户端申请退款
```
This commit is contained in:
2026-02-24 16:27:21 +08:00
parent 694b5e1e7a
commit 939d7b3ec2
2 changed files with 125 additions and 132 deletions

View File

@@ -132,6 +132,8 @@ function OrderList(props: OrderListProps) {
const [orderToConfirmReceive, setOrderToConfirmReceive] = useState<ShopOrder | null>(null) const [orderToConfirmReceive, setOrderToConfirmReceive] = useState<ShopOrder | null>(null)
const isReadOnly = props.readOnly || props.mode === 'store' || props.mode === 'rider' const isReadOnly = props.readOnly || props.mode === 'store' || props.mode === 'rider'
const isOrderCompleted = (order: ShopOrder) => Number(order.orderStatus) === 1 || order.formId === 10074;
// 获取订单状态文本 // 获取订单状态文本
const getOrderStatusText = (order: ShopOrder) => { const getOrderStatusText = (order: ShopOrder) => {
@@ -141,31 +143,27 @@ function OrderList(props: OrderListProps) {
if (order.orderStatus === 5) return '退款被拒绝'; if (order.orderStatus === 5) return '退款被拒绝';
if (order.orderStatus === 6) return '退款成功'; if (order.orderStatus === 6) return '退款成功';
if (order.orderStatus === 7) return '客户端申请退款'; if (order.orderStatus === 7) return '客户端申请退款';
if (isOrderCompleted(order)) return '已完成';
// 检查支付状态 (payStatus为boolean类型false/0表示未付款true/1表示已付款) // 检查支付状态 (payStatus为boolean类型false/0表示未付款true/1表示已付款)
if (!order.payStatus) return '等待买家付款'; if (!order.payStatus) return '等待买家付款';
// 已付款后检查发货状态 // 已付款后检查发货状态
if (order.deliveryStatus === 10) return '待发货'; if (order.deliveryStatus === 10) return '待发货';
if (order.formId === 10074) return '已完成';
if (order.deliveryStatus === 20) { if (order.deliveryStatus === 20) {
// 若订单没有配送员,沿用原“待收货”语义 // 若订单没有配送员,沿用原“待收货”语义
if (!order.riderId) return '待收货'; if (!order.riderId || Number(order.riderId) === 0) return '待收货';
// 配送员确认送达后sendEndTime有值才进入“待确认收货” // 配送员确认送达后sendEndTime有值才进入“待确认收货”
if (order.sendEndTime && order.orderStatus !== 1) return '待确认收货'; if (order.sendEndTime && !isOrderCompleted(order)) return '待确认收货';
return '配送中'; return '配送中';
} }
if (order.deliveryStatus === 30) return '部分发货'; if (order.deliveryStatus === 30) return '部分发货';
// 最后检查订单完成状态
if (order.orderStatus === 1) return '已完成';
if (order.orderStatus === 0) return '未使用'; if (order.orderStatus === 0) return '未使用';
return '未知状态'; return '未知状态';
}; };
const isOrderCompleted = (order: ShopOrder) => order.orderStatus === 1 || order.formId === 10074;
// 获取订单状态颜色 // 获取订单状态颜色
const getOrderStatusColor = (order: ShopOrder) => { const getOrderStatusColor = (order: ShopOrder) => {
// 优先检查订单状态 // 优先检查订单状态
@@ -174,6 +172,7 @@ function OrderList(props: OrderListProps) {
if (order.orderStatus === 5) return 'text-red-500'; // 退款被拒绝 if (order.orderStatus === 5) return 'text-red-500'; // 退款被拒绝
if (order.orderStatus === 6) return 'text-green-500'; // 退款成功 if (order.orderStatus === 6) return 'text-green-500'; // 退款成功
if (order.orderStatus === 7) return 'text-orange-500'; // 客户端申请退款 if (order.orderStatus === 7) return 'text-orange-500'; // 客户端申请退款
if (isOrderCompleted(order)) return 'text-green-600'; // 已完成
// 检查支付状态 // 检查支付状态
if (!order.payStatus) return 'text-orange-500'; // 等待买家付款 if (!order.payStatus) return 'text-orange-500'; // 等待买家付款
@@ -181,14 +180,12 @@ function OrderList(props: OrderListProps) {
// 已付款后检查发货状态 // 已付款后检查发货状态
if (order.deliveryStatus === 10) return 'text-blue-500'; // 待发货 if (order.deliveryStatus === 10) return 'text-blue-500'; // 待发货
if (order.deliveryStatus === 20) { if (order.deliveryStatus === 20) {
if (!order.riderId) return 'text-purple-500'; // 待收货 if (!order.riderId || Number(order.riderId) === 0) return 'text-purple-500'; // 待收货
if (order.sendEndTime && order.orderStatus !== 1) return 'text-purple-500'; // 待确认收货 if (order.sendEndTime && !isOrderCompleted(order)) return 'text-purple-500'; // 待确认收货
return 'text-blue-500'; // 配送中 return 'text-blue-500'; // 配送中
} }
if (order.deliveryStatus === 30) return 'text-blue-500'; // 部分发货 if (order.deliveryStatus === 30) return 'text-blue-500'; // 部分发货
// 最后检查订单完成状态
if (order.orderStatus === 1) return 'text-green-600'; // 已完成
if (order.orderStatus === 0) return 'text-gray-500'; // 未使用 if (order.orderStatus === 0) return 'text-gray-500'; // 未使用
return 'text-gray-600'; // 默认颜色 return 'text-gray-600'; // 默认颜色
@@ -329,30 +326,11 @@ function OrderList(props: OrderListProps) {
}; };
// 申请退款 (待发货状态) // 申请退款 (待发货状态)
const applyRefund = async (order: ShopOrder) => { const applyRefund = (order: ShopOrder) => {
try { // 跳转到退款申请页面(订单状态在选择退款原因后再更新)
// 更新订单状态为"退款申请中" Taro.navigateTo({
await updateShopOrder({ url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
orderId: order.orderId, });
orderStatus: 4 // 退款申请中
});
// 更新本地状态
setList(prev => prev.map(item =>
item.orderId === order.orderId ? {...item, orderStatus: 4} : item
));
// 跳转到退款申请页面
Taro.navigateTo({
url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
});
} catch (error) {
console.error('更新订单状态失败:', error);
Taro.showToast({
title: '操作失败,请重试',
icon: 'none'
});
}
}; };
// 查看物流 (待收货状态) // 查看物流 (待收货状态)
@@ -839,7 +817,7 @@ function OrderList(props: OrderListProps) {
)} )}
{/* 待发货状态:显示申请退款 */} {/* 待发货状态:显示申请退款 */}
{item.payStatus && isWithinRefundWindow(item.payTime, 60) && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && !isOrderCompleted(item) && ( {item.payStatus && isWithinRefundWindow(item.payTime, 60) && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && item.orderStatus !== 6 && item.orderStatus !== 7 && !isOrderCompleted(item) && (
<Button size={'small'} onClick={(e) => { <Button size={'small'} onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
applyRefund(item); applyRefund(item);
@@ -847,7 +825,7 @@ function OrderList(props: OrderListProps) {
)} )}
{/* 待收货状态:显示查看物流和确认收货 */} {/* 待收货状态:显示查看物流和确认收货 */}
{item.deliveryStatus === 20 && (!item.riderId || !!item.sendEndTime) && item.orderStatus !== 2 && item.orderStatus !== 6 && !isOrderCompleted(item) && ( {item.deliveryStatus === 20 && (!item.riderId || Number(item.riderId) === 0 || !!item.sendEndTime) && item.orderStatus !== 2 && item.orderStatus !== 6 && !isOrderCompleted(item) && (
<Space> <Space>
{/*<Button size={'small'} onClick={(e) => {*/} {/*<Button size={'small'} onClick={(e) => {*/}
{/* e.stopPropagation();*/} {/* e.stopPropagation();*/}

View File

@@ -8,13 +8,12 @@ import {
RadioGroup, RadioGroup,
TextArea, TextArea,
Button, Button,
Uploader,
Loading, Loading,
Empty,
InputNumber InputNumber
} from '@nutui/nutui-react-taro' } from '@nutui/nutui-react-taro'
import { applyAfterSale } from '@/api/afterSale' import { applyAfterSale } from '@/api/afterSale'
import { updateShopOrder } from '@/api/shop/shopOrder' import { getShopOrder, updateShopOrder } from '@/api/shop/shopOrder'
import { listShopOrderGoods } from '@/api/shop/shopOrderGoods'
import './index.scss' import './index.scss'
// 订单商品信息 // 订单商品信息
@@ -60,6 +59,7 @@ const RefundPage: React.FC = () => {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [submitting, setSubmitting] = useState(false) const [submitting, setSubmitting] = useState(false)
const [markedClientRefund, setMarkedClientRefund] = useState(false)
const [orderGoods, setOrderGoods] = useState<OrderGoods[]>([]) const [orderGoods, setOrderGoods] = useState<OrderGoods[]>([])
const [orderAmount, setOrderAmount] = useState(0) const [orderAmount, setOrderAmount] = useState(0)
const [refundApp, setRefundApp] = useState<RefundApplication>({ const [refundApp, setRefundApp] = useState<RefundApplication>({
@@ -73,6 +73,40 @@ const RefundPage: React.FC = () => {
isUrgent: false 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(() => { useEffect(() => {
if (orderId) { if (orderId) {
loadOrderInfo() loadOrderInfo()
@@ -84,45 +118,52 @@ const RefundPage: React.FC = () => {
try { try {
setLoading(true) setLoading(true)
// 模拟API调用 if (!orderId) {
const mockOrderGoods: OrderGoods[] = [ throw new Error('缺少订单ID')
{ }
goodsId: '1',
goodsName: 'iPhone 15 Pro Max 256GB 深空黑色',
goodsImage: 'https://via.placeholder.com/100x100',
goodsPrice: 9999,
goodsNum: 1,
canRefundNum: 1,
skuInfo: '颜色深空黑色容量256GB'
},
{
goodsId: '2',
goodsName: 'AirPods Pro 第三代',
goodsImage: 'https://via.placeholder.com/100x100',
goodsPrice: 1999,
goodsNum: 2,
canRefundNum: 2,
skuInfo: '颜色:白色'
}
]
const totalAmount = mockOrderGoods.reduce((sum, goods) => const orderIdNum = Number.parseInt(String(orderId), 10)
sum + goods.goodsPrice * goods.goodsNum, 0 if (!Number.isFinite(orderIdNum)) {
) throw new Error('订单ID不合法')
}
await new Promise(resolve => setTimeout(resolve, 1000)) // 以订单实付金额为准(避免商品单价合计与优惠/运费等不一致)
const order = await getShopOrder(orderIdNum)
const payAmount = toMoneyNumber(order?.payPrice ?? order?.totalPrice, 0)
setOrderGoods(mockOrderGoods) // 商品信息加载失败时,不阻塞退款申请(全额退款不依赖商品明细)
setOrderAmount(totalAmount) 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 => ({ setRefundApp(prev => ({
...prev, ...prev,
refundAmount: totalAmount, refundType: 'full',
refundGoods: mockOrderGoods.map(goods => ({ refundAmount: payAmount,
goodsId: goods.goodsId, refundGoods: mappedGoods.map(g => ({
refundNum: goods.goodsNum goodsId: g.goodsId,
})) refundNum: g.goodsNum
})),
evidenceImages: []
})) }))
} catch (error) { } catch (error) {
@@ -180,27 +221,6 @@ const RefundPage: React.FC = () => {
updateRefundApp('refundAmount', newRefundAmount) updateRefundApp('refundAmount', newRefundAmount)
} }
// 处理图片上传
const handleImageUpload = async (files: any) => {
try {
const uploadedImages: string[] = []
for (const file of files) {
if (file.url) {
uploadedImages.push(file.url)
}
}
updateRefundApp('evidenceImages', uploadedImages)
} catch (error) {
console.error('图片上传失败:', error)
Taro.showToast({
title: '图片上传失败',
icon: 'none'
})
}
}
// 提交退款申请 // 提交退款申请
const submitRefund = async () => { const submitRefund = async () => {
try { try {
@@ -243,23 +263,30 @@ const RefundPage: React.FC = () => {
amount: refundApp.refundAmount, amount: refundApp.refundAmount,
contactPhone: refundApp.contactPhone, contactPhone: refundApp.contactPhone,
evidenceImages: refundApp.evidenceImages, evidenceImages: refundApp.evidenceImages,
goodsItems: refundApp.refundGoods.filter(item => item.refundNum > 0).map(item => ({ ...(refundApp.refundGoods.some(item => item.refundNum > 0)
goodsId: item.goodsId, ? {
quantity: item.refundNum goodsItems: refundApp.refundGoods
})) .filter(item => item.refundNum > 0)
.map(item => ({
goodsId: item.goodsId,
quantity: item.refundNum
}))
}
: {})
} }
// 调用API提交退款申请 // 调用API提交退款申请
const result = await applyAfterSale(params) const result = await applyAfterSale(params)
if (result.success) { if (result.success) {
// 更新订单状态为"退款申请中" // 更新订单状态为"客户端申请退款"
if (orderId) { if (orderId) {
try { try {
await updateShopOrder({ await updateShopOrder({
orderId: parseInt(orderId), orderId: parseInt(orderId),
orderStatus: 4 // 退款申请中 orderStatus: 7 // 客户端申请退款
}) })
setMarkedClientRefund(true)
} catch (updateError) { } catch (updateError) {
console.error('更新订单状态失败:', updateError) console.error('更新订单状态失败:', updateError)
// 即使更新订单状态失败,也继续执行后续操作 // 即使更新订单状态失败,也继续执行后续操作
@@ -301,27 +328,12 @@ const RefundPage: React.FC = () => {
) )
} }
if (orderGoods.length === 0) {
return (
<View className="refund-page">
<Empty
description="暂无订单信息"
imageSize={80}
>
<Button type="primary" size="small" onClick={() => Taro.navigateBack()}>
</Button>
</Empty>
</View>
)
}
return ( return (
<View className="refund-page"> <View className="refund-page">
{/* 订单信息 */} {/* 订单信息 */}
<View className="order-info"> <View className="order-info">
<Text className="order-no">{orderNo}</Text> <Text className="order-no">{orderNo}</Text>
<Text className="order-amount">¥{orderAmount}</Text> <Text className="order-amount">¥{formatMoney(orderAmount)}</Text>
</View> </View>
{/* 退款类型选择 */} {/* 退款类型选择 */}
@@ -369,7 +381,7 @@ const RefundPage: React.FC = () => {
value={refundNum} value={refundNum}
min={0} min={0}
max={goods.canRefundNum} max={goods.canRefundNum}
onChange={(value) => updateGoodsRefundNum(goods.goodsId, value)} onChange={(value) => updateGoodsRefundNum(goods.goodsId, Number(value) || 0)}
/> />
<Text className="max-num">{goods.canRefundNum}</Text> <Text className="max-num">{goods.canRefundNum}</Text>
</View> </View>
@@ -382,7 +394,7 @@ const RefundPage: React.FC = () => {
{/* 退款金额 */} {/* 退款金额 */}
<CellGroup title="退款金额"> <CellGroup title="退款金额">
<Cell> <Cell>
<Text className="refund-amount">¥{refundApp.refundAmount}</Text> <Text className="refund-amount">¥{formatMoney(refundApp.refundAmount)}</Text>
</Cell> </Cell>
</CellGroup> </CellGroup>
@@ -390,7 +402,10 @@ const RefundPage: React.FC = () => {
<CellGroup title="退款原因"> <CellGroup title="退款原因">
<RadioGroup <RadioGroup
value={refundApp.refundReason} value={refundApp.refundReason}
onChange={(value) => updateRefundApp('refundReason', value)} onChange={(value) => {
updateRefundApp('refundReason', value)
void markOrderClientRefund()
}}
> >
{REFUND_REASONS.map(reason => ( {REFUND_REASONS.map(reason => (
<Cell key={reason} className="reason-cell"> <Cell key={reason} className="reason-cell">
@@ -414,18 +429,18 @@ const RefundPage: React.FC = () => {
/> />
</View> </View>
{/* 凭证图片 */} {/* 凭证图片:当前上传链路不可用,先隐藏(保留数据结构,后续可恢复) */}
<View className="evidence-section"> {/*<View className="evidence-section">*/}
<View className="section-title"></View> {/* <View className="section-title">上传凭证(可选)</View>*/}
<Uploader {/* <Uploader*/}
value={refundApp.evidenceImages.map(url => ({ url }))} {/* value={refundApp.evidenceImages.map(url => ({ url }))}*/}
onChange={handleImageUpload} {/* onChange={handleImageUpload}*/}
multiple {/* multiple*/}
maxCount={6} {/* maxCount={6}*/}
previewType="picture" {/* previewType="picture"*/}
deletable {/* deletable*/}
/> {/* />*/}
</View> {/*</View>*/}
{/* 其他选项 */} {/* 其他选项 */}
{/*<CellGroup>*/} {/*<CellGroup>*/}