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;
|
||||
Reference in New Issue
Block a user