457 lines
13 KiB
TypeScript
457 lines
13 KiB
TypeScript
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,
|
||
Uploader,
|
||
Loading,
|
||
Empty,
|
||
InputNumber
|
||
} from '@nutui/nutui-react-taro'
|
||
import { applyAfterSale } from '@/api/afterSale'
|
||
import { updateShopOrder } from '@/api/shop/shopOrder'
|
||
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 [orderGoods, setOrderGoods] = useState<OrderGoods[]>([])
|
||
const [orderAmount, setOrderAmount] = useState(0)
|
||
const [refundApp, setRefundApp] = useState<RefundApplication>({
|
||
refundType: 'full',
|
||
refundReason: '',
|
||
refundDescription: '',
|
||
refundAmount: 0,
|
||
refundGoods: [],
|
||
evidenceImages: [],
|
||
contactPhone: '',
|
||
isUrgent: false
|
||
})
|
||
|
||
useEffect(() => {
|
||
if (orderId) {
|
||
loadOrderInfo()
|
||
}
|
||
}, [orderId])
|
||
|
||
// 加载订单信息
|
||
const loadOrderInfo = async () => {
|
||
try {
|
||
setLoading(true)
|
||
|
||
// 模拟API调用
|
||
const mockOrderGoods: OrderGoods[] = [
|
||
{
|
||
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) =>
|
||
sum + goods.goodsPrice * goods.goodsNum, 0
|
||
)
|
||
|
||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||
|
||
setOrderGoods(mockOrderGoods)
|
||
setOrderAmount(totalAmount)
|
||
|
||
// 初始化退款申请信息
|
||
setRefundApp(prev => ({
|
||
...prev,
|
||
refundAmount: totalAmount,
|
||
refundGoods: mockOrderGoods.map(goods => ({
|
||
goodsId: goods.goodsId,
|
||
refundNum: goods.goodsNum
|
||
}))
|
||
}))
|
||
|
||
} 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 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 () => {
|
||
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,
|
||
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: 4 // 退款申请中
|
||
})
|
||
} 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 (
|
||
<View className="refund-page">
|
||
<View className="loading-container">
|
||
<Loading type="spinner" />
|
||
<Text className="loading-text">正在加载订单信息...</Text>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
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 (
|
||
<View className="refund-page">
|
||
{/* 订单信息 */}
|
||
<View className="order-info">
|
||
<Text className="order-no">订单号:{orderNo}</Text>
|
||
<Text className="order-amount">订单金额:¥{orderAmount}</Text>
|
||
</View>
|
||
|
||
{/* 退款类型选择 */}
|
||
{/*<CellGroup title="退款类型">*/}
|
||
{/* <RadioGroup */}
|
||
{/* value={refundApp.refundType}*/}
|
||
{/* onChange={(value) => handleRefundTypeChange(value as 'full' | 'partial')}*/}
|
||
{/* >*/}
|
||
{/* <Cell>*/}
|
||
{/* <Radio value="full">全额退款</Radio>*/}
|
||
{/* </Cell>*/}
|
||
{/* <Cell>*/}
|
||
{/* <Radio value="partial">部分退款</Radio>*/}
|
||
{/* </Cell>*/}
|
||
{/* </RadioGroup>*/}
|
||
{/*</CellGroup>*/}
|
||
|
||
{/* 商品列表 */}
|
||
{refundApp.refundType === 'partial' && (
|
||
<View className="goods-section">
|
||
<View className="section-title">选择退款商品</View>
|
||
{orderGoods.map(goods => {
|
||
const refundGoods = refundApp.refundGoods.find(item => item.goodsId === goods.goodsId)
|
||
const refundNum = refundGoods?.refundNum || 0
|
||
|
||
return (
|
||
<View key={goods.goodsId} className="goods-item">
|
||
<View className="goods-info">
|
||
<Image
|
||
className="goods-image"
|
||
src={goods.goodsImage}
|
||
mode="aspectFill"
|
||
/>
|
||
<View className="goods-detail">
|
||
<Text className="goods-name">{goods.goodsName}</Text>
|
||
{goods.skuInfo && (
|
||
<Text className="goods-sku">{goods.skuInfo}</Text>
|
||
)}
|
||
<Text className="goods-price">¥{goods.goodsPrice}</Text>
|
||
</View>
|
||
</View>
|
||
<View className="refund-control">
|
||
<Text className="control-label">退款数量</Text>
|
||
<InputNumber
|
||
value={refundNum}
|
||
min={0}
|
||
max={goods.canRefundNum}
|
||
onChange={(value) => updateGoodsRefundNum(goods.goodsId, value)}
|
||
/>
|
||
<Text className="max-num">最多{goods.canRefundNum}件</Text>
|
||
</View>
|
||
</View>
|
||
)
|
||
})}
|
||
</View>
|
||
)}
|
||
|
||
{/* 退款金额 */}
|
||
<CellGroup title="退款金额">
|
||
<Cell>
|
||
<Text className="refund-amount">¥{refundApp.refundAmount}</Text>
|
||
</Cell>
|
||
</CellGroup>
|
||
|
||
{/* 退款原因 */}
|
||
<CellGroup title="退款原因">
|
||
<RadioGroup
|
||
value={refundApp.refundReason}
|
||
onChange={(value) => updateRefundApp('refundReason', value)}
|
||
>
|
||
{REFUND_REASONS.map(reason => (
|
||
<Cell key={reason} className="reason-cell">
|
||
<Radio value={reason}>{reason}</Radio>
|
||
</Cell>
|
||
))}
|
||
</RadioGroup>
|
||
</CellGroup>
|
||
|
||
{/* 退款说明 */}
|
||
<View className="description-section">
|
||
<View className="section-title">退款说明</View>
|
||
<TextArea
|
||
placeholder="请详细说明退款原因..."
|
||
value={refundApp.refundDescription}
|
||
onChange={(value) => updateRefundApp('refundDescription', value)}
|
||
maxLength={500}
|
||
showCount
|
||
rows={4}
|
||
autoHeight
|
||
/>
|
||
</View>
|
||
|
||
{/* 凭证图片 */}
|
||
<View className="evidence-section">
|
||
<View className="section-title">上传凭证(可选)</View>
|
||
<Uploader
|
||
value={refundApp.evidenceImages.map(url => ({ url }))}
|
||
onChange={handleImageUpload}
|
||
multiple
|
||
maxCount={6}
|
||
previewType="picture"
|
||
deletable
|
||
/>
|
||
</View>
|
||
|
||
{/* 其他选项 */}
|
||
{/*<CellGroup>*/}
|
||
{/* <Cell className="option-cell">*/}
|
||
{/* <Checkbox*/}
|
||
{/* checked={refundApp.isUrgent}*/}
|
||
{/* onChange={(checked) => updateRefundApp('isUrgent', checked)}*/}
|
||
{/* >*/}
|
||
{/* 加急处理(可能产生额外费用)*/}
|
||
{/* </Checkbox>*/}
|
||
{/* </Cell>*/}
|
||
{/*</CellGroup>*/}
|
||
|
||
{/* 提交按钮 */}
|
||
<View className="submit-section">
|
||
<Button
|
||
type="primary"
|
||
block
|
||
loading={submitting}
|
||
onClick={submitRefund}
|
||
>
|
||
{submitting ? '提交中...' : '提交退款申请'}
|
||
</Button>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default RefundPage |