forked from gxwebsoft/mp-10550
feat(order): 添加支付倒计时组件并优化订单相关功能
- 新增 PaymentCountdown 组件用于显示支付倒计时 - 实现 usePaymentCountdown Hook 以支持倒计时逻辑 - 添加 useOrderStats Hook 用于获取订单统计信息 - 在订单列表和详情页面集成支付倒计时功能 - 优化订单状态显示和相关操作逻辑
This commit is contained in:
168
src/components/PaymentCountdown.md
Normal file
168
src/components/PaymentCountdown.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# PaymentCountdown 支付倒计时组件
|
||||||
|
|
||||||
|
基于订单创建时间的支付倒计时组件,支持静态显示和实时更新两种模式。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- ✅ **双模式支持**:静态显示(列表页)和实时更新(详情页)
|
||||||
|
- ✅ **智能状态判断**:自动判断紧急程度并应用不同样式
|
||||||
|
- ✅ **过期自动处理**:倒计时结束后触发回调
|
||||||
|
- ✅ **灵活样式**:支持徽章模式和纯文本模式
|
||||||
|
- ✅ **性能优化**:避免不必要的重渲染
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 基础用法
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import PaymentCountdown from '@/components/PaymentCountdown';
|
||||||
|
|
||||||
|
// 订单列表页 - 静态显示
|
||||||
|
<PaymentCountdown
|
||||||
|
createTime={order.createTime}
|
||||||
|
payStatus={order.payStatus}
|
||||||
|
realTime={false}
|
||||||
|
mode="badge"
|
||||||
|
/>
|
||||||
|
|
||||||
|
// 订单详情页 - 实时更新
|
||||||
|
<PaymentCountdown
|
||||||
|
createTime={order.createTime}
|
||||||
|
payStatus={order.payStatus}
|
||||||
|
realTime={true}
|
||||||
|
showSeconds={true}
|
||||||
|
mode="badge"
|
||||||
|
onExpired={() => {
|
||||||
|
console.log('支付已过期');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 高级用法
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 自定义超时时间(12小时)
|
||||||
|
<PaymentCountdown
|
||||||
|
createTime={order.createTime}
|
||||||
|
payStatus={order.payStatus}
|
||||||
|
realTime={true}
|
||||||
|
timeoutHours={12}
|
||||||
|
showSeconds={true}
|
||||||
|
mode="badge"
|
||||||
|
className="custom-countdown"
|
||||||
|
onExpired={handlePaymentExpired}
|
||||||
|
/>
|
||||||
|
|
||||||
|
// 纯文本模式
|
||||||
|
<PaymentCountdown
|
||||||
|
createTime={order.createTime}
|
||||||
|
payStatus={order.payStatus}
|
||||||
|
realTime={false}
|
||||||
|
mode="text"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| createTime | string | - | 订单创建时间 |
|
||||||
|
| payStatus | boolean | false | 支付状态 |
|
||||||
|
| realTime | boolean | false | 是否实时更新 |
|
||||||
|
| timeoutHours | number | 24 | 超时小时数 |
|
||||||
|
| showSeconds | boolean | false | 是否显示秒数 |
|
||||||
|
| className | string | '' | 自定义样式类名 |
|
||||||
|
| onExpired | function | - | 过期回调函数 |
|
||||||
|
| mode | 'badge' \| 'text' | 'badge' | 显示模式 |
|
||||||
|
|
||||||
|
## 样式状态
|
||||||
|
|
||||||
|
### 正常状态
|
||||||
|
- 红色渐变背景
|
||||||
|
- 白色文字
|
||||||
|
- 轻微阴影效果
|
||||||
|
|
||||||
|
### 紧急状态(< 1小时)
|
||||||
|
- 更深的红色背景
|
||||||
|
- 脉冲动画效果
|
||||||
|
|
||||||
|
### 非常紧急状态(< 10分钟)
|
||||||
|
- 最深的红色背景
|
||||||
|
- 快速闪烁动画
|
||||||
|
|
||||||
|
### 过期状态
|
||||||
|
- 灰色背景
|
||||||
|
- 无动画效果
|
||||||
|
|
||||||
|
## Hook 使用
|
||||||
|
|
||||||
|
如果需要单独使用倒计时逻辑,可以直接使用 Hook:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { usePaymentCountdown, formatCountdownText } from '@/hooks/usePaymentCountdown';
|
||||||
|
|
||||||
|
const MyComponent = ({ order }) => {
|
||||||
|
const timeLeft = usePaymentCountdown(
|
||||||
|
order.createTime,
|
||||||
|
order.payStatus,
|
||||||
|
true, // 实时更新
|
||||||
|
24 // 24小时超时
|
||||||
|
);
|
||||||
|
|
||||||
|
const countdownText = formatCountdownText(timeLeft, true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
剩余时间:{countdownText}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 工具函数
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import {
|
||||||
|
formatCountdownText,
|
||||||
|
isUrgentCountdown,
|
||||||
|
isCriticalCountdown
|
||||||
|
} from '@/hooks/usePaymentCountdown';
|
||||||
|
|
||||||
|
// 格式化倒计时文本
|
||||||
|
const text = formatCountdownText(timeLeft, true); // "2小时30分15秒"
|
||||||
|
|
||||||
|
// 判断是否紧急
|
||||||
|
const isUrgent = isUrgentCountdown(timeLeft); // < 1小时
|
||||||
|
|
||||||
|
// 判断是否非常紧急
|
||||||
|
const isCritical = isCriticalCountdown(timeLeft); // < 10分钟
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **性能考虑**:列表页建议使用 `realTime={false}` 避免过多定时器
|
||||||
|
2. **内存泄漏**:组件会自动清理定时器,无需手动处理
|
||||||
|
3. **时区问题**:确保 `createTime` 格式正确,建议使用 ISO 格式
|
||||||
|
4. **过期处理**:`onExpired` 回调只在实时模式下触发
|
||||||
|
|
||||||
|
## 样式定制
|
||||||
|
|
||||||
|
可以通过 CSS 变量或覆盖样式类来自定义外观:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.custom-countdown {
|
||||||
|
.payment-countdown-badge {
|
||||||
|
background: linear-gradient(135deg, #your-color-1, #your-color-2);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
&.urgent {
|
||||||
|
animation: customPulse 1.5s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes customPulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.8; }
|
||||||
|
}
|
||||||
|
```
|
||||||
170
src/components/PaymentCountdown.scss
Normal file
170
src/components/PaymentCountdown.scss
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
/* 支付倒计时样式 */
|
||||||
|
|
||||||
|
/* 徽章模式样式 */
|
||||||
|
.payment-countdown-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, #ff4757, #ff3838);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 2px 4px rgba(255, 71, 87, 0.3);
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
.countdown-text {
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 紧急状态(少于1小时) */
|
||||||
|
&.urgent {
|
||||||
|
background: linear-gradient(135deg, #ff6b6b, #ee5a52);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 非常紧急状态(少于10分钟) */
|
||||||
|
&.critical {
|
||||||
|
background: linear-gradient(135deg, #ff4757, #c44569);
|
||||||
|
animation: flash 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 过期状态 */
|
||||||
|
&.expired {
|
||||||
|
background: linear-gradient(135deg, #95a5a6, #7f8c8d);
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 纯文本模式样式 */
|
||||||
|
.payment-countdown-text {
|
||||||
|
color: #ff4757;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
/* 紧急状态 */
|
||||||
|
&.urgent {
|
||||||
|
color: #ff6b6b;
|
||||||
|
animation: textPulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 非常紧急状态 */
|
||||||
|
&.critical {
|
||||||
|
color: #ff4757;
|
||||||
|
animation: textFlash 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 过期状态 */
|
||||||
|
&.expired {
|
||||||
|
color: #95a5a6;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画效果 */
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes flash {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes textPulse {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes textFlash {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
25% { opacity: 0.5; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
75% { opacity: 0.5; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式调整 */
|
||||||
|
@media (max-width: 375px) {
|
||||||
|
.payment-countdown-badge {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
|
||||||
|
.countdown-text {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-countdown-text {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 详情页专用样式 */
|
||||||
|
.order-detail-countdown {
|
||||||
|
.payment-countdown-badge {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 16px;
|
||||||
|
margin: 8px 0;
|
||||||
|
|
||||||
|
.countdown-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-countdown-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表页专用样式 */
|
||||||
|
.order-list-countdown {
|
||||||
|
.payment-countdown-badge {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: 6px;
|
||||||
|
|
||||||
|
.countdown-text {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-countdown-text {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/components/PaymentCountdown.tsx
Normal file
89
src/components/PaymentCountdown.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View, Text } from '@tarojs/components';
|
||||||
|
import {
|
||||||
|
usePaymentCountdown,
|
||||||
|
formatCountdownText,
|
||||||
|
isUrgentCountdown,
|
||||||
|
isCriticalCountdown
|
||||||
|
} from '@/hooks/usePaymentCountdown';
|
||||||
|
import './PaymentCountdown.scss';
|
||||||
|
|
||||||
|
export interface PaymentCountdownProps {
|
||||||
|
/** 订单创建时间 */
|
||||||
|
createTime?: string;
|
||||||
|
/** 支付状态 */
|
||||||
|
payStatus?: boolean;
|
||||||
|
/** 是否实时更新(详情页用true,列表页用false) */
|
||||||
|
realTime?: boolean;
|
||||||
|
/** 超时小时数,默认24小时 */
|
||||||
|
timeoutHours?: number;
|
||||||
|
/** 是否显示秒数 */
|
||||||
|
showSeconds?: boolean;
|
||||||
|
/** 自定义样式类名 */
|
||||||
|
className?: string;
|
||||||
|
/** 过期回调 */
|
||||||
|
onExpired?: () => void;
|
||||||
|
/** 显示模式:badge(徽章模式) | text(纯文本模式) */
|
||||||
|
mode?: 'badge' | 'text';
|
||||||
|
}
|
||||||
|
|
||||||
|
const PaymentCountdown: React.FC<PaymentCountdownProps> = ({
|
||||||
|
createTime,
|
||||||
|
payStatus = false,
|
||||||
|
realTime = false,
|
||||||
|
timeoutHours = 24,
|
||||||
|
showSeconds = false,
|
||||||
|
className = '',
|
||||||
|
onExpired,
|
||||||
|
mode = 'badge'
|
||||||
|
}) => {
|
||||||
|
const timeLeft = usePaymentCountdown(createTime, payStatus, realTime, timeoutHours);
|
||||||
|
|
||||||
|
// 如果已支付或没有创建时间,不显示倒计时
|
||||||
|
if (payStatus || !createTime) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已过期,触发回调并显示过期状态
|
||||||
|
if (timeLeft.isExpired) {
|
||||||
|
onExpired?.();
|
||||||
|
if (mode === 'text') {
|
||||||
|
return (
|
||||||
|
<Text className={`payment-countdown-text expired ${className}`}>
|
||||||
|
支付已过期
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View className={`payment-countdown-badge expired ${className}`}>
|
||||||
|
<Text className="countdown-text">支付已过期</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断紧急程度
|
||||||
|
const isUrgent = isUrgentCountdown(timeLeft);
|
||||||
|
const isCritical = isCriticalCountdown(timeLeft);
|
||||||
|
|
||||||
|
// 格式化倒计时文本
|
||||||
|
const countdownText = formatCountdownText(timeLeft, showSeconds);
|
||||||
|
const fullText = `等待付款 ${countdownText}`;
|
||||||
|
|
||||||
|
// 纯文本模式
|
||||||
|
if (mode === 'text') {
|
||||||
|
return (
|
||||||
|
<Text className={`payment-countdown-text ${isUrgent ? 'urgent' : ''} ${isCritical ? 'critical' : ''} ${className}`}>
|
||||||
|
{fullText}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 徽章模式
|
||||||
|
return (
|
||||||
|
<View className={`payment-countdown-badge ${isUrgent ? 'urgent' : ''} ${isCritical ? 'critical' : ''} ${className}`}>
|
||||||
|
<Text className="countdown-text">{fullText}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PaymentCountdown;
|
||||||
117
src/hooks/useOrderStats.ts
Normal file
117
src/hooks/useOrderStats.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { UserOrderStats } from '@/api/user';
|
||||||
|
import Taro from '@tarojs/taro';
|
||||||
|
import {pageShopOrder} from "@/api/shop/shopOrder";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单统计Hook
|
||||||
|
* 用于管理用户订单各状态的数量统计
|
||||||
|
*/
|
||||||
|
export const useOrderStats = () => {
|
||||||
|
const [orderStats, setOrderStats] = useState<UserOrderStats>({
|
||||||
|
pending: 0, // 待付款
|
||||||
|
paid: 0, // 待发货
|
||||||
|
shipped: 0, // 待收货
|
||||||
|
completed: 0, // 已完成
|
||||||
|
refund: 0, // 退货/售后
|
||||||
|
total: 0 // 总订单数
|
||||||
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单统计数据
|
||||||
|
*/
|
||||||
|
const fetchOrderStats = useCallback(async (showToast = false) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
// TODO 读取订单数量
|
||||||
|
const pending = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 0})
|
||||||
|
const paid = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 1})
|
||||||
|
const shipped = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 3})
|
||||||
|
const completed = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 5})
|
||||||
|
const refund = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 6})
|
||||||
|
const total = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId')})
|
||||||
|
setOrderStats({
|
||||||
|
pending: pending?.count || 0,
|
||||||
|
paid: paid?.count || 0,
|
||||||
|
shipped: shipped?.count || 0,
|
||||||
|
completed: completed?.count || 0,
|
||||||
|
refund: refund?.count || 0,
|
||||||
|
total: total?.count || 0
|
||||||
|
})
|
||||||
|
|
||||||
|
if (showToast) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '数据已更新',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 1500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
const errorMessage = err.message || '获取订单统计失败';
|
||||||
|
setError(errorMessage);
|
||||||
|
|
||||||
|
console.error('获取订单统计失败:', err);
|
||||||
|
|
||||||
|
if (showToast) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: errorMessage,
|
||||||
|
icon: 'error',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新订单统计数据
|
||||||
|
*/
|
||||||
|
const refreshOrderStats = useCallback(() => {
|
||||||
|
return fetchOrderStats(true);
|
||||||
|
}, [fetchOrderStats]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定状态的订单数量
|
||||||
|
*/
|
||||||
|
const getOrderCount = useCallback((status: keyof UserOrderStats) => {
|
||||||
|
return orderStats[status] || 0;
|
||||||
|
}, [orderStats]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否有待处理的订单
|
||||||
|
*/
|
||||||
|
const hasPendingOrders = useCallback(() => {
|
||||||
|
return orderStats.pending > 0 || orderStats.paid > 0 || orderStats.shipped > 0;
|
||||||
|
}, [orderStats]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取总的待处理订单数量
|
||||||
|
*/
|
||||||
|
const getTotalPendingCount = useCallback(() => {
|
||||||
|
return orderStats.pending + orderStats.paid + orderStats.shipped;
|
||||||
|
}, [orderStats]);
|
||||||
|
|
||||||
|
// 组件挂载时自动获取数据
|
||||||
|
useEffect(() => {
|
||||||
|
fetchOrderStats();
|
||||||
|
}, [fetchOrderStats]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
orderStats,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
fetchOrderStats,
|
||||||
|
refreshOrderStats,
|
||||||
|
getOrderCount,
|
||||||
|
hasPendingOrders,
|
||||||
|
getTotalPendingCount
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useOrderStats;
|
||||||
163
src/hooks/usePaymentCountdown.ts
Normal file
163
src/hooks/usePaymentCountdown.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import duration from 'dayjs/plugin/duration';
|
||||||
|
|
||||||
|
// 扩展dayjs支持duration
|
||||||
|
dayjs.extend(duration);
|
||||||
|
|
||||||
|
export interface CountdownTime {
|
||||||
|
hours: number;
|
||||||
|
minutes: number;
|
||||||
|
seconds: number;
|
||||||
|
isExpired: boolean;
|
||||||
|
totalMinutes: number; // 总剩余分钟数
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付倒计时Hook
|
||||||
|
* @param createTime 订单创建时间
|
||||||
|
* @param payStatus 支付状态
|
||||||
|
* @param realTime 是否实时更新(详情页用true,列表页用false)
|
||||||
|
* @param timeoutHours 超时小时数,默认24小时
|
||||||
|
*/
|
||||||
|
export const usePaymentCountdown = (
|
||||||
|
createTime?: string,
|
||||||
|
payStatus?: boolean,
|
||||||
|
realTime: boolean = false,
|
||||||
|
timeoutHours: number = 24
|
||||||
|
): CountdownTime => {
|
||||||
|
const [timeLeft, setTimeLeft] = useState<CountdownTime>({
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0,
|
||||||
|
isExpired: false,
|
||||||
|
totalMinutes: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算剩余时间的函数
|
||||||
|
const calculateTimeLeft = useMemo(() => {
|
||||||
|
return (): CountdownTime => {
|
||||||
|
if (!createTime || payStatus) {
|
||||||
|
return {
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0,
|
||||||
|
isExpired: false,
|
||||||
|
totalMinutes: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTimeObj = dayjs(createTime);
|
||||||
|
const expireTime = createTimeObj.add(timeoutHours, 'hour');
|
||||||
|
const now = dayjs();
|
||||||
|
const diff = expireTime.diff(now);
|
||||||
|
|
||||||
|
if (diff <= 0) {
|
||||||
|
return {
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0,
|
||||||
|
isExpired: true,
|
||||||
|
totalMinutes: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const durationObj = dayjs.duration(diff);
|
||||||
|
const hours = Math.floor(durationObj.asHours());
|
||||||
|
const minutes = durationObj.minutes();
|
||||||
|
const seconds = durationObj.seconds();
|
||||||
|
const totalMinutes = Math.floor(durationObj.asMinutes());
|
||||||
|
|
||||||
|
return {
|
||||||
|
hours,
|
||||||
|
minutes,
|
||||||
|
seconds,
|
||||||
|
isExpired: false,
|
||||||
|
totalMinutes
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}, [createTime, payStatus, timeoutHours]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!createTime || payStatus) {
|
||||||
|
setTimeLeft({
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0,
|
||||||
|
isExpired: false,
|
||||||
|
totalMinutes: 0
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即计算一次
|
||||||
|
const initialTime = calculateTimeLeft();
|
||||||
|
setTimeLeft(initialTime);
|
||||||
|
|
||||||
|
// 如果不需要实时更新,直接返回
|
||||||
|
if (!realTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要实时更新,设置定时器
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
const newTimeLeft = calculateTimeLeft();
|
||||||
|
setTimeLeft(newTimeLeft);
|
||||||
|
|
||||||
|
// 如果已过期,清除定时器
|
||||||
|
if (newTimeLeft.isExpired) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [createTime, payStatus, realTime, calculateTimeLeft]);
|
||||||
|
|
||||||
|
return timeLeft;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化倒计时文本
|
||||||
|
* @param timeLeft 倒计时时间对象
|
||||||
|
* @param showSeconds 是否显示秒数
|
||||||
|
*/
|
||||||
|
export const formatCountdownText = (
|
||||||
|
timeLeft: CountdownTime,
|
||||||
|
showSeconds: boolean = false
|
||||||
|
): string => {
|
||||||
|
if (timeLeft.isExpired) {
|
||||||
|
return '已过期';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeLeft.hours > 0) {
|
||||||
|
if (showSeconds) {
|
||||||
|
return `${timeLeft.hours}小时${timeLeft.minutes}分${timeLeft.seconds}秒`;
|
||||||
|
} else {
|
||||||
|
return `${timeLeft.hours}小时${timeLeft.minutes}分钟`;
|
||||||
|
}
|
||||||
|
} else if (timeLeft.minutes > 0) {
|
||||||
|
if (showSeconds) {
|
||||||
|
return `${timeLeft.minutes}分${timeLeft.seconds}秒`;
|
||||||
|
} else {
|
||||||
|
return `${timeLeft.minutes}分钟`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `${timeLeft.seconds}秒`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为紧急状态(剩余时间少于1小时)
|
||||||
|
*/
|
||||||
|
export const isUrgentCountdown = (timeLeft: CountdownTime): boolean => {
|
||||||
|
return !timeLeft.isExpired && timeLeft.totalMinutes < 60;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为非常紧急状态(剩余时间少于10分钟)
|
||||||
|
*/
|
||||||
|
export const isCriticalCountdown = (timeLeft: CountdownTime): boolean => {
|
||||||
|
return !timeLeft.isExpired && timeLeft.totalMinutes < 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default usePaymentCountdown;
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
import {useEffect} from "react";
|
|
||||||
import navTo from "@/utils/common";
|
import navTo from "@/utils/common";
|
||||||
import {View, Text} from '@tarojs/components';
|
import {View, Text} from '@tarojs/components';
|
||||||
import {Badge} from '@nutui/nutui-react-taro';
|
import {Badge} from '@nutui/nutui-react-taro';
|
||||||
import {ArrowRight, Wallet, Comment, Transit, Refund, Package} from '@nutui/icons-react-taro';
|
import {ArrowRight, Wallet, Comment, Transit, Refund, Package} from '@nutui/icons-react-taro';
|
||||||
|
import {useOrderStats} from "@/hooks/useOrderStats";
|
||||||
|
|
||||||
function UserOrder() {
|
function UserOrder() {
|
||||||
|
const { orderStats, refreshOrderStats } = useOrderStats();
|
||||||
|
|
||||||
const reload = () => {
|
// 处理长按刷新
|
||||||
|
const handleLongPress = () => {
|
||||||
|
refreshOrderStats();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
reload()
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View className={'px-4 pb-2'}>
|
<View className={'px-4 pb-2'}>
|
||||||
@@ -29,45 +27,90 @@ function UserOrder() {
|
|||||||
>
|
>
|
||||||
<View className={'title-bar flex justify-between pt-2'}>
|
<View className={'title-bar flex justify-between pt-2'}>
|
||||||
<Text className={'title font-medium px-4'}>我的订单</Text>
|
<Text className={'title font-medium px-4'}>我的订单</Text>
|
||||||
<View className={'more flex items-center px-2'} onClick={() => navTo('/user/order/order', true)}>
|
<View
|
||||||
|
className={'more flex items-center px-2'}
|
||||||
|
onClick={() => navTo('/user/order/order', true)}
|
||||||
|
onLongPress={handleLongPress}
|
||||||
|
>
|
||||||
<Text className={'text-xs text-gray-500'}>全部订单</Text>
|
<Text className={'text-xs text-gray-500'}>全部订单</Text>
|
||||||
<ArrowRight color="#cccccc" size={12}/>
|
<ArrowRight color="#cccccc" size={12}/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className={'flex justify-around pb-1 mt-4'}>
|
<View className={'flex justify-around pb-1 mt-4'}>
|
||||||
<Badge value={8}>
|
{/* 待付款 */}
|
||||||
<View className={'item flex justify-center flex-col items-center'}>
|
{orderStats.pending > 0 ? (
|
||||||
<Wallet size={26} className={'font-normal text-gray-500'}
|
<Badge value={orderStats.pending} max={99}>
|
||||||
onClick={() => navTo('/user/order/order?statusFilter=0', true)}/>
|
<View className={'item flex justify-center flex-col items-center'}>
|
||||||
|
<Wallet size={26} className={'font-normal text-gray-500'}
|
||||||
|
onClick={() => navTo('/user/order/order?statusFilter=0', true)}/>
|
||||||
|
<Text className={'text-sm text-gray-600 py-1'}>待付款</Text>
|
||||||
|
</View>
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<View className={'item flex justify-center flex-col items-center'}
|
||||||
|
onClick={() => navTo('/user/order/order?statusFilter=0', true)}>
|
||||||
|
<Wallet size={26} className={'font-normal text-gray-500'}/>
|
||||||
<Text className={'text-sm text-gray-600 py-1'}>待付款</Text>
|
<Text className={'text-sm text-gray-600 py-1'}>待付款</Text>
|
||||||
</View>
|
</View>
|
||||||
</Badge>
|
)}
|
||||||
<Badge value={8}>
|
|
||||||
|
{/* 待发货 */}
|
||||||
|
{orderStats.paid > 0 ? (
|
||||||
|
<Badge value={orderStats.paid} max={99}>
|
||||||
|
<View className={'item flex justify-center flex-col items-center'}
|
||||||
|
onClick={() => navTo('/user/order/order?statusFilter=1', true)}>
|
||||||
|
<Package size={26} className={'text-gray-500 font-normal'}/>
|
||||||
|
<Text className={'text-sm text-gray-600 py-1'}>待发货</Text>
|
||||||
|
</View>
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
<View className={'item flex justify-center flex-col items-center'}
|
<View className={'item flex justify-center flex-col items-center'}
|
||||||
onClick={() => navTo('/user/order/order?statusFilter=1', true)}>
|
onClick={() => navTo('/user/order/order?statusFilter=1', true)}>
|
||||||
<Package size={26} className={'text-gray-500 font-normal'}/>
|
<Package size={26} className={'text-gray-500 font-normal'}/>
|
||||||
<Text className={'text-sm text-gray-600 py-1'}>待发货</Text>
|
<Text className={'text-sm text-gray-600 py-1'}>待发货</Text>
|
||||||
</View>
|
</View>
|
||||||
</Badge>
|
)}
|
||||||
<Badge value={8}>
|
|
||||||
|
{/* 待收货 */}
|
||||||
|
{orderStats.shipped > 0 ? (
|
||||||
|
<Badge value={orderStats.shipped} max={99}>
|
||||||
|
<View className={'item flex justify-center flex-col items-center'}
|
||||||
|
onClick={() => navTo('/user/order/order?statusFilter=3', true)}>
|
||||||
|
<Transit size={24} className={'text-gray-500 font-normal'}/>
|
||||||
|
<Text className={'text-sm text-gray-600 py-1'}>待收货</Text>
|
||||||
|
</View>
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
<View className={'item flex justify-center flex-col items-center'}
|
<View className={'item flex justify-center flex-col items-center'}
|
||||||
onClick={() => navTo('/user/order/order?statusFilter=3', true)}>
|
onClick={() => navTo('/user/order/order?statusFilter=3', true)}>
|
||||||
<Transit size={24} className={'text-gray-500 font-normal'}/>
|
<Transit size={24} className={'text-gray-500 font-normal'}/>
|
||||||
<Text className={'text-sm text-gray-600 py-1'}>待收货</Text>
|
<Text className={'text-sm text-gray-600 py-1'}>待收货</Text>
|
||||||
</View>
|
</View>
|
||||||
</Badge>
|
)}
|
||||||
|
|
||||||
|
{/* 已完成 - 不显示badge */}
|
||||||
<View className={'item flex justify-center flex-col items-center'}
|
<View className={'item flex justify-center flex-col items-center'}
|
||||||
onClick={() => navTo('/user/order/order?statusFilter=5', true)}>
|
onClick={() => navTo('/user/order/order?statusFilter=5', true)}>
|
||||||
<Comment size={24} className={'text-gray-500 font-normal'}/>
|
<Comment size={24} className={'text-gray-500 font-normal'}/>
|
||||||
<Text className={'text-sm text-gray-600 py-1'}>已完成</Text>
|
<Text className={'text-sm text-gray-600 py-1'}>已完成</Text>
|
||||||
</View>
|
</View>
|
||||||
<Badge value={8}>
|
|
||||||
|
{/* 退货/售后 */}
|
||||||
|
{orderStats.refund > 0 ? (
|
||||||
|
<Badge value={orderStats.refund} max={99}>
|
||||||
|
<View className={'item flex justify-center flex-col items-center'}
|
||||||
|
onClick={() => navTo('/user/order/order?statusFilter=6', true)}>
|
||||||
|
<Refund size={26} className={'font-normal text-gray-500'}/>
|
||||||
|
<Text className={'text-sm text-gray-600 py-1'}>退货/售后</Text>
|
||||||
|
</View>
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
<View className={'item flex justify-center flex-col items-center'}
|
<View className={'item flex justify-center flex-col items-center'}
|
||||||
onClick={() => navTo('/user/order/order?statusFilter=6', true)}>
|
onClick={() => navTo('/user/order/order?statusFilter=6', true)}>
|
||||||
<Refund size={26} className={'font-normal text-gray-500'}/>
|
<Refund size={26} className={'font-normal text-gray-500'}/>
|
||||||
<Text className={'text-sm text-gray-600 py-1'}>退货/售后</Text>
|
<Text className={'text-sm text-gray-600 py-1'}>退货/售后</Text>
|
||||||
</View>
|
</View>
|
||||||
</Badge>
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import {useEffect, useState} from "react";
|
|||||||
import {Cell, CellGroup, Image, Space, Button} from '@nutui/nutui-react-taro'
|
import {Cell, CellGroup, Image, Space, Button} from '@nutui/nutui-react-taro'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import {ShopOrder} from "@/api/shop/shopOrder/model";
|
import {ShopOrder} from "@/api/shop/shopOrder/model";
|
||||||
import {getShopOrder} from "@/api/shop/shopOrder";
|
import {getShopOrder, updateShopOrder} from "@/api/shop/shopOrder";
|
||||||
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
||||||
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import PaymentCountdown from "@/components/PaymentCountdown";
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
const OrderDetail = () => {
|
const OrderDetail = () => {
|
||||||
@@ -14,6 +15,30 @@ const OrderDetail = () => {
|
|||||||
const router = Taro.getCurrentInstance().router;
|
const router = Taro.getCurrentInstance().router;
|
||||||
const orderId = router?.params?.orderId;
|
const orderId = router?.params?.orderId;
|
||||||
|
|
||||||
|
// 处理支付超时
|
||||||
|
const handlePaymentExpired = async () => {
|
||||||
|
if (!order) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 自动取消过期订单
|
||||||
|
await updateShopOrder({
|
||||||
|
...order,
|
||||||
|
orderStatus: 2 // 已取消
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新本地状态
|
||||||
|
setOrder(prev => prev ? { ...prev, orderStatus: 2 } : null);
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单已自动取消',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('自动取消订单失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getOrderStatusText = (order: ShopOrder) => {
|
const getOrderStatusText = (order: ShopOrder) => {
|
||||||
// 优先检查订单状态
|
// 优先检查订单状态
|
||||||
if (order.orderStatus === 2) return '已取消';
|
if (order.orderStatus === 2) return '已取消';
|
||||||
@@ -74,6 +99,20 @@ const OrderDetail = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'order-detail-page'}>
|
<div className={'order-detail-page'}>
|
||||||
|
{/* 支付倒计时显示 - 详情页实时更新 */}
|
||||||
|
{!order.payStatus && order.orderStatus !== 2 && (
|
||||||
|
<div className="order-detail-countdown p-4 bg-red-50 border-b border-red-100">
|
||||||
|
<PaymentCountdown
|
||||||
|
createTime={order.createTime}
|
||||||
|
payStatus={order.payStatus}
|
||||||
|
realTime={true}
|
||||||
|
showSeconds={true}
|
||||||
|
mode="badge"
|
||||||
|
onExpired={handlePaymentExpired}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<CellGroup title="订单信息">
|
<CellGroup title="订单信息">
|
||||||
<Cell title="订单编号" description={order.orderNo} />
|
<Cell title="订单编号" description={order.orderNo} />
|
||||||
<Cell title="下单时间" description={dayjs(order.createTime).format('YYYY-MM-DD HH:mm:ss')} />
|
<Cell title="下单时间" description={dayjs(order.createTime).format('YYYY-MM-DD HH:mm:ss')} />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
|
|||||||
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
||||||
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
||||||
import {copyText} from "@/utils/common";
|
import {copyText} from "@/utils/common";
|
||||||
|
import PaymentCountdown from "@/components/PaymentCountdown";
|
||||||
|
|
||||||
const getInfiniteUlStyle = (showSearch: boolean = false): CSSProperties => ({
|
const getInfiniteUlStyle = (showSearch: boolean = false): CSSProperties => ({
|
||||||
marginTop: showSearch ? '0' : '0', // 如果显示搜索框,增加更多的上边距
|
marginTop: showSearch ? '0' : '0', // 如果显示搜索框,增加更多的上边距
|
||||||
@@ -188,7 +189,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await pageShopOrder(searchConditions);
|
const res = await pageShopOrder(searchConditions);
|
||||||
let newList: OrderWithGoods[] = [];
|
let newList: OrderWithGoods[];
|
||||||
|
|
||||||
if (res?.list && res?.list.length > 0) {
|
if (res?.list && res?.list.length > 0) {
|
||||||
// 批量获取订单商品信息,限制并发数量
|
// 批量获取订单商品信息,限制并发数量
|
||||||
@@ -261,7 +262,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '确认收货成功',
|
title: '确认收货成功',
|
||||||
});
|
});
|
||||||
reload(true); // 重新加载列表
|
await reload(true); // 重新加载列表
|
||||||
props.onReload?.(); // 通知父组件刷新
|
props.onReload?.(); // 通知父组件刷新
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
@@ -293,7 +294,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
title: '订单已取消',
|
title: '订单已取消',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
});
|
});
|
||||||
reload(true).then(); // 重新加载列表
|
void reload(true); // 重新加载列表
|
||||||
props.onReload?.(); // 通知父组件刷新
|
props.onReload?.(); // 通知父组件刷新
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('取消订单失败:', error);
|
console.error('取消订单失败:', error);
|
||||||
@@ -305,7 +306,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload(true).then(); // 首次加载或tab切换时重置页码
|
void reload(true); // 首次加载或tab切换时重置页码
|
||||||
}, [tapIndex]); // 监听tapIndex变化
|
}, [tapIndex]); // 监听tapIndex变化
|
||||||
|
|
||||||
// 监听外部statusFilter变化,同步更新tab索引
|
// 监听外部statusFilter变化,同步更新tab索引
|
||||||
@@ -332,25 +333,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
}
|
}
|
||||||
}, [props.searchParams?.statusFilter]); // 监听statusFilter变化
|
}, [props.searchParams?.statusFilter]); // 监听statusFilter变化
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// 当外部传入的搜索参数变化时(不包括statusFilter,因为上面已经处理)
|
|
||||||
// 只有当搜索关键词等其他条件变化时才重新加载
|
|
||||||
const {statusFilter, ...otherParams} = props.searchParams || {};
|
|
||||||
|
|
||||||
// 检查是否有除statusFilter外的其他搜索条件变化
|
|
||||||
const hasOtherSearchParams = Object.keys(otherParams).some(key =>
|
|
||||||
otherParams[key] !== undefined && otherParams[key] !== ''
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('searchParams变化 (非statusFilter):', {
|
|
||||||
otherParams,
|
|
||||||
hasOtherSearchParams
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hasOtherSearchParams) {
|
|
||||||
reload(true).then(); // 只有其他搜索条件变化时才重新加载
|
|
||||||
}
|
|
||||||
}, [props.searchParams?.keywords, props.searchParams?.orderNo]); // 只监听具体的搜索字段
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -367,7 +350,6 @@ function OrderList(props: OrderListProps) {
|
|||||||
}}
|
}}
|
||||||
value={tapIndex}
|
value={tapIndex}
|
||||||
onChange={(paneKey) => {
|
onChange={(paneKey) => {
|
||||||
console.log('Tab切换到:', paneKey, '对应状态:', tabs[paneKey]?.title, 'statusFilter:', tabs[paneKey]?.statusFilter);
|
|
||||||
setTapIndex(paneKey)
|
setTapIndex(paneKey)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -428,11 +410,23 @@ function OrderList(props: OrderListProps) {
|
|||||||
onClick={() => Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}>
|
onClick={() => Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}>
|
||||||
<Space direction={'vertical'} className={'w-full flex flex-col'}>
|
<Space direction={'vertical'} className={'w-full flex flex-col'}>
|
||||||
<View className={'order-no flex justify-between'}>
|
<View className={'order-no flex justify-between'}>
|
||||||
<View className={'text-gray-600 font-bold text-sm'}
|
<View className={'flex items-center'}>
|
||||||
onClick={(e) => {
|
<View className={'text-gray-600 font-bold text-sm'}
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
copyText(`${item.orderNo}`)
|
e.stopPropagation();
|
||||||
}}>{item.orderNo}</View>
|
copyText(`${item.orderNo}`)
|
||||||
|
}}>{item.orderNo}</View>
|
||||||
|
{/* 添加倒计时显示 - 列表页不实时更新 */}
|
||||||
|
<View className="order-list-countdown">
|
||||||
|
<PaymentCountdown
|
||||||
|
createTime={item.createTime}
|
||||||
|
payStatus={item.payStatus}
|
||||||
|
realTime={false}
|
||||||
|
showSeconds={false}
|
||||||
|
mode="badge"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
<View className={`${getOrderStatusColor(item)} font-medium`}>{getOrderStatusText(item)}</View>
|
<View className={`${getOrderStatusColor(item)} font-medium`}>{getOrderStatusText(item)}</View>
|
||||||
</View>
|
</View>
|
||||||
<div
|
<div
|
||||||
@@ -482,7 +476,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
<Space>
|
<Space>
|
||||||
<Button size={'small'} onClick={(e) => {
|
<Button size={'small'} onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
cancelOrder(item)
|
void cancelOrder(item);
|
||||||
}}>取消订单</Button>
|
}}>取消订单</Button>
|
||||||
<Button size={'small'} type="primary" onClick={(e) => {
|
<Button size={'small'} type="primary" onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -494,7 +488,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
{item.deliveryStatus === 20 && (
|
{item.deliveryStatus === 20 && (
|
||||||
<Button size={'small'} type="primary" onClick={(e) => {
|
<Button size={'small'} type="primary" onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
confirmReceive(item)
|
void confirmReceive(item);
|
||||||
}}>确认收货</Button>
|
}}>确认收货</Button>
|
||||||
)}
|
)}
|
||||||
{/* 已完成状态:显示申请退款 */}
|
{/* 已完成状态:显示申请退款 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user