fix(api): 替换所有接口请求的域名为 guilixu-api.websoft.top
- 将获取 STS Token 的接口地址由 paopao-api 改为 guilixu-api - 更新图片上传接口地址为新的 guilixu-api 域名 - 修改用户推广页面中邀请码链接和二维码接口的域名 - 更改注册页微信登录接口请求的域名为 guilixu-api
This commit is contained in:
340
src/pages/order/after-sale-apply.tsx
Normal file
340
src/pages/order/after-sale-apply.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { View, Text, Input, Image, ScrollView } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { Button, Uploader } from '@nutui/nutui-react-taro'
|
||||
import { getShopOrder } from '@/api/shop/shopOrder'
|
||||
import type { ShopOrder } from '@/api/shop/shopOrder/model'
|
||||
import Price from '@/components/common/Price'
|
||||
|
||||
definePageConfig({
|
||||
navigationBarTitleText: '申请售后',
|
||||
})
|
||||
|
||||
// 售后类型
|
||||
const AFTER_SALE_TYPES = [
|
||||
{ value: 'refund', label: '退款', desc: '仅退款,商品无需退回' },
|
||||
{ value: 'return', label: '退货退款', desc: '退款并退货,商品需退回' },
|
||||
]
|
||||
|
||||
// 退款原因选项
|
||||
const REASON_OPTIONS = [
|
||||
'商品损坏/有瑕疵',
|
||||
'商品与描述不符',
|
||||
'收到错误商品',
|
||||
'商品少发/漏发',
|
||||
'不想要了',
|
||||
'其他原因',
|
||||
]
|
||||
|
||||
const AfterSaleApplyPage: React.FC = () => {
|
||||
const { orderId, type } = Taro.getCurrentInstance().router?.params || {}
|
||||
const [order, setOrder] = useState<ShopOrder | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// 售后类型
|
||||
const [afterSaleType, setAfterSaleType] = useState<'refund' | 'return'>(
|
||||
(type as 'refund' | 'return') || 'refund'
|
||||
)
|
||||
|
||||
// 退款原因
|
||||
const [selectedReason, setSelectedReason] = useState('')
|
||||
const [reasonDesc, setReasonDesc] = useState('')
|
||||
|
||||
// 图片上传
|
||||
const [images, setImages] = useState<string[]>([])
|
||||
|
||||
// 选中的商品
|
||||
const [selectedItems, setSelectedItems] = useState<number[]>([])
|
||||
|
||||
// 可退金额
|
||||
const [refundAmount, setRefundAmount] = useState('0.00')
|
||||
|
||||
useEffect(() => {
|
||||
if (orderId) {
|
||||
loadOrder()
|
||||
}
|
||||
}, [orderId])
|
||||
|
||||
const loadOrder = async () => {
|
||||
try {
|
||||
const res = await getShopOrder(Number(orderId))
|
||||
if (res) {
|
||||
setOrder(res)
|
||||
// 默认选中所有商品
|
||||
const allItemIndices = (res.orderGoods || []).map((_, idx) => idx)
|
||||
setSelectedItems(allItemIndices)
|
||||
// 计算可退金额
|
||||
const amount = calculateRefundAmount(res, allItemIndices)
|
||||
setRefundAmount(amount)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载订单失败', err)
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
// 计算可退金额
|
||||
const calculateRefundAmount = (orderData: ShopOrder, selectedIdx: number[]): string => {
|
||||
if (!orderData.orderGoods || selectedIdx.length === 0) return '0.00'
|
||||
|
||||
let total = 0
|
||||
orderData.orderGoods.forEach((item, idx) => {
|
||||
if (selectedIdx.includes(idx)) {
|
||||
total += Number(item.price || 0) * (item.num || item.quantity || 1)
|
||||
}
|
||||
})
|
||||
|
||||
// 按比例计算优惠分摊
|
||||
const totalPrice = Number(orderData.totalPrice || 0)
|
||||
if (totalPrice > 0) {
|
||||
const payPrice = Number(orderData.payPrice || 0)
|
||||
const ratio = payPrice / totalPrice
|
||||
total = total * ratio
|
||||
}
|
||||
|
||||
return total.toFixed(2)
|
||||
}
|
||||
|
||||
// 切换商品选中
|
||||
const toggleItem = (idx: number) => {
|
||||
const newSelected = selectedItems.includes(idx)
|
||||
? selectedItems.filter(i => i !== idx)
|
||||
: [...selectedItems, idx]
|
||||
setSelectedItems(newSelected)
|
||||
if (order) {
|
||||
setRefundAmount(calculateRefundAmount(order, newSelected))
|
||||
}
|
||||
}
|
||||
|
||||
// 全选/取消全选
|
||||
const toggleAll = () => {
|
||||
if (!order?.orderGoods) return
|
||||
if (selectedItems.length === order.orderGoods.length) {
|
||||
setSelectedItems([])
|
||||
setRefundAmount('0.00')
|
||||
} else {
|
||||
const allIdx = order.orderGoods.map((_, idx) => idx)
|
||||
setSelectedItems(allIdx)
|
||||
setRefundAmount(calculateRefundAmount(order, allIdx))
|
||||
}
|
||||
}
|
||||
|
||||
// 提交申请
|
||||
const handleSubmit = async () => {
|
||||
if (selectedItems.length === 0) {
|
||||
Taro.showToast({ title: '请选择要售后的商品', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!selectedReason) {
|
||||
Taro.showToast({ title: '请选择退款原因', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
Taro.showModal({
|
||||
title: '确认提交',
|
||||
content: `确认提交${afterSaleType === 'refund' ? '退款' : '退货退款'}申请?`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
await submitApply()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const submitApply = async () => {
|
||||
if (!selectedReason) {
|
||||
Taro.showToast({ title: '请选择退款原因', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await applyAfterSale({
|
||||
orderId: orderId || '',
|
||||
type: afterSaleType,
|
||||
reason: selectedReason,
|
||||
description: reasonDesc,
|
||||
amount: parseFloat(refundAmount),
|
||||
evidenceImages: images
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
Taro.showToast({ title: '申请提交成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
Taro.redirectTo({ url: '/pages/order/after-sale-list' })
|
||||
}, 1500)
|
||||
} else {
|
||||
Taro.showToast({ title: res.message || '提交失败', icon: 'none' })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('申请售后失败:', err)
|
||||
Taro.showToast({ title: '提交失败', icon: 'none' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (!order) {
|
||||
return (
|
||||
<View className='min-h-screen bg-gray-50 flex items-center justify-center'>
|
||||
<Text className='text-gray-400 text-sm'>加载中...</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='min-h-screen bg-gray-50 pb-20 flex flex-col'>
|
||||
<ScrollView scrollY className='flex-1'>
|
||||
{/* 售后类型选择 */}
|
||||
<View className='bg-white mx-3 mt-3 p-4 rounded-xl'>
|
||||
<Text className='text-sm font-medium text-gray-800 mb-3 block'>售后类型</Text>
|
||||
<View className='flex gap-3'>
|
||||
{AFTER_SALE_TYPES.map(item => (
|
||||
<View
|
||||
key={item.value}
|
||||
className={`flex-1 p-3 rounded-lg border-2 text-center ${
|
||||
afterSaleType === item.value
|
||||
? 'border-green-500 bg-green-50'
|
||||
: 'border-gray-200 bg-white'
|
||||
}`}
|
||||
onClick={() => setAfterSaleType(item.value as 'refund' | 'return')}
|
||||
>
|
||||
<Text className={`text-sm font-medium block ${
|
||||
afterSaleType === item.value ? 'text-green-600' : 'text-gray-600'
|
||||
}`}>
|
||||
{item.label}
|
||||
</Text>
|
||||
<Text className='text-xs text-gray-400 mt-1 block'>{item.desc}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 商品选择 */}
|
||||
<View className='bg-white mx-3 mt-3 p-4 rounded-xl'>
|
||||
<View className='flex justify-between items-center mb-3'>
|
||||
<Text className='text-sm font-medium text-gray-800'>选择商品</Text>
|
||||
<Text
|
||||
className='text-xs text-green-600'
|
||||
onClick={toggleAll}
|
||||
>
|
||||
{selectedItems.length === order.orderGoods?.length ? '取消全选' : '全选'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{order.orderGoods?.map((item, idx) => (
|
||||
<View
|
||||
key={idx}
|
||||
className='flex items-center gap-3 py-3 border-b border-gray-50'
|
||||
style={idx === (order.orderGoods?.length ?? 0) - 1 ? { borderBottom: 'none' } : undefined}
|
||||
onClick={() => toggleItem(idx)}
|
||||
>
|
||||
{/* 选中状态 */}
|
||||
<View className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${
|
||||
selectedItems.includes(idx)
|
||||
? 'border-green-500 bg-green-500'
|
||||
: 'border-gray-300'
|
||||
}`}>
|
||||
{selectedItems.includes(idx) && (
|
||||
<Text className='text-white text-xs'>✓</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 商品图片 */}
|
||||
<Image
|
||||
className='w-16 h-16 rounded-lg'
|
||||
src={item.image || ''}
|
||||
mode='aspectFill'
|
||||
/>
|
||||
|
||||
{/* 商品信息 */}
|
||||
<View className='flex-1'>
|
||||
<Text className='text-sm text-gray-700 line-clamp-2'>{item.goodsName}</Text>
|
||||
<View className='flex justify-between items-center mt-1'>
|
||||
<Price price={item.price || '0'} size='small' />
|
||||
<Text className='text-xs text-gray-500'>x{item.num || item.quantity || 1}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 退款原因 */}
|
||||
<View className='bg-white mx-3 mt-3 p-4 rounded-xl'>
|
||||
<Text className='text-sm font-medium text-gray-800 mb-3 block'>退款原因</Text>
|
||||
<View className='flex flex-wrap gap-2'>
|
||||
{REASON_OPTIONS.map(reason => (
|
||||
<View
|
||||
key={reason}
|
||||
className={`px-3 py-2 rounded-full text-sm ${
|
||||
selectedReason === reason
|
||||
? 'bg-green-50 text-green-600 border border-green-500'
|
||||
: 'bg-gray-50 text-gray-600 border border-gray-200'
|
||||
}`}
|
||||
onClick={() => setSelectedReason(reason)}
|
||||
>
|
||||
<Text>{reason}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 补充说明 */}
|
||||
<View className='mt-3'>
|
||||
<Text className='text-xs text-gray-500 mb-1 block'>补充说明(选填)</Text>
|
||||
<View className='bg-gray-50 rounded-lg p-3'>
|
||||
<Input
|
||||
className='w-full text-sm'
|
||||
placeholder='请详细描述您遇到的问题'
|
||||
value={reasonDesc}
|
||||
onInput={e => setReasonDesc(e.detail.value)}
|
||||
style={{ minHeight: '80px' }}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 上传凭证 */}
|
||||
<View className='bg-white mx-3 mt-3 p-4 rounded-xl'>
|
||||
<Text className='text-sm font-medium text-gray-800 mb-3 block'>上传凭证(选填)</Text>
|
||||
<Uploader
|
||||
value={images.map((url, idx) => ({ id: String(idx), url }))}
|
||||
onChange={(files) => {
|
||||
setImages(files.map(f => f.url || ''))
|
||||
}}
|
||||
maxCount={3}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 退款金额 */}
|
||||
<View className='bg-white mx-3 mt-3 p-4 rounded-xl mb-4'>
|
||||
<View className='flex justify-between items-center'>
|
||||
<Text className='text-sm font-medium text-gray-800'>预计退款金额</Text>
|
||||
<Text className='text-lg font-bold text-red-500'>
|
||||
{'\u00A5'}{refundAmount}
|
||||
</Text>
|
||||
</View>
|
||||
{afterSaleType === 'return' && (
|
||||
<Text className='text-xs text-gray-400 mt-2 block'>
|
||||
退货时请将商品寄回,商家收货后退款将原路返回
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* 提交按钮 */}
|
||||
<View className='bg-white border-t border-gray-100 p-3' style={{ paddingBottom: '20px' }}>
|
||||
<Button
|
||||
type='primary'
|
||||
className='w-full rounded-full'
|
||||
loading={loading}
|
||||
style={{ backgroundColor: '#0e932e' }}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
提交申请
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default AfterSaleApplyPage
|
||||
Reference in New Issue
Block a user