forked from gxwebsoft/mp-10550
389 lines
11 KiB
TypeScript
389 lines
11 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import Taro, { useRouter } from '@tarojs/taro'
|
||
import { View, Text } from '@tarojs/components'
|
||
import {
|
||
Cell,
|
||
CellGroup,
|
||
Loading,
|
||
Empty,
|
||
Button,
|
||
Steps,
|
||
Step,
|
||
Tag,
|
||
Divider
|
||
} from '@nutui/nutui-react-taro'
|
||
import './index.scss'
|
||
|
||
// 售后类型
|
||
type AfterSaleType = 'refund' | 'return' | 'exchange' | 'repair'
|
||
|
||
// 售后状态
|
||
type AfterSaleStatus =
|
||
| 'pending' // 待审核
|
||
| 'approved' // 已同意
|
||
| 'rejected' // 已拒绝
|
||
| 'processing' // 处理中
|
||
| 'completed' // 已完成
|
||
| 'cancelled' // 已取消
|
||
|
||
// 售后进度记录
|
||
interface ProgressRecord {
|
||
id: string
|
||
time: string
|
||
status: string
|
||
description: string
|
||
operator?: string
|
||
remark?: string
|
||
images?: string[]
|
||
}
|
||
|
||
// 售后详情
|
||
interface AfterSaleDetail {
|
||
id: string
|
||
orderId: string
|
||
orderNo: string
|
||
type: AfterSaleType
|
||
status: AfterSaleStatus
|
||
reason: string
|
||
description: string
|
||
amount: number
|
||
applyTime: string
|
||
processTime?: string
|
||
completeTime?: string
|
||
rejectReason?: string
|
||
contactPhone?: string
|
||
evidenceImages: string[]
|
||
progressRecords: ProgressRecord[]
|
||
}
|
||
|
||
// 售后类型映射
|
||
const AFTER_SALE_TYPE_MAP = {
|
||
'refund': '退款',
|
||
'return': '退货',
|
||
'exchange': '换货',
|
||
'repair': '维修'
|
||
}
|
||
|
||
// 售后状态映射
|
||
const AFTER_SALE_STATUS_MAP = {
|
||
'pending': '待审核',
|
||
'approved': '已同意',
|
||
'rejected': '已拒绝',
|
||
'processing': '处理中',
|
||
'completed': '已完成',
|
||
'cancelled': '已取消'
|
||
}
|
||
|
||
// 状态颜色映射
|
||
const STATUS_COLOR_MAP = {
|
||
'pending': '#faad14',
|
||
'approved': '#52c41a',
|
||
'rejected': '#ff4d4f',
|
||
'processing': '#1890ff',
|
||
'completed': '#52c41a',
|
||
'cancelled': '#999'
|
||
}
|
||
|
||
const AfterSaleProgressPage: React.FC = () => {
|
||
const router = useRouter()
|
||
const { orderId, orderNo, type = 'refund' } = router.params
|
||
|
||
const [loading, setLoading] = useState(true)
|
||
const [afterSaleDetail, setAfterSaleDetail] = useState<AfterSaleDetail | null>(null)
|
||
const [error, setError] = useState<string>('')
|
||
|
||
useEffect(() => {
|
||
if (orderId) {
|
||
loadAfterSaleDetail()
|
||
}
|
||
}, [orderId])
|
||
|
||
// 加载售后详情
|
||
const loadAfterSaleDetail = async () => {
|
||
try {
|
||
setLoading(true)
|
||
setError('')
|
||
|
||
// 模拟API调用 - 实际项目中替换为真实API
|
||
const mockAfterSaleDetail: AfterSaleDetail = {
|
||
id: 'AS' + Date.now(),
|
||
orderId: orderId || '',
|
||
orderNo: orderNo || '',
|
||
type: type as AfterSaleType,
|
||
status: 'processing',
|
||
reason: '商品质量问题',
|
||
description: '收到的商品有明显瑕疵,希望申请退款',
|
||
amount: 9999,
|
||
applyTime: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
||
processTime: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
|
||
contactPhone: '138****5678',
|
||
evidenceImages: [
|
||
'https://via.placeholder.com/200x200',
|
||
'https://via.placeholder.com/200x200'
|
||
],
|
||
progressRecords: [
|
||
{
|
||
id: '1',
|
||
time: new Date().toISOString(),
|
||
status: '处理中',
|
||
description: '客服正在处理您的申请,请耐心等待',
|
||
operator: '客服小王',
|
||
remark: '预计1-2个工作日内完成处理'
|
||
},
|
||
{
|
||
id: '2',
|
||
time: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString(),
|
||
status: '已审核',
|
||
description: '您的申请已通过审核,正在安排处理',
|
||
operator: '审核员张三'
|
||
},
|
||
{
|
||
id: '3',
|
||
time: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
|
||
status: '已受理',
|
||
description: '我们已收到您的申请,正在进行审核',
|
||
operator: '系统'
|
||
},
|
||
{
|
||
id: '4',
|
||
time: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
||
status: '已提交',
|
||
description: '您已成功提交售后申请',
|
||
operator: '用户'
|
||
}
|
||
]
|
||
}
|
||
|
||
// 模拟网络延迟
|
||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||
|
||
setAfterSaleDetail(mockAfterSaleDetail)
|
||
|
||
} catch (error) {
|
||
console.error('加载售后详情失败:', error)
|
||
setError('加载售后详情失败,请重试')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
// 刷新进度
|
||
const refreshProgress = () => {
|
||
loadAfterSaleDetail()
|
||
}
|
||
|
||
// 撤销申请
|
||
const cancelApplication = async () => {
|
||
try {
|
||
const result = await Taro.showModal({
|
||
title: '撤销申请',
|
||
content: '确定要撤销售后申请吗?撤销后无法恢复',
|
||
confirmText: '确定撤销',
|
||
cancelText: '取消'
|
||
})
|
||
|
||
if (!result.confirm) {
|
||
return
|
||
}
|
||
|
||
Taro.showLoading({
|
||
title: '撤销中...'
|
||
})
|
||
|
||
// 模拟API调用
|
||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||
|
||
Taro.hideLoading()
|
||
|
||
Taro.showToast({
|
||
title: '撤销成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 延迟返回上一页
|
||
setTimeout(() => {
|
||
Taro.navigateBack()
|
||
}, 1500)
|
||
|
||
} catch (error) {
|
||
Taro.hideLoading()
|
||
console.error('撤销申请失败:', error)
|
||
Taro.showToast({
|
||
title: '撤销失败,请重试',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 联系客服
|
||
const contactService = () => {
|
||
Taro.showModal({
|
||
title: '联系客服',
|
||
content: '客服电话:400-123-4567\n工作时间:9:00-18:00\n\n您也可以通过在线客服获得帮助',
|
||
showCancel: false
|
||
})
|
||
}
|
||
|
||
// 格式化时间
|
||
const formatTime = (timeStr: string) => {
|
||
const date = new Date(timeStr)
|
||
return `${date.getMonth() + 1}-${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||
}
|
||
|
||
// 格式化完整时间
|
||
const formatFullTime = (timeStr: string) => {
|
||
const date = new Date(timeStr)
|
||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||
}
|
||
|
||
if (loading) {
|
||
return (
|
||
<View className="progress-page">
|
||
<View className="loading-container">
|
||
<Loading type="spinner" />
|
||
<Text className="loading-text">正在加载售后进度...</Text>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<View className="progress-page">
|
||
<Empty
|
||
description={error}
|
||
imageSize={80}
|
||
>
|
||
<Button type="primary" size="small" onClick={refreshProgress}>
|
||
重新加载
|
||
</Button>
|
||
</Empty>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
if (!afterSaleDetail) {
|
||
return (
|
||
<View className="progress-page">
|
||
<Empty
|
||
description="暂无售后信息"
|
||
imageSize={80}
|
||
>
|
||
<Button type="primary" size="small" onClick={() => Taro.navigateBack()}>
|
||
返回
|
||
</Button>
|
||
</Empty>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<View className="progress-page">
|
||
{/* 售后基本信息 */}
|
||
<View className="after-sale-header">
|
||
<View className="header-top">
|
||
<View className="type-status">
|
||
<Text className="type-text">
|
||
{AFTER_SALE_TYPE_MAP[afterSaleDetail.type]}申请
|
||
</Text>
|
||
<Tag
|
||
color={STATUS_COLOR_MAP[afterSaleDetail.status]}
|
||
className="status-tag"
|
||
>
|
||
{AFTER_SALE_STATUS_MAP[afterSaleDetail.status]}
|
||
</Tag>
|
||
</View>
|
||
<Button size="small" fill="outline" onClick={refreshProgress}>
|
||
刷新
|
||
</Button>
|
||
</View>
|
||
|
||
<View className="header-info">
|
||
<Text className="order-no">订单号:{afterSaleDetail.orderNo}</Text>
|
||
<Text className="apply-time">
|
||
申请时间:{formatFullTime(afterSaleDetail.applyTime)}
|
||
</Text>
|
||
<Text className="amount">申请金额:¥{afterSaleDetail.amount}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 进度时间线 */}
|
||
<View className="progress-timeline">
|
||
<View className="timeline-header">
|
||
<Text className="timeline-title">处理进度</Text>
|
||
</View>
|
||
|
||
<View className="timeline-list">
|
||
{afterSaleDetail.progressRecords.map((record, index) => (
|
||
<View key={record.id} className={`timeline-item ${index === 0 ? 'current' : ''}`}>
|
||
<View className="timeline-dot" />
|
||
<View className="timeline-content">
|
||
<View className="timeline-info">
|
||
<Text className="timeline-status">{record.status}</Text>
|
||
<Text className="timeline-time">{formatTime(record.time)}</Text>
|
||
</View>
|
||
<Text className="timeline-description">{record.description}</Text>
|
||
{record.operator && (
|
||
<Text className="timeline-operator">操作人:{record.operator}</Text>
|
||
)}
|
||
{record.remark && (
|
||
<Text className="timeline-remark">{record.remark}</Text>
|
||
)}
|
||
</View>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
|
||
{/* 申请详情 */}
|
||
<CellGroup title="申请详情">
|
||
<Cell title="申请原因" value={afterSaleDetail.reason} />
|
||
<Cell title="问题描述" value={afterSaleDetail.description} />
|
||
{afterSaleDetail.contactPhone && (
|
||
<Cell title="联系电话" value={afterSaleDetail.contactPhone} />
|
||
)}
|
||
</CellGroup>
|
||
|
||
{/* 凭证图片 */}
|
||
{afterSaleDetail.evidenceImages.length > 0 && (
|
||
<View className="evidence-section">
|
||
<View className="section-title">凭证图片</View>
|
||
<View className="image-list">
|
||
{afterSaleDetail.evidenceImages.map((image, index) => (
|
||
<View key={index} className="image-item">
|
||
<image
|
||
src={image}
|
||
mode="aspectFill"
|
||
className="evidence-image"
|
||
onClick={() => {
|
||
Taro.previewImage({
|
||
urls: afterSaleDetail.evidenceImages,
|
||
current: image
|
||
})
|
||
}}
|
||
/>
|
||
</View>
|
||
))}
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* 底部操作 */}
|
||
<View className="progress-footer">
|
||
<View className="footer-buttons">
|
||
<Button onClick={contactService}>
|
||
联系客服
|
||
</Button>
|
||
{(afterSaleDetail.status === 'pending' || afterSaleDetail.status === 'approved') && (
|
||
<Button type="primary" onClick={cancelApplication}>
|
||
撤销申请
|
||
</Button>
|
||
)}
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default AfterSaleProgressPage
|