forked from gxwebsoft/mp-10550
283 lines
9.4 KiB
TypeScript
283 lines
9.4 KiB
TypeScript
import {useEffect, useState} from "react";
|
||
import {Cell, CellGroup, Image, Space, Button, Dialog} from '@nutui/nutui-react-taro'
|
||
import Taro from '@tarojs/taro'
|
||
import {View} from '@tarojs/components'
|
||
import {ShopOrder} from "@/api/shop/shopOrder/model";
|
||
import {getShopOrder, updateShopOrder} from "@/api/shop/shopOrder";
|
||
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
||
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
||
import dayjs from "dayjs";
|
||
import PaymentCountdown from "@/components/PaymentCountdown";
|
||
import './index.scss'
|
||
|
||
// 申请退款:支付成功后仅允许在指定时间窗内发起(前端展示层限制,后端仍应校验)
|
||
const isWithinRefundWindow = (payTime?: string, windowMinutes: number = 60): boolean => {
|
||
if (!payTime) return false;
|
||
const raw = String(payTime).trim();
|
||
const t = /^\d+$/.test(raw)
|
||
? dayjs(Number(raw) < 1e12 ? Number(raw) * 1000 : Number(raw)) // 兼容秒/毫秒时间戳
|
||
: dayjs(raw);
|
||
if (!t.isValid()) return false;
|
||
return dayjs().diff(t, 'minute') <= windowMinutes;
|
||
};
|
||
|
||
const OrderDetail = () => {
|
||
const [order, setOrder] = useState<ShopOrder | null>(null);
|
||
const [orderGoodsList, setOrderGoodsList] = useState<ShopOrderGoods[]>([]);
|
||
const [confirmReceiveDialogVisible, setConfirmReceiveDialogVisible] = useState(false)
|
||
const router = Taro.getCurrentInstance().router;
|
||
const orderId = router?.params?.orderId;
|
||
|
||
// 处理支付超时
|
||
const handlePaymentExpired = async () => {
|
||
if (!order) return;
|
||
if (!order.orderId) return;
|
||
|
||
try {
|
||
// 自动取消过期订单
|
||
await updateShopOrder({
|
||
// 只传最小字段,避免误取消/误走售后流程
|
||
orderId: order.orderId,
|
||
orderStatus: 2 // 已取消
|
||
});
|
||
|
||
// 更新本地状态
|
||
setOrder(prev => prev ? {...prev, orderStatus: 2} : null);
|
||
|
||
Taro.showToast({
|
||
title: '订单已自动取消',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
} catch (error) {
|
||
console.error('自动取消订单失败:', error);
|
||
}
|
||
};
|
||
|
||
// 申请退款
|
||
const handleApplyRefund = async () => {
|
||
if (order) {
|
||
try {
|
||
const confirm = await Taro.showModal({
|
||
title: '申请退款',
|
||
content: '确认要申请退款吗?',
|
||
confirmText: '确认',
|
||
cancelText: '取消'
|
||
})
|
||
if (!confirm?.confirm) return
|
||
|
||
Taro.showLoading({ title: '提交中...' })
|
||
|
||
// 退款相关操作使用退款接口:PUT /api/shop/shop-order/refund
|
||
await updateShopOrder({
|
||
orderId: order.orderId,
|
||
refundMoney: order.payPrice || order.totalPrice,
|
||
orderStatus: 7
|
||
})
|
||
|
||
// 乐观更新本地状态
|
||
setOrder(prev => prev ? { ...prev, orderStatus: 7 } : null)
|
||
|
||
Taro.showToast({ title: '退款申请已提交', icon: 'success' })
|
||
} catch (error) {
|
||
console.error('申请退款失败:', error);
|
||
Taro.showToast({
|
||
title: '操作失败,请重试',
|
||
icon: 'none'
|
||
});
|
||
} finally {
|
||
try {
|
||
Taro.hideLoading()
|
||
} catch (_e) {
|
||
// ignore
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
// 确认收货(客户)
|
||
const handleConfirmReceive = async () => {
|
||
if (!order?.orderId) return
|
||
try {
|
||
setConfirmReceiveDialogVisible(false)
|
||
await updateShopOrder({
|
||
orderId: order.orderId,
|
||
deliveryStatus: order.deliveryStatus, // 10未发货 20已发货 30部分发货
|
||
orderStatus: 1 // 已完成
|
||
})
|
||
Taro.showToast({title: '确认收货成功', icon: 'success'})
|
||
setOrder(prev => (prev ? {...prev, orderStatus: 1} : prev))
|
||
} catch (e) {
|
||
console.error('确认收货失败:', e)
|
||
Taro.showToast({title: '确认收货失败', icon: 'none'})
|
||
setConfirmReceiveDialogVisible(true)
|
||
}
|
||
}
|
||
|
||
const getOrderStatusText = (order: ShopOrder) => {
|
||
// 优先检查订单状态
|
||
if (order.orderStatus === 2) return '已取消';
|
||
if (order.orderStatus === 3) return '取消中';
|
||
if (order.orderStatus === 4) return '退款申请中';
|
||
if (order.orderStatus === 5) return '退款被拒绝';
|
||
if (order.orderStatus === 6) return '退款成功';
|
||
if (order.orderStatus === 7) return '客户端申请退款';
|
||
|
||
// 检查支付状态 (payStatus为boolean类型)
|
||
if (!order.payStatus) return '待付款';
|
||
|
||
// 已付款后检查发货状态
|
||
if (order.deliveryStatus === 10) return '待发货';
|
||
if (order.deliveryStatus === 20) {
|
||
// 若订单有配送员,则以配送员送达时间作为“可确认收货”的依据
|
||
if (order.riderId) {
|
||
if (order.sendEndTime && order.orderStatus !== 1) return '待确认收货';
|
||
return '配送中';
|
||
}
|
||
return '待收货';
|
||
}
|
||
if (order.deliveryStatus === 30) return '部分发货';
|
||
|
||
// 最后检查订单完成状态
|
||
if (order.orderStatus === 1) return '已完成';
|
||
if (order.orderStatus === 0) return '未使用';
|
||
|
||
return '未知状态';
|
||
};
|
||
|
||
const getPayTypeText = (payType?: number) => {
|
||
switch (payType) {
|
||
case 0:
|
||
return '余额支付';
|
||
case 1:
|
||
return '微信支付';
|
||
case 102:
|
||
return '微信Native';
|
||
case 2:
|
||
return '会员卡支付';
|
||
case 3:
|
||
return '支付宝';
|
||
case 4:
|
||
return '现金';
|
||
case 5:
|
||
return 'POS机';
|
||
default:
|
||
return '未知支付方式';
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (orderId) {
|
||
console.log('shop-goods', orderId)
|
||
getShopOrder(Number(orderId)).then(async (res) => {
|
||
setOrder(res);
|
||
|
||
// 获取订单商品列表
|
||
const goodsRes = await listShopOrderGoods({orderId: Number(orderId)});
|
||
if (goodsRes && goodsRes.length > 0) {
|
||
setOrderGoodsList(goodsRes);
|
||
}
|
||
}).catch(error => {
|
||
console.error("Failed to fetch order detail:", error);
|
||
});
|
||
}
|
||
}, [orderId]);
|
||
|
||
if (!order) {
|
||
return <div>加载中...</div>;
|
||
}
|
||
|
||
const currentUserId = Number(Taro.getStorageSync('UserId'))
|
||
const isOwner = !!currentUserId && currentUserId === order.userId
|
||
const canConfirmReceive =
|
||
isOwner &&
|
||
order.payStatus &&
|
||
order.orderStatus !== 1 &&
|
||
order.deliveryStatus === 20 &&
|
||
(!order.riderId || !!order.sendEndTime)
|
||
|
||
return (
|
||
<div className={'order-detail-page'}>
|
||
{/* 支付倒计时显示 - 详情页实时更新 */}
|
||
{!order.payStatus && order.orderStatus !== 2 && (
|
||
<div className="order-detail-countdown flex justify-center p-4 border-b border-gray-50">
|
||
<PaymentCountdown
|
||
expirationTime={order.expirationTime}
|
||
createTime={order.createTime}
|
||
payStatus={order.payStatus}
|
||
realTime={true}
|
||
showSeconds={true}
|
||
mode="badge"
|
||
onExpired={handlePaymentExpired}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
<CellGroup>
|
||
{orderGoodsList.map((item, index) => (
|
||
<Cell key={index}>
|
||
<div className={'flex items-center'}>
|
||
<Image src={item.image || '/default-goods.png'} width="80" height="80" lazyLoad={false}/>
|
||
<div className={'ml-2'}>
|
||
<div className={'text-sm font-bold'}>{item.goodsName}</div>
|
||
{item.spec && <div className={'text-gray-500 text-xs'}>规格:{item.spec}</div>}
|
||
<div className={'text-gray-500 text-xs'}>数量:{item.totalNum}</div>
|
||
<div className={'text-red-500 text-lg'}>¥{item.price}</div>
|
||
</div>
|
||
</div>
|
||
</Cell>
|
||
))}
|
||
</CellGroup>
|
||
|
||
<CellGroup>
|
||
<Cell title="订单编号" description={order.orderNo}/>
|
||
<Cell title="下单时间" description={dayjs(order.createTime).format('YYYY-MM-DD HH:mm:ss')}/>
|
||
<Cell title="订单状态" description={getOrderStatusText(order)}/>
|
||
</CellGroup>
|
||
|
||
<CellGroup>
|
||
<Cell title="收货人" description={order.realName}/>
|
||
<Cell title="手机号" description={order.phone}/>
|
||
<Cell title="收货地址" description={order.address}/>
|
||
</CellGroup>
|
||
|
||
{order.payStatus && (
|
||
<CellGroup>
|
||
<Cell title="支付方式" description={getPayTypeText(order.payType)}/>
|
||
<Cell title="实付金额" description={`¥${order.payPrice}`}/>
|
||
</CellGroup>
|
||
)}
|
||
|
||
<View className={'h5-div fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-5 border-t border-gray-200'}>
|
||
<View className={'flex justify-end px-4'}>
|
||
<Space>
|
||
{!order.payStatus && <Button onClick={() => console.log('取消订单')}>取消订单</Button>}
|
||
{!order.payStatus && <Button type="primary" onClick={() => console.log('立即支付')}>立即支付</Button>}
|
||
{order.orderStatus === 1 && order.payStatus && isWithinRefundWindow(order.payTime, 60) && (
|
||
<Button onClick={handleApplyRefund}>申请退款</Button>
|
||
)}
|
||
{canConfirmReceive && (
|
||
<Button type="primary" onClick={() => setConfirmReceiveDialogVisible(true)}>
|
||
确认收货
|
||
</Button>
|
||
)}
|
||
</Space>
|
||
</View>
|
||
</View>
|
||
|
||
<Dialog
|
||
title="确认收货"
|
||
visible={confirmReceiveDialogVisible}
|
||
confirmText="确认收货"
|
||
cancelText="我再想想"
|
||
onConfirm={handleConfirmReceive}
|
||
onCancel={() => setConfirmReceiveDialogVisible(false)}
|
||
>
|
||
确定已经收到商品了吗?确认后订单将完成。
|
||
</Dialog>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default OrderDetail;
|