forked from gxwebsoft/mp-10550
feat(order): 添加支付倒计时组件并优化订单相关功能
- 新增 PaymentCountdown 组件用于显示支付倒计时 - 实现 usePaymentCountdown Hook 以支持倒计时逻辑 - 添加 useOrderStats Hook 用于获取订单统计信息 - 在订单列表和详情页面集成支付倒计时功能 - 优化订单状态显示和相关操作逻辑
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user