feat(payment): 更新支付倒计时组件以支持过期时间

- 添加 expirationTime 属性作为首选时间源
- 当 expirationTime 缺失时回退到 createTime + timeoutHours 方式
- 更新订单详情页和订单列表页组件以传递 expirationTime
- 修改 usePaymentCountdown Hook 以支持新的参数结构
- 更新组件文档以反映新的 API 和使用方式
- 增强时间计算逻辑以处理无效时间情况
This commit is contained in:
2026-02-07 13:16:31 +08:00
parent 6e0a5aa1fe
commit 80653f7ac2
5 changed files with 73 additions and 30 deletions

View File

@@ -1,6 +1,6 @@
# PaymentCountdown 支付倒计时组件 # PaymentCountdown 支付倒计时组件
基于订单创建时间的支付倒计时组件,支持静态显示和实时更新两种模式。 基于订单过期时间(`expirationTime`的支付倒计时组件,支持静态显示和实时更新两种模式。
## 功能特性 ## 功能特性
@@ -19,7 +19,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
// 订单列表页 - 静态显示 // 订单列表页 - 静态显示
<PaymentCountdown <PaymentCountdown
createTime={order.createTime} expirationTime={order.expirationTime}
payStatus={order.payStatus} payStatus={order.payStatus}
realTime={false} realTime={false}
mode="badge" mode="badge"
@@ -27,7 +27,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
// 订单详情页 - 实时更新 // 订单详情页 - 实时更新
<PaymentCountdown <PaymentCountdown
createTime={order.createTime} expirationTime={order.expirationTime}
payStatus={order.payStatus} payStatus={order.payStatus}
realTime={true} realTime={true}
showSeconds={true} showSeconds={true}
@@ -43,7 +43,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
```tsx ```tsx
// 自定义超时时间12小时 // 自定义超时时间12小时
<PaymentCountdown <PaymentCountdown
createTime={order.createTime} expirationTime={order.expirationTime}
payStatus={order.payStatus} payStatus={order.payStatus}
realTime={true} realTime={true}
timeoutHours={12} timeoutHours={12}
@@ -55,7 +55,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
// 纯文本模式 // 纯文本模式
<PaymentCountdown <PaymentCountdown
createTime={order.createTime} expirationTime={order.expirationTime}
payStatus={order.payStatus} payStatus={order.payStatus}
realTime={false} realTime={false}
mode="text" mode="text"
@@ -67,6 +67,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
| 参数 | 类型 | 默认值 | 说明 | | 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------| |------|------|--------|------|
| createTime | string | - | 订单创建时间 | | createTime | string | - | 订单创建时间 |
| expirationTime | string | - | 订单过期时间(推荐) |
| payStatus | boolean | false | 支付状态 | | payStatus | boolean | false | 支付状态 |
| realTime | boolean | false | 是否实时更新 | | realTime | boolean | false | 是否实时更新 |
| timeoutHours | number | 24 | 超时小时数 | | timeoutHours | number | 24 | 超时小时数 |
@@ -102,12 +103,13 @@ import PaymentCountdown from '@/components/PaymentCountdown';
import { usePaymentCountdown, formatCountdownText } from '@/hooks/usePaymentCountdown'; import { usePaymentCountdown, formatCountdownText } from '@/hooks/usePaymentCountdown';
const MyComponent = ({ order }) => { const MyComponent = ({ order }) => {
const timeLeft = usePaymentCountdown( const timeLeft = usePaymentCountdown({
order.createTime, expirationTime: order.expirationTime,
order.payStatus, createTime: order.createTime, // expirationTime 缺失时回退
true, // 实时更新 payStatus: order.payStatus,
24 // 24小时超时 realTime: true,
); timeoutHours: 24
});
const countdownText = formatCountdownText(timeLeft, true); const countdownText = formatCountdownText(timeLeft, true);

View File

@@ -11,6 +11,8 @@ import './PaymentCountdown.scss';
export interface PaymentCountdownProps { export interface PaymentCountdownProps {
/** 订单创建时间 */ /** 订单创建时间 */
createTime?: string; createTime?: string;
/** 订单过期时间(推荐直接传后端返回的 expirationTime */
expirationTime?: string;
/** 支付状态 */ /** 支付状态 */
payStatus?: boolean; payStatus?: boolean;
/** 是否实时更新详情页用true列表页用false */ /** 是否实时更新详情页用true列表页用false */
@@ -29,6 +31,7 @@ export interface PaymentCountdownProps {
const PaymentCountdown: React.FC<PaymentCountdownProps> = ({ const PaymentCountdown: React.FC<PaymentCountdownProps> = ({
createTime, createTime,
expirationTime,
payStatus = false, payStatus = false,
realTime = false, realTime = false,
timeoutHours = 24, timeoutHours = 24,
@@ -37,10 +40,16 @@ const PaymentCountdown: React.FC<PaymentCountdownProps> = ({
onExpired, onExpired,
mode = 'badge' mode = 'badge'
}) => { }) => {
const timeLeft = usePaymentCountdown(createTime, payStatus, realTime, timeoutHours); const timeLeft = usePaymentCountdown({
createTime,
expirationTime,
payStatus,
realTime,
timeoutHours
});
// 如果已支付或没有创建时间,不显示倒计时 // 如果已支付或没有可计算的截止时间,不显示倒计时
if (payStatus || !createTime) { if (payStatus || (!expirationTime && !createTime)) {
return null; return null;
} }

View File

@@ -13,19 +13,30 @@ export interface CountdownTime {
totalMinutes: number; // 总剩余分钟数 totalMinutes: number; // 总剩余分钟数
} }
export interface UsePaymentCountdownParams {
/** 订单创建时间(用于兼容:当 expirationTime 缺失时按 createTime + timeoutHours 计算) */
createTime?: string;
/** 订单过期时间(推荐直接传后端返回的 expirationTime */
expirationTime?: string;
/** 支付状态 */
payStatus?: boolean;
/** 是否实时更新详情页用true列表页用false */
realTime?: boolean;
/** 超时小时数默认24小时仅在 expirationTime 缺失时生效) */
timeoutHours?: number;
}
/** /**
* 支付倒计时Hook * 支付倒计时Hook
* @param createTime 订单创建时间 * 优先使用 expirationTime当 expirationTime 缺失时回退到 createTime + timeoutHours。
* @param payStatus 支付状态
* @param realTime 是否实时更新详情页用true列表页用false
* @param timeoutHours 超时小时数默认24小时
*/ */
export const usePaymentCountdown = ( export const usePaymentCountdown = ({
createTime?: string, createTime,
payStatus?: boolean, expirationTime,
realTime: boolean = false, payStatus,
timeoutHours: number = 24 realTime = false,
): CountdownTime => { timeoutHours = 24
}: UsePaymentCountdownParams): CountdownTime => {
const [timeLeft, setTimeLeft] = useState<CountdownTime>({ const [timeLeft, setTimeLeft] = useState<CountdownTime>({
hours: 0, hours: 0,
minutes: 0, minutes: 0,
@@ -37,7 +48,7 @@ export const usePaymentCountdown = (
// 计算剩余时间的函数 // 计算剩余时间的函数
const calculateTimeLeft = useMemo(() => { const calculateTimeLeft = useMemo(() => {
return (): CountdownTime => { return (): CountdownTime => {
if (!createTime || payStatus) { if (payStatus || (!expirationTime && !createTime)) {
return { return {
hours: 0, hours: 0,
minutes: 0, minutes: 0,
@@ -47,8 +58,27 @@ export const usePaymentCountdown = (
}; };
} }
const createTimeObj = dayjs(createTime); // 优先使用后端过期时间;如果无法解析,再回退到 createTime + timeoutHours
const expireTime = createTimeObj.add(timeoutHours, 'hour'); const expireTimeFromExpiration = expirationTime ? dayjs(expirationTime) : null;
const expireTimeFromCreate =
createTime ? dayjs(createTime).add(timeoutHours, 'hour') : null;
const expireTime =
expireTimeFromExpiration?.isValid()
? expireTimeFromExpiration
: expireTimeFromCreate?.isValid()
? expireTimeFromCreate
: null;
if (!expireTime) {
return {
hours: 0,
minutes: 0,
seconds: 0,
isExpired: true,
totalMinutes: 0
};
}
const now = dayjs(); const now = dayjs();
const diff = expireTime.diff(now); const diff = expireTime.diff(now);
@@ -76,10 +106,10 @@ export const usePaymentCountdown = (
totalMinutes totalMinutes
}; };
}; };
}, [createTime, payStatus, timeoutHours]); }, [createTime, expirationTime, payStatus, timeoutHours]);
useEffect(() => { useEffect(() => {
if (!createTime || payStatus) { if (payStatus || (!expirationTime && !createTime)) {
setTimeLeft({ setTimeLeft({
hours: 0, hours: 0,
minutes: 0, minutes: 0,
@@ -111,7 +141,7 @@ export const usePaymentCountdown = (
}, 1000); }, 1000);
return () => clearInterval(timer); return () => clearInterval(timer);
}, [createTime, payStatus, realTime, calculateTimeLeft]); }, [createTime, expirationTime, payStatus, realTime, calculateTimeLeft]);
return timeLeft; return timeLeft;
}; };

View File

@@ -175,6 +175,7 @@ const OrderDetail = () => {
{!order.payStatus && order.orderStatus !== 2 && ( {!order.payStatus && order.orderStatus !== 2 && (
<div className="order-detail-countdown flex justify-center p-4 border-b border-gray-50"> <div className="order-detail-countdown flex justify-center p-4 border-b border-gray-50">
<PaymentCountdown <PaymentCountdown
expirationTime={order.expirationTime}
createTime={order.createTime} createTime={order.createTime}
payStatus={order.payStatus} payStatus={order.payStatus}
realTime={true} realTime={true}

View File

@@ -733,6 +733,7 @@ function OrderList(props: OrderListProps) {
<View className={`${getOrderStatusColor(item)} font-medium`}> <View className={`${getOrderStatusColor(item)} font-medium`}>
{!item.payStatus && item.orderStatus !== 2 ? ( {!item.payStatus && item.orderStatus !== 2 ? (
<PaymentCountdown <PaymentCountdown
expirationTime={item.expirationTime}
createTime={item.createTime} createTime={item.createTime}
payStatus={item.payStatus} payStatus={item.payStatus}
realTime={false} realTime={false}