forked from gxwebsoft/mp-10550
feat(user/order): 新增订单评价功能
- 添加订单评价页面组件 - 实现订单信息和商品列表展示 - 添加评价功能相关的样式和布局 - 优化订单列表页面,增加申请退款和查看物流等功能
This commit is contained in:
388
src/user/order/progress/index.tsx
Normal file
388
src/user/order/progress/index.tsx
Normal file
@@ -0,0 +1,388 @@
|
||||
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
|
||||
Reference in New Issue
Block a user